diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index 093592b870b3..36837c6812a7 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -146,6 +146,7 @@ if(CONFIG_BT_ENABLED) host/bluedroid/bta/hd/include host/bluedroid/bta/hh/include host/bluedroid/bta/jv/include + host/bluedroid/bta/pba/include host/bluedroid/bta/sdp/include host/bluedroid/bta/sys/include host/bluedroid/device/include @@ -194,6 +195,7 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/api/esp_spp_api.c" "host/bluedroid/api/esp_sdp_api.c" "host/bluedroid/api/esp_l2cap_bt_api.c" + "host/bluedroid/api/esp_pbac_api.c" "host/bluedroid/bta/ar/bta_ar.c" "host/bluedroid/bta/av/bta_av_aact.c" "host/bluedroid/bta/av/bta_av_act.c" @@ -257,6 +259,10 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/bta/hf_client/bta_hf_client_rfc.c" "host/bluedroid/bta/hf_client/bta_hf_client_sco.c" "host/bluedroid/bta/hf_client/bta_hf_client_sdp.c" + "host/bluedroid/bta/pba/bta_pba_client_act.c" + "host/bluedroid/bta/pba/bta_pba_client_api.c" + "host/bluedroid/bta/pba/bta_pba_client_main.c" + "host/bluedroid/bta/pba/bta_pba_client_sdp.c" "host/bluedroid/bta/sdp/bta_sdp.c" "host/bluedroid/bta/sdp/bta_sdp_act.c" "host/bluedroid/bta/sdp/bta_sdp_api.c" @@ -299,6 +305,7 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/btc/profile/std/spp/btc_spp.c" "host/bluedroid/btc/profile/std/sdp/btc_sdp.c" "host/bluedroid/btc/profile/std/l2cap/btc_l2cap.c" + "host/bluedroid/btc/profile/std/pba/btc_pba_client.c" "host/bluedroid/device/bdaddr.c" "host/bluedroid/device/controller.c" "host/bluedroid/device/interop.c" @@ -411,6 +418,7 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/stack/obex/obex_api.c" "host/bluedroid/stack/obex/obex_main.c" "host/bluedroid/stack/obex/obex_tl_l2cap.c" + "host/bluedroid/stack/obex/obex_tl_rfcomm.c" "host/bluedroid/stack/rfcomm/port_api.c" "host/bluedroid/stack/rfcomm/port_rfc.c" "host/bluedroid/stack/rfcomm/port_utils.c" diff --git a/components/bt/common/btc/core/btc_task.c b/components/bt/common/btc/core/btc_task.c index 839b3741bcbd..0049328f50a8 100644 --- a/components/bt/common/btc/core/btc_task.c +++ b/components/bt/common/btc/core/btc_task.c @@ -57,6 +57,9 @@ #if BTC_HH_INCLUDED == TRUE #include "btc_hh.h" #endif /* BTC_HH_INCLUDED */ +#if BTC_PBA_CLIENT_INCLUDED +#include "btc_pba_client.h" +#endif #endif /* #if CLASSIC_BT_INCLUDED */ #endif @@ -155,6 +158,9 @@ static const btc_func_t profile_tab[BTC_PID_NUM] = { #if BTC_HH_INCLUDED [BTC_PID_HH] = {btc_hh_call_handler, btc_hh_cb_handler }, #endif +#if BTC_PBA_CLIENT_INCLUDED + [BTC_PID_PBA_CLIENT] = {btc_pba_client_call_handler, btc_pba_client_cb_handler}, +#endif #endif /* #if CLASSIC_BT_INCLUDED */ #endif #if CONFIG_BLE_MESH diff --git a/components/bt/common/btc/include/btc/btc_task.h b/components/bt/common/btc/include/btc/btc_task.h index 232186b51c5b..166b7e0f1631 100644 --- a/components/bt/common/btc/include/btc/btc_task.h +++ b/components/bt/common/btc/include/btc/btc_task.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -67,6 +67,9 @@ typedef enum { #if (BTC_HF_CLIENT_INCLUDED == TRUE) BTC_PID_HF_CLIENT, #endif /* BTC_HF_CLIENT_INCLUDED */ +#if (BTC_PBA_CLIENT_INCLUDED == TRUE) + BTC_PID_PBA_CLIENT, +#endif /* BTC_PBA_CLIENT_INCLUDED */ #endif /* CLASSIC_BT_INCLUDED */ #if CONFIG_BLE_MESH BTC_PID_PROV, @@ -123,8 +126,8 @@ extern "C" { /** * transfer an message to another module in the different task. * @param msg message - * @param arg paramter - * @param arg_len length of paramter + * @param arg parameter + * @param arg_len length of parameter * @param copy_func deep copy function * @param free_func deep free function * @return BT_STATUS_SUCCESS: success @@ -134,7 +137,7 @@ bt_status_t btc_transfer_context(btc_msg_t *msg, void *arg, int arg_len, btc_arg btc_arg_deep_free_t free_func); /** - * transfer an message to another module in tha same task. + * transfer an message to another module in the same task. * @param msg message * @return BT_STATUS_SUCCESS: success * others: fail diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index dc235c2b9975..2c6412d862f6 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -191,6 +191,29 @@ config BT_HID_DEVICE_ENABLED help This enables the BT HID Device +menuconfig BT_PBAC_ENABLED + bool "PBAP Client" + depends on BT_CLASSIC_ENABLED + default n + select BT_GOEPC_ENABLED + help + This enables the Phone Book Access Profile Client + +config BT_PBAC_SUPPORTED_FEAT + hex "PBAP Client Supported Features" + depends on BT_PBAC_ENABLED + default 0x000003FF + help + Set the supported features of PBAP Client, the default value is supported all features + +config BT_PBAC_PREFERRED_MTU + int "PBAP Client Preferred MTU" + depends on BT_PBAC_ENABLED + default 0 + help + MTU is limited by the max MTU of transport layer, and should not be smaller than 255, + but can be set to zero to use a default MTU of transport layer + config BT_GOEPC_ENABLED bool depends on BT_CLASSIC_ENABLED diff --git a/components/bt/host/bluedroid/api/esp_pbac_api.c b/components/bt/host/bluedroid/api/esp_pbac_api.c new file mode 100644 index 000000000000..3799038f94be --- /dev/null +++ b/components/bt/host/bluedroid/api/esp_pbac_api.c @@ -0,0 +1,251 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_err.h" +#include "esp_bt_main.h" +#include "esp_pbac_api.h" +#include "btc/btc_manage.h" +#include "btc_pba_client.h" + +#if BTC_PBA_CLIENT_INCLUDED + +esp_err_t esp_pbac_init(void) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_INIT_EVT; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, NULL, 0, NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_deinit(void) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_DEINIT_EVT; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, NULL, 0, NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_register_callback(esp_pbac_callback_t callback) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (callback == NULL) { + return ESP_FAIL; + } + + btc_profile_cb_set(BTC_PID_PBA_CLIENT, callback); + return ESP_OK; +} + +esp_err_t esp_pbac_connect(esp_bd_addr_t bd_addr) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (bd_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_CONNECT_EVT; + + btc_pba_client_args_t args = {0}; + memcpy(args.connect.bd_addr.address, bd_addr, sizeof(esp_bd_addr_t)); + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_disconnect(esp_pbac_conn_hdl_t handle) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (handle == ESP_PBAC_INVALID_HANDLE) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_DISCONNECT_EVT; + + btc_pba_client_args_t args = {0}; + args.disconnect.handle = handle; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + + +esp_err_t esp_pbac_pull_phone_book(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_phone_book_app_param_t *app_param) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (handle == ESP_PBAC_INVALID_HANDLE || name == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT; + + btc_pba_client_args_t args = {0}; + args.pull_phone_book.handle = handle; + args.pull_phone_book.name = (char *)name; + if (app_param != NULL) { + args.pull_phone_book.include_app_param = true; + memcpy(&args.pull_phone_book.app_param, app_param, sizeof(esp_pbac_pull_phone_book_app_param_t)); + } + else { + args.pull_phone_book.include_app_param = false; + } + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_set_phone_book(esp_pbac_conn_hdl_t handle, esp_pbac_set_phone_book_flags_t flags, const char *name) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + /* since ESP_PBAC_SET_PHONE_BOOK_FLAGS_ROOT is equal to ESP_PBAC_SET_PHONE_BOOK_FLAGS_DOWN, we dont check XXX_DOWN */ + if (handle == ESP_PBAC_INVALID_HANDLE || (flags != ESP_PBAC_SET_PHONE_BOOK_FLAGS_ROOT && flags != ESP_PBAC_SET_PHONE_BOOK_FLAGS_UP)) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT; + + btc_pba_client_args_t args = {0}; + args.set_phone_book.handle = handle; + args.set_phone_book.flags = flags; + /* set phone book name is allowed to be NULL */ + args.set_phone_book.name = (char *)name; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_set_phone_book2(esp_pbac_conn_hdl_t handle, const char *path) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT; + + btc_pba_client_args_t args = {0}; + args.set_phone_book.handle = handle; + args.set_phone_book.name = (char *)path; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_pull_vcard_listing(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_vcard_listing_app_param_t *app_param) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (handle == ESP_PBAC_INVALID_HANDLE || name == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT; + + btc_pba_client_args_t args = {0}; + args.pull_vcard_listing.handle = handle; + args.pull_vcard_listing.name = (char *)name; + if (app_param != NULL) { + args.pull_vcard_listing.include_app_param = true; + memcpy(&args.pull_vcard_listing.app_param, app_param, sizeof(esp_pbac_pull_vcard_listing_app_param_t)); + } + else { + args.pull_vcard_listing.include_app_param = false; + } + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_pull_vcard_entry(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_vcard_entry_app_param_t *app_param) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (handle == ESP_PBAC_INVALID_HANDLE || name == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT; + + btc_pba_client_args_t args = {0}; + args.pull_vcard_entry.handle = handle; + args.pull_vcard_entry.name = (char *)name; + if (app_param != NULL) { + args.pull_vcard_entry.include_app_param = true; + memcpy(&args.pull_vcard_entry.app_param, app_param, sizeof(esp_pbac_pull_vcard_entry_app_param_t)); + } + else { + args.pull_vcard_entry.include_app_param = false; + } + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +#endif diff --git a/components/bt/host/bluedroid/api/include/api/esp_pba_defs.h b/components/bt/host/bluedroid/api/include/api/esp_pba_defs.h new file mode 100644 index 000000000000..c3a6f5109ee7 --- /dev/null +++ b/components/bt/host/bluedroid/api/include/api/esp_pba_defs.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_bt_defs.h" + +/* Supported repositories bit mask */ +#define ESP_PBA_SUPPORTED_REPO_LOCAL_PHONE_BOOK 0x01 +#define ESP_PBA_SUPPORTED_REPO_SIM_CARD 0x02 +#define ESP_PBA_SUPPORTED_REPO_SPEED_DIAL 0x04 +#define ESP_PBA_SUPPORTED_REPO_FAVORITES 0x08 + +/* Supported features bit mask */ +#define ESP_PBA_SUPPORTED_FEAT_DOWNLOAD 0x0001 +#define ESP_PBA_SUPPORTED_FEAT_BROWSING 0x0002 +#define ESP_PBA_SUPPORTED_FEAT_DATABASE_IDENTIFIER 0x0004 +#define ESP_PBA_SUPPORTED_FEAT_FOLDER_VERSION_COUNTERS 0x0008 +#define ESP_PBA_SUPPORTED_FEAT_VCARD_SELECTING 0x0010 +#define ESP_PBA_SUPPORTED_FEAT_ENHANCED_MISSED_CALLS 0x0020 +#define ESP_PBA_SUPPORTED_FEAT_X_BT_UCI_VCARD_PROPERTY 0x0040 +#define ESP_PBA_SUPPORTED_FEAT_X_BT_UID_VCARD_PROPERTY 0x0080 +#define ESP_PBA_SUPPORTED_FEAT_CONTACT_REFERENCING 0x0100 +#define ESP_PBA_SUPPORTED_FEAT_DEFAULT_CONTACT_IMAGE_FORMAT 0x0200 diff --git a/components/bt/host/bluedroid/api/include/api/esp_pbac_api.h b/components/bt/host/bluedroid/api/include/api/esp_pbac_api.h new file mode 100644 index 000000000000..131d53f85966 --- /dev/null +++ b/components/bt/host/bluedroid/api/include/api/esp_pbac_api.h @@ -0,0 +1,340 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_bt_defs.h" +#include "esp_pba_defs.h" + +#define ESP_PBAC_INVALID_HANDLE 0 /*!< invalid handle value */ + +typedef uint16_t esp_pbac_conn_hdl_t; + +/** + * @brief PBA client callback events + */ +typedef enum { + ESP_PBAC_INIT_EVT, /*!< PBA client initialized event */ + ESP_PBAC_DEINIT_EVT, /*!< PBA client de-initialized event */ + ESP_PBAC_CONNECTION_STATE_EVT, /*!< PBA client connection state changed event */ + ESP_PBAC_PULL_PHONE_BOOK_RESPONSE_EVT, /*!< Response of pull phone book */ + ESP_PBAC_SET_PHONE_BOOK_RESPONSE_EVT, /*!< Response of set phone book */ + ESP_PBAC_PULL_VCARD_LISTING_RESPONSE_EVT, /*!< Response of pull vCard listing */ + ESP_PBAC_PULL_VCARD_ENTRY_RESPONSE_EVT, /*!< Response of pull vCard entry */ +} esp_pbac_event_t; + +/** + * @brief PBA client status code + */ +typedef enum { + ESP_PBAC_SUCCESS = 0, /*!< Operation success */ + ESP_PBAC_FAILURE, /*!< Generic failure */ + ESP_PBAC_ALREADY_CONN, /*!< Connection to peer device already exist */ + ESP_PBAC_NO_RESOURCE, /*!< No more resource */ + ESP_PBAC_SDP_FAIL, /*!< Connection failed in SDP */ + ESP_PBAC_GOEP_FAIL, /*!< Operation failed in GOEP */ + ESP_PBAC_AUTH_FAIL, /*!< Connection failed in OBEX authentication */ + ESP_PBAC_DEINIT, /*!< Connection closed due to pba client is deinit */ + + /* these error code is related to OBEX */ + ESP_PBAC_BAD_REQUEST = 0xC0, /*!< Server couldn't understand request */ + ESP_PBAC_UNAUTHORIZED = 0xC1, /*!< Unauthorized */ + ESP_PBAC_FORBIDDEN = 0xC3, /*!< Operation is understood but refused */ + ESP_PBAC_NOT_FOUND = 0xC4, /*!< Not found */ + ESP_PBAC_NOT_ACCEPTABLE = 0xC6, /*!< Not Acceptable */ + ESP_PBAC_PRECONDITION_FAILED = 0xCC, /*!< Precondition failed */ + ESP_PBAC_NOT_IMPLEMENTED = 0xD1, /*!< Not implemented */ + ESP_PBAC_SERVICE_UNAVAILABLE = 0xD3, /*!< Service unavailable */ +} esp_pbac_status_t; + +/** + * @brief PBA client set phone book flags + */ +typedef enum { + ESP_PBAC_SET_PHONE_BOOK_FLAGS_ROOT = 0x02, /*!< Go back to root, name should set to empty string, not NULL */ + ESP_PBAC_SET_PHONE_BOOK_FLAGS_DOWN = 0x02, /*!< Go down 1 level, name should set to child folder */ + ESP_PBAC_SET_PHONE_BOOK_FLAGS_UP = 0x03, /*!< Go up 1 level, name is optional */ +} esp_pbac_set_phone_book_flags_t; + +/** + * @brief PBA client pull phone book optional application parameter + */ +typedef struct { + uint8_t include_property_selector : 1; /*!< 1 if app param include property_selector */ + uint8_t include_format : 1; /*!< 1 if app param include format */ + uint8_t include_max_list_count : 1; /*!< 1 if app param include max_list_count */ + uint8_t include_list_start_offset : 1; /*!< 1 if app param include list_start_offset */ + uint8_t include_reset_new_missed_calls : 1; /*!< 1 if app param include reset_new_missed_calls */ + uint8_t include_vcard_selector : 1; /*!< 1 if app param include vcard_selector */ + uint8_t include_vcard_selector_operator : 1; /*!< 1 if app param include vcard_selector_operator */ + uint8_t format; /*!< 0x00 = 2.1, 0x01 = 3.0 */ + uint8_t reset_new_missed_calls; /*!< 0x01 = Reset */ + uint8_t vcard_selector_operator; /*!< 0x00 = OR, 0x01 = AND */ + uint16_t max_list_count; /*!< 0x0000 to 0xFFFF */ + uint16_t list_start_offset; /*!< 0x0000 to 0xFFFF */ + uint64_t property_selector; /*!< 64 bits mask */ + uint64_t vcard_selector; /*!< 64 bits mask */ +} esp_pbac_pull_phone_book_app_param_t; + +/** + * @brief PBA client pull vCard listing optional application parameter + */ +typedef struct { + uint8_t include_order : 1; /*!< 1 if app param include order */ + uint8_t include_search_value : 1; /*!< 1 if app param include search_value */ + uint8_t include_search_property : 1; /*!< 1 if app param include search_property */ + uint8_t include_max_list_count : 1; /*!< 1 if app param include max_list_count */ + uint8_t include_list_start_offset : 1; /*!< 1 if app param include list_start_offset */ + uint8_t include_reset_new_missed_calls : 1; /*!< 1 if app param include reset_new_missed_calls */ + uint8_t include_vcard_selector : 1; /*!< 1 if app param include vcard_selector */ + uint8_t include_vcard_selector_operator : 1; /*!< 1 if app param include vcard_selector_operator */ + uint8_t order; /*!< 0x00 = indexed, 0x01 = alphanumeric */ + uint8_t search_property; /*!< 0x00 = Name, 0x01 = Number, 0x02 = Sound */ + uint8_t reset_new_missed_calls; /*!< 0x01 = Reset */ + uint8_t vcard_selector_operator; /*!< 0x00 = OR, 0x01 = AND */ + uint16_t max_list_count; /*!< 0x0000 to 0xFFFF */ + uint16_t list_start_offset; /*!< 0x0000 to 0xFFFF */ + char *search_value; /*!< Text */ + uint64_t vcard_selector; /*!< 64 bits mask */ +} esp_pbac_pull_vcard_listing_app_param_t; + +/** + * @brief PBA client pull vCard entry optional application parameter + */ +typedef struct { + uint8_t include_property_selector : 1; /*!< 1 if app param include property_selector */ + uint8_t include_format : 1; /*!< 1 if app param include format */ + uint8_t format; /*!< 0x00 = 2.1, 0x01 = 3.0 */ + uint64_t property_selector; /*!< 64 bits mask */ +} esp_pbac_pull_vcard_entry_app_param_t; + +/** + * @brief PBA client callback parameters + */ +typedef union { + /** + * @brief ESP_PBAC_CONNECTION_STATE_EVT + */ + struct pbac_conn_stat_param { + bool connected; /*!< whether pba client is connected to server */ + esp_pbac_conn_hdl_t handle; /*!< connection handle, non zeros if exist */ + uint8_t peer_supported_repo; /*!< peer supported repositories */ + uint32_t peer_supported_feat; /*!< peer supported features */ + esp_bd_addr_t remote_bda; /*!< remote bluetooth device address */ + esp_pbac_status_t reason; /*!< reason if disconnect */ + } conn_stat; /*!< PBA client connection status */ + + /** + * @brief ESP_PBAC_PULL_PHONE_BOOK_RESPONSE_EVT + */ + struct pbac_pull_phone_book_rsp_param { + esp_pbac_conn_hdl_t handle; /*!< PBA client connection handle */ + esp_pbac_status_t result; /*!< operation result, ESP_PBAC_SUCCESS if success */ + bool final; /*!< whether this is the final response packet */ + uint8_t *data; /*!< response data */ + uint16_t data_len; /*!< response data len */ + /* The following are the application parameters */ + uint8_t include_phone_book_size : 1; /*!< 1 if app param include phone_book_size */ + uint8_t include_new_missed_calls : 1; /*!< 1 if app param include new_missed_calls */ + uint8_t include_primary_folder_version : 1; /*!< 1 if app param include primary_folder_version */ + uint8_t include_secondary_folder_version : 1; /*!< 1 if app param include secondary_folder_version */ + uint8_t include_database_identifier : 1; /*!< 1 if app param include database_identifier */ + uint8_t new_missed_calls; /*!< 0x00 to 0xFF */ + uint16_t phone_book_size; /*!< 0x0000 to 0xFFFF */ + uint8_t *primary_folder_version; /*!< 0 to (2^128 -1) */ + uint8_t *secondary_folder_version; /*!< 0 to (2^128 -1) */ + uint8_t *database_identifier; /*!< 0 to (2^128 -1) */ + } pull_phone_book_rsp; /*!< pull phone book response */ + + /** + * @brief ESP_PBAC_SET_PHONE_BOOK_RESPONSE_EVT + */ + struct pbac_set_phone_book_rsp_param { + esp_pbac_conn_hdl_t handle; /*!< PBA client connection handle */ + esp_pbac_status_t result; /*!< operation result, ESP_PBAC_SUCCESS if success */ + } set_phone_book_rsp; /*!< set phone book response, always the final response */ + + /** + * @brief ESP_PBAC_PULL_VCARD_LISTING_RESPONSE_EVT + */ + struct pbac_pull_vcard_listing_rsp_param { + esp_pbac_conn_hdl_t handle; /*!< PBA client connection handle */ + esp_pbac_status_t result; /*!< operation result, ESP_PBAC_SUCCESS if success */ + bool final; /*!< whether this is the final response packet */ + uint8_t *data; /*!< response data */ + uint16_t data_len; /*!< response data len */ + /* The following are the application parameters */ + uint8_t include_phone_book_size : 1; /*!< 1 if app param include phone_book_size */ + uint8_t include_new_missed_calls : 1; /*!< 1 if app param include new_missed_calls */ + uint8_t include_primary_folder_version : 1; /*!< 1 if app param include primary_folder_version */ + uint8_t include_secondary_folder_version : 1; /*!< 1 if app param include secondary_folder_version */ + uint8_t include_database_identifier : 1; /*!< 1 if app param include database_identifier */ + uint8_t new_missed_calls; /*!< 0x00 to 0xFF */ + uint16_t phone_book_size; /*!< 0x0000 to 0xFFFF */ + uint8_t *primary_folder_version; /*!< 0 to (2^128 -1) */ + uint8_t *secondary_folder_version; /*!< 0 to (2^128 -1) */ + uint8_t *database_identifier; /*!< 0 to (2^128 -1) */ + } pull_vcard_listing_rsp; /*!< pull vcard listing response */ + + /** + * @brief ESP_PBAC_PULL_VCARD_ENTRY_RESPONSE_EVT + */ + struct pbac_pull_vcard_entry_rsp_param { + esp_pbac_conn_hdl_t handle; /*!< PBA client connection handle */ + esp_pbac_status_t result; /*!< operation result, ESP_PBAC_SUCCESS if success */ + bool final; /*!< whether this is the final response packet */ + uint8_t *data; /*!< response data */ + uint16_t data_len; /*!< response data len */ + /* The following are the application parameters */ + uint8_t include_database_identifier : 1; /*!< 1 if app param include database_identifier */ + uint8_t *database_identifier; /*!< 0 to (2^128 -1) */ + } pull_vcard_entry_rsp; /*!< pull vcard listing response */ +} esp_pbac_param_t; + +/** + * @brief PBA client callback function type + * + * @param event : Event type + * + * @param param : Pointer to callback parameter + */ +typedef void (*esp_pbac_callback_t)(esp_pbac_event_t event, esp_pbac_param_t *param); + + +/** + * @brief This function is called to register a user callbacks in PBA client. + * + * @param[in] callback: pointer to the user callback function. + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_register_callback(esp_pbac_callback_t callback); + +/** + * @brief Initializes PBA client interface. This function should be called after bluedroid + * enable successfully, and should be called after esp_pbac_register_callback. + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_init(void); + +/** + * @brief De-initializes PBA client interface. This will close all PBA client connection. + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_deinit(void); + +/** + * @brief Start the process to establish a connection to PBA server. + * + * @param[in] bd_addr: peer bluetooth device address + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_connect(esp_bd_addr_t bd_addr); + +/** + * @brief Disconnects from the current connected PBA server. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_disconnect(esp_pbac_conn_hdl_t handle); + +/** + * @brief Send a request to pull phone book. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] name: phone book object path and name, shall contain the absolute path + * in the virtual folder architecture + * + * @param[in] app_param: optional application parameter + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_pull_phone_book(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_phone_book_app_param_t *app_param); + +/** + * @brief Send a request to set the current folder in the virtual folder architecture. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] flags: operation flags, one of ESP_PBAC_SET_PHONE_BOOK_FLAGS_XXX + * + * @param[in] name: folder name, if flags is set to ROOT, name should be empty string (""), + * if flags is set to UP, name is optional, if flags is set to DOWN, name + * is mandatory + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_set_phone_book(esp_pbac_conn_hdl_t handle, esp_pbac_set_phone_book_flags_t flags, const char *name); + +/** + * @brief Set the current folder in the virtual folder architecture, use absolute path. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] path: absolute path of the folder intend to set. NULL or empty string will + * set to ROOT + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_set_phone_book2(esp_pbac_conn_hdl_t handle, const char *path); + +/** + * @brief Send a request to pull vCard listing. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] name: specifies the name of the folder to be retrieved, uses relative paths, + * shall not include any path information. An empty name (empty string "") + * may be sent to retrieve the vCard Listing object of the current folder. + * However, it is illegal to issue a pull vCard listing request with an + * empty name header from the ‘telecom/’ folder + * + * @param[in] app_param: optional application parameter + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_pull_vcard_listing(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_vcard_listing_app_param_t *app_param); + +/** + * @brief Send a request to pull vCard entry. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] name: vCard name or, if supported, the X-BT-UID of the object to be retrieved. + * uses relative paths,shall not include any path information + * + * @param[in] app_param: optional application parameter + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_pull_vcard_entry(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_vcard_entry_app_param_t *app_param); diff --git a/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c b/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c index a2fdcbaddc5b..d886f593d182 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c @@ -278,6 +278,7 @@ void bta_av_ca_api_get(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, COVER_ART_HEADER_ID_IMG_HANDLE, (UINT8 *)image_handle_utf16, BTA_AV_CA_IMG_HDL_UTF16_LEN); if (p_data->api_ca_get.type == BTA_AV_CA_GET_IMAGE) { GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, COVER_ART_HEADER_ID_IMG_DESCRIPTOR, (UINT8 *)p_data->api_ca_get.image_descriptor, p_data->api_ca_get.image_descriptor_len); + osi_free(p_data->api_ca_get.image_descriptor); } /* always request to enable srm */ GOEPC_RequestSetSRM(p_rcb->cover_art_goep_hdl, TRUE, FALSE); diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_pba_client_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_pba_client_api.h new file mode 100644 index 000000000000..404f8ffd81fe --- /dev/null +++ b/components/bt/host/bluedroid/bta/include/bta/bta_pba_client_api.h @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "common/bt_target.h" +#include "stack/obex_api.h" +#include "bta/bta_pba_defs.h" + +#if BTA_PBA_CLIENT_INCLUDED + +/* Phone Book Access Profile (client) version */ +#define PBAP_PCE_VERSION 0x102 /* v1.2 */ +#define PBAP_PCE_SUPPORTED_FEATURES 0x0000003F /* support all features */ + +/* PBA Client callback events */ +#define BTA_PBA_CLIENT_ENABLE_EVT 0 /* PBA Client enabled */ +#define BTA_PBA_CLIENT_DISABLE_EVT 1 /* PBA Client disabled */ +#define BTA_PBA_CLIENT_REGISTER_EVT 2 /* PBA Client registered */ +#define BTA_PBA_CLIENT_DEREGISTER_EVT 3 /* PBA Client deregistered */ +#define BTA_PBA_CLIENT_CONN_OPEN_EVT 4 /* PBA Client connection opened */ +#define BTA_PBA_CLIENT_CONN_CLOSE_EVT 5 /* PBA Client connection closed */ +#define BTA_PBA_CLIENT_PULL_PHONE_BOOK_RSP_EVT 6 /* PBA Client pull phone book response */ +#define BTA_PBA_CLIENT_SET_PHONE_BOOK_RSP_EVT 7 /* PBA Client set phone book response */ +#define BTA_PBA_CLIENT_PULL_VCARD_LISTING_RSP_EVT 8 /* PBA Client pull vCard listing response */ +#define BTA_PBA_CLIENT_PULL_VCARD_ENTRY_RSP_EVT 9 /* PBA Client pull vCard entry response */ + +typedef enum { + BTA_PBA_CLIENT_NO_ERROR, /* operation success */ + BTA_PBA_CLIENT_FAIL, /* general fail */ + BTA_PBA_CLIENT_ALREADY_CONN, /* connection to peer device already exist */ + BTA_PBA_CLIENT_NO_RESOURCE, /* no resource */ + BTA_PBA_CLIENT_SDP_ERROR, /* error in sdp */ + BTA_PBA_CLIENT_GOEP_ERROR, /* error in goep */ + BTA_PBA_CLIENT_AUTH_FAIL, /* authenticate failed */ + BTA_PBA_CLIENT_DISABLE, /* operation failed due to pba client is disable */ + + /* these error code is related to OBEX */ + BTA_PBA_CLIENT_BAD_REQUEST = 0xC0, /* Server couldn't understand request */ + BTA_PBA_CLIENT_UNAUTHORIZED = 0xC1, /* Unauthorized */ + BTA_PBA_CLIENT_FORBIDDEN = 0xC3, /* Operation is understood but refused > */ + BTA_PBA_CLIENT_NOT_FOUND = 0xC4, /* Not found */ + BTA_PBA_CLIENT_NOT_ACCEPTABLE = 0xC6, /* Not Acceptable */ + BTA_PBA_CLIENT_PRECONDITION_FAILED = 0xCC, /* Precondition failed */ + BTA_PBA_CLIENT_NOT_IMPLEMENTED = 0xD1, /* Not implemented */ + BTA_PBA_CLIENT_SERVICE_UNAVAILABLE = 0xD3, /* Service unavailable */ +} tBTA_PBA_CLIENT_ERR; + +typedef struct { + UINT16 handle; /* connection handle */ + tBTA_PBA_CLIENT_ERR error; /* error code */ + BD_ADDR bd_addr; /* peer BD addr */ + UINT8 peer_supported_repo; /* peer supported repositories */ + UINT32 peer_supported_feat; /* peer supported feature */ +} tBTA_PBA_CLIENT_CONN; + +typedef struct { + tBTA_PBA_CLIENT_ERR status; + UINT16 handle; /* connection handle */ + BOOLEAN final; /* final data event */ + UINT8 *data; /* body data in response packet */ + UINT16 data_len; /* body data length */ + UINT8 *app_param; /* application parameters */ + UINT16 app_param_len; /* application parameters length */ + BT_HDR *pkt; /* raw buff that store all data, should be freed before return */ +} tBTA_PBA_CLIENT_RESPONSE; + +/* union of data associated with PBA Client callback */ +typedef union { + tBTA_PBA_CLIENT_CONN conn; /* CONN_OPEN_EVT, CONN_CLOSE_EVT */ + tBTA_PBA_CLIENT_RESPONSE response; /* XXX_RSP_EVT */ +} tBTA_PBA_CLIENT; + +typedef UINT8 tBTA_PBA_CLIENT_EVT; + +typedef void (tBTA_PBA_CLIENT_CBACK)(tBTA_PBA_CLIENT_EVT event, tBTA_PBA_CLIENT *p_data); + +void BTA_PbaClientEnable(tBTA_PBA_CLIENT_CBACK *p_cback); +void BTA_PbaClientDisable(void); +void BTA_PbaClientRegister(const char *server_name); +void BTA_PbaClientDeregister(void); +void BTA_PbaClientOpen(BD_ADDR bd_addr, tBTA_SEC sec_mask, UINT32 supported_feat, UINT16 mtu); +void BTA_PbaClientClose(UINT16 handle); +void BTA_PbaClientPullPhoneBook(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len); +void BTA_PbaClientSetPhoneBook(UINT16 handle, UINT8 flags, char *name); +void BTA_PbaClientPullvCardListing(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len); +void BTA_PbaClientPullvCardEntry(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len); + +#endif diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_pba_defs.h b/components/bt/host/bluedroid/bta/include/bta/bta_pba_defs.h new file mode 100644 index 000000000000..ce92f9cd3be5 --- /dev/null +++ b/components/bt/host/bluedroid/bta/include/bta/bta_pba_defs.h @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/* PBAP supported repositories */ +#define BTA_PBAP_REPO_LOCAL_PHONEBOOK 0x01 +#define BTA_PBAP_REPO_SIM_CARD 0x02 +#define BTA_PBAP_REPO_SPEED_DIAL 0x04 +#define BTA_PBAP_REPO_FAVORITES 0x08 + +/* PBAP supported features */ +#define BTA_PBAP_FEAT_DOWNLOAD 0x0001 +#define BTA_PBAP_FEAT_BROWSING 0x0002 +#define BTA_PBAP_FEAT_DATABASE_IDENTIFIER 0x0004 +#define BTA_PBAP_FEAT_FLODER_VERSION_COUNTER 0x0008 +#define BTA_PBAP_FEAT_VCARD_SELECTING 0x0010 +#define BTA_PBAP_FEAT_ENHANCED_MISSED_CALLS 0x0020 +#define BTA_PBAP_FEAT_X_BT_UCI_VCARD_PROPERTY 0x0040 +#define BTA_PBAP_FEAT_X_BT_UID_VCARD_PROPERTY 0x0080 +#define BTA_PBAP_FEAT_CONTACT_REFERENCING 0x0100 +#define BTA_PBAP_FEAT_DEFAULT_CONTACT_IMAGE_FORMAT 0x0200 + +/* PBAP default supported features */ +#define BTA_PBAP_DEFAULT_SUPPORTED_FEATURES 0x0003 + +/* Application parameters tag id */ +#define BTA_PBAP_APP_PARAM_ORDER 0x01 +#define BTA_PBAP_APP_PARAM_SEARCH_VALUE 0x02 +#define BTA_PBAP_APP_PARAM_SEARCH_PROPERTY 0x03 +#define BTA_PBAP_APP_PARAM_MAX_LIST_COUNT 0x04 +#define BTA_PBAP_APP_PARAM_LIST_START_OFFSET 0x05 +#define BTA_PBAP_APP_PARAM_PROPERTY_SELECTOR 0x06 +#define BTA_PBAP_APP_PARAM_FORMAT 0x07 +#define BTA_PBAP_APP_PARAM_PHONE_BOOK_SIZE 0x08 +#define BTA_PBAP_APP_PARAM_NEW_MISSED_CALLS 0x09 +#define BTA_PBAP_APP_PARAM_PRIMARY_FOLDER_VERSION 0x0A +#define BTA_PBAP_APP_PARAM_SECONDARY_FOLDER_VERSION 0x0B +#define BTA_PBAP_APP_PARAM_VCARD_SELECTOR 0x0C +#define BTA_PBAP_APP_PARAM_DATABASE_IDENTIFIER 0x0D +#define BTA_PBAP_APP_PARAM_VCARD_SELECTOR_OPERATOR 0x0E +#define BTA_PBAP_APP_PARAM_RESET_NEW_MISSED_CALLS 0x0F +#define BTA_PBAP_APP_PARAM_PBAP_SUPPORTED_FEATURES 0x10 + +/* Application parameters length (except SearchValue) */ +#define BTA_PBAP_APP_PARAM_LENGTH_ORDER 1 +#define BTA_PBAP_APP_PARAM_LENGTH_SEARCH_PROPERTY 1 +#define BTA_PBAP_APP_PARAM_LENGTH_MAX_LIST_COUNT 2 +#define BTA_PBAP_APP_PARAM_LENGTH_LIST_START_OFFSET 2 +#define BTA_PBAP_APP_PARAM_LENGTH_PROPERTY_SELECTOR 8 +#define BTA_PBAP_APP_PARAM_LENGTH_FORMAT 1 +#define BTA_PBAP_APP_PARAM_LENGTH_PHONE_BOOK_SIZE 2 +#define BTA_PBAP_APP_PARAM_LENGTH_NEW_MISSED_CALLS 1 +#define BTA_PBAP_APP_PARAM_LENGTH_PRIMARY_FOLDER_VERSION 16 +#define BTA_PBAP_APP_PARAM_LENGTH_SECONDARY_FOLDER_VERSION 16 +#define BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR 8 +#define BTA_PBAP_APP_PARAM_LENGTH_DATABASE_IDENTIFIER 16 +#define BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR_OPERATOR 1 +#define BTA_PBAP_APP_PARAM_LENGTH_RESET_NEW_MISSED_CALLS 1 +#define BTA_PBAP_APP_PARAM_LENGTH_PBAP_SUPPORTED_FEATURES 4 + +/* Application parameter tag (1 byte) + Application parameter length (1 byte) */ +#define BTA_PBAP_APP_PARAM_HEADER_LENGTH 2 + +/* The minimal buff size that can hold all pull phone book application parameters */ +#define BTA_PBAP_PULL_PHONE_BOOK_APP_PARAM_BUFF_SIZE_MIN 37 + +/* The minimal buff size that can hold pull vCard listing application parameters, except the value of SearchValue */ +#define BTA_PBAP_PULL_VCARD_LISTING_APP_PARAM_BUFF_SIZE_MIN 34 + +/* The minimal buff size that can hold all pull vCard entry application parameters */ +#define BTA_PBAP_PULL_VCARD_ENTRY_APP_PARAM_BUFF_SIZE_MIN 13 + +/* Application parameters bit mask */ +#define PBAP_APP_PARAM_BIT_MASK_ORDER 0x0001 +#define PBAP_APP_PARAM_BIT_MASK_SEARCH_VALUE 0x0002 +#define PBAP_APP_PARAM_BIT_MASK_SEARCH_PROPERTY 0x0004 +#define PBAP_APP_PARAM_BIT_MASK_MAX_LIST_COUNT 0x0008 +#define PBAP_APP_PARAM_BIT_MASK_LIST_START_OFFSET 0x0010 +#define PBAP_APP_PARAM_BIT_MASK_PROPERTY_SELECTOR 0x0020 +#define PBAP_APP_PARAM_BIT_MASK_FORMAT 0x0040 +#define PBAP_APP_PARAM_BIT_MASK_PHONE_BOOK_SIZE 0x0080 +#define PBAP_APP_PARAM_BIT_MASK_NEW_MISSED_CALLS 0x0100 +#define PBAP_APP_PARAM_BIT_MASK_PRIMARY_FOLDER_VERSION 0x0200 +#define PBAP_APP_PARAM_BIT_MASK_SECONDARY_FOLDER_VERSION 0x0400 +#define PBAP_APP_PARAM_BIT_MASK_VCARD_SELECTOR 0x0800 +#define PBAP_APP_PARAM_BIT_MASK_DATABASE_IDENTIFIER 0x1000 +#define PBAP_APP_PARAM_BIT_MASK_VCARD_SELECTOR_OPERATOR 0x2000 +#define PBAP_APP_PARAM_BIT_MASK_RESET_NEW_MISSED_CALLS 0x4000 +#define PBAP_APP_PARAM_BIT_MASK_PBAP_SUPPORTED_FEATURES 0x8000 diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c new file mode 100644 index 000000000000..8c69e6244a64 --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c @@ -0,0 +1,702 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "osi/allocator.h" +#include "common/bt_target.h" +#include "stack/bt_types.h" +#include "stack/obex_api.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "bta_pba_client_int.h" + +#if BTA_PBA_CLIENT_INCLUDED + +static const UINT8 pbap_target_uuid[16] = {0x79, 0x61, 0x35, 0xf0, 0xf0, 0xc5, 0x11, 0xd8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66}; +static const char *type_pull_phone_book = "x-bt/phonebook"; +static const char *type_pull_vcard_listing = "x-bt/vcard-listing"; +static const char *type_pull_vcard_entry = "x-bt/vcard"; + +#define TYPE_LEN_PULL_PHONE_BOOK 15 +#define TYPE_LEN_PULL_VCARD_LISTING 19 +#define TYPE_LEN_PULL_VCARD_ENTRY 11 + +static void free_ccb(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + /* free sdp db */ + bta_pba_client_free_db(p_ccb); + + /* clear all field, set allocated to 0 */ + memset(p_ccb, 0, sizeof(tBTA_PBA_CLIENT_CCB)); +} + +static void close_goepc_and_report(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_ERR reason) +{ + /* remove goep connection */ + if (p_ccb->goep_handle != 0) { + GOEPC_Close(p_ccb->goep_handle); + p_ccb->goep_handle = 0; + } + + /* report connection closed event */ + tBTA_PBA_CLIENT_CONN conn; + conn.handle = p_ccb->allocated; + conn.error = reason; + bdcpy(conn.bd_addr, p_ccb->bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_CLOSE_EVT, (tBTA_PBA_CLIENT *)&conn); + + /* free ccb */ + free_ccb(p_ccb); +} + +static void build_and_send_empty_get_req(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + tOBEX_PARSE_INFO info = {0}; + info.opcode = OBEX_OPCODE_GET_FINAL; + /* empty get request, try to use a smaller buff size */ + UINT16 tx_buff_size = BT_SMALL_BUFFER_SIZE < p_ccb->max_tx ? BT_SMALL_BUFFER_SIZE : p_ccb->max_tx; + UINT16 ret = GOEPC_PrepareRequest(p_ccb->goep_handle, &info, tx_buff_size); + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_ccb->goep_cid), 4); + ret |= GOEPC_SendRequest(p_ccb->goep_handle); + if (ret != GOEP_SUCCESS) { + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_GOEP_ERROR); + } +} + +static uint8_t get_operation_response_event(tBTA_PBA_CLIENT_OP operation) +{ + uint8_t event = 0; + switch (operation) + { + case BTA_PBA_CLIENT_OP_PULL_PHONE_BOOK: + event = BTA_PBA_CLIENT_PULL_PHONE_BOOK_RSP_EVT; + break; + case BTA_PBA_CLIENT_OP_SET_PHONE_BOOK: + event = BTA_PBA_CLIENT_SET_PHONE_BOOK_RSP_EVT; + break; + case BTA_PBA_CLIENT_OP_PULL_VCARD_LISTING: + event = BTA_PBA_CLIENT_PULL_VCARD_LISTING_RSP_EVT; + break; + case BTA_PBA_CLIENT_OP_PULL_VCARD_ENTRY: + event = BTA_PBA_CLIENT_PULL_VCARD_ENTRY_RSP_EVT; + break; + default: + assert(0); + break; + } + return event; +} + +static tBTA_PBA_CLIENT_ERR calculate_response_error(uint8_t response_code) +{ + tBTA_PBA_CLIENT_ERR error = BTA_PBA_CLIENT_FAIL; + /* always treat error response code as final response */ + response_code |= OBEX_FINAL_BIT_MASK; + switch (response_code) + { + case (OBEX_RESPONSE_CODE_BAD_REQUEST | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_BAD_REQUEST; + break; + case (OBEX_RESPONSE_CODE_UNAUTHORIZED | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_UNAUTHORIZED; + break; + case (OBEX_RESPONSE_CODE_FORBIDDEN | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_FORBIDDEN; + break; + case (OBEX_RESPONSE_CODE_NOT_FOUND | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_NOT_FOUND; + break; + case (OBEX_RESPONSE_CODE_NOT_ACCEPTABLE | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_NOT_ACCEPTABLE; + break; + case (OBEX_RESPONSE_CODE_PRECONDITION_FAILED | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_PRECONDITION_FAILED; + break; + case (OBEX_RESPONSE_CODE_NOT_IMPLEMENTED | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_NOT_IMPLEMENTED; + break; + case (OBEX_RESPONSE_CODE_SERVICE_UNAVAILABLE | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_SERVICE_UNAVAILABLE; + break; + default: + break; + } + return error; +} + +static void report_data_event(tBTA_PBA_CLIENT_CCB *p_ccb, UINT8 *data, UINT16 data_len, UINT8 *app_param, + UINT16 app_param_len, BOOLEAN final, BT_HDR *pkt) +{ + uint8_t event = get_operation_response_event(p_ccb->operation); + tBTA_PBA_CLIENT_RESPONSE response; + response.status = BTA_PBA_CLIENT_NO_ERROR; + response.handle = p_ccb->allocated; + response.final = final; + response.data_len = data_len; + response.data = data; + response.app_param_len = app_param_len; + response.app_param = app_param; + response.pkt = pkt; + bta_pba_client_cb.p_cback(event, (tBTA_PBA_CLIENT *)&response); +} + +static void report_error_data_event(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_ERR status) +{ + uint8_t event = get_operation_response_event(p_ccb->operation); + tBTA_PBA_CLIENT_RESPONSE response; + memset(&response, 0, sizeof(tBTA_PBA_CLIENT_RESPONSE)); + response.status = status; + response.handle = p_ccb->allocated; + response.final = TRUE; + bta_pba_client_cb.p_cback(event, (tBTA_PBA_CLIENT *)&response); +} + +static void ascii_to_utf16(const UINT8 *src, UINT16 len, UINT8 *dst) +{ + UINT16 pos = 0; + for (int i = 0 ; i < len ; i++){ + dst[pos++] = 0; + dst[pos++] = src[i]; + } +} + +void bta_pba_client_api_enable(tBTA_PBA_CLIENT_DATA *p_data) +{ + /* initialize control block */ + memset(&bta_pba_client_cb, 0, sizeof(tBTA_PBA_CLIENT_CB)); + + /* store callback function */ + bta_pba_client_cb.p_cback = p_data->api_enable.p_cback; + + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_ENABLE_EVT, NULL); +} + +void bta_pba_client_api_disable(tBTA_PBA_CLIENT_DATA *p_data) +{ + if (!bta_sys_is_register(BTA_ID_PBC)) { + return; + } + /* deregister with BTA system manager */ + bta_sys_deregister(BTA_ID_PBC); + + /* close all connections */ + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated) { + close_goepc_and_report(&bta_pba_client_cb.ccb[i], BTA_PBA_CLIENT_DISABLE); + } + } + + /* store and clear callback function */ + tBTA_PBA_CLIENT_CBACK *p_cback = bta_pba_client_cb.p_cback; + bta_pba_client_cb.p_cback = NULL; + + if(p_cback) { + p_cback(BTA_PBA_CLIENT_DISABLE_EVT, NULL); + } +} + +void bta_pba_client_api_register(tBTA_PBA_CLIENT_DATA *p_data) +{ + /* create SDP records */ + bta_pba_client_create_record(p_data->api_register.name); + + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_REGISTER_EVT, NULL); +} + +void bta_pba_client_api_deregister(tBTA_PBA_CLIENT_DATA *p_data) +{ + /* delete SDP records */ + bta_pba_client_del_record(); + + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_DEREGISTER_EVT, NULL); +} + +void bta_pba_client_api_open(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + p_ccb->sec_mask = p_data->api_open.sec_mask; + if (p_data->api_open.mtu < PBA_CLIENT_MIN_MTU || p_data->api_open.mtu > PBA_CLIENT_MAX_MTU) { + p_ccb->max_rx = PBA_CLIENT_MAX_MTU; + p_ccb->max_tx = PBA_CLIENT_MAX_MTU; + } + else { + p_ccb->max_rx = p_data->api_open.mtu; + p_ccb->max_tx = p_data->api_open.mtu; + } + bdcpy(p_ccb->bd_addr, p_data->api_open.bd_addr); + p_ccb->our_supported_feat = p_data->api_open.supported_feat; + if (!bta_pba_client_do_disc(p_ccb)) { + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_SDP_ERROR); + } +} + +void bta_pba_client_api_close(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + UNUSED(p_data); + tOBEX_PARSE_INFO info = {0}; + + info.opcode = OBEX_OPCODE_DISCONNECT; + UINT16 tx_buff_size = BT_SMALL_BUFFER_SIZE < p_ccb->max_tx ? BT_SMALL_BUFFER_SIZE : p_ccb->max_tx; + UINT16 ret = GOEPC_PrepareRequest(p_ccb->goep_handle, &info, tx_buff_size); + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_ccb->goep_cid), 4); + ret |= GOEPC_SendRequest(p_ccb->goep_handle); + + if (ret != GOEP_SUCCESS) { + /* anyway, this close operation is requested by upper, set reason to success */ + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_NO_ERROR); + } +} + +void bta_pba_client_api_req(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tOBEX_PARSE_INFO info = {0}; + UINT16 ret; + + if (p_data->api_req.operation == BTA_PBA_CLIENT_OP_SET_PHONE_BOOK) { + info.opcode = OBEX_OPCODE_SETPATH; + info.flags = p_data->api_req.flags; + } + else { + info.opcode = OBEX_OPCODE_GET_FINAL; + } + ret = GOEPC_PrepareRequest(p_ccb->goep_handle, &info, p_ccb->max_tx); + if (ret != GOEP_SUCCESS) { + goto error; + } + + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_ccb->goep_cid), 4); + + if (p_data->api_req.operation != BTA_PBA_CLIENT_OP_SET_PHONE_BOOK) { + ret |= GOEPC_RequestSetSRM(p_ccb->goep_handle, TRUE, FALSE); + } + + if (p_data->api_req.name) { + UINT16 name_len = strlen(p_data->api_req.name) + 1; + if (name_len == 1) { + /* empty string, add empty name header */ + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_NAME, NULL, 0); + } + else { + UINT8 *utf16_name = osi_malloc(2 * name_len); + assert(utf16_name != NULL); + ascii_to_utf16((UINT8 *)p_data->api_req.name, name_len, utf16_name); + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_NAME, utf16_name, 2 * name_len); + osi_free(utf16_name); + } + osi_free(p_data->api_req.name); + p_data->api_req.name = NULL; + } + + switch (p_data->api_req.operation) + { + case BTA_PBA_CLIENT_OP_PULL_PHONE_BOOK: + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_TYPE, (const UINT8 *)type_pull_phone_book, TYPE_LEN_PULL_PHONE_BOOK); + break; + case BTA_PBA_CLIENT_OP_PULL_VCARD_LISTING: + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_TYPE, (const UINT8 *)type_pull_vcard_listing, TYPE_LEN_PULL_VCARD_LISTING); + break; + case BTA_PBA_CLIENT_OP_PULL_VCARD_ENTRY: + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_TYPE, (const UINT8 *)type_pull_vcard_entry, TYPE_LEN_PULL_VCARD_ENTRY); + break; + default: + break; + } + + if (p_data->api_req.app_param) { + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_APP_PARAM, p_data->api_req.app_param, p_data->api_req.app_param_len); + osi_free(p_data->api_req.app_param); + } + + ret |= GOEPC_SendRequest(p_ccb->goep_handle); + p_ccb->operation = p_data->api_req.operation; + if (ret != GOEP_SUCCESS) { + goto error; + } + return; + +error: + if (p_data->api_req.name) { + osi_free(p_data->api_req.name); + } + if (p_data->api_req.app_param) { + osi_free(p_data->api_req.app_param); + } + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_GOEP_ERROR); +} + +static void goep_event_callback(UINT16 handle, UINT8 event, tGOEPC_MSG *p_msg) +{ + tBTA_PBA_CLIENT_DATA *p_data = NULL; + + switch (event) + { + case GOEPC_OPENED_EVT: + p_data = (tBTA_PBA_CLIENT_DATA *)osi_malloc(sizeof(tBTA_PBA_CLIENT_GOEP_CONNECT)); + assert(p_data != NULL); + p_data->goep_connect.hdr.event = BTA_PBA_CLIENT_GOEP_CONNECT_EVT; + p_data->goep_connect.hdr.layer_specific = handle; + p_data->goep_connect.our_mtu = p_msg->opened.our_mtu; + p_data->goep_connect.peer_mtu = p_msg->opened.peer_mtu; + break; + case GOEPC_CLOSED_EVT: + p_data = (tBTA_PBA_CLIENT_DATA *)osi_malloc(sizeof(tBTA_PBA_CLIENT_GOEP_DISCONNECT)); + assert(p_data != NULL); + p_data->goep_disconnect.hdr.event = BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT; + p_data->goep_disconnect.hdr.layer_specific = handle; + p_data->goep_disconnect.reason = p_msg->closed.reason; + break; + case GOEPC_RESPONSE_EVT: + p_data = (tBTA_PBA_CLIENT_DATA *)osi_malloc(sizeof(tBTA_PBA_CLIENT_GOEP_RESPONSE)); + assert(p_data != NULL); + p_data->goep_response.hdr.layer_specific = handle; + p_data->goep_response.pkt = p_msg->response.pkt; + p_data->goep_response.opcode = p_msg->response.opcode; + p_data->goep_response.srm_en = p_msg->response.srm_en; + p_data->goep_response.srm_wait = p_msg->response.srm_wait; + if (p_msg->response.final) { + p_data->goep_response.hdr.event = BTA_PBA_CLIENT_RESPONSE_FINAL_EVT; + } + else { + p_data->hdr.event = BTA_PBA_CLIENT_RESPONSE_EVT; + } + break; + case GOEPC_MTU_CHANGED_EVT: + case GOEPC_CONGEST_EVT: + case GOEPC_UNCONGEST_EVT: + /* ignore these event */ + break; + default: + APPL_TRACE_WARNING("%s, unknown goep event: %d", __FUNCTION__, event); + break; + } + if (p_data != NULL) { + bta_sys_sendmsg(p_data); + } +} + +void bta_pba_client_do_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tBTA_PBA_CLIENT_ERR reason = BTA_PBA_CLIENT_FAIL; + + /* find attr in sdp discovery result */ + BOOLEAN found = bta_pba_client_sdp_find_attr(p_ccb); + if (found) { + tOBEX_SVR_INFO svr = {0}; + if (p_ccb->peer_l2cap_psm != 0) { + /* peer support obex over l2cap, use it */ + svr.tl = OBEX_OVER_L2CAP; + svr.l2cap.psm = p_ccb->peer_l2cap_psm; + svr.l2cap.sec_mask = p_ccb->sec_mask; + svr.l2cap.pref_mtu = 0; + bdcpy(svr.l2cap.addr, p_ccb->bd_addr); + } + else { + /* otherwise, use obex over rfcomm */ + svr.tl = OBEX_OVER_RFCOMM; + svr.rfcomm.scn = p_ccb->peer_rfcomm_scn; + svr.rfcomm.sec_mask = p_ccb->sec_mask; + svr.rfcomm.pref_mtu = 0; + bdcpy(svr.rfcomm.addr, p_ccb->bd_addr); + } + + if (GOEPC_Open(&svr, goep_event_callback, &p_ccb->goep_handle) == GOEP_SUCCESS) { + /* start connection success */ + return; + } + else { + reason = BTA_PBA_CLIENT_GOEP_ERROR; + } + } + else { + reason = BTA_PBA_CLIENT_SDP_ERROR; + } + + /* critical sdp attribute not found or start goep connection failed */ + tBTA_PBA_CLIENT_CONN conn; + conn.handle = 0; + conn.error = reason; + bdcpy(conn.bd_addr, p_ccb->bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_CLOSE_EVT, (tBTA_PBA_CLIENT *)&conn); + + /* free ccb */ + free_ccb(p_ccb); +} + +void bta_pba_client_authenticate(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + /* [todo]: support authenticate */ +} + +void bta_pba_client_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tBTA_PBA_CLIENT_CONN conn; + conn.handle = p_ccb->allocated; + conn.peer_supported_repo = p_ccb->peer_supported_repo; + conn.peer_supported_feat = p_ccb->peer_supported_feat; + conn.error = BTA_PBA_CLIENT_NO_ERROR; + bdcpy(conn.bd_addr, p_ccb->bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_OPEN_EVT, (tBTA_PBA_CLIENT *)&conn); +} + +void bta_pba_client_force_disconnect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + UNUSED(p_data); + + /* force disconnect is requested by upper, set reason to success */ + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_NO_ERROR); +} + +void bta_pba_client_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tOBEX_PARSE_INFO info; + tBTA_PBA_CLIENT_ERR reason = BTA_PBA_CLIENT_GOEP_ERROR; + + OBEX_ParseResponse(p_data->goep_response.pkt, p_data->goep_response.opcode, &info); + if (p_data->goep_response.opcode == OBEX_OPCODE_GET_FINAL && + (info.response_code == OBEX_RESPONSE_CODE_CONTINUE || info.response_code == (OBEX_RESPONSE_CODE_CONTINUE | OBEX_FINAL_BIT_MASK))) { + UINT8 *header = NULL; + UINT8 *body_data = NULL; + UINT16 body_data_len = 0; + UINT8 *app_param = NULL; + UINT16 app_param_len = 0; + while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { + switch (*header) + { + case OBEX_HEADER_ID_BODY: + case OBEX_HEADER_ID_END_OF_BODY: + if (body_data == NULL) { + /* first body header */ + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + else { + /* another body header found */ + report_data_event(p_ccb, body_data, body_data_len, NULL, 0, FALSE, NULL); + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + break; + case OBEX_HEADER_ID_APP_PARAM: + app_param = header + 3; + app_param_len = OBEX_GetHeaderLength(header) - 3; + break; + default: + break; + } + } + if (body_data != NULL || app_param != NULL) { + /* report body data and app param, dont free packet here */ + report_data_event(p_ccb, body_data, body_data_len, app_param, app_param_len, FALSE, p_data->goep_response.pkt); + } + else { + /* not any body data or app param */ + osi_free(p_data->goep_response.pkt); + } + + /* if SRM not enable, we need to send a empty get request */ + if (!p_data->goep_response.srm_en || p_data->goep_response.srm_wait) { + build_and_send_empty_get_req(p_ccb); + } + } + else { + /* unexpected opcode or response code */ + reason = calculate_response_error(info.response_code); + goto error; + } + return; + +error: + if (p_data->goep_response.pkt != NULL) { + osi_free(p_data->goep_response.pkt); + } + close_goepc_and_report(p_ccb, reason); +} + +void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tOBEX_PARSE_INFO info; + UINT8 *header = NULL; + tBTA_PBA_CLIENT_ERR reason = BTA_PBA_CLIENT_FAIL; + + OBEX_ParseResponse(p_data->goep_response.pkt, p_data->goep_response.opcode, &info); + if (p_data->goep_response.opcode == OBEX_OPCODE_CONNECT) { + if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + /* obex connect success */ + if (info.max_packet_length < 255) { + p_ccb->max_tx = 255; + } + else if (p_ccb->max_tx > info.max_packet_length) { + p_ccb->max_tx = info.max_packet_length; + } + BOOLEAN cid_found = false; + while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { + if (*header == OBEX_HEADER_ID_CONNECTION_ID) { + cid_found = true; + memcpy((UINT8 *)(&p_ccb->goep_cid), header + 1, 4); + break; + } + } + if (!cid_found) { + goto error; + } + + BT_HDR *p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR)); + assert(p_buf != NULL); + p_buf->event = BTA_PBA_CLIENT_CONNECT_EVT; + p_buf->layer_specific = p_ccb->allocated; + bta_sys_sendmsg(p_buf); + } + else if (info.response_code == (OBEX_RESPONSE_CODE_UNAUTHORIZED | OBEX_FINAL_BIT_MASK)){ + /* need to authenticate */ + if (p_ccb->authenticate) { + /* already try to authenticate, but failed */ + reason = BTA_PBA_CLIENT_AUTH_FAIL; + goto error; + } + + p_ccb->authenticate = TRUE; + /* [todo]: we don't support authenticate currently */ + goto error; + } + else { + /* other unexpected response code */ + goto error; + } + osi_free(p_data->goep_response.pkt); + } + else if (p_data->goep_response.opcode == OBEX_OPCODE_GET_FINAL) { + /* check response code is success */ + if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + UINT8 *body_data = NULL; + UINT16 body_data_len = 0; + UINT8 *app_param = NULL; + UINT16 app_param_len = 0; + while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { + switch (*header) + { + /* actually, BODY should not in this final response */ + case OBEX_HEADER_ID_BODY: + case OBEX_HEADER_ID_END_OF_BODY: + if (body_data == NULL) { + /* first body header */ + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + else { + /* another body header found */ + report_data_event(p_ccb, body_data, body_data_len, NULL, 0, FALSE, NULL); + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + break; + case OBEX_HEADER_ID_APP_PARAM: + app_param = header + 3; + app_param_len = OBEX_GetHeaderLength(header) - 3; + break; + default: + break; + } + } + if (body_data != NULL || app_param != NULL) { + /* report body data and app param, dont free packet here */ + report_data_event(p_ccb, body_data, body_data_len, app_param, app_param_len, TRUE, p_data->goep_response.pkt); + /* done, return */ + return; + } + } + /* unexpected response code or body data not found */ + reason = calculate_response_error(info.response_code); + report_error_data_event(p_ccb, reason); + osi_free(p_data->goep_response.pkt); + + /* state machine is good, don't goto error */ + } + else if (p_data->goep_response.opcode == OBEX_OPCODE_SETPATH) { + if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + report_data_event(p_ccb, NULL, 0, NULL, 0, TRUE, NULL); + } + else { + reason = calculate_response_error(info.response_code); + report_error_data_event(p_ccb, reason); + } + osi_free(p_data->goep_response.pkt); + } + else if (p_data->goep_response.opcode == OBEX_OPCODE_DISCONNECT) { + /* received disconnect response, close goep connection now */ + reason = BTA_PBA_CLIENT_NO_ERROR; + close_goepc_and_report(p_ccb, reason); + osi_free(p_data->goep_response.pkt); + } + else { + /* unexpected opcode or response code */ + reason = calculate_response_error(info.response_code); + goto error; + } + return; + +error: + if (p_data->goep_response.pkt != NULL) { + osi_free(p_data->goep_response.pkt); + } + close_goepc_and_report(p_ccb, reason); +} + +void bta_pba_client_goep_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + /* limit max rx to our goep mtu */ + if (p_ccb->max_rx > p_data->goep_connect.our_mtu) { + p_ccb->max_rx = p_data->goep_connect.our_mtu; + } + /* limit max tx to peer goep mtu */ + if (p_ccb->max_tx > p_data->goep_connect.peer_mtu) { + p_ccb->max_tx = p_data->goep_connect.peer_mtu; + } + + /* build and send obex connect request */ + tOBEX_PARSE_INFO info = {0}; + info.opcode = OBEX_OPCODE_CONNECT; + info.obex_version_number = OBEX_VERSION_NUMBER; + info.max_packet_length = p_ccb->max_rx; + /* before obex connect response, we dont know the real max_tx, use BT_SMALL_BUFFER_SIZE as tx buff size */ + UINT16 ret = GOEPC_PrepareRequest(p_ccb->goep_handle, &info, BT_SMALL_BUFFER_SIZE); + /* add target header */ + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_TARGET, pbap_target_uuid, 16); + if (p_ccb->send_supported_feat) { + /* add application parameters with supported features */ + UINT8 app_param[6]; + app_param[0] = BTA_PBAP_APP_PARAM_PBAP_SUPPORTED_FEATURES; + app_param[1] = BTA_PBAP_APP_PARAM_LENGTH_PBAP_SUPPORTED_FEATURES; + UINT32_TO_FIELD(&app_param[2], p_ccb->our_supported_feat); + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_APP_PARAM, app_param, 6); + } + ret |= GOEPC_SendRequest(p_ccb->goep_handle); + if (ret != GOEP_SUCCESS) { + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_GOEP_ERROR); + } +} + +void bta_pba_client_goep_disconnect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + p_ccb->goep_handle = 0; + + /* report connection closed event */ + tBTA_PBA_CLIENT_CONN conn; + conn.handle = p_ccb->allocated; + bdcpy(conn.bd_addr, p_ccb->bd_addr); + conn.error = BTA_PBA_CLIENT_GOEP_ERROR; + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_CLOSE_EVT, (tBTA_PBA_CLIENT *)&conn); + free_ccb(p_ccb); +} + +void bta_pba_client_free_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + if (p_data->goep_response.pkt != NULL) { + osi_free(p_data->goep_response.pkt); + } + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_GOEP_ERROR); +} + +#endif diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_api.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_api.c new file mode 100644 index 000000000000..4670c28cf16a --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_api.c @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "osi/allocator.h" +#include "common/bt_target.h" +#include "stack/obex_api.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "bta/bta_sys.h" +#include "bta/bta_api.h" +#include "bta_pba_client_int.h" + +#if BTA_PBA_CLIENT_INCLUDED + +static const tBTA_SYS_REG bta_pba_client_reg = { + bta_pba_client_hdl_event, + BTA_PbaClientDisable +}; + +void BTA_PbaClientEnable(tBTA_PBA_CLIENT_CBACK *p_cback) +{ + tBTA_PBA_CLIENT_API_ENABLE *p_buf; + + if (bta_sys_is_register(BTA_ID_PBC)) { + APPL_TRACE_ERROR("BTA PBA Client already enabled"); + return; + } + + /* register with BTA system manager */ + bta_sys_register(BTA_ID_PBC, &bta_pba_client_reg); + + if ((p_buf = (tBTA_PBA_CLIENT_API_ENABLE *)osi_malloc(sizeof(tBTA_PBA_CLIENT_API_ENABLE))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_ENABLE_EVT; + p_buf->p_cback = p_cback; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientDisable(void) +{ + BT_HDR *p_buf; + + if ((p_buf = (BT_HDR *) osi_malloc(sizeof(BT_HDR))) != NULL) { + p_buf->event = BTA_PBA_CLIENT_API_DISABLE_EVT; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientRegister(const char *server_name) +{ + tBTA_PBA_CLIENT_API_REGISTER *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REGISTER *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REGISTER))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REGISTER_EVT; + memcpy(p_buf->name, server_name, strlen(server_name) + 1); + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientDeregister(void) +{ + BT_HDR *p_buf; + + if ((p_buf = (BT_HDR *) osi_malloc(sizeof(BT_HDR))) != NULL) { + p_buf->event = BTA_PBA_CLIENT_API_DEREGISTER_EVT; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientOpen(BD_ADDR bd_addr, tBTA_SEC sec_mask, UINT32 supported_feat, UINT16 mtu) +{ + tBTA_PBA_CLIENT_API_OPEN *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_OPEN *)osi_malloc(sizeof(tBTA_PBA_CLIENT_API_OPEN))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_OPEN_EVT; + p_buf->sec_mask = sec_mask; + p_buf->mtu = mtu; + bdcpy(p_buf->bd_addr, bd_addr); + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientClose(UINT16 handle) +{ + BT_HDR *p_buf; + + if ((p_buf = (BT_HDR *) osi_malloc(sizeof(BT_HDR))) != NULL) { + p_buf->event = BTA_PBA_CLIENT_API_CLOSE_EVT; + p_buf->layer_specific = handle; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientPullPhoneBook(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len) +{ + tBTA_PBA_CLIENT_API_REQ *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REQ *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REQ))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REQ_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->operation = BTA_PBA_CLIENT_OP_PULL_PHONE_BOOK; + p_buf->name = name; + p_buf->app_param = app_param; + p_buf->app_param_len = app_param_len; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientSetPhoneBook(UINT16 handle, UINT8 flags, char *name) +{ + tBTA_PBA_CLIENT_API_REQ *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REQ *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REQ))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REQ_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->operation = BTA_PBA_CLIENT_OP_SET_PHONE_BOOK; + p_buf->flags = flags; + p_buf->name = name; + p_buf->app_param = NULL; + p_buf->app_param_len = 0; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientPullvCardListing(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len) +{ + tBTA_PBA_CLIENT_API_REQ *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REQ *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REQ))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REQ_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->operation = BTA_PBA_CLIENT_OP_PULL_VCARD_LISTING; + p_buf->name = name; + p_buf->app_param = app_param; + p_buf->app_param_len = app_param_len; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientPullvCardEntry(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len) +{ + tBTA_PBA_CLIENT_API_REQ *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REQ *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REQ))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REQ_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->operation = BTA_PBA_CLIENT_OP_PULL_VCARD_ENTRY; + p_buf->name = name; + p_buf->app_param = app_param; + p_buf->app_param_len = app_param_len; + bta_sys_sendmsg(p_buf); + } +} + +#endif diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_main.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_main.c new file mode 100644 index 000000000000..81c389bb3e06 --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_main.c @@ -0,0 +1,293 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "osi/allocator.h" +#include "common/bt_target.h" + +#include "stack/obex_api.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "bta_pba_client_int.h" + +#if BTA_PBA_CLIENT_INCLUDED + +/* state machine states */ +enum { + BTA_PBA_CLIENT_INIT_ST, + BTA_PBA_CLIENT_OPENING_ST, + BTA_PBA_CLIENT_OPENED_ST, + BTA_PBA_CLIENT_REQUESTING_ST, + BTA_PBA_CLIENT_CLOSING_ST +}; + +/* state machine action enumeration list */ +enum { + BTA_PBA_CLIENT_API_OPEN, + BTA_PBA_CLIENT_API_CLOSE, + BTA_PBA_CLIENT_API_REQ, + BTA_PBA_CLIENT_DO_CONNECT, + BTA_PBA_CLIENT_AUTHENTICATE, + BTA_PBA_CLIENT_CONNECT, + BTA_PBA_CLIENT_RESPONSE, + BTA_PBA_CLIENT_RESPONSE_FINAL, + BTA_PBA_CLIENT_GOEP_CONNECT, + BTA_PBA_CLIENT_GOEP_DISCONNECT, + BTA_PBA_CLIENT_FORCE_DISCONNECT, + BTA_PBA_CLIENT_FREE_RESPONSE, + BTA_PBA_CLIENT_NUM_ACTIONS +}; + +#define BTA_PBA_CLIENT_IGNORE BTA_PBA_CLIENT_NUM_ACTIONS + +/* type for action functions */ +typedef void (*tBTA_PBA_CLIENT_ACTION)(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); + +/* action functions table, indexed with action enum */ +const tBTA_PBA_CLIENT_ACTION bta_pba_client_action[] = { + /* BTA_PBA_CLIENT_API_OPEN */ bta_pba_client_api_open, + /* BTA_PBA_CLIENT_API_CLOSE */ bta_pba_client_api_close, + /* BTA_PBA_CLIENT_API_REQ */ bta_pba_client_api_req, + /* BTA_PBA_CLIENT_DO_CONNECT */ bta_pba_client_do_connect, + /* BTA_PBA_CLIENT_AUTHENTICATE */ bta_pba_client_authenticate, + /* BTA_PBA_CLIENT_CONNECT */ bta_pba_client_connect, + /* BTA_PBA_CLIENT_RESPONSE */ bta_pba_client_response, + /* BTA_PBA_CLIENT_RESPONSE_FINAL */ bta_pba_client_response_final, + /* BTA_PBA_CLIENT_GOEP_CONNECT */ bta_pba_client_goep_connect, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT*/ bta_pba_client_goep_disconnect, + /* BTA_PBA_CLIENT_FORCE_DISCONNECT */ bta_pba_client_force_disconnect, + /* BTA_PBA_CLIENT_FREE_RESPONSE */ bta_pba_client_free_response, +}; + +/* state table information */ +#define BTA_PBA_CLIENT_ACTION 0 /* position of action */ +#define BTA_PBA_CLIENT_NEXT_STATE 1 /* position of next state */ +#define BTA_PBA_CLIENT_NUM_COLS 2 /* number of columns */ + +const uint8_t bta_pba_client_st_init[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_API_OPEN, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, +}; + +const uint8_t bta_pba_client_st_opening[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_FORCE_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_DO_CONNECT, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_AUTHENTICATE, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_CONNECT, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_FREE_RESPONSE, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_RESPONSE_FINAL, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_CONNECT, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, +}; + +const uint8_t bta_pba_client_st_opened[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_API_CLOSE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_API_REQ, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_FREE_RESPONSE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_FREE_RESPONSE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, +}; + +const uint8_t bta_pba_client_st_getting[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_FORCE_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_RESPONSE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_RESPONSE_FINAL, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, +}; + +const uint8_t bta_pba_client_st_closing[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_FORCE_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_RESPONSE_FINAL, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, +}; + +/* type for state table */ +typedef const UINT8 (*tBTA_PBA_CLIENT_ST_TBL)[BTA_PBA_CLIENT_NUM_COLS]; + +/* state table */ +const tBTA_PBA_CLIENT_ST_TBL bta_pba_client_st_tbl[] = { + bta_pba_client_st_init, + bta_pba_client_st_opening, + bta_pba_client_st_opened, + bta_pba_client_st_getting, + bta_pba_client_st_closing +}; + +/* PBA Client control block */ +#if BTA_DYNAMIC_MEMORY == FALSE +tBTA_PBA_CLIENT_CB bta_pba_client_cb; +#else +tBTA_PBA_CLIENT_CB *bta_pba_client_cb_ptr; +#endif + +static tBTA_PBA_CLIENT_CCB *allocate_ccb(void) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated == 0) { + bta_pba_client_cb.ccb[i].allocated = i + 1; + p_ccb = &bta_pba_client_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tBTA_PBA_CLIENT_CCB *find_ccb_by_handle(UINT16 handle) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated != 0 && bta_pba_client_cb.ccb[i].allocated == handle) { + p_ccb = &bta_pba_client_cb.ccb[i]; + } + } + return p_ccb; +} + +static tBTA_PBA_CLIENT_CCB *find_ccb_by_goep_handle(UINT16 goep_handle) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated != 0 && bta_pba_client_cb.ccb[i].goep_handle == goep_handle) { + p_ccb = &bta_pba_client_cb.ccb[i]; + } + } + return p_ccb; +} + +static tBTA_PBA_CLIENT_CCB *find_ccb_by_bd_addr(BD_ADDR bd_addr) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated != 0 && bdcmp(bta_pba_client_cb.ccb[i].bd_addr, bd_addr) == 0) { + p_ccb = &bta_pba_client_cb.ccb[i]; + } + } + return p_ccb; +} + +void bta_pba_client_sm_execute(tBTA_PBA_CLIENT_CCB *p_ccb, UINT16 event, tBTA_PBA_CLIENT_DATA *p_data) +{ + tBTA_PBA_CLIENT_ST_TBL state_table; + UINT8 action; + + state_table = bta_pba_client_st_tbl[p_ccb->state]; + + event &= 0xff; + + p_ccb->state = state_table[event][BTA_PBA_CLIENT_NEXT_STATE]; + + if ((action = state_table[event][BTA_PBA_CLIENT_ACTION]) != BTA_PBA_CLIENT_IGNORE) { + (*bta_pba_client_action[action])(p_ccb, p_data); + } + + return; +} + +BOOLEAN bta_pba_client_hdl_event(BT_HDR *p_msg) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + BOOLEAN execute_sm = FALSE; + tBTA_PBA_CLIENT_CONN conn = {0}; + tBTA_PBA_CLIENT_DATA *p_data = (tBTA_PBA_CLIENT_DATA *)p_msg; + + switch (p_msg->event) { + case BTA_PBA_CLIENT_API_ENABLE_EVT: + bta_pba_client_api_enable(p_data); + break; + case BTA_PBA_CLIENT_API_DISABLE_EVT: + bta_pba_client_api_disable(p_data); + break; + case BTA_PBA_CLIENT_API_REGISTER_EVT: + bta_pba_client_api_register(p_data); + break; + case BTA_PBA_CLIENT_API_DEREGISTER_EVT: + bta_pba_client_api_deregister(p_data); + break; + case BTA_PBA_CLIENT_API_OPEN_EVT: + if (find_ccb_by_bd_addr(p_data->api_open.bd_addr) != NULL) { + /* already connected */ + conn.handle = 0; + conn.error = BTA_PBA_CLIENT_ALREADY_CONN; + bdcpy(conn.bd_addr, p_data->api_open.bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_OPEN_EVT, (tBTA_PBA_CLIENT *)&conn); + /* break, don't execute sm */ + break; + } + p_ccb = allocate_ccb(); + if (p_ccb == NULL) { + /* no resource to allocate ccb */ + conn.handle = 0; + conn.error = BTA_PBA_CLIENT_NO_RESOURCE; + bdcpy(conn.bd_addr, p_data->api_open.bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_OPEN_EVT, (tBTA_PBA_CLIENT *)&conn); + /* break, don't execute sm */ + break; + } + execute_sm = TRUE; + break; + case BTA_PBA_CLIENT_GOEP_CONNECT_EVT: + case BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT: + case BTA_PBA_CLIENT_RESPONSE_EVT: + case BTA_PBA_CLIENT_RESPONSE_FINAL_EVT: + p_ccb = find_ccb_by_goep_handle(p_msg->layer_specific); + if (p_ccb == NULL) { + /* ignore event with invalid goep handle */ + break; + } + execute_sm = TRUE; + break; + default: + p_ccb = find_ccb_by_handle(p_msg->layer_specific); + if (p_ccb == NULL) { + /* ignore event with invalid handle */ + break; + } + execute_sm = TRUE; + } + + if (execute_sm) { + bta_pba_client_sm_execute(p_ccb, p_msg->event, (tBTA_PBA_CLIENT_DATA *) p_msg); + } + + return TRUE; +} + +#endif diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_sdp.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_sdp.c new file mode 100644 index 000000000000..901e3beab4e8 --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_sdp.c @@ -0,0 +1,280 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "osi/allocator.h" +#include "common/bt_defs.h" +#include "stack/sdp_api.h" +#include "bta/bta_api.h" +#include "bta/bta_pba_defs.h" +#include "bta/bta_pba_client_api.h" +#include "bta_pba_client_int.h" + +#if BTA_PBA_CLIENT_INCLUDED + +/* Number of elements in service class id list. */ +#define BTA_PBA_CLIENT_NUM_SVC_ELEMS 1 + +/******************************************************************************* +** +** Function bta_pba_client_sdp_cback +** +** Description SDP callback function. +** +** +** Returns void +** +*******************************************************************************/ +static void bta_pba_client_sdp_cback(UINT16 status, void *user_data) +{ + tBTA_PBA_CLIENT_DISC_RESULT *p_buf; + tBTA_PBA_CLIENT_CCB *p_ccb = (tBTA_PBA_CLIENT_CCB *)user_data; + + APPL_TRACE_DEBUG("bta_pba_client_sdp_cback status:0x%x", status); + + if ((p_buf = (tBTA_PBA_CLIENT_DISC_RESULT *) osi_malloc(sizeof(tBTA_PBA_CLIENT_DISC_RESULT))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_DISC_RES_EVT; + p_buf->hdr.layer_specific = p_ccb->allocated; + p_buf->status = status; + bta_sys_sendmsg(p_buf); + } +} + +/****************************************************************************** +** +** Function bta_pba_client_add_record +** +** Description Add PBA Client information to an SDP record. Prior to +** calling this function the application must call +** SDP_CreateRecord() to create an SDP record. +** +** Returns TRUE if function execution succeeded, +** FALSE if function execution failed. +** +******************************************************************************/ +static BOOLEAN bta_pba_client_add_record(const char *p_service_name, UINT32 sdp_handle) +{ + UINT16 svc_class_id_list[BTA_PBA_CLIENT_NUM_SVC_ELEMS]; + UINT16 version; + UINT16 profile_uuid; + BOOLEAN result = TRUE; + + APPL_TRACE_DEBUG("bta_pba_client_add_record"); + + /* add service class id list */ + svc_class_id_list[0] = UUID_SERVCLASS_PBAP_PCE; + result &= SDP_AddServiceClassIdList(sdp_handle, BTA_PBA_CLIENT_NUM_SVC_ELEMS, svc_class_id_list); + + /* add service name */ + if (p_service_name != NULL && p_service_name[0] != 0) { + result &= SDP_AddAttribute(sdp_handle, ATTR_ID_SERVICE_NAME, TEXT_STR_DESC_TYPE, + (UINT32)(strlen(p_service_name) + 1), (UINT8 *) p_service_name); + } + + /* add profile descriptor list */ + profile_uuid = UUID_SERVCLASS_PHONE_ACCESS; + version = PBAP_PCE_VERSION; + result &= SDP_AddProfileDescriptorList(sdp_handle, profile_uuid, version); + + return result; +} + +/******************************************************************************* +** +** Function bta_pba_client_create_record +** +** Description Create SDP record for registered service. +** +** +** Returns void +** +*******************************************************************************/ +void bta_pba_client_create_record(const char *p_service_name) +{ + /* add sdp record if not already registered */ + if (bta_pba_client_cb.sdp_handle == 0) { + bta_pba_client_cb.sdp_handle = SDP_CreateRecord(); + bta_pba_client_add_record(p_service_name, bta_pba_client_cb.sdp_handle); + bta_sys_add_uuid(UUID_SERVCLASS_PBAP_PCE); + } + +} + +/******************************************************************************* +** +** Function bta_pba_client_del_record +** +** Description Delete SDP record for registered service. +** +** +** Returns void +** +*******************************************************************************/ +void bta_pba_client_del_record(void) +{ + APPL_TRACE_DEBUG("bta_pba_client_del_record"); + + if (bta_pba_client_cb.sdp_handle != 0) { + SDP_DeleteRecord(bta_pba_client_cb.sdp_handle); + bta_pba_client_cb.sdp_handle = 0; + bta_sys_remove_uuid(UUID_SERVCLASS_PBAP_PCE); + } +} + +/******************************************************************************* +** +** Function bta_pba_client_sdp_find_attr +** +** Description Process SDP discovery results to find requested attribute +** +** +** Returns TRUE if results found, FALSE otherwise. +** +*******************************************************************************/ +BOOLEAN bta_pba_client_sdp_find_attr(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + tSDP_DISC_REC *p_rec = NULL; + tSDP_DISC_ATTR *p_attr; + tSDP_PROTOCOL_ELEM pe; + BOOLEAN result = FALSE; + + /* loop through all records we found */ + while (TRUE) { + /* get next record; if none found, we're done */ + if ((p_rec = SDP_FindServiceInDb(p_ccb->p_disc_db, UUID_SERVCLASS_PBAP_PSE, p_rec)) == NULL) { + break; + } + + /* get rfcomm scn from proto desc list */ + if (SDP_FindProtocolListElemInRec(p_rec, UUID_PROTOCOL_RFCOMM, &pe)) { + p_ccb->peer_rfcomm_scn = (UINT8) pe.params[0]; + } + else { + /* not found, go to next record */ + continue; + } + + /* get supported repositories */ + if ((p_attr = SDP_FindAttributeInRec(p_rec, ATTR_ID_SUPPORTED_REPOSITORIES)) != NULL) { + /* Found attribute, get value */ + p_ccb->peer_supported_repo = p_attr->attr_value.v.u8; + } + else { + /* not found, clear rfcomm scn and go to next record */ + p_ccb->peer_rfcomm_scn = 0; + continue; + } + + /* get profile version */ + SDP_FindProfileVersionInRec(p_rec, UUID_SERVCLASS_PHONE_ACCESS, &p_ccb->peer_version); + + /* get GOEP L2CAP PSM */ + if ((p_attr = SDP_FindAttributeInRec(p_rec, ATTR_ID_GOEP_L2CAP_PSM)) != NULL) { + /* Found attribute, get value */ + p_ccb->peer_l2cap_psm = p_attr->attr_value.v.u16; + } + + /* try to get supported features */ + if ((p_attr = SDP_FindAttributeInRec(p_rec, ATTR_ID_PBAP_SUPPORTED_FEATURES)) != NULL) { + /* found attribute, get value */ + p_ccb->peer_supported_feat = p_attr->attr_value.v.u32; + p_ccb->send_supported_feat = TRUE; + } + else { + /* assume as default value if not found in sdp record */ + p_ccb->peer_supported_feat = BTA_PBAP_DEFAULT_SUPPORTED_FEATURES; + p_ccb->send_supported_feat = FALSE; + } + + /* found what we needed */ + result = TRUE; + break; + } + + APPL_TRACE_DEBUG("%s peer_version:0x%x, supported repositories:0x%x, supported features:0x%x", + __FUNCTION__, p_ccb->peer_version, p_ccb->peer_supported_repo, p_ccb->peer_supported_feat); + + return result; +} + +/******************************************************************************* +** +** Function bta_pba_client_do_disc +** +** Description Do service discovery. +** +** +** Returns TRUE if start service discovery successfully +** +*******************************************************************************/ +BOOLEAN bta_pba_client_do_disc(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + tSDP_UUID uuid_list[1]; + UINT16 num_uuid = 1; + UINT16 attr_list[4]; + UINT8 num_attr; + BOOLEAN db_inited = FALSE; + + /* get proto list and features */ + attr_list[0] = ATTR_ID_SERVICE_CLASS_ID_LIST; + attr_list[1] = ATTR_ID_PROTOCOL_DESC_LIST; + attr_list[2] = ATTR_ID_BT_PROFILE_DESC_LIST; + attr_list[3] = ATTR_ID_GOEP_L2CAP_PSM; + attr_list[4] = ATTR_ID_SUPPORTED_REPOSITORIES; + attr_list[5] = ATTR_ID_PBAP_SUPPORTED_FEATURES; + num_attr = 6; + uuid_list[0].uu.uuid16 = UUID_SERVCLASS_PBAP_PSE; + uuid_list[0].len = LEN_UUID_16; + + if (p_ccb->p_disc_db != NULL) { + APPL_TRACE_WARNING("%s service discovery already in progress", __FUNCTION__); + return FALSE; + } + + /* allocate buffer for sdp database */ + p_ccb->p_disc_db = (tSDP_DISCOVERY_DB *) osi_malloc(BT_DEFAULT_BUFFER_SIZE); + + if (p_ccb->p_disc_db) { + /* set up service discovery database; attr happens to be attr_list len */ + db_inited = SDP_InitDiscoveryDb(p_ccb->p_disc_db, BT_DEFAULT_BUFFER_SIZE, num_uuid, + uuid_list, num_attr, attr_list); + } + + if (db_inited) { + /*start service discovery */ + /* todo: avoid p_ccb being free during sdp */ + db_inited = SDP_ServiceSearchAttributeRequest2(p_ccb->bd_addr, p_ccb->p_disc_db, + bta_pba_client_sdp_cback, p_ccb); + } + + if (!db_inited) { + /*free discover db */ + bta_pba_client_free_db(p_ccb); + APPL_TRACE_ERROR("%s start service discovery failed", __FUNCTION__); + return FALSE; + } + return TRUE; +} + +/******************************************************************************* +** +** Function bta_hf_client_free_db +** +** Description Free discovery database. +** +** +** Returns void +** +*******************************************************************************/ +void bta_pba_client_free_db(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + if (p_ccb->p_disc_db != NULL) { + osi_free(p_ccb->p_disc_db); + p_ccb->p_disc_db = NULL; + } +} + +#endif diff --git a/components/bt/host/bluedroid/bta/pba/include/bta_pba_client_int.h b/components/bt/host/bluedroid/bta/pba/include/bta_pba_client_int.h new file mode 100644 index 000000000000..5d48eab9db16 --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/include/bta_pba_client_int.h @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "osi/list.h" +#include "common/bt_target.h" +#include "stack/sdp_api.h" +#include "stack/obex_api.h" +#include "bta/bta_sys.h" +#include "bta/bta_api.h" +#include "bta/bta_pba_client_api.h" + +#if BTA_PBA_CLIENT_INCLUDED + +#define PBA_CLIENT_MAX_CONNECTION 2 + +#define PBA_CLIENT_MAX_MTU L2CAP_MTU_SIZE /* RFCOMM is base on L2CAP, its MTU will smaller than this */ +#define PBA_CLIENT_MIN_MTU 255 + +enum { + /* these events are handled by the state machine */ + BTA_PBA_CLIENT_API_OPEN_EVT = BTA_SYS_EVT_START(BTA_ID_PBC), + BTA_PBA_CLIENT_API_CLOSE_EVT, + BTA_PBA_CLIENT_API_REQ_EVT, + BTA_PBA_CLIENT_DISC_RES_EVT, + BTA_PBA_CLIENT_AUTHENTICATE_EVT, + BTA_PBA_CLIENT_CONNECT_EVT, + BTA_PBA_CLIENT_RESPONSE_EVT, + BTA_PBA_CLIENT_RESPONSE_FINAL_EVT, + BTA_PBA_CLIENT_GOEP_CONNECT_EVT, + BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT, + + /* these events are handled outside of the state machine */ + BTA_PBA_CLIENT_API_ENABLE_EVT, + BTA_PBA_CLIENT_API_DISABLE_EVT, + BTA_PBA_CLIENT_API_REGISTER_EVT, + BTA_PBA_CLIENT_API_DEREGISTER_EVT, +}; + +typedef enum { + BTA_PBA_CLIENT_OP_PULL_PHONE_BOOK, + BTA_PBA_CLIENT_OP_SET_PHONE_BOOK, + BTA_PBA_CLIENT_OP_PULL_VCARD_LISTING, + BTA_PBA_CLIENT_OP_PULL_VCARD_ENTRY, +} tBTA_PBA_CLIENT_OP; + +typedef struct { + BT_HDR hdr; + tBTA_PBA_CLIENT_CBACK *p_cback; +} tBTA_PBA_CLIENT_API_ENABLE; + +typedef struct { + BT_HDR hdr; + char name[BTA_SERVICE_NAME_LEN + 1]; +} tBTA_PBA_CLIENT_API_REGISTER; + +typedef struct { + BT_HDR hdr; + BD_ADDR bd_addr; + tBTA_SEC sec_mask; + UINT16 mtu; + UINT32 supported_feat; +} tBTA_PBA_CLIENT_API_OPEN; + +typedef struct { + BT_HDR hdr; + UINT8 operation; + UINT8 flags; + char *name; + UINT16 app_param_len; + UINT8 *app_param; +} tBTA_PBA_CLIENT_API_REQ; + +typedef struct { + BT_HDR hdr; + UINT16 status; +} tBTA_PBA_CLIENT_DISC_RESULT; + +typedef struct { + BT_HDR hdr; + UINT16 our_mtu; + UINT16 peer_mtu; +} tBTA_PBA_CLIENT_GOEP_CONNECT; + +typedef struct { + BT_HDR hdr; + UINT16 reason; +} tBTA_PBA_CLIENT_GOEP_DISCONNECT; + +typedef struct { + BT_HDR hdr; + BT_HDR *pkt; + UINT8 opcode; + BOOLEAN srm_en; + BOOLEAN srm_wait; +} tBTA_PBA_CLIENT_GOEP_RESPONSE; + +typedef union { + BT_HDR hdr; + tBTA_PBA_CLIENT_API_ENABLE api_enable; + tBTA_PBA_CLIENT_API_REGISTER api_register; + tBTA_PBA_CLIENT_API_OPEN api_open; + tBTA_PBA_CLIENT_API_REQ api_req; + tBTA_PBA_CLIENT_DISC_RESULT disc_result; + tBTA_PBA_CLIENT_GOEP_CONNECT goep_connect; + tBTA_PBA_CLIENT_GOEP_DISCONNECT goep_disconnect; + tBTA_PBA_CLIENT_GOEP_RESPONSE goep_response; +} tBTA_PBA_CLIENT_DATA; + +typedef struct { + BD_ADDR bd_addr; /* peer BD address */ + tSDP_DISCOVERY_DB *p_disc_db; /* pointer to discovery database */ + tBTA_SEC sec_mask; /* security mask */ + UINT16 peer_version; /* peer profile version */ + UINT16 peer_l2cap_psm; /* peer l2cap psm */ + UINT8 peer_rfcomm_scn; /* peer rfcomm scn */ + UINT8 peer_supported_repo; /* peer supported repositories */ + UINT32 peer_supported_feat; /* peer supported features */ + UINT32 our_supported_feat; /* we supported features */ + BOOLEAN send_supported_feat; /* whether we should send supported features in connect request */ + UINT16 goep_handle; /* goep connection handle */ + UINT32 goep_cid; /* goep connection id */ + UINT16 max_rx; /* max rx bytes */ + UINT16 max_tx; /* max tx bytes */ + BOOLEAN authenticate; /* whether we are authenticated */ + tBTA_PBA_CLIENT_OP operation; /* ongoing or last operations */ + UINT8 state; /* main state machine */ + UINT8 allocated; /* index + 1 if allocated, otherwise 0 */ +} tBTA_PBA_CLIENT_CCB; + +typedef struct { + tBTA_PBA_CLIENT_CCB ccb[PBA_CLIENT_MAX_CONNECTION]; /* connection control block */ + tBTA_PBA_CLIENT_CBACK *p_cback; /* message callback to upper */ + UINT32 sdp_handle; /* sdp record handle */ + UINT8 trace_level; /* debug trace level */ +} tBTA_PBA_CLIENT_CB; + +#if BTA_DYNAMIC_MEMORY == FALSE +extern tBTA_PBA_CLIENT_CB bta_pba_client_cb; +#else +extern tBTA_PBA_CLIENT_CB *bta_pba_client_cb_ptr; +#define bta_pba_client_cb (*bta_pba_client_cb_ptr) +#endif + +extern void bta_pba_client_api_enable(tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_disable(tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_register(tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_deregister(tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_open(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_close(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_req(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_do_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_authenticate(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_force_disconnect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); + +extern void bta_pba_client_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +void bta_pba_client_goep_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +void bta_pba_client_goep_disconnect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +void bta_pba_client_free_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +void bta_pba_client_free_sdp_db(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); + +extern void bta_pba_client_create_record(const char *p_service_name); +extern void bta_pba_client_del_record(void); +extern BOOLEAN bta_pba_client_sdp_find_attr(tBTA_PBA_CLIENT_CCB *p_ccb); +extern BOOLEAN bta_pba_client_do_disc(tBTA_PBA_CLIENT_CCB *p_ccb); +extern void bta_pba_client_free_db(tBTA_PBA_CLIENT_CCB *p_ccb); + +extern void bta_pba_client_sm_execute(tBTA_PBA_CLIENT_CCB *p_ccb, UINT16 event, tBTA_PBA_CLIENT_DATA *p_data); +extern BOOLEAN bta_pba_client_hdl_event(BT_HDR *p_msg); + +#endif diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_pba_client.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_pba_client.h new file mode 100644 index 000000000000..0a718fa30a03 --- /dev/null +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_pba_client.h @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "common/bt_defs.h" +#include "esp_pbac_api.h" + +#if BTC_PBA_CLIENT_INCLUDED + +typedef enum { + BTC_PBA_CLIENT_INIT_EVT = 0, + BTC_PBA_CLIENT_DEINIT_EVT, + BTC_PBA_CLIENT_CONNECT_EVT, + BTC_PBA_CLIENT_DISCONNECT_EVT, + BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT, + BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT, + BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT, + BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT, + BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT +} BTC_PBA_CLIENT_EVT; + +/* equal to BTA max conn */ +#define BTC_PBA_CLIENT_MAX_CONN_NUM 2 + +typedef struct { + uint16_t handle; + bt_bdaddr_t bd_addr; + char *path; + uint16_t path_len; + uint16_t path_pos; + bool busy; +} btc_pba_client_ccb_t; + +typedef struct { + btc_pba_client_ccb_t ccb[BTC_PBA_CLIENT_MAX_CONN_NUM]; +} btc_pba_client_cb_t; + +typedef union { + // BTC_PBA_CLIENT_CONNECT_EVT + struct pba_client_connect_arg { + bt_bdaddr_t bd_addr; + } connect; + + // BTC_PBA_CLIENT_DISCONNECT_EVT + struct pba_client_disconnect_arg { + uint16_t handle; + } disconnect; + + // BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT + struct pba_client_pull_phone_book_arg { + uint16_t handle; + char *name; + bool include_app_param; + esp_pbac_pull_phone_book_app_param_t app_param; + } pull_phone_book; + + // BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT + struct pba_client_set_phone_book_arg { + uint16_t handle; + uint8_t flags; + char *name; + } set_phone_book; + + // BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT + struct pba_client_pull_vcard_listing_arg { + uint16_t handle; + char *name; + bool include_app_param; + esp_pbac_pull_vcard_listing_app_param_t app_param; + } pull_vcard_listing; + + // BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT + struct pba_client_pull_vcard_entry_arg { + uint16_t handle; + char *name; + bool include_app_param; + esp_pbac_pull_vcard_entry_app_param_t app_param; + } pull_vcard_entry; +} btc_pba_client_args_t; + +void btc_pba_client_call_handler(btc_msg_t *msg); +void btc_pba_client_cb_handler(btc_msg_t *msg); +void btc_pba_client_args_deep_free(btc_msg_t *msg); +void btc_pba_client_args_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +#endif diff --git a/components/bt/host/bluedroid/btc/profile/std/pba/btc_pba_client.c b/components/bt/host/bluedroid/btc/profile/std/pba/btc_pba_client.c new file mode 100644 index 000000000000..f1ef0981f3ce --- /dev/null +++ b/components/bt/host/bluedroid/btc/profile/std/pba/btc_pba_client.c @@ -0,0 +1,971 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "osi/allocator.h" + +#include "bta/bta_api.h" +#include "bta/bta_pba_defs.h" +#include "bta/bta_pba_client_api.h" +#include "btc/btc_profile_queue.h" +#include "btc/btc_manage.h" +#include "btc/btc_task.h" +#include "btc_pba_client.h" +#include "esp_pbac_api.h" + +#if BTC_PBA_CLIENT_INCLUDED + +#define BTC_PBA_CLIENT_SECURITY (BTA_SEC_AUTHENTICATE | BTA_SEC_ENCRYPT) + +static bool s_btc_pba_client_init = 0; + +btc_pba_client_cb_t btc_pba_client_cb; + +static void bte_pba_client_evt(tBTA_PBA_CLIENT_EVT event, tBTA_PBA_CLIENT *p_data) +{ + bt_status_t status; + int param_len = 0; + bool ignore = false; + + switch (event) { + case BTA_PBA_CLIENT_CONN_OPEN_EVT: + case BTA_PBA_CLIENT_CONN_CLOSE_EVT: + param_len = sizeof(tBTA_PBA_CLIENT_CONN); + break; + case BTA_PBA_CLIENT_PULL_PHONE_BOOK_RSP_EVT: + case BTA_PBA_CLIENT_SET_PHONE_BOOK_RSP_EVT: + case BTA_PBA_CLIENT_PULL_VCARD_LISTING_RSP_EVT: + case BTA_PBA_CLIENT_PULL_VCARD_ENTRY_RSP_EVT: + param_len = sizeof(tBTA_PBA_CLIENT_RESPONSE); + break; + case BTA_PBA_CLIENT_REGISTER_EVT: + param_len = 0; + break; + case BTA_PBA_CLIENT_DISABLE_EVT: + param_len = 0; + break; + case BTA_PBA_CLIENT_ENABLE_EVT: + case BTA_PBA_CLIENT_DEREGISTER_EVT: + default: + ignore = true; + break; + } + + if (ignore) { + return; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = event; + + status = btc_transfer_context(&msg, p_data, param_len, NULL, NULL); + if (status != BT_STATUS_SUCCESS) { + BTC_TRACE_ERROR("context transfer failed"); + } +} + +static void btc_pba_client_callback_to_app(esp_pbac_event_t event, esp_pbac_param_t *param) +{ + esp_pbac_callback_t callback = (esp_pbac_callback_t)btc_profile_cb_get(BTC_PID_PBA_CLIENT); + if (callback) { + callback(event, param); + } +} + +static void btc_pba_client_init(void) +{ + if (!s_btc_pba_client_init) { + s_btc_pba_client_init = true; + memset(&btc_pba_client_cb, 0, sizeof(btc_pba_client_cb_t)); + /* enable pba client */ + BTA_PbaClientEnable(bte_pba_client_evt); + /* register sdp record */ + BTA_PbaClientRegister("Phonebook Access PCE"); + } +} + +static void btc_pba_client_deinit(void) +{ + if (s_btc_pba_client_init) { + s_btc_pba_client_init = false; + /* deregister sdp record */ + BTA_PbaClientDeregister(); + /* disable pba client */ + BTA_PbaClientDisable(); + } +} + +static BOOLEAN is_connected(bt_bdaddr_t *bd_addr) +{ + for (int i = 0; i < BTC_PBA_CLIENT_MAX_CONN_NUM; ++i) { + if (btc_pba_client_cb.ccb[i].handle != 0 && bdcmp(bd_addr->address, btc_pba_client_cb.ccb[i].bd_addr.address) == 0) { + return TRUE; + } + } + + return FALSE; +} + +static bt_status_t connect_int(bt_bdaddr_t *bd_addr, uint16_t uuid) +{ + if (is_connected(bd_addr)) { + return BT_STATUS_BUSY; + } + + BTA_PbaClientOpen(bd_addr->address, BTC_PBA_CLIENT_SECURITY, (uint32_t)BTC_PBA_SUPPORTED_FEAT, (uint16_t)BTC_PBA_PREFERRED_MTU); + + return BT_STATUS_SUCCESS; +} + +static void btc_pba_client_connect(bt_bdaddr_t *bd_addr) +{ + if (!s_btc_pba_client_init) { + return; + } + + btc_queue_connect(UUID_SERVCLASS_PBAP_PCE, bd_addr, connect_int); +} + +static void btc_pba_client_disconnect(uint16_t handle) +{ + do { + if (!s_btc_pba_client_init) { + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + break; + } + + BTA_PbaClientClose(handle); + } while (0); +} + +static bool btc_pba_client_pull_phone_book(uint16_t handle, char *name, bool include_app_param, esp_pbac_pull_phone_book_app_param_t *app_param) +{ + bt_status_t err = BT_STATUS_FAIL; + uint8_t *app_param_buff = NULL; + uint16_t app_param_len = 0; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + if (include_app_param) { + app_param_buff = osi_malloc(BTA_PBAP_PULL_PHONE_BOOK_APP_PARAM_BUFF_SIZE_MIN); + if (app_param_buff == NULL) { + err = BT_STATUS_NOMEM; + break; + } + uint8_t *p = app_param_buff; + if (app_param->include_property_selector) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_PROPERTY_SELECTOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_PROPERTY_SELECTOR); + UINT64_TO_BE_STREAM(p, app_param->property_selector); + } + if (app_param->include_format) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_FORMAT); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_FORMAT); + UINT8_TO_BE_STREAM(p, app_param->format); + } + if (app_param->include_max_list_count) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_MAX_LIST_COUNT); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_MAX_LIST_COUNT); + UINT16_TO_BE_STREAM(p, app_param->max_list_count); + } + if (app_param->include_list_start_offset) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LIST_START_OFFSET); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_LIST_START_OFFSET); + UINT16_TO_BE_STREAM(p, app_param->list_start_offset); + } + if (app_param->include_reset_new_missed_calls) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_RESET_NEW_MISSED_CALLS); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_RESET_NEW_MISSED_CALLS); + UINT8_TO_BE_STREAM(p, app_param->reset_new_missed_calls); + } + if (app_param->include_vcard_selector) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_VCARD_SELECTOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR); + UINT64_TO_BE_STREAM(p, app_param->vcard_selector); + } + if (app_param->include_vcard_selector_operator) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_VCARD_SELECTOR_OPERATOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR_OPERATOR); + UINT8_TO_BE_STREAM(p, app_param->vcard_selector_operator); + } + app_param_len = p - app_param_buff; + assert(app_param_len <= BTA_PBAP_PULL_PHONE_BOOK_APP_PARAM_BUFF_SIZE_MIN); + if (app_param_len == 0) { + /* user give us an empty app param, allow but not recommend */ + osi_free(app_param_buff); + app_param_buff = NULL; + } + } + + p_ccb->busy = true; + BTA_PbaClientPullPhoneBook(handle, name, app_param_buff, app_param_len); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +static bool btc_pba_client_set_phone_book(uint16_t handle, uint8_t flags, char *name) +{ + bt_status_t err = BT_STATUS_FAIL; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + p_ccb->busy = true; + BTA_PbaClientSetPhoneBook(handle, flags, (char *)name); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +static bool btc_pba_client_set_phone_book2(uint16_t handle, char *path) +{ + bt_status_t err = BT_STATUS_FAIL; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + p_ccb->busy = true; + if (path != NULL) { + p_ccb->path_len = strlen(path) + 1; + /* ignore the first slash */ + if (path[0] == '/') { + p_ccb->path_pos = 1; + } + else { + p_ccb->path_pos = 0; + } + /* since we use absolute path, treat empty path as go to ROOT */ + if (p_ccb->path_len == p_ccb->path_pos + 1) { + p_ccb->path_len = 0; + p_ccb->path_pos = 0; + osi_free(path); + path = NULL; + } + else { + p_ccb->path = path; + } + } + /* anyway, go to ROOT first */ + char *empty_name = osi_malloc(1); + assert(empty_name != NULL); + *empty_name = '\0'; + BTA_PbaClientSetPhoneBook(handle, ESP_PBAC_SET_PHONE_BOOK_FLAGS_ROOT, empty_name); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +static bool btc_pba_client_pull_vcard_listing(uint16_t handle, char *name, bool include_app_param, esp_pbac_pull_vcard_listing_app_param_t *app_param) +{ + bt_status_t err = BT_STATUS_FAIL; + uint8_t *app_param_buff = NULL; + uint16_t app_param_len = 0; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + if (include_app_param) { + uint8_t search_value_len = 0; + if (app_param->include_search_value) { + search_value_len = strlen(app_param->search_value) + 1; + } + app_param_buff = osi_malloc(BTA_PBAP_PULL_VCARD_LISTING_APP_PARAM_BUFF_SIZE_MIN + search_value_len); + if (app_param_buff == NULL) { + err = BT_STATUS_NOMEM; + break; + } + + uint8_t *p = app_param_buff; + if (app_param->include_order) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_ORDER); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_ORDER); + UINT8_TO_BE_STREAM(p, app_param->order); + } + if (app_param->include_search_value) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_SEARCH_VALUE); + UINT8_TO_BE_STREAM(p, search_value_len); + memcpy(p, app_param->search_value, search_value_len); + p += search_value_len; + } + if (app_param->include_search_property) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_SEARCH_PROPERTY); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_SEARCH_PROPERTY); + UINT8_TO_BE_STREAM(p, app_param->search_property); + } + if (app_param->include_max_list_count) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_MAX_LIST_COUNT); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_MAX_LIST_COUNT); + UINT16_TO_BE_STREAM(p, app_param->max_list_count); + } + if (app_param->include_list_start_offset) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LIST_START_OFFSET); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_LIST_START_OFFSET); + UINT16_TO_BE_STREAM(p, app_param->list_start_offset); + } + if (app_param->include_reset_new_missed_calls) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_RESET_NEW_MISSED_CALLS); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_RESET_NEW_MISSED_CALLS); + UINT8_TO_BE_STREAM(p, app_param->reset_new_missed_calls); + } + if (app_param->include_vcard_selector) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_VCARD_SELECTOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR); + UINT64_TO_BE_STREAM(p, app_param->vcard_selector); + } + if (app_param->include_vcard_selector_operator) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_VCARD_SELECTOR_OPERATOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR_OPERATOR); + UINT8_TO_BE_STREAM(p, app_param->vcard_selector_operator); + } + app_param_len = p - app_param_buff; + assert(app_param_len <= BTA_PBAP_PULL_VCARD_LISTING_APP_PARAM_BUFF_SIZE_MIN + search_value_len); + if (app_param_len == 0) { + /* user give us an empty app param, allow but not recommend */ + osi_free(app_param_buff); + app_param_buff = NULL; + } + /* free search_value (allocated by deep_copy) */ + if (app_param->include_search_value && app_param->search_value) { + osi_free(app_param->search_value); + app_param->search_value = NULL; + } + } + + p_ccb->busy = true; + BTA_PbaClientPullvCardListing(handle, (char *)name, app_param_buff, app_param_len); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +static bool btc_pba_client_pull_vcard_entry(uint16_t handle, char *name, bool include_app_param, esp_pbac_pull_vcard_entry_app_param_t *app_param) +{ + bt_status_t err = BT_STATUS_FAIL; + uint8_t *app_param_buff = NULL; + uint16_t app_param_len = 0; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + if (include_app_param) { + app_param_buff = osi_malloc(BTA_PBAP_PULL_VCARD_ENTRY_APP_PARAM_BUFF_SIZE_MIN); + if (app_param_buff == NULL) { + err = BT_STATUS_NOMEM; + break; + } + uint8_t *p = app_param_buff; + if (app_param->include_property_selector) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_PROPERTY_SELECTOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_PROPERTY_SELECTOR); + UINT64_TO_BE_STREAM(p, app_param->property_selector); + } + if (app_param->include_format) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_FORMAT); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_FORMAT); + UINT8_TO_BE_STREAM(p, app_param->format); + } + app_param_len = p - app_param_buff; + assert(app_param_len <= BTA_PBAP_PULL_VCARD_ENTRY_APP_PARAM_BUFF_SIZE_MIN); + if (app_param_len == 0) { + /* user give us an empty app param, allow but not recommend */ + osi_free(app_param_buff); + app_param_buff = NULL; + } + } + + p_ccb->busy = true; + BTA_PbaClientPullvCardEntry(handle, (char *)name, app_param_buff, app_param_len); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +void btc_pba_client_args_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_pba_client_args_t *dst = (btc_pba_client_args_t *)p_dest; + btc_pba_client_args_t *src = (btc_pba_client_args_t *)p_src; + size_t len; + + switch (msg->act) { + case BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT: + len = strlen(src->pull_phone_book.name) + 1; + dst->pull_phone_book.name = (char *)osi_malloc(len); + if (dst->pull_phone_book.name) { + memcpy(dst->pull_phone_book.name, src->pull_phone_book.name, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + break; + case BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT: + len = strlen(src->pull_vcard_listing.name) + 1; + dst->pull_vcard_listing.name = (char *)osi_malloc(len); + if (dst->pull_vcard_listing.name) { + memcpy(dst->pull_vcard_listing.name, src->pull_vcard_listing.name, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + if (src->pull_vcard_listing.include_app_param && src->pull_vcard_listing.app_param.include_search_value) { + len = strlen(src->pull_vcard_listing.app_param.search_value) + 1; + dst->pull_vcard_listing.app_param.search_value = (char *)osi_malloc(len); + if (dst->pull_vcard_listing.app_param.search_value) { + memcpy(dst->pull_vcard_listing.app_param.search_value, src->pull_vcard_listing.app_param.search_value, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + } + break; + case BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT: + len = strlen(src->pull_vcard_entry.name) + 1; + dst->pull_vcard_entry.name = (char *)osi_malloc(len); + if (dst->pull_vcard_entry.name) { + memcpy(dst->pull_vcard_entry.name, src->pull_vcard_entry.name, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + break; + case BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT: + case BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT: + /* set phone book name may be NULL */ + if (src->set_phone_book.name) { + len = strlen(src->set_phone_book.name) + 1; + dst->set_phone_book.name = (char *)osi_malloc(len); + if (dst->set_phone_book.name) { + memcpy(dst->set_phone_book.name, src->set_phone_book.name, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + } + break; + default: + BTC_TRACE_DEBUG("%s Unhandled deep copy %d\n", __FUNCTION__, msg->act); + UNUSED(dst); + UNUSED(src); + UNUSED(len); + break; + } +} + +void btc_pba_client_args_deep_free(btc_msg_t *msg) +{ + btc_pba_client_args_t *arg = (btc_pba_client_args_t *)msg->arg; + + switch (msg->act) { + case BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT: + if (arg->pull_phone_book.name) { + osi_free(arg->pull_phone_book.name); + } + break; + case BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT: + if (arg->pull_vcard_listing.name) { + osi_free(arg->pull_vcard_listing.name); + } + if (arg->pull_vcard_listing.include_app_param + && arg->pull_vcard_listing.app_param.include_search_value + && arg->pull_vcard_listing.app_param.search_value) { + osi_free(arg->pull_vcard_listing.app_param.search_value); + } + break; + case BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT: + if (arg->pull_vcard_entry.name) { + osi_free(arg->pull_vcard_entry.name); + } + break; + case BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT: + case BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT: + if (arg->set_phone_book.name) { + osi_free(arg->set_phone_book.name); + } + break; + default: + BTC_TRACE_DEBUG("%s Unhandled deep free %d\n", __FUNCTION__, msg->act); + UNUSED(arg); + break; + } +} + +void btc_pba_client_call_handler(btc_msg_t *msg) +{ + bool ret = true; + btc_pba_client_args_t *arg = (btc_pba_client_args_t *)(msg->arg); + switch (msg->act) { + case BTC_PBA_CLIENT_INIT_EVT: + btc_pba_client_init(); + break; + case BTC_PBA_CLIENT_DEINIT_EVT: + btc_pba_client_deinit(); + break; + case BTC_PBA_CLIENT_CONNECT_EVT: + btc_pba_client_connect(&arg->connect.bd_addr); + break; + case BTC_PBA_CLIENT_DISCONNECT_EVT: + btc_pba_client_disconnect(arg->disconnect.handle); + break; + case BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT: + ret = btc_pba_client_pull_phone_book(arg->pull_phone_book.handle, arg->pull_phone_book.name, arg->pull_phone_book.include_app_param, &arg->pull_phone_book.app_param); + break; + case BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT: + ret = btc_pba_client_set_phone_book(arg->set_phone_book.handle, arg->set_phone_book.flags, arg->set_phone_book.name); + break; + case BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT: + ret = btc_pba_client_set_phone_book2(arg->set_phone_book.handle, arg->set_phone_book.name); + break; + case BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT: + ret = btc_pba_client_pull_vcard_listing(arg->pull_vcard_listing.handle, arg->pull_vcard_listing.name, arg->pull_vcard_listing.include_app_param, &arg->pull_vcard_listing.app_param); + break; + case BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT: + ret = btc_pba_client_pull_vcard_entry(arg->pull_vcard_entry.handle, arg->pull_vcard_entry.name, arg->pull_vcard_entry.include_app_param, &arg->pull_vcard_entry.app_param); + break; + default: + BTC_TRACE_WARNING("unknown pba client action %i", msg->act); + break; + } + + if (!ret) { + /* operation failed, do deep free */ + btc_pba_client_args_deep_free(msg); + } +} + +static void parse_pull_phone_book_app_param(esp_pbac_param_t *param, uint8_t *app_param, uint16_t app_param_len) +{ + if (app_param == NULL || app_param_len == 0) { + return; + } + uint8_t *ptr = app_param; + while(ptr < app_param + app_param_len) { + switch (*ptr) + { + case BTA_PBAP_APP_PARAM_PHONE_BOOK_SIZE: + param->pull_phone_book_rsp.include_phone_book_size = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + BE_STREAM_TO_UINT16(param->pull_phone_book_rsp.phone_book_size, ptr); + break; + case BTA_PBAP_APP_PARAM_NEW_MISSED_CALLS: + param->pull_phone_book_rsp.include_new_missed_calls = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + BE_STREAM_TO_UINT8(param->pull_phone_book_rsp.new_missed_calls, ptr); + break; + case BTA_PBAP_APP_PARAM_PRIMARY_FOLDER_VERSION: + param->pull_phone_book_rsp.include_primary_folder_version = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_phone_book_rsp.primary_folder_version = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_PRIMARY_FOLDER_VERSION; + break; + case BTA_PBAP_APP_PARAM_SECONDARY_FOLDER_VERSION: + param->pull_phone_book_rsp.include_secondary_folder_version = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_phone_book_rsp.secondary_folder_version = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_SECONDARY_FOLDER_VERSION; + break; + case BTA_PBAP_APP_PARAM_DATABASE_IDENTIFIER: + param->pull_phone_book_rsp.include_database_identifier = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_phone_book_rsp.database_identifier = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_DATABASE_IDENTIFIER; + break; + default: + goto error; + break; + } + } +error: + return; +} + +static void parse_pull_vcard_listing_app_param(esp_pbac_param_t *param, uint8_t *app_param, uint16_t app_param_len) +{ + if (app_param == NULL || app_param_len == 0) { + return; + } + uint8_t *ptr = app_param; + while(ptr < app_param + app_param_len) { + switch (*ptr) + { + case BTA_PBAP_APP_PARAM_PHONE_BOOK_SIZE: + param->pull_vcard_listing_rsp.include_phone_book_size = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + BE_STREAM_TO_UINT16(param->pull_vcard_listing_rsp.phone_book_size, ptr); + break; + case BTA_PBAP_APP_PARAM_NEW_MISSED_CALLS: + param->pull_vcard_listing_rsp.include_new_missed_calls = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + BE_STREAM_TO_UINT8(param->pull_vcard_listing_rsp.new_missed_calls, ptr); + break; + case BTA_PBAP_APP_PARAM_PRIMARY_FOLDER_VERSION: + param->pull_vcard_listing_rsp.include_primary_folder_version = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_vcard_listing_rsp.primary_folder_version = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_PRIMARY_FOLDER_VERSION; + break; + case BTA_PBAP_APP_PARAM_SECONDARY_FOLDER_VERSION: + param->pull_vcard_listing_rsp.include_secondary_folder_version = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_vcard_listing_rsp.secondary_folder_version = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_SECONDARY_FOLDER_VERSION; + break; + case BTA_PBAP_APP_PARAM_DATABASE_IDENTIFIER: + param->pull_vcard_listing_rsp.include_database_identifier = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_vcard_listing_rsp.database_identifier = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_DATABASE_IDENTIFIER; + break; + default: + goto error; + break; + } + } +error: + return; +} + +static void parse_pull_vcard_entry_app_param(esp_pbac_param_t *param, uint8_t *app_param, uint16_t app_param_len) +{ + if (app_param == NULL || app_param_len == 0) { + return; + } + uint8_t *ptr = app_param; + while(ptr < app_param + app_param_len) { + switch (*ptr) + { + case BTA_PBAP_APP_PARAM_DATABASE_IDENTIFIER: + param->pull_vcard_entry_rsp.include_database_identifier = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_vcard_entry_rsp.database_identifier = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_DATABASE_IDENTIFIER; + break; + default: + goto error; + break; + } + } +error: + return; +} + +static uint16_t get_next_dir_len_from_path(char *path, uint16_t path_len, uint16_t path_pos) +{ + uint16_t ret = 0; + for (int i = path_pos; i < path_len; ++i) { + if (path[i] == '/' || path[i] == '\0') { + break; + } + else { + ++ret; + } + } + return ret; +} + +void btc_pba_client_cb_handler(btc_msg_t *msg) +{ + uint16_t event = msg->act; + tBTA_PBA_CLIENT *p_data = (tBTA_PBA_CLIENT *)msg->arg; + btc_pba_client_ccb_t *p_ccb = NULL; + esp_pbac_param_t param = {0}; + + switch (event) { + case BTA_PBA_CLIENT_CONN_OPEN_EVT: + if (p_data->conn.error == BTA_PBA_CLIENT_NO_ERROR) { + /* allocate ccb */ + p_ccb = &btc_pba_client_cb.ccb[p_data->conn.handle - 1]; + p_ccb->handle = p_data->conn.handle; + bdcpy(p_ccb->bd_addr.address, p_data->conn.bd_addr); + p_ccb->busy = false; + param.conn_stat.connected = true; + param.conn_stat.peer_supported_repo = p_data->conn.peer_supported_repo; + param.conn_stat.peer_supported_feat = p_data->conn.peer_supported_feat; + param.conn_stat.reason = BTA_PBA_CLIENT_NO_ERROR; + } + else { + param.conn_stat.connected = false; + /* error codes are compatible */ + param.conn_stat.reason = p_data->conn.error; + } + bdcpy(param.conn_stat.remote_bda, p_data->conn.bd_addr); + param.conn_stat.handle = p_data->conn.handle; + btc_pba_client_callback_to_app(ESP_PBAC_CONNECTION_STATE_EVT, ¶m); + btc_queue_advance(); + break; + case BTA_PBA_CLIENT_CONN_CLOSE_EVT: + /* clear ccb */ + p_ccb = &btc_pba_client_cb.ccb[p_data->conn.handle - 1]; + p_ccb->handle = 0; + p_ccb->busy = false; + p_ccb->path_len = 0; + p_ccb->path_pos = 0; + if (p_ccb->path) { + osi_free(p_ccb->path); + p_ccb->path = NULL; + } + memset(p_ccb->bd_addr.address, 0, BD_ADDR_LEN); + param.conn_stat.connected = false; + bdcpy(param.conn_stat.remote_bda, p_data->conn.bd_addr); + param.conn_stat.handle = p_data->conn.handle; + /* error codes are compatible */ + param.conn_stat.reason = p_data->conn.error; + btc_pba_client_callback_to_app(ESP_PBAC_CONNECTION_STATE_EVT, ¶m); + btc_queue_advance(); + break; + case BTA_PBA_CLIENT_PULL_PHONE_BOOK_RSP_EVT: + if (p_data->response.final) { + p_ccb = &btc_pba_client_cb.ccb[p_data->response.handle - 1]; + p_ccb->busy = false; + } + param.pull_phone_book_rsp.handle = p_data->response.handle; + param.pull_phone_book_rsp.final = p_data->response.final; + param.pull_phone_book_rsp.result = p_data->response.status; + param.pull_phone_book_rsp.data = p_data->response.data; + param.pull_phone_book_rsp.data_len = p_data->response.data_len; + parse_pull_phone_book_app_param(¶m, p_data->response.app_param, p_data->response.app_param_len); + btc_pba_client_callback_to_app(ESP_PBAC_PULL_PHONE_BOOK_RESPONSE_EVT, ¶m); + if (p_data->response.pkt != NULL) { + osi_free(p_data->response.pkt); + } + break; + case BTA_PBA_CLIENT_SET_PHONE_BOOK_RSP_EVT: + p_ccb = &btc_pba_client_cb.ccb[p_data->response.handle - 1]; + if (p_data->response.status == BTA_PBA_CLIENT_NO_ERROR && p_ccb->path_pos < p_ccb->path_len) { + /* since path_len is not zero, path should not be NULL, use asset to check */ + assert(p_ccb->path != NULL); + uint16_t dir_name_len = get_next_dir_len_from_path(p_ccb->path, p_ccb->path_len, p_ccb->path_pos); + if (dir_name_len > 0) { + char *dir_name = osi_malloc(dir_name_len + 1); + assert(dir_name != NULL); + memcpy(dir_name, p_ccb->path + p_ccb->path_pos, dir_name_len); + dir_name[dir_name_len] = '\0'; + p_ccb->path_pos += dir_name_len + 1; + BTA_PbaClientSetPhoneBook(p_data->response.handle, ESP_PBAC_SET_PHONE_BOOK_FLAGS_DOWN, dir_name); + /* break here, don't report event to upper */ + break; + } + } + /* set path done or failed, clear status */ + p_ccb->path_len = 0; + p_ccb->path_pos = 0; + if (p_ccb->path) { + osi_free(p_ccb->path); + p_ccb->path = NULL; + } + p_ccb->busy = false; + param.set_phone_book_rsp.handle = p_data->response.handle; + param.set_phone_book_rsp.result = p_data->response.status; + btc_pba_client_callback_to_app(ESP_PBAC_SET_PHONE_BOOK_RESPONSE_EVT, ¶m); + if (p_data->response.pkt != NULL) { + osi_free(p_data->response.pkt); + } + break; + case BTA_PBA_CLIENT_PULL_VCARD_LISTING_RSP_EVT: + if (p_data->response.final) { + p_ccb = &btc_pba_client_cb.ccb[p_data->response.handle - 1]; + p_ccb->busy = false; + } + param.pull_vcard_listing_rsp.handle = p_data->response.handle; + param.pull_vcard_listing_rsp.final = p_data->response.final; + param.pull_vcard_listing_rsp.result = p_data->response.status; + param.pull_vcard_listing_rsp.data = p_data->response.data; + param.pull_vcard_listing_rsp.data_len = p_data->response.data_len; + parse_pull_vcard_listing_app_param(¶m, p_data->response.app_param, p_data->response.app_param_len); + btc_pba_client_callback_to_app(ESP_PBAC_PULL_VCARD_LISTING_RESPONSE_EVT, ¶m); + if (p_data->response.pkt != NULL) { + osi_free(p_data->response.pkt); + } + break; + case BTA_PBA_CLIENT_PULL_VCARD_ENTRY_RSP_EVT: + if (p_data->response.final) { + p_ccb = &btc_pba_client_cb.ccb[p_data->response.handle - 1]; + p_ccb->busy = false; + } + param.pull_vcard_entry_rsp.handle = p_data->response.handle; + param.pull_vcard_entry_rsp.final = p_data->response.final; + param.pull_vcard_entry_rsp.result = p_data->response.status; + param.pull_vcard_entry_rsp.data = p_data->response.data; + param.pull_vcard_entry_rsp.data_len = p_data->response.data_len; + parse_pull_vcard_entry_app_param(¶m, p_data->response.app_param, p_data->response.app_param_len); + btc_pba_client_callback_to_app(ESP_PBAC_PULL_VCARD_ENTRY_RESPONSE_EVT, ¶m); + if (p_data->response.pkt != NULL) { + osi_free(p_data->response.pkt); + } + break; + case BTA_PBA_CLIENT_REGISTER_EVT: + /* init process: Enable -> Register */ + btc_pba_client_callback_to_app(ESP_PBAC_INIT_EVT, NULL); + break; + case BTA_PBA_CLIENT_DISABLE_EVT: + /* deinit process: Deregister -> Disable */ + btc_pba_client_callback_to_app(ESP_PBAC_DEINIT_EVT, NULL); + break; + default: + BTC_TRACE_WARNING("%s: unknown event (%d)", __func__, event); + break; + } +} + +#endif diff --git a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index 482c1c9a457e..9cf478c4f617 100644 --- a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h +++ b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h @@ -124,6 +124,25 @@ #define UC_BT_ENC_KEY_SIZE_CTRL_MODE 0 #endif +//PBAP Client +#ifdef CONFIG_BT_PBAC_ENABLED +#define UC_BT_PBAC_ENABLED CONFIG_BT_PBAC_ENABLED +#else +#define UC_BT_PBAC_ENABLED FALSE +#endif + +#ifdef CONFIG_BT_PBAC_SUPPORTED_FEAT +#define UC_BT_PBAC_SUPPORTED_FEAT CONFIG_BT_PBAC_SUPPORTED_FEAT +#else +#define UC_BT_PBAC_SUPPORTED_FEAT 0x00 +#endif + +#ifdef CONFIG_BT_PBAC_PREFERRED_MTU +#define UC_BT_PBAC_PREFERRED_MTU CONFIG_BT_PBAC_PREFERRED_MTU +#else +#define UC_BT_PBAC_PREFERRED_MTU 0 +#endif + //GOEPC (BT) #ifdef CONFIG_BT_GOEPC_ENABLED #define UC_BT_GOEPC_ENABLED CONFIG_BT_GOEPC_ENABLED diff --git a/components/bt/host/bluedroid/common/include/common/bt_target.h b/components/bt/host/bluedroid/common/include/common/bt_target.h index 0e869dad45a1..239433620b26 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -177,12 +177,22 @@ #endif /* UC_BT_HID_DEVICE_ENABLED */ #if UC_BT_GOEPC_ENABLED +#ifndef RFCOMM_INCLUDED +#define RFCOMM_INCLUDED TRUE +#endif #ifndef OBEX_INCLUDED #define OBEX_INCLUDED TRUE #endif #define GOEPC_INCLUDED TRUE #endif /* UC_BT_GOEPC_ENABLED */ +#if UC_BT_PBAC_ENABLED +#define BTC_PBA_CLIENT_INCLUDED TRUE +#define BTC_PBA_SUPPORTED_FEAT UC_BT_PBAC_SUPPORTED_FEAT +#define BTC_PBA_PREFERRED_MTU UC_BT_PBAC_PREFERRED_MTU +#define BTA_PBA_CLIENT_INCLUDED TRUE +#endif + #endif /* UC_BT_CLASSIC_ENABLED */ /* This is set to enable use of GAP L2CAP connections. */ diff --git a/components/bt/host/bluedroid/common/include/common/bt_trace.h b/components/bt/host/bluedroid/common/include/common/bt_trace.h index 74b7f10574d7..9acaf69097af 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_trace.h +++ b/components/bt/host/bluedroid/common/include/common/bt_trace.h @@ -330,6 +330,13 @@ static inline void trc_dump_buffer(const char *prefix, uint8_t *data, uint16_t l #define OBEX_TL_L2CAP_TRACE_EVENT(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("OBEX_TL_L2CAP", fmt, ## args);} #define OBEX_TL_L2CAP_TRACE_DEBUG(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("OBEX_TL_L2CAP", fmt, ## args);} +/* Define tracing for OBEX_TL_RFCOMM */ +#define OBEX_TL_RFCOMM_TRACE_ERROR(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("OBEX_TL_RFCOMM", fmt, ## args);} +#define OBEX_TL_RFCOMM_TRACE_WARNING(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("OBEX_TL_RFCOMM", fmt, ## args);} +#define OBEX_TL_RFCOMM_TRACE_API(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_API && BT_LOG_LEVEL_CHECK(AVRC,API)) BT_PRINT_I("OBEX_TL_RFCOMM", fmt, ## args);} +#define OBEX_TL_RFCOMM_TRACE_EVENT(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("OBEX_TL_RFCOMM", fmt, ## args);} +#define OBEX_TL_RFCOMM_TRACE_DEBUG(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("OBEX_TL_RFCOMM", fmt, ## args);} + /* Define tracing for GOEPC */ #define GOEPC_TRACE_ERROR(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("BT_GOEPC", fmt, ## args);} #define GOEPC_TRACE_WARNING(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("BT_GOEPC", fmt, ## args);} @@ -512,12 +519,20 @@ extern UINT8 btif_trace_level; #define OBEX_TRACE_EVENT(fmt, args...) #define OBEX_TRACE_DEBUG(fmt, args...) +/* Define tracing for OBEX L2CAP transport layer */ #define OBEX_TL_L2CAP_TRACE_ERROR(fmt, args...) #define OBEX_TL_L2CAP_TRACE_WARNING(fmt, args...) #define OBEX_TL_L2CAP_TRACE_API(fmt, args...) #define OBEX_TL_L2CAP_TRACE_EVENT(fmt, args...) #define OBEX_TL_L2CAP_TRACE_DEBUG(fmt, args...) +/* Define tracing for OBEX RFCOMM transport layer */ +#define OBEX_TL_RFCOMM_TRACE_ERROR(fmt, args...) +#define OBEX_TL_RFCOMM_TRACE_WARNING(fmt, args...) +#define OBEX_TL_RFCOMM_TRACE_API(fmt, args...) +#define OBEX_TL_RFCOMM_TRACE_EVENT(fmt, args...) +#define OBEX_TL_RFCOMM_TRACE_DEBUG(fmt, args...) + /* Define tracing for GOEPC */ #define GOEPC_TRACE_ERROR(fmt, args...) #define GOEPC_TRACE_WARNING(fmt, args...) diff --git a/components/bt/host/bluedroid/main/bte_init.c b/components/bt/host/bluedroid/main/bte_init.c index 77508b37773e..c60f6f7bd173 100644 --- a/components/bt/host/bluedroid/main/bte_init.c +++ b/components/bt/host/bluedroid/main/bte_init.c @@ -156,6 +156,10 @@ #include "bta_pan_int.h" #endif +#if BTA_PBA_CLIENT_INCLUDED == TRUE +#include "bta_pba_client_int.h" +#endif + #include "bta_sys_int.h" // control block for patch ram downloading @@ -184,6 +188,12 @@ void BTE_DeinitStack(void) { //BTA Modules #if (BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE) +#if BTA_PBA_CLIENT_INCLUDED == TRUE + if (bta_pba_client_cb_ptr) { + osi_free(bta_pba_client_cb_ptr); + bta_pba_client_cb_ptr = NULL; + } +#endif #if GATTS_INCLUDED == TRUE if (bta_gatts_cb_ptr){ osi_free(bta_gatts_cb_ptr); @@ -521,6 +531,12 @@ bt_status_t BTE_InitStack(void) #if BTA_PAN_INCLUDED==TRUE memset((void *)bta_pan_cb_ptr, 0, sizeof(tBTA_PAN_CB)); #endif +#if BTA_PBA_CLIENT_INCLUDED == TRUE + if ((bta_pba_client_cb_ptr = (tBTA_PBA_CLIENT_CB *)osi_malloc(sizeof(tBTA_PBA_CLIENT_CB))) == NULL) { + goto error_exit; + } + memset((void *)bta_pba_client_cb_ptr, 0, sizeof(tBTA_PBA_CLIENT_CB)); +#endif #endif // BTA_INCLUDED == TRUE return BT_STATUS_SUCCESS; diff --git a/components/bt/host/bluedroid/stack/goep/goepc_api.c b/components/bt/host/bluedroid/stack/goep/goepc_api.c index 60badbaeec0c..350e80c9c27d 100644 --- a/components/bt/host/bluedroid/stack/goep/goepc_api.c +++ b/components/bt/host/bluedroid/stack/goep/goepc_api.c @@ -359,7 +359,7 @@ UINT16 GOEPC_RequestAddHeader(UINT16 handle, UINT8 header_id, const UINT8 *data, break; } - if (data == NULL || data_len == 0) { + if ((data == NULL) ^ (data_len == 0)) { ret = GOEP_INVALID_PARAM; break; } diff --git a/components/bt/host/bluedroid/stack/include/stack/goep_common.h b/components/bt/host/bluedroid/stack/include/stack/goep_common.h index 82b315691e8d..74cb3ca8db5f 100644 --- a/components/bt/host/bluedroid/stack/include/stack/goep_common.h +++ b/components/bt/host/bluedroid/stack/include/stack/goep_common.h @@ -8,11 +8,12 @@ #include "common/bt_target.h" -#define GOEP_SUCCESS 0 /* Operation successful */ -#define GOEP_FAILURE 1 /* Operation failed */ -#define GOEP_NO_RESOURCES 3 /* Not enough resources */ -#define GOEP_BAD_HANDLE 4 /* Bad handle */ -#define GOEP_INVALID_PARAM 5 /* Invalid parameter */ -#define GOEP_INVALID_STATE 6 /* Operation not allow in current state */ -#define GOEP_CONGEST 7 /* Congest */ -#define GOEP_TL_ERROR 8 /* Lower transport layer error */ +/* GOEP Client or Server(not supported yet) API return code */ +#define GOEP_SUCCESS 0x00 /* Operation successful */ +#define GOEP_FAILURE 0x01 /* Operation failed */ +#define GOEP_NO_RESOURCES 0x02 /* Not enough resources */ +#define GOEP_BAD_HANDLE 0x04 /* Bad handle */ +#define GOEP_INVALID_PARAM 0x08 /* Invalid parameter */ +#define GOEP_INVALID_STATE 0x10 /* Operation not allow in current state */ +#define GOEP_CONGEST 0x20 /* Congest */ +#define GOEP_TL_ERROR 0x40 /* Lower transport layer error */ diff --git a/components/bt/host/bluedroid/stack/include/stack/obex_api.h b/components/bt/host/bluedroid/stack/include/stack/obex_api.h index e130a48734ed..d22cd33c527f 100644 --- a/components/bt/host/bluedroid/stack/include/stack/obex_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/obex_api.h @@ -19,8 +19,6 @@ #define OBEX_NOT_OPEN 6 /* Connection not open */ #define OBEX_PACKET_TOO_LARGE 7 /* Packet size large than MTU */ #define OBEX_ERROR_TL 8 /* Operation failed in transport layer */ -#define OBEX_TRY_AGAIN 9 /* Operation failed, connection congestion, try again */ - /* * OBEX profile definitions @@ -162,12 +160,21 @@ typedef struct BD_ADDR addr; /* peer bluetooth device address */ } tOBEX_OVER_L2CAP_SVR; +typedef struct +{ + UINT8 scn; /* service channel number */ + UINT16 sec_mask; /* security mask */ + UINT16 pref_mtu; /* preferred mtu, limited by rfcomm mtu */ + BD_ADDR addr; /* peer bluetooth device address */ +} tOBEX_OVER_RFCOMM_SVR; + typedef struct { UINT8 tl; /* transport type, OBEX_OVER_L2CAP or OBEX_OVER_RFCOMM */ union { tOBEX_OVER_L2CAP_SVR l2cap; + tOBEX_OVER_RFCOMM_SVR rfcomm; }; } tOBEX_SVR_INFO; diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_int.h b/components/bt/host/bluedroid/stack/obex/include/obex_int.h index 4512e89c2a7f..28c4eab45d5d 100644 --- a/components/bt/host/bluedroid/stack/obex/include/obex_int.h +++ b/components/bt/host/bluedroid/stack/obex/include/obex_int.h @@ -14,7 +14,13 @@ #if (OBEX_INCLUDED == TRUE) -#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET /* should set to max value of all transport layer */ +#if (RFCOMM_INCLUDED == TRUE) +#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET /* should set to max value of all transport layer */ +#define OBEX_BT_HDR_RESERVE_LEN OBEX_TL_RFCOMM_BT_HDR_RESERVE_LEN /* should set to max value of all transport layer */ +#else +#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_L2CAP_BT_HDR_OFFSET_MIN +#define OBEX_BT_HDR_RESERVE_LEN OBEX_TL_L2CAP_BT_HDR_RESERVE_LEN +#endif #define OBEX_ROLE_CLIENT 0x01 #define OBEX_ROLE_SERVER 0x02 @@ -24,10 +30,6 @@ #define OBEX_STATE_OPENING 1 /* Starting to open a connection */ #define OBEX_STATE_OPENED 2 /* Connection opened */ -/* Store 16 bits data in big endian format, not modify the p_buf */ -#define STORE16BE(p_buf, data) do { *p_buf = ((data)>>8)&0xff; \ - *(p_buf+1) = (data)&0xff;} while(0) - /* OBEX Connection Control block */ typedef struct { tOBEX_MSG_CBACK *callback; /* Connection msg callback function */ @@ -65,6 +67,7 @@ extern tOBEX_CB *obex_cb_ptr; #endif void obex_tl_l2cap_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg); +void obex_tl_rfcomm_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg); tOBEX_CCB *obex_allocate_ccb(void); tOBEX_SCB *obex_allocate_scb(void); void obex_free_ccb(tOBEX_CCB *p_ccb); diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_tl.h b/components/bt/host/bluedroid/stack/obex/include/obex_tl.h index 6a60ed51dc61..9211b9b0c207 100644 --- a/components/bt/host/bluedroid/stack/obex/include/obex_tl.h +++ b/components/bt/host/bluedroid/stack/obex/include/obex_tl.h @@ -68,9 +68,18 @@ typedef struct BD_ADDR addr; /* peer bluetooth device address */ } tOBEX_TL_L2CAP_SVR; +typedef struct +{ + UINT8 scn; /* service channel number */ + UINT16 sec_mask; /* security mask */ + UINT16 pref_mtu; /* preferred mtu, limited by rfcomm mtu */ + BD_ADDR addr; /* peer bluetooth device address */ +} tOBEX_TL_RFCOMM_SVR; + typedef union { tOBEX_TL_L2CAP_SVR l2cap; + tOBEX_TL_RFCOMM_SVR rfcomm; } tOBEX_TL_SVR_INFO; typedef void (tOBEX_TL_CBACK)(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg); diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h b/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h index 83f062bb6966..326677665a1a 100644 --- a/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h +++ b/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h @@ -10,7 +10,8 @@ #if (OBEX_INCLUDED == TRUE) -#define OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET 13 /* L2CAP_MIN_OFFSET */ +#define OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET 13 /* equal to L2CAP_MIN_OFFSET */ +#define OBEX_TL_L2CAP_BT_HDR_RESERVE_LEN 0 /* not require any additional byte */ tOBEX_TL_OPS *obex_tl_l2cap_ops_get(void); diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h b/components/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h new file mode 100644 index 000000000000..61b9d2958999 --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "obex_tl.h" + +#if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) + +#define OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET 18 /* RFCOMM_MIN_OFFSET + L2CAP_MIN_OFFSET */ +#define OBEX_TL_RFCOMM_BT_HDR_RESERVE_LEN 1 /* reserve 1 byte for rfcomm fcs */ + +tOBEX_TL_OPS *obex_tl_rfcomm_ops_get(void); + +#endif /* #if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/obex/obex_api.c b/components/bt/host/bluedroid/stack/obex/obex_api.c index befabb0e92b3..8851a0f81a1a 100644 --- a/components/bt/host/bluedroid/stack/obex/obex_api.c +++ b/components/bt/host/bluedroid/stack/obex/obex_api.c @@ -14,6 +14,7 @@ #include "obex_int.h" #include "obex_tl.h" #include "obex_tl_l2cap.h" +#include "obex_tl_rfcomm.h" #if (OBEX_INCLUDED == TRUE) @@ -25,6 +26,12 @@ static inline void obex_server_to_tl_server(tOBEX_SVR_INFO *server, tOBEX_TL_SVR tl_server->l2cap.pref_mtu = server->l2cap.pref_mtu; bdcpy(tl_server->l2cap.addr, server->l2cap.addr); } + else if (server->tl == OBEX_OVER_RFCOMM) { + tl_server->rfcomm.scn = server->rfcomm.scn; + tl_server->rfcomm.sec_mask = server->rfcomm.sec_mask; + tl_server->rfcomm.pref_mtu = server->rfcomm.pref_mtu; + bdcpy(tl_server->rfcomm.addr, server->rfcomm.addr); + } else { OBEX_TRACE_ERROR("Unsupported OBEX transport type\n"); assert(0); @@ -34,7 +41,7 @@ static inline void obex_server_to_tl_server(tOBEX_SVR_INFO *server, tOBEX_TL_SVR static inline void obex_updata_packet_length(BT_HDR *p_buf, UINT16 len) { UINT8 *p_pkt_len = (UINT8 *)(p_buf + 1) + p_buf->offset + 1; - STORE16BE(p_pkt_len, len); + UINT16_TO_BE_FIELD(p_pkt_len, len); } /******************************************************************************* @@ -62,13 +69,12 @@ UINT16 OBEX_Init(void) if (obex_cb.tl_ops[OBEX_OVER_L2CAP]->init != NULL) { obex_cb.tl_ops[OBEX_OVER_L2CAP]->init(obex_tl_l2cap_callback); } - /* Not implement yet */ - /* +#if (RFCOMM_INCLUDED == TRUE) obex_cb.tl_ops[OBEX_OVER_RFCOMM] = obex_tl_rfcomm_ops_get(); if (obex_cb.tl_ops[OBEX_OVER_RFCOMM]->init != NULL) { obex_cb.tl_ops[OBEX_OVER_RFCOMM]->init(obex_tl_rfcomm_callback); } - */ +#endif obex_cb.trace_level = BT_TRACE_LEVEL_ERROR; return OBEX_SUCCESS; } @@ -317,7 +323,7 @@ UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_ if (buff_size < OBEX_MIN_PACKET_SIZE || info == NULL || out_pkt == NULL) { return OBEX_INVALID_PARAM; } - buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET; + buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET + OBEX_BT_HDR_RESERVE_LEN; BT_HDR *p_buf= (BT_HDR *)osi_malloc(buff_size); if (p_buf == NULL) { @@ -327,7 +333,7 @@ UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_ UINT16 pkt_len = OBEX_MIN_PACKET_SIZE; p_buf->offset = OBEX_BT_HDR_MIN_OFFSET; /* use layer_specific to store the max data length allowed */ - p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET; + p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET - OBEX_BT_HDR_RESERVE_LEN; UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; /* byte 0: opcode */ *p_data++ = info->opcode; @@ -343,7 +349,7 @@ UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_ /* byte 4: flags */ *p_data++ = info->flags; /* byte 5, 6: maximum OBEX packet length, recommend to set as our mtu*/ - STORE16BE(p_data, info->max_packet_length); + UINT16_TO_BE_FIELD(p_data, info->max_packet_length); pkt_len += 4; break; case OBEX_OPCODE_SETPATH: @@ -357,7 +363,7 @@ UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_ break; } - STORE16BE(p_pkt_len, pkt_len); + UINT16_TO_BE_FIELD(p_pkt_len, pkt_len); p_buf->len = pkt_len; *out_pkt = p_buf; return OBEX_SUCCESS; @@ -378,7 +384,7 @@ UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out if (buff_size < OBEX_MIN_PACKET_SIZE || info == NULL || out_pkt == NULL) { return OBEX_INVALID_PARAM; } - buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET; + buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET + OBEX_BT_HDR_RESERVE_LEN; BT_HDR *p_buf= (BT_HDR *)osi_malloc(buff_size); if (p_buf == NULL) { @@ -388,7 +394,7 @@ UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out UINT16 pkt_len = OBEX_MIN_PACKET_SIZE; p_buf->offset = OBEX_BT_HDR_MIN_OFFSET; /* use layer_specific to store the max data length allowed */ - p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET; + p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET - OBEX_BT_HDR_RESERVE_LEN; UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; /* byte 0: response code */ *p_data++ = info->response_code; @@ -405,14 +411,14 @@ UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out /* byte 4: flags */ *p_data++ = info->flags; /* byte 5, 6: maximum OBEX packet length, recommend to set as our mtu */ - STORE16BE(p_data, info->max_packet_length); + UINT16_TO_BE_FIELD(p_data, info->max_packet_length); pkt_len += 4; break; default: break; } - STORE16BE(p_pkt_len, pkt_len); + UINT16_TO_BE_FIELD(p_pkt_len, pkt_len); p_buf->len = pkt_len; *out_pkt = p_buf; return OBEX_SUCCESS; @@ -465,7 +471,7 @@ UINT16 OBEX_AppendHeader(BT_HDR *pkt, const UINT8 *header) pkt->len += header_len; /* point to packet len */ p_data++; - STORE16BE(p_data, pkt->len); + UINT16_TO_BE_FIELD(p_data, pkt->len); return OBEX_SUCCESS; } @@ -481,7 +487,11 @@ UINT16 OBEX_AppendHeader(BT_HDR *pkt, const UINT8 *header) *******************************************************************************/ UINT16 OBEX_AppendHeaderRaw(BT_HDR *pkt, UINT8 header_id, const UINT8 *data, UINT16 data_len) { - if (pkt == NULL || data == NULL) { + if (pkt == NULL) { + return OBEX_INVALID_PARAM; + } + + if ((data == NULL) ^ (data_len == 0)) { return OBEX_INVALID_PARAM; } @@ -524,15 +534,17 @@ UINT16 OBEX_AppendHeaderRaw(BT_HDR *pkt, UINT8 header_id, const UINT8 *data, UIN *p_start++ = header_id; if (store_header_len) { /* store header length */ - STORE16BE(p_start, header_len); + UINT16_TO_BE_FIELD(p_start, header_len); p_start+= 2; } - /* store data */ - memcpy(p_start, data, data_len); + if (data != NULL) { + /* store data */ + memcpy(p_start, data, data_len); + } pkt->len += header_len; /* point to packet len */ p_data++; - STORE16BE(p_data, pkt->len); + UINT16_TO_BE_FIELD(p_data, pkt->len); return OBEX_SUCCESS; } diff --git a/components/bt/host/bluedroid/stack/obex/obex_main.c b/components/bt/host/bluedroid/stack/obex/obex_main.c index c5f04539021d..7810ff3c3324 100644 --- a/components/bt/host/bluedroid/stack/obex/obex_main.c +++ b/components/bt/host/bluedroid/stack/obex/obex_main.c @@ -208,4 +208,9 @@ void obex_tl_l2cap_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg) obex_tl_evt_handler(OBEX_OVER_L2CAP, evt, msg); } +void obex_tl_rfcomm_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg) +{ + obex_tl_evt_handler(OBEX_OVER_RFCOMM, evt, msg); +} + #endif /* #if (OBEX_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c b/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c index dfd423c73d9a..d509b4cfabf7 100644 --- a/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c +++ b/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c @@ -484,6 +484,10 @@ void obex_tl_l2cap_disconnect_ind(UINT16 lcid, BOOLEAN is_conf_needed) return; } + if (p_ccb->initiator && find_scb_by_psm(p_ccb->vpsm) == NULL) { + L2CA_Deregister(p_ccb->vpsm); + } + tOBEX_TL_MSG msg = {0}; msg.any.hdl = p_ccb->allocated; obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); diff --git a/components/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c b/components/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c new file mode 100644 index 000000000000..ce7799c8673f --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c @@ -0,0 +1,439 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "osi/osi.h" +#include "osi/allocator.h" +#include "common/bt_target.h" + +#include "stack/port_api.h" +#include "stack/btm_api.h" +#include "stack/sdpdefs.h" +#include "obex_tl.h" +#include "obex_tl_rfcomm.h" + +#if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) + +#define OBEX_TL_RFCOMM_NUM_CONN 4 +#define OBEX_TL_RFCOMM_NUM_SERVER 2 + +#define OBEX_TL_RFCOMM_EVENT_MARK (PORT_EV_FC | PORT_EV_FCS) + +typedef struct { + UINT16 rfc_handle; /* rfcomm handle */ + UINT16 mtu; /* rfcomm mtu */ + BOOLEAN initiator; /* TRUE if is initiator, otherwise FALSE */ + UINT8 scn; /* service channel number */ + BD_ADDR addr; /* peer bluetooth device address */ + UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, equal to handle */ +} tOBEX_TL_RFCOMM_CCB; + +typedef struct { + UINT16 rfc_handle; /* rfcomm handle */ + UINT8 scn; /* service channel number */ + UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, handle of server will left shift 8 bits */ +} tOBEX_TL_RFCOMM_SCB; + +typedef struct { + tOBEX_TL_CBACK *callback; /* Upper layer callback */ + tOBEX_TL_RFCOMM_CCB ccb[OBEX_TL_RFCOMM_NUM_CONN]; + tOBEX_TL_RFCOMM_SCB scb[OBEX_TL_RFCOMM_NUM_SERVER]; + UINT8 trace_level; /* trace level */ +} tOBEX_TL_RFCOMM_CB; + +#if OBEX_DYNAMIC_MEMORY == FALSE +static tOBEX_TL_RFCOMM_CB obex_tl_rfcomm_cb; +#else +static tOBEX_TL_RFCOMM_CB *obex_tl_rfcomm_cb_ptr = NULL; +#define obex_tl_rfcomm_cb (*obex_tl_rfcomm_cb_ptr) +#endif + +static tOBEX_TL_RFCOMM_CCB *allocate_ccb(void) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_CONN; ++i) { + if (obex_tl_rfcomm_cb.ccb[i].allocated == 0) { + obex_tl_rfcomm_cb.ccb[i].allocated = i + 1; + p_ccb = &obex_tl_rfcomm_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tOBEX_TL_RFCOMM_SCB *allocate_scb(void) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) { + if (obex_tl_rfcomm_cb.scb[i].allocated == 0) { + obex_tl_rfcomm_cb.scb[i].allocated = i + 1; + p_scb = &obex_tl_rfcomm_cb.scb[i]; + break; + } + } + return p_scb; +} + +static void free_ccb(tOBEX_TL_RFCOMM_CCB *p_ccb) +{ + memset(p_ccb, 0, sizeof(tOBEX_TL_RFCOMM_CCB)); +} + +static void free_scb(tOBEX_TL_RFCOMM_SCB *p_scb) +{ + memset(p_scb, 0, sizeof(tOBEX_TL_RFCOMM_SCB)); +} + +static tOBEX_TL_RFCOMM_CCB *find_ccb_by_handle(UINT16 handle) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + if (handle > 0 && handle <= OBEX_TL_RFCOMM_NUM_CONN) { + if (obex_tl_rfcomm_cb.ccb[handle-1].allocated == handle) { + p_ccb = &obex_tl_rfcomm_cb.ccb[handle-1]; + } + } + return p_ccb; +} + +static tOBEX_TL_RFCOMM_CCB *find_ccb_by_rfc_handle(UINT16 rfc_handle) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_CONN; ++i) { + if (obex_tl_rfcomm_cb.ccb[i].allocated && obex_tl_rfcomm_cb.ccb[i].rfc_handle == rfc_handle) { + p_ccb = &obex_tl_rfcomm_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tOBEX_TL_RFCOMM_SCB *find_scb_by_handle(UINT16 handle) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = NULL; + handle = handle >> 8; + if (handle > 0 && handle <= OBEX_TL_RFCOMM_NUM_SERVER) { + if (obex_tl_rfcomm_cb.scb[handle-1].allocated == handle) { + p_scb = &obex_tl_rfcomm_cb.scb[handle-1]; + } + } + return p_scb; +} + +static tOBEX_TL_RFCOMM_SCB *find_scb_by_rfc_handle(UINT16 rfc_handle) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) { + if (obex_tl_rfcomm_cb.scb[i].allocated && obex_tl_rfcomm_cb.scb[i].rfc_handle == rfc_handle) { + p_scb = &obex_tl_rfcomm_cb.scb[i]; + break; + } + } + return p_scb; +} + +static tOBEX_TL_RFCOMM_SCB *find_scb_by_scn(UINT16 scn) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) { + if (obex_tl_rfcomm_cb.scb[i].allocated && obex_tl_rfcomm_cb.scb[i].scn == scn) { + p_scb = &obex_tl_rfcomm_cb.scb[i]; + break; + } + } + return p_scb; +} + +static void rfcomm_mgmt_event_handler(tOBEX_TL_RFCOMM_CCB *p_ccb, UINT32 code) +{ + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + switch (code) + { + case PORT_SUCCESS: + /* event already handled, do nothing */ + break; + default: + /* other event, disconnect */ + obex_tl_rfcomm_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + break; + } +} + +static void rfcomm_client_mgmt_callback(UINT32 code, UINT16 rfc_handle, void* data) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb == NULL) { + OBEX_TL_RFCOMM_TRACE_DEBUG("No ccb to handle rfcomm event\n"); + return; + } + /* connection opened, handle event here */ + if (code == PORT_SUCCESS) { + assert(data != NULL); + tPORT_MGMT_CL_CALLBACK_ARG *cl_mgmt_cb_arg = (tPORT_MGMT_CL_CALLBACK_ARG *)data; + p_ccb->mtu = cl_mgmt_cb_arg->peer_mtu; + + tOBEX_TL_MSG msg = {0}; + msg.conn_open.hdl = p_ccb->allocated; + msg.conn_open.peer_mtu = p_ccb->mtu; + msg.conn_open.our_mtu = p_ccb->mtu; + obex_tl_rfcomm_cb.callback(OBEX_TL_CONN_OPEN_EVT, &msg); + } + rfcomm_mgmt_event_handler(p_ccb, code); +} + +static void rfcomm_server_mgmt_callback(UINT32 code, UINT16 rfc_handle, void* data) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + /* incoming connection, handle event here */ + if (code == PORT_SUCCESS) { + assert(data != NULL); + tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_rfc_handle(rfc_handle); + tPORT_MGMT_SR_CALLBACK_ARG *sr_mgmt_cb_arg = (tPORT_MGMT_SR_CALLBACK_ARG *)data; + if (p_scb == NULL) { + OBEX_TL_RFCOMM_TRACE_WARNING("No scb to this rfcomm connection\n"); + /* tell rfcomm to reject this connection */ + sr_mgmt_cb_arg->accept = FALSE; + return; + } + + /* try to find p_ccb with this rfc_handle, we expect to get a NULL */ + p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb == NULL) { + p_ccb = allocate_ccb(); + if (p_ccb == NULL) { + OBEX_TL_RFCOMM_TRACE_WARNING("can not allocate a ccb for new connection\n"); + sr_mgmt_cb_arg->accept = FALSE; + return; + } + } + else { + OBEX_TL_RFCOMM_TRACE_WARNING("found duplicate rfcomm connection\n"); + } + + p_ccb->initiator = FALSE; + p_ccb->rfc_handle = rfc_handle; + p_ccb->scn = p_scb->scn; + p_ccb->mtu = sr_mgmt_cb_arg->peer_mtu; + /* get peer bd_addr */ + PORT_CheckConnection(rfc_handle, FALSE, p_ccb->addr, NULL); + + tOBEX_TL_MSG msg = {0}; + msg.conn_income.hdl = p_ccb->allocated; + msg.conn_income.peer_mtu = p_ccb->mtu; + msg.conn_income.our_mtu = p_ccb->mtu; + msg.conn_income.svr_hdl = (p_scb->allocated << 8); + obex_tl_rfcomm_cb.callback(OBEX_TL_CONN_INCOME_EVT, &msg); + } + else { + /* other event, it means server is connected */ + p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb == NULL) { + OBEX_TL_RFCOMM_TRACE_DEBUG("No ccb to handle rfcomm event\n"); + return; + } + } + rfcomm_mgmt_event_handler(p_ccb, code); +} + +static int rfcomm_data_callback(UINT16 rfc_handle, UINT8 *p_buf, UINT16 len, int type) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb != NULL && type == DATA_CO_CALLBACK_TYPE_INCOMING) { + tOBEX_TL_MSG msg = {0}; + msg.data.hdl = p_ccb->allocated; + msg.data.p_buf = (BT_HDR *)p_buf; + obex_tl_rfcomm_cb.callback(OBEX_TL_DATA_EVT, &msg); + PORT_FlowControl_GiveCredit(rfc_handle, TRUE, 1); + } + else if(p_buf != NULL) { + osi_free(p_buf); + } + return 1; +} + +static void rfcomm_event_callback(UINT32 code, UINT16 rfc_handle) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb == NULL) { + OBEX_TL_RFCOMM_TRACE_WARNING("No ccb to handle rfcomm event\n"); + return; + } + + if (code & PORT_EV_FC) { + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + if (code & PORT_EV_FCS) { + obex_tl_rfcomm_cb.callback(OBEX_TL_UNCONGEST_EVT, &msg); + } + else { + obex_tl_rfcomm_cb.callback(OBEX_TL_CONGEST_EVT, &msg); + } + } +} + +void obex_tl_rfcomm_init(tOBEX_TL_CBACK *callback) +{ + assert(callback != NULL); +#if (OBEX_DYNAMIC_MEMORY) + if (!obex_tl_rfcomm_cb_ptr) { + obex_tl_rfcomm_cb_ptr = (tOBEX_TL_RFCOMM_CB *)osi_malloc(sizeof(tOBEX_TL_RFCOMM_CB)); + if (!obex_tl_rfcomm_cb_ptr) { + OBEX_TL_RFCOMM_TRACE_ERROR("OBEX over RFCOMM transport layer initialize failed, no memory\n"); + assert(0); + } + } +#endif /* #if (OBEX_DYNAMIC_MEMORY) */ + memset(&obex_tl_rfcomm_cb, 0, sizeof(tOBEX_TL_RFCOMM_CB)); + obex_tl_rfcomm_cb.callback = callback; + obex_tl_rfcomm_cb.trace_level = BT_TRACE_LEVEL_ERROR; +} + +void obex_tl_rfcomm_deinit(void) +{ +#if (OBEX_DYNAMIC_MEMORY) + if (obex_tl_rfcomm_cb_ptr) { + osi_free(obex_tl_rfcomm_cb_ptr); + obex_tl_rfcomm_cb_ptr = NULL; + } +#endif /* #if (OBEX_DYNAMIC_MEMORY) */ +} + +UINT16 obex_tl_rfcomm_connect(tOBEX_TL_SVR_INFO *server) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = allocate_ccb(); + if (p_ccb == NULL) { + return 0; + } + + BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_OBEX, server->rfcomm.sec_mask, BT_PSM_RFCOMM, BTM_SEC_PROTO_RFCOMM, server->rfcomm.scn); + if (RFCOMM_CreateConnection(UUID_PROTOCOL_OBEX, server->rfcomm.scn, FALSE, server->rfcomm.pref_mtu, + server->rfcomm.addr, &p_ccb->rfc_handle, rfcomm_client_mgmt_callback) != PORT_SUCCESS) { + free_ccb(p_ccb); + return 0; + } + + /* set up data callback, event mask and event callback */ + PORT_SetDataCOCallback(p_ccb->rfc_handle, rfcomm_data_callback); + PORT_SetEventMask(p_ccb->rfc_handle, OBEX_TL_RFCOMM_EVENT_MARK); + PORT_SetEventCallback(p_ccb->rfc_handle, rfcomm_event_callback); + + bdcpy(p_ccb->addr, server->rfcomm.addr); + p_ccb->scn = server->rfcomm.scn; + p_ccb->initiator = TRUE; + + return p_ccb->allocated; +} + +void obex_tl_rfcomm_disconnect(UINT16 handle) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_handle(handle); + if (p_ccb != NULL) { + RFCOMM_RemoveConnection(p_ccb->rfc_handle); + free_ccb(p_ccb); + } +} + +UINT16 obex_tl_rfcomm_send(UINT16 handle, BT_HDR *p_buf) +{ + UINT16 ret = OBEX_TL_FAILED; + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_handle(handle); + do { + if (p_ccb == NULL) { + osi_free(p_buf); + break; + } + + /* Can not send data size larger than MTU */ + /* Offset should not smaller than OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET */ + if (p_buf->len > p_ccb->mtu || p_buf->offset < OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET) { + osi_free(p_buf); + break; + } + + if (PORT_Write(p_ccb->rfc_handle, p_buf) == PORT_SUCCESS) { + ret = OBEX_TL_SUCCESS; + } + } while (0); + return ret; +} + +UINT16 obex_tl_rfcomm_bind(tOBEX_TL_SVR_INFO *server) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_scn(server->rfcomm.scn); + if (p_scb != NULL) { + /* scn already used */ + return 0; + } + + p_scb = allocate_scb(); + if (p_scb == NULL) { + OBEX_TL_RFCOMM_TRACE_WARNING("Can not allocate scb, out of number\n"); + return 0; + } + + BTM_SetSecurityLevel(FALSE, "", BTM_SEC_SERVICE_OBEX, server->rfcomm.sec_mask, BT_PSM_RFCOMM, BTM_SEC_PROTO_RFCOMM, server->rfcomm.scn); + if (RFCOMM_CreateConnection(UUID_PROTOCOL_OBEX, server->rfcomm.scn, TRUE, server->rfcomm.pref_mtu, + server->rfcomm.addr, &p_scb->rfc_handle, rfcomm_server_mgmt_callback) != PORT_SUCCESS) { + free_scb(p_scb); + return 0; + } + + /* set up data callback, event mask and event callback */ + PORT_SetDataCOCallback(p_scb->rfc_handle, rfcomm_data_callback); + PORT_SetEventMask(p_scb->rfc_handle, OBEX_TL_RFCOMM_EVENT_MARK); + PORT_SetEventCallback(p_scb->rfc_handle, rfcomm_event_callback); + + p_scb->scn = server->rfcomm.scn; + + /* left shift 8 bits as server handle, avoid confuse with connection handle */ + return (p_scb->allocated << 8); +} + +void obex_tl_rfcomm_unbind(UINT16 handle) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_handle(handle); + if (p_scb) { + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + while ((p_ccb = find_ccb_by_rfc_handle(p_scb->rfc_handle)) != NULL) { + RFCOMM_RemoveConnection(p_ccb->rfc_handle); + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + obex_tl_rfcomm_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + } + RFCOMM_RemoveServer(p_scb->rfc_handle); + free_scb(p_scb); + } +} + +static tOBEX_TL_OPS obex_tl_rfcomm_ops = { + .init = obex_tl_rfcomm_init, + .deinit = obex_tl_rfcomm_deinit, + .connect = obex_tl_rfcomm_connect, + .disconnect = obex_tl_rfcomm_disconnect, + .bind = obex_tl_rfcomm_bind, + .unbind = obex_tl_rfcomm_unbind, + .send = obex_tl_rfcomm_send +}; + +/******************************************************************************* +** +** Function obex_tl_rfcomm_ops_get +** +** Description Get the operation function structure pointer of OBEX over +** RFCOMM transport layer +** +** Returns Pointer to operation function structure +** +*******************************************************************************/ +tOBEX_TL_OPS *obex_tl_rfcomm_ops_get(void) +{ + return &obex_tl_rfcomm_ops; +} + +#endif /* #if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) */ diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md index ab8040e276dc..9ed38dab29c0 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md @@ -64,6 +64,18 @@ PCM Signal supports three configurations in menuconfig: PCM Role, PCM Polar and - The default configuration is `Stereo Mode`, you can change the PCM Channel mode in `menuconfig` path: `Component config --> Bluetooth --> Controller Options --> PCM Signal Configurations --> PCM Signal Configurations: Role, Polar and Channel Mode(Stereo/Mono) --> Channel Mode(Stereo/Mono)` +### Special Configurations for PBA Client + +To use PBA Client function, we need to enable PBA Client in `menuconfig` path: `Component config --> Bluetooth --> Bluedroid Options --> Classic Bluetooth --> Classic BT PBA Client`, this example already enable PBA Client by `sdkconfig.defaults`. + +Step to initialize PBA Client connection: + +- Register user callback: `esp_pbac_register_callback(bt_app_pbac_cb)` +- Initialize PBA Client API: `esp_pbac_init()` +- Connect to peer device ... +- Call `esp_pbac_connect(peer_addr, supported_features, 0)`, this will initiate service discover and try to connect to PBA Server. +- After the operation done, whether success or not, we will receive a `ESP_PBAC_CONNECTION_STATE_EVT` event in user callback. + ### Codec Choice ESP32 supports two types of codec for HFP audio data: `CVSD` and `mSBC`. diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt index 728e74c35295..d91b3279d0b6 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt @@ -2,6 +2,7 @@ idf_component_register(SRCS "app_hf_msg_set.c" "bt_app_core.c" "bt_app_hf.c" "gpio_pcm_config.c" + "bt_app_pbac.c" "main.c" PRIV_REQUIRES bt nvs_flash esp_driver_gpio console esp_ringbuf INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c index 26f2af87d854..65169f183289 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c @@ -17,6 +17,7 @@ #include "esp_bt_device.h" #include "esp_gap_bt_api.h" #include "esp_hf_client_api.h" +#include "esp_pbac_api.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" @@ -244,6 +245,9 @@ void bt_app_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_ param->conn_stat.peer_feat, param->conn_stat.chld_feat); memcpy(peer_addr,param->conn_stat.remote_bda,ESP_BD_ADDR_LEN); + if (param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_SLC_CONNECTED) { + esp_pbac_connect(peer_addr); + } break; } diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.c b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.c new file mode 100644 index 000000000000..09ac19811c9a --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.c @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_pbac_api.h" +#include "bt_app_core.h" +#include "bt_app_pbac.h" + +#define BT_PBAC_TAG "BT_PBAC" + +esp_pbac_conn_hdl_t pba_conn_handle; + +void bt_app_pbac_cb(esp_pbac_event_t event, esp_pbac_param_t *param) +{ + switch (event) + { + case ESP_PBAC_CONNECTION_STATE_EVT: + ESP_LOGI(BT_PBAC_TAG, "PBA client connection event, state: %s, reason: 0x%x", (param->conn_stat.connected ? "Connected" : "Disconnected"), param->conn_stat.reason); + ESP_LOGI(BT_PBAC_TAG, "Peer supported repositories: 0x%x, supported features: 0x%lx", param->conn_stat.peer_supported_repo, param->conn_stat.peer_supported_feat); + if (param->conn_stat.connected) { + pba_conn_handle = param->conn_stat.handle; + /* set phone book to "telecom" folder, just to test set phone book function */ + esp_pbac_set_phone_book(pba_conn_handle, ESP_PBAC_SET_PHONE_BOOK_FLAGS_DOWN, "telecom"); + } + break; + case ESP_PBAC_PULL_PHONE_BOOK_RESPONSE_EVT: + /* if multiple PBA connection, we should check param->pull_phone_book_rsp.handle */ + ESP_LOGI(BT_PBAC_TAG, "PBA client pull phone book response, handle:%d, result: 0x%x", param->pull_phone_book_rsp.handle, param->pull_phone_book_rsp.result); + if (param->pull_phone_book_rsp.result == ESP_PBAC_SUCCESS && param->pull_phone_book_rsp.data_len > 0) { + printf("%.*s\n", param->pull_phone_book_rsp.data_len, param->pull_phone_book_rsp.data); + /* copy data to other buff before return, if phone book size is too large, it will be sent in multiple response event */ + } + if (param->pull_phone_book_rsp.final) { + ESP_LOGI(BT_PBAC_TAG, "PBA client pull phone book final response"); + /* pull phone book done, now we can perform other operation */ + if (param->pull_phone_book_rsp.result == ESP_PBAC_SUCCESS && param->pull_phone_book_rsp.include_phone_book_size) { + ESP_LOGI(BT_PBAC_TAG, "Phone Book Size:%d", param->pull_phone_book_rsp.phone_book_size); + esp_pbac_pull_phone_book_app_param_t app_param = {0}; + app_param.include_property_selector = 1; + /* property bit mask, filter out photo, refer to Phone Book Access Profile */ + app_param.property_selector = 0xFFFFFFF7; + /* pull again, without 'max_list_count = 0', then we can get the entire phone book */ + esp_pbac_pull_phone_book(pba_conn_handle, "telecom/pb.vcf", &app_param); + } + } + break; + case ESP_PBAC_SET_PHONE_BOOK_RESPONSE_EVT: + ESP_LOGI(BT_PBAC_TAG, "PBA client set phone book response, handle:%d, result: 0x%x", param->set_phone_book_rsp.handle, param->set_phone_book_rsp.result); + /* done, set phone book response will always be a final response */ + if (param->set_phone_book_rsp.result == ESP_PBAC_SUCCESS) { + esp_pbac_pull_phone_book_app_param_t app_param = {0}; + app_param.include_max_list_count = 1; + /* set max_list_count to zero, then we can get phone book size in peer response */ + app_param.max_list_count = 0; + /* pull phone book use a absolute path; if no app param, we can pass a NULL to API */ + esp_pbac_pull_phone_book(pba_conn_handle, "telecom/pb.vcf", &app_param); + } + break; + case ESP_PBAC_PULL_VCARD_LISTING_RESPONSE_EVT: + ESP_LOGI(BT_PBAC_TAG, "PBA client pull vCard listing response, handle:%d, result: 0x%x", param->pull_vcard_listing_rsp.handle, param->pull_vcard_listing_rsp.result); + if (param->pull_vcard_listing_rsp.result == ESP_PBAC_SUCCESS) { + printf("%.*s\n", param->pull_vcard_listing_rsp.data_len, param->pull_vcard_listing_rsp.data); + } + if (param->pull_vcard_listing_rsp.final) { + ESP_LOGI(BT_PBAC_TAG, "PBA client pull vCard listing final response"); + } + break; + case ESP_PBAC_PULL_VCARD_ENTRY_RESPONSE_EVT: + ESP_LOGI(BT_PBAC_TAG, "PBA client pull vCard entry response, handle:%d, result: 0x%x", param->pull_vcard_entry_rsp.handle, param->pull_vcard_entry_rsp.result); + if (param->pull_vcard_entry_rsp.result == ESP_PBAC_SUCCESS) { + printf("%.*s\n", param->pull_vcard_entry_rsp.data_len, param->pull_vcard_entry_rsp.data); + } + if (param->pull_vcard_entry_rsp.final) { + ESP_LOGI(BT_PBAC_TAG, "PBA client pull vCard entry final response"); + } + break; + default: + break; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.h b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.h new file mode 100644 index 000000000000..2e0b87b53be9 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_bt.h" +#include "esp_pbac_api.h" + +void bt_app_pbac_cb(esp_pbac_event_t event, esp_pbac_param_t *param); diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/main.c b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/main.c index 984695aaaac1..3e40498143da 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/main.c +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/main.c @@ -21,10 +21,12 @@ #include "esp_bt_device.h" #include "esp_gap_bt_api.h" #include "esp_hf_client_api.h" +#include "esp_pbac_api.h" #include "bt_app_hf.h" #include "gpio_pcm_config.h" #include "esp_console.h" #include "app_hf_msg_set.h" +#include "bt_app_pbac.h" esp_bd_addr_t peer_addr = {0}; static char peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; @@ -251,6 +253,8 @@ static void bt_hf_client_hdl_stack_evt(uint16_t event, void *p_param) esp_bt_gap_register_callback(esp_bt_gap_cb); esp_hf_client_register_callback(bt_app_hf_client_cb); esp_hf_client_init(); + esp_pbac_register_callback(bt_app_pbac_cb); + esp_pbac_init(); #if (CONFIG_EXAMPLE_SSP_ENABLED == true) /* Set default parameters for Secure Simple Pairing */ diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults index 351210d4f831..643ce88d5da6 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults @@ -8,3 +8,4 @@ CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_CLASSIC_ENABLED=y CONFIG_BT_HFP_ENABLE=y CONFIG_BT_HFP_CLIENT_ENABLE=y +CONFIG_BT_PBAC_ENABLED=y