diff --git a/.codespellrc b/.codespellrc index 99cb99a9d74c..493a2c9aa644 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,4 +1,4 @@ [codespell] skip = build,*.yuv,components/fatfs/src/*,alice.txt,*.rgb,components/wpa_supplicant/*,components/esp_wifi/*,*.pem -ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane,assertIn +ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane,assertIn,registr write-changes = true diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index cc61e549fbe4..f279585217bf 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -156,6 +156,8 @@ if(CONFIG_BT_ENABLED) host/bluedroid/stack/avdt/include host/bluedroid/stack/a2dp/include host/bluedroid/stack/rfcomm/include + host/bluedroid/stack/obex/include + host/bluedroid/stack/goep/include host/bluedroid/stack/include host/bluedroid/common/include host/bluedroid/config/include) @@ -183,6 +185,8 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/bta/av/bta_av_aact.c" "host/bluedroid/bta/av/bta_av_act.c" "host/bluedroid/bta/av/bta_av_api.c" + "host/bluedroid/bta/av/bta_av_ca_act.c" + "host/bluedroid/bta/av/bta_av_ca_sm.c" "host/bluedroid/bta/av/bta_av_cfg.c" "host/bluedroid/bta/av/bta_av_ci.c" "host/bluedroid/bta/av/bta_av_main.c" @@ -378,6 +382,8 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/stack/gatt/gatt_sr.c" "host/bluedroid/stack/gatt/gatt_sr_hash.c" "host/bluedroid/stack/gatt/gatt_utils.c" + "host/bluedroid/stack/goep/goepc_api.c" + "host/bluedroid/stack/goep/goepc_main.c" "host/bluedroid/stack/hcic/hciblecmds.c" "host/bluedroid/stack/hcic/hcicmds.c" "host/bluedroid/stack/l2cap/l2c_api.c" @@ -389,6 +395,9 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/stack/l2cap/l2c_ucd.c" "host/bluedroid/stack/l2cap/l2c_utils.c" "host/bluedroid/stack/l2cap/l2cap_client.c" + "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/rfcomm/port_api.c" "host/bluedroid/stack/rfcomm/port_rfc.c" "host/bluedroid/stack/rfcomm/port_utils.c" diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index 4271b47869fb..53a3755c7902 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -84,9 +84,30 @@ config BT_A2DP_ENABLE bool "A2DP" depends on BT_CLASSIC_ENABLED default n + select BT_AVRCP_ENABLED help Advanced Audio Distribution Profile +config BT_AVRCP_ENABLED + bool + depends on BT_A2DP_ENABLE + default y + help + Audio/Video Remote Control Profile, AVRCP and A2DP are coupled in Bluedroid, + AVRCP still controlled by A2DP option, this is a dummy option currently + +menu "AVRCP Features" + depends on BT_AVRCP_ENABLED + + config BT_AVRCP_CT_COVER_ART_ENABLED + bool "AVRCP CT Cover Art" + default y + select BT_GOEPC_ENABLED + help + This enable Cover Art feature of AVRCP CT role + +endmenu + config BT_SPP_ENABLED bool "SPP" depends on BT_CLASSIC_ENABLED @@ -143,13 +164,12 @@ endchoice config BT_HFP_WBS_ENABLE bool "Wide Band Speech" - depends on BT_HFP_AUDIO_DATA_PATH_HCI + depends on BT_HFP_ENABLE && BT_HFP_AUDIO_DATA_PATH_HCI default y help This enables Wide Band Speech. Should disable it when SCO data path is PCM. Otherwise there will be no data transmitted via GPIOs. - menuconfig BT_HID_ENABLED bool "Classic BT HID" depends on BT_CLASSIC_ENABLED @@ -170,6 +190,13 @@ config BT_HID_DEVICE_ENABLED help This enables the BT HID Device +config BT_GOEPC_ENABLED + bool + depends on BT_CLASSIC_ENABLED + default n + help + This enables the BT GOEP Profile Client role + config BT_BLE_ENABLED bool "Bluetooth Low Energy" depends on BT_BLUEDROID_ENABLED diff --git a/components/bt/host/bluedroid/api/esp_avrc_api.c b/components/bt/host/bluedroid/api/esp_avrc_api.c index 2d3e15325f63..125bfff111d2 100644 --- a/components/bt/host/bluedroid/api/esp_avrc_api.c +++ b/components/bt/host/bluedroid/api/esp_avrc_api.c @@ -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 */ @@ -232,6 +232,149 @@ esp_err_t esp_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code, uint8_t return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; } +#if BTC_AV_CA_INCLUDED + +esp_err_t esp_avrc_ct_cover_art_connect(uint16_t mtu) +{ + if ((esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) || + (!btc_avrc_ct_connected_p())) { + return ESP_ERR_INVALID_STATE; + } + + if (!btc_avrc_ct_check_cover_art_support()) { + return ESP_ERR_NOT_SUPPORTED; + } + + if (mtu > ESP_AVRC_CA_MTU_MAX || mtu < ESP_AVRC_CA_MTU_MIN) { + mtu = ESP_AVRC_CA_MTU_MAX; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_AVRC_CT; + msg.act = BTC_AVRC_CT_API_COVER_ART_CONNECT_EVT; + + btc_avrc_args_t arg; + memset(&arg, 0, sizeof(btc_avrc_args_t)); + arg.ca_conn.mtu = mtu; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_avrc_ct_cover_art_disconnect(void) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (!btc_avrc_ct_check_cover_art_support()) { + return ESP_ERR_NOT_SUPPORTED; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_AVRC_CT; + msg.act = BTC_AVRC_CT_API_COVER_ART_DISCONNECT_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_avrc_ct_cover_art_get_image_properties(uint8_t *image_handle) +{ + if ((esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) || + (!btc_avrc_ct_connected_p())) { + return ESP_ERR_INVALID_STATE; + } + + if (!btc_avrc_ct_check_cover_art_support()) { + return ESP_ERR_NOT_SUPPORTED; + } + + if (image_handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_AVRC_CT; + msg.act = BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_PROPERTIES_EVT; + + btc_avrc_args_t arg; + memset(&arg, 0, sizeof(btc_avrc_args_t)); + memcpy(arg.ca_get_img_prop.image_handle, image_handle, ESP_AVRC_CA_IMAGE_HANDLE_LEN); + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_avrc_ct_cover_art_get_image(uint8_t *image_handle, uint8_t *image_descriptor, uint16_t image_descriptor_len) +{ + if ((esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) || + (!btc_avrc_ct_connected_p())) { + return ESP_ERR_INVALID_STATE; + } + + if (!btc_avrc_ct_check_cover_art_support()) { + return ESP_ERR_NOT_SUPPORTED; + } + + if (image_handle == NULL || image_descriptor == NULL || image_descriptor_len == 0) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_AVRC_CT; + msg.act = BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT; + + btc_avrc_args_t arg; + memset(&arg, 0, sizeof(btc_avrc_args_t)); + + memcpy(arg.ca_get_img.image_handle, image_handle, ESP_AVRC_CA_IMAGE_HANDLE_LEN); + arg.ca_get_img.image_descriptor_len = image_descriptor_len; + arg.ca_get_img.image_descriptor = image_descriptor; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), btc_avrc_arg_deep_copy, btc_avrc_arg_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_avrc_ct_cover_art_get_linked_thumbnail(uint8_t *image_handle) +{ + if ((esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) || + (!btc_avrc_ct_connected_p())) { + return ESP_ERR_INVALID_STATE; + } + + if (!btc_avrc_ct_check_cover_art_support()) { + return ESP_ERR_NOT_SUPPORTED; + } + + if (image_handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_AVRC_CT; + msg.act = BTC_AVRC_CT_API_COVER_ART_GET_LINKED_THUMBNAIL_EVT; + + btc_avrc_args_t arg; + memset(&arg, 0, sizeof(btc_avrc_args_t)); + memcpy(arg.ca_get_lk_thn.image_handle, image_handle, ESP_AVRC_CA_IMAGE_HANDLE_LEN); + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +#endif /* #if BTC_AV_CA_INCLUDED */ + /*********************************************************************************************/ /** following is the API of AVRCP target role **/ /*********************************************************************************************/ diff --git a/components/bt/host/bluedroid/api/include/api/esp_avrc_api.h b/components/bt/host/bluedroid/api/include/api/esp_avrc_api.h index f226577efc35..1c1e34ab2a5b 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_avrc_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_avrc_api.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 */ @@ -18,6 +18,10 @@ extern "C" { #define ESP_AVRC_TRANS_LABEL_MAX 15 /*!< max transaction label */ +#define ESP_AVRC_CA_IMAGE_HANDLE_LEN 7 /* The image handle length is fixed to 7, specified by Basic Image Profile */ +#define ESP_AVRC_CA_MTU_MIN 255 /* Minimal MTU can be used in Cover Art OBEX connection */ +#define ESP_AVRC_CA_MTU_MAX 1691 /* Maximum MTU can be used in Cover Art OBEX connection */ + /// AVRC feature bit mask typedef enum { ESP_AVRC_FEAT_RCTG = 0x0001, /*!< remote control target */ @@ -30,14 +34,18 @@ typedef enum { /// AVRC supported features flag retrieved in SDP record typedef enum { + /* CT and TG common features flag */ ESP_AVRC_FEAT_FLAG_CAT1 = 0x0001, /*!< category 1 */ ESP_AVRC_FEAT_FLAG_CAT2 = 0x0002, /*!< category 2 */ ESP_AVRC_FEAT_FLAG_CAT3 = 0x0004, /*!< category 3 */ ESP_AVRC_FEAT_FLAG_CAT4 = 0x0008, /*!< category 4 */ ESP_AVRC_FEAT_FLAG_BROWSING = 0x0040, /*!< browsing */ - ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE_PROP = 0x0080, /*!< Cover Art GetImageProperties */ - ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE = 0x0100, /*!< Cover Art GetImage */ - ESP_AVRC_FEAT_FLAG_COVER_ART_GET_LINKED_THUMBNAIL = 0x0200, /*!< Cover Art GetLinkedThumbnail */ + /* CT only features flag */ + ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE_PROP = 0x0080, /*!< CT support Cover Art GetImageProperties */ + ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE = 0x0100, /*!< CT support Cover Art GetImage */ + ESP_AVRC_FEAT_FLAG_COVER_ART_GET_LINKED_THUMBNAIL = 0x0200, /*!< CT support Cover Art GetLinkedThumbnail */ + /* TG only features flag */ + ESP_AVRC_FEAT_FLAG_TG_COVER_ART = 0x0100, /*!< TG support Cover Art */ } esp_avrc_feature_flag_t; /// AVRC passthrough command code @@ -133,8 +141,10 @@ typedef enum { ESP_AVRC_CT_PLAY_STATUS_RSP_EVT = 3, /*!< play status response event */ ESP_AVRC_CT_CHANGE_NOTIFY_EVT = 4, /*!< notification event */ ESP_AVRC_CT_REMOTE_FEATURES_EVT = 5, /*!< feature of remote device indication event */ - ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT = 6, /*!< supported notification events capability of peer device */ + ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT = 6, /*!< supported notification events capability of peer device */ ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT = 7, /*!< set absolute volume response event */ + ESP_AVRC_CT_COVER_ART_STATE_EVT = 8, /*!< cover art client connection state changed event */ + ESP_AVRC_CT_COVER_ART_DATA_EVT = 9, /*!< cover art client data event */ } esp_avrc_ct_cb_event_t; /// AVRC Target callback events @@ -155,7 +165,8 @@ typedef enum { ESP_AVRC_MD_ATTR_TRACK_NUM = 0x8, /*!< track position on the album */ ESP_AVRC_MD_ATTR_NUM_TRACKS = 0x10, /*!< number of tracks on the album */ ESP_AVRC_MD_ATTR_GENRE = 0x20, /*!< track genre */ - ESP_AVRC_MD_ATTR_PLAYING_TIME = 0x40 /*!< total album playing time in miliseconds */ + ESP_AVRC_MD_ATTR_PLAYING_TIME = 0x40, /*!< total album playing time in milliseconds */ + ESP_AVRC_MD_ATTR_COVER_ART = 0x80 /*!< cover art image handle */ } esp_avrc_md_attr_mask_t; /// AVRC event notification ids @@ -261,6 +272,12 @@ typedef enum { ESP_AVRC_PLAYBACK_ERROR = 0xFF, /*!< error */ } esp_avrc_playback_stat_t; +/// AVRC Cover Art connection error code +typedef enum { + ESP_AVRC_COVER_ART_DISCONNECTED, /*!< Cover Art connection disconnected or connection failed */ + ESP_AVRC_COVER_ART_CONNECTED, /*!< Cover Art connection established */ +} esp_avrc_cover_art_conn_state_t; + /// AVRCP notification parameters typedef union { @@ -337,6 +354,24 @@ typedef union { struct avrc_ct_set_volume_rsp_param { uint8_t volume; /*!< the volume which has actually been set, range is 0 to 0x7f, means 0% to 100% */ } set_volume_rsp; /*!< set absolute volume response event */ + + /** + * @brief ESP_AVRC_CT_COVER_ART_STATE_EVT + */ + struct avrc_ct_cover_art_state_param { + esp_avrc_cover_art_conn_state_t state; /*!< indicate the Cover Art connection status */ + esp_bt_status_t reason; /*!< the disconnect reason of Cover Art connection */ + } cover_art_state; /*!< AVRC Cover Art connection state change event */ + + /** + * @brief ESP_AVRC_CT_COVER_ART_DATA_EVT + */ + struct avrc_ct_cover_art_data_param { + esp_bt_status_t status; /*!< indicate whether the get operation is success, p_data is valid only when status is ESP_BT_STATUS_SUCCESS */ + bool final; /*!< indicate whether this data event is the final one, true if we have received the entire object */ + uint16_t data_len; /*!< the data length of this data event, in bytes */ + uint8_t *p_data; /*!< pointer to data, should copy to other buff before event callback return */ + } cover_art_data; /*!< AVRC Cover Art data event */ } esp_avrc_ct_cb_param_t; /// AVRC target callback parameters @@ -656,11 +691,11 @@ bool esp_avrc_psth_bit_mask_operation(esp_avrc_bit_mask_op_t op, esp_avrc_psth_b /** * - * @brief Get the requested event notification capabilies on local AVRC target. The capability is returned + * @brief Get the requested event notification capabilities on local AVRC target. The capability is returned * in a bit mask representation in evt_set. This function should be called after esp_avrc_tg_init(). * * For capability type "ESP_AVRC_RN_CAP_ALLOWED_EVT, the retrieved event set is constant and - * it covers all of the notifcation events that can possibly be supported with current + * it covers all of the notification events that can possibly be supported with current * implementation. * * For capability type ESP_AVRC_RN_CAP_SUPPORTED_EVT, the event set covers the notification @@ -729,6 +764,92 @@ bool esp_avrc_rn_evt_bit_mask_operation(esp_avrc_bit_mask_op_t op, esp_avrc_rn_e esp_err_t esp_avrc_tg_send_rn_rsp(esp_avrc_rn_event_ids_t event_id, esp_avrc_rn_rsp_t rsp, esp_avrc_rn_param_t *param); +/** + * + * @brief Start the process to establish OBEX connection used in Cover Art Client. Once the operation done, + * ESP_AVRC_CT_COVER_ART_STATE_EVT will come, operation result can be found in event param. This API + * can be used only when AVRC Cover Art feature is enabled. + * + * @param[in] mtu: MTU used in lower level connection, should not smaller than ESP_AVRC_CA_MTU_MIN or larger than + * ESP_AVRC_CA_MTU_MAX, if value is not valid, will be reset to ESP_AVRC_CA_MTU_MAX. This can limit + * the max data length in cover_art_data event. + * + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized + * - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function + * + */ +esp_err_t esp_avrc_ct_cover_art_connect(uint16_t mtu); + +/** + * + * @brief Start the process to release the OBEX connection used in Cover Art Client.Once the operation done, + * ESP_AVRC_CT_COVER_ART_STATE_EVT will come, operation result can be found in event param. This API + * can be used only when AVRC Cover Art feature is enabled. + * + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized + * - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function + * + */ +esp_err_t esp_avrc_ct_cover_art_disconnect(void); + +/** + * + * @brief Start the process to get image properties from Cover Art server. This API can be used only when AVRC + * Cover Art feature is enabled. + * + * @param[in] image_handle: pointer to image handle with a length of ESP_AVRC_CA_IMAGE_HANDLE_LEN bytes, can be freed + * after this function return + * + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized + * - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function + * + */ +esp_err_t esp_avrc_ct_cover_art_get_image_properties(uint8_t *image_handle); + +/** + * + * @brief Start the process to get image from Cover Art server. This API can be used only when AVRC Cover Art + * feature is enabled. + * + * @param[in] image_handle: pointer to image handle with a length of ESP_AVRC_CA_IMAGE_HANDLE_LEN bytes, can be freed + * after this function return + * + * @param[in] image_descriptor: pointer to image descriptor, will be cache internally by bluetooth stack, can be freed + * once this api return + * + * @param[in] image_descriptor_len: the length of image descriptor + * + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized + * - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function + * + */ +esp_err_t esp_avrc_ct_cover_art_get_image(uint8_t *image_handle, uint8_t *image_descriptor, uint16_t image_descriptor_len); + +/** + * + * @brief Start the process to get linked thumbnail from Cover Art server. This API can be used only when AVRC + * Cover Art feature is enabled. + * + * @param[in] image_handle: pointer to image handle with a length of ESP_AVRC_CA_IMAGE_HANDLE_LEN bytes, can be freed + * after this function return + * + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized + * - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function + * + */ +esp_err_t esp_avrc_ct_cover_art_get_linked_thumbnail(uint8_t *image_handle); + + #ifdef __cplusplus } #endif diff --git a/components/bt/host/bluedroid/bta/av/bta_av_act.c b/components/bt/host/bluedroid/bta/av/bta_av_act.c index 13a575071396..1fd59a044d48 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_act.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_act.c @@ -37,6 +37,10 @@ #if( defined BTA_AR_INCLUDED ) && (BTA_AR_INCLUDED == TRUE) #include "bta/bta_ar_api.h" #endif +#if BTA_AV_CA_INCLUDED +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#endif #define LOG_TAG "bt_bta_av" // #include "osi/include/log.h" @@ -98,6 +102,10 @@ void bta_av_del_rc(tBTA_AV_RCB *p_rcb) p_scb = NULL; if (p_rcb->handle != BTA_AV_RC_HANDLE_NONE) { +#if BTA_AV_CA_INCLUDED + /* reset cover art state */ + bta_av_ca_reset(p_rcb); +#endif if (p_rcb->shdl) { /* Validate array index*/ if ((p_rcb->shdl - 1) < BTA_AV_NUM_STRS) { @@ -127,9 +135,6 @@ void bta_av_del_rc(tBTA_AV_RCB *p_rcb) } /* else ACP && connected. do not clear the handle yet */ AVRC_Close(rc_handle); - if (rc_handle == bta_av_cb.rc_acp_handle) { - bta_av_cb.rc_acp_handle = BTA_AV_RC_HANDLE_NONE; - } APPL_TRACE_EVENT("end del_rc handle: %d status=0x%x, rc_acp_handle:%d, lidx:%d", p_rcb->handle, p_rcb->status, bta_av_cb.rc_acp_handle, p_rcb->lidx); } @@ -302,7 +307,7 @@ UINT8 bta_av_rc_create(tBTA_AV_CB *p_cb, UINT8 role, UINT8 shdl, UINT8 lidx) bda = p_scb->peer_addr; status = BTA_AV_RC_ROLE_INT; } else { - if ((p_rcb = bta_av_get_rcb_by_shdl(shdl)) != NULL ) { + if (shdl != 0 && ((p_rcb = bta_av_get_rcb_by_shdl(shdl)) != NULL)) { APPL_TRACE_ERROR("bta_av_rc_create ACP handle exist for shdl:%d", shdl); return p_rcb->handle; } @@ -1143,7 +1148,7 @@ void bta_av_conn_chg(tBTA_AV_DATA *p_data) p_data->conn_chg.peer_addr[5]); if (p_lcb_rc->conn_msk && bdcmp(p_lcb_rc->addr, p_data->conn_chg.peer_addr) == 0) { /* AVRCP is already connected. - * need to update the association betwen SCB and RCB */ + * need to update the association between SCB and RCB */ p_lcb_rc->conn_msk = 0; /* indicate RC ONLY is not connected */ p_lcb_rc->lidx = 0; p_scb->rc_handle = p_cb->rc_acp_handle; @@ -1484,6 +1489,51 @@ static void bta_av_acp_sig_timer_cback (TIMER_LIST_ENT *p_tle) } } +#if BTA_AV_CA_INCLUDED + +/******************************************************************************* +** +** Function bta_av_extra_tg_cover_art_l2cap_psm +** +** Description Extra the AVRC Cover Art L2CAP PSM of peer device from the +** SDP record +** +** Returns void +** +*******************************************************************************/ +static UINT16 bta_av_extra_tg_cover_art_l2cap_psm(void) +{ + tBTA_AV_CB *p_cb = &bta_av_cb; + tSDP_DISC_REC *p_rec = NULL; + tSDP_DISC_ATTR *p_add_prot_desc, *p_prot_desc; + tSDP_PROTOCOL_ELEM elem_l2cap, elem_obex; + UINT16 l2cap_psm = 0; + + while (TRUE) { + /* get next record; if none found, we're done */ + if ((p_rec = SDP_FindServiceInDb(p_cb->p_disc_db, UUID_SERVCLASS_AV_REM_CTRL_TARGET, p_rec)) == NULL) { + break; + } + + p_add_prot_desc = SDP_FindAttributeInRec(p_rec, ATTR_ID_ADDITION_PROTO_DESC_LISTS); + if ((p_add_prot_desc != NULL) && (SDP_DISC_ATTR_TYPE(p_add_prot_desc->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE)) { + /* Walk through all protocol descriptor list */ + for (p_prot_desc = p_add_prot_desc->attr_value.v.p_sub_attr; p_prot_desc; p_prot_desc = p_prot_desc->p_next_attr) { + if(SDP_FindProtocolListElem(p_prot_desc, UUID_PROTOCOL_L2CAP, &elem_l2cap) + && SDP_FindProtocolListElem(p_prot_desc, UUID_PROTOCOL_OBEX, &elem_obex)) + { + /* found */ + l2cap_psm = elem_l2cap.params[0]; + break; + } + } + } + } + return l2cap_psm; +} + +#endif /* BTA_AV_CA_INCLUDED */ + /******************************************************************************* ** ** Function bta_av_check_peer_rc_features @@ -1539,6 +1589,10 @@ tBTA_AV_FEAT bta_av_check_peer_rc_features (UINT16 service_uuid, UINT16 *rc_feat if (categories & AVRC_SUPF_CT_BROWSE) { peer_features |= (BTA_AV_FEAT_BROWSE); } + if ((service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET) && (categories & AVRC_SUPF_TG_COVER_ART)) { + /* remote target support cover art */ + peer_features |= BTA_AV_FEAT_COVER_ART; + } } } } @@ -1573,6 +1627,9 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data) tBTA_AV_FEAT peer_features; /* peer features mask */ UINT16 peer_ct_features; /* peer features mask as controller */ UINT16 peer_tg_features; /* peer features mask as target */ +#if BTA_AV_CA_INCLUDED + UINT16 obex_l2cap_psm = 0; /* target obex l2cap psm */ +#endif UNUSED(p_data); APPL_TRACE_DEBUG("bta_av_rc_disc_done disc:x%x", p_cb->disc); @@ -1600,7 +1657,11 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data) /* check peer version and whether support CT and TG role */ peer_features = bta_av_check_peer_rc_features (UUID_SERVCLASS_AV_REMOTE_CONTROL, &peer_ct_features); peer_features |= bta_av_check_peer_rc_features (UUID_SERVCLASS_AV_REM_CTRL_TARGET, &peer_tg_features); - +#if BTA_AV_CA_INCLUDED + if (peer_features & BTA_AV_FEAT_COVER_ART) { + obex_l2cap_psm = bta_av_extra_tg_cover_art_l2cap_psm(); + } +#endif p_cb->disc = 0; utl_freebuf((void **) &p_cb->p_disc_db); @@ -1618,6 +1679,9 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data) p_cb->rcb[rc_handle].peer_features = peer_features; p_cb->rcb[rc_handle].peer_ct_features = peer_ct_features; p_cb->rcb[rc_handle].peer_tg_features = peer_tg_features; +#if BTA_AV_CA_INCLUDED + p_cb->rcb[rc_handle].cover_art_l2cap_psm = obex_l2cap_psm; +#endif } #if (BT_USE_TRACES == TRUE || BT_TRACE_APPL == TRUE) else { @@ -1636,6 +1700,11 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data) } } else { p_cb->rcb[rc_handle].peer_features = peer_features; + p_cb->rcb[rc_handle].peer_ct_features = peer_ct_features; + p_cb->rcb[rc_handle].peer_tg_features = peer_tg_features; +#if BTA_AV_CA_INCLUDED + p_cb->rcb[rc_handle].cover_art_l2cap_psm = obex_l2cap_psm; +#endif rc_feat.rc_handle = rc_handle; rc_feat.peer_features = peer_features; rc_feat.peer_ct_features = peer_ct_features; @@ -1676,6 +1745,10 @@ void bta_av_rc_closed(tBTA_AV_DATA *p_data) p_rcb->peer_features = 0; p_rcb->peer_ct_features = 0; p_rcb->peer_tg_features = 0; +#if BTA_AV_CA_INCLUDED + /* reset cover art state */ + bta_av_ca_reset(p_rcb); +#endif APPL_TRACE_DEBUG(" shdl:%d, lidx:%d", p_rcb->shdl, p_rcb->lidx); if (p_rcb->shdl) { if ((p_rcb->shdl - 1) < BTA_AV_NUM_STRS) { @@ -1711,7 +1784,8 @@ void bta_av_rc_closed(tBTA_AV_DATA *p_data) bta_av_del_rc(p_rcb); /* if the AVRCP is no longer listening, create the listening channel */ - if (bta_av_cb.rc_acp_handle == BTA_AV_RC_HANDLE_NONE && bta_av_cb.features & BTA_AV_FEAT_RCTG) { + if (bta_av_cb.rc_acp_handle == p_msg->handle && bta_av_cb.features & BTA_AV_FEAT_RCTG) { + bta_av_cb.rc_acp_handle = BTA_AV_RC_HANDLE_NONE; bta_av_rc_create(&bta_av_cb, AVCT_ACP, 0, BTA_AV_NUM_LINKS + 1); } } @@ -1748,6 +1822,7 @@ void bta_av_rc_disc(UINT8 disc) tAVRC_SDP_DB_PARAMS db_params; UINT16 attr_list[] = {ATTR_ID_SERVICE_CLASS_ID_LIST, ATTR_ID_BT_PROFILE_DESC_LIST, + ATTR_ID_ADDITION_PROTO_DESC_LISTS, ATTR_ID_SUPPORTED_FEATURES }; UINT8 hdi; @@ -1785,7 +1860,7 @@ void bta_av_rc_disc(UINT8 disc) if (p_cb->p_disc_db) { /* set up parameters */ db_params.db_len = BTA_AV_DISC_BUF_SIZE; - db_params.num_attr = 3; + db_params.num_attr = 4; db_params.p_db = p_cb->p_disc_db; db_params.p_attrs = attr_list; diff --git a/components/bt/host/bluedroid/bta/av/bta_av_api.c b/components/bt/host/bluedroid/bta/av/bta_av_api.c index 01993eec8e2f..673cbe8539b9 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_api.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_api.c @@ -613,4 +613,77 @@ void BTA_AvMetaCmd(UINT8 rc_handle, UINT8 label, tBTA_AV_CMD cmd_code, BT_HDR *p } } +#if BTA_AV_CA_INCLUDED + +/******************************************************************************* +** +** Function BTA_AvCaOpen +** +** Description Open a Cover Art OBEX connection to peer device. This function +** can only be used if peer device TG support Cover Art feature and +** AV is enabled with feature BTA_AV_FEAT_METADATA. +** +** Returns void +** +*******************************************************************************/ +void BTA_AvCaOpen(UINT8 rc_handle, UINT16 mtu) +{ + tBTA_AV_API_CA_OPEN *p_buf; + + if ((p_buf = (tBTA_AV_API_CA_OPEN *) osi_malloc(sizeof(tBTA_AV_API_CA_OPEN))) != NULL) { + p_buf->hdr.event = BTA_AV_API_CA_OPEN_EVT; + p_buf->hdr.layer_specific = rc_handle; + p_buf->mtu = mtu; + bta_sys_sendmsg(p_buf); + } +} + +/******************************************************************************* +** +** Function BTA_AvCaClose +** +** Description Close a Cover Art OBEX connection. +** +** Returns void +** +*******************************************************************************/ +void BTA_AvCaClose(UINT8 rc_handle) +{ + tBTA_AV_API_CA_CLOSE *p_buf; + + if ((p_buf = (tBTA_AV_API_CA_CLOSE *) osi_malloc(sizeof(tBTA_AV_API_CA_CLOSE))) != NULL) { + p_buf->hdr.event = BTA_AV_API_CA_CLOSE_EVT; + p_buf->hdr.layer_specific = rc_handle; + bta_sys_sendmsg(p_buf); + } +} + +/******************************************************************************* +** +** Function BTA_AvCaGet +** +** Description Start the process to get image properties, get image or get +** linked thumbnail. This function can only be used if Cover Art +** OBEX connection is established. +** +** Returns void +** +*******************************************************************************/ +void BTA_AvCaGet(UINT8 rc_handle, tBTA_AV_GET_TYPE type, UINT8 *image_handle, UINT8 *image_descriptor, UINT16 image_descriptor_len) +{ + tBTA_AV_API_CA_GET *p_buf; + + if ((p_buf = (tBTA_AV_API_CA_GET *) osi_malloc(sizeof(tBTA_AV_API_CA_GET))) != NULL) { + p_buf->hdr.event = BTA_AV_API_CA_GET_EVT; + p_buf->hdr.layer_specific = rc_handle; + p_buf->type = type; + memcpy(p_buf->image_handle, image_handle, BTA_AV_CA_IMG_HDL_LEN); + p_buf->image_descriptor = image_descriptor; + p_buf->image_descriptor_len = image_descriptor_len; + bta_sys_sendmsg(p_buf); + } +} + +#endif /* BTA_AV_CA_INCLUDED */ + #endif /* BTA_AV_INCLUDED */ 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 new file mode 100644 index 000000000000..a2fdcbaddc5b --- /dev/null +++ b/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c @@ -0,0 +1,495 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "common/bt_target.h" +#if BTA_AV_CA_INCLUDED + +#include +#include "bta/bta_av_api.h" +#include "bta_av_int.h" +#include "stack/avdt_api.h" +#include "bta/utl.h" +#include "stack/l2c_api.h" +#include "osi/allocator.h" +#include "osi/list.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "stack/obex_api.h" +#include "common/bt_trace.h" + +#define COVER_ART_HEADER_ID_IMG_HANDLE 0x30 +#define COVER_ART_HEADER_ID_IMG_DESCRIPTOR 0x71 +static const UINT8 cover_art_uuid[16] = {0x71, 0x63, 0xDD, 0x54, 0x4A, 0x7E, 0x11, 0xE2, 0xB4, 0x7C, 0x00, 0x50, 0xC2, 0x49, 0x00, 0x48}; +static const char *cover_art_img_type_img = "x-bt/img-img"; +static const char *cover_art_img_type_thm = "x-bt/img-thm"; +static const char *cover_art_img_type_prop = "x-bt/img-properties"; + +#define COVER_ART_IMG_TYPE_IMG_LEN 13 +#define COVER_ART_IMG_TYPE_THM_LEN 13 +#define COVER_ART_IMG_TYPE_PROP_LEN 20 + + +static BOOLEAN find_rcb_idx_by_goep_handle(UINT16 handle, UINT16 *out_idx) +{ + for (UINT16 i = 0; i < BTA_AV_NUM_RCB; ++i) { + if (bta_av_cb.rcb[i].handle != BTA_AV_RC_HANDLE_NONE && bta_av_cb.rcb[i].cover_art_goep_hdl == handle) { + *out_idx = i; + return TRUE; + } + } + return FALSE; +} + +static UINT8 get_rcb_idx(tBTA_AV_RCB *p_rcb) +{ + return (p_rcb - &bta_av_cb.rcb[0]); +} + +static void get_peer_bd_addr(tBTA_AV_RCB *p_rcb, BD_ADDR out_addr) +{ + /* check if this rcb is related to a scb */ + if (p_rcb->shdl && p_rcb->shdl <= BTA_AV_NUM_STRS) { + tBTA_AV_SCB *p_scb = bta_av_cb.p_scb[p_rcb->shdl - 1]; + bdcpy(out_addr, p_scb->peer_addr); + } + /* else, try get peer addr from lcb */ + else if (p_rcb->lidx && p_rcb->lidx <= BTA_AV_NUM_LINKS + 1) + { + bdcpy(out_addr, bta_av_cb.lcb[p_rcb->lidx-1].addr); + } +} + +static void report_data_event(BT_HDR *pkt, UINT8 *p_data, UINT16 data_len, BOOLEAN final) +{ + tBTA_AV_CA_DATA ca_data; + ca_data.status = BT_STATUS_SUCCESS; + ca_data.final = final; + ca_data.data_len = data_len; + ca_data.p_data = p_data; + ca_data.p_hdr = pkt; + (*bta_av_cb.p_cback)(BTA_AV_CA_DATA_EVT, (tBTA_AV *) &ca_data); +} + +static void report_error_data_event(UINT16 status) +{ + tBTA_AV_CA_DATA ca_data; + ca_data.status = status; + ca_data.final = TRUE; + ca_data.data_len = 0; + ca_data.p_data = NULL; + ca_data.p_hdr = NULL; + (*bta_av_cb.p_cback)(BTA_AV_CA_DATA_EVT, (tBTA_AV *) &ca_data); +} + +static void build_and_send_connect_req(tBTA_AV_RCB *p_rcb) +{ + tOBEX_PARSE_INFO info = {0}; + info.opcode = OBEX_OPCODE_CONNECT; + info.obex_version_number = 0x15; + info.max_packet_length = p_rcb->cover_art_max_rx; + /* before OBEX connect response, we dont know cover_art_max_tx, use BT_SMALL_BUFFER_SIZE as tx buff size */ + if (GOEPC_PrepareRequest(p_rcb->cover_art_goep_hdl, &info, BT_SMALL_BUFFER_SIZE) == GOEP_SUCCESS) { + GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_TARGET, (UINT8 *)cover_art_uuid, 16); + GOEPC_SendRequest(p_rcb->cover_art_goep_hdl); + } +} + +static void build_and_send_disconnect_req(tBTA_AV_RCB *p_rcb) +{ + tOBEX_PARSE_INFO info = {0}; + info.opcode = OBEX_OPCODE_DISCONNECT; + if (GOEPC_PrepareRequest(p_rcb->cover_art_goep_hdl, &info, BT_SMALL_BUFFER_SIZE) == GOEP_SUCCESS) { + GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_rcb->cover_art_cid), 4); + GOEPC_SendRequest(p_rcb->cover_art_goep_hdl); + } +} + +static void build_and_send_empty_get_req(tBTA_AV_RCB *p_rcb) +{ + tOBEX_PARSE_INFO info = {0}; + info.opcode = OBEX_OPCODE_GET_FINAL; + /* empty get request, use a small buff size */ + UINT16 tx_buff_size = BT_SMALL_BUFFER_SIZE < p_rcb->cover_art_max_tx ? BT_SMALL_BUFFER_SIZE : p_rcb->cover_art_max_tx; + if (GOEPC_PrepareRequest(p_rcb->cover_art_goep_hdl, &info, tx_buff_size) == GOEP_SUCCESS) { + GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_rcb->cover_art_cid), 4); + GOEPC_SendRequest(p_rcb->cover_art_goep_hdl); + } +} + +static void close_goepc_and_disconnect(tBTA_AV_RCB *p_rcb) +{ + if (p_rcb->cover_art_goep_hdl) { + GOEPC_Close(p_rcb->cover_art_goep_hdl); + } + p_rcb->cover_art_goep_hdl = 0; + p_rcb->cover_art_cid = 0; + p_rcb->cover_art_max_tx = 0; + p_rcb->cover_art_max_rx = 0; + + tBTA_AV_DATA *p_data = (tBTA_AV_DATA *) osi_malloc(sizeof(tBTA_AV_DATA)); + if (p_data == NULL) { + assert(0); + } + p_data->hdr.event = BTA_AV_CA_GOEP_DISCONNECT_EVT; + p_data->hdr.layer_specific = get_rcb_idx(p_rcb); + p_data->ca_disconnect.reason = BT_STATUS_FAIL; + bta_sys_sendmsg(p_data); +} + +static void image_handle_to_utf16(const UINT8 *image_handle, UINT8 *buffer) +{ + UINT8 pos = 0; + for (int i = 0 ; i < BTA_AV_CA_IMG_HDL_LEN ; i++){ + buffer[pos++] = 0; + buffer[pos++] = image_handle[i]; + } + buffer[pos++] = 0; + buffer[pos++] = 0; +} + +void bta_av_ca_goep_event_handler(UINT16 handle, UINT8 event, tGOEPC_MSG *p_msg) +{ + tBTA_AV_DATA *p_data = NULL; + UINT16 rcb_idx; + if (!find_rcb_idx_by_goep_handle(handle, &rcb_idx)) { + /* can not find a rcb, go error */ + goto error; + } + + if (event == GOEPC_RESPONSE_EVT || event == GOEPC_OPENED_EVT || event == GOEPC_CLOSED_EVT) { + p_data = (tBTA_AV_DATA *) osi_malloc(sizeof(tBTA_AV_DATA)); + assert(p_data != NULL); + } + + switch (event) + { + case GOEPC_OPENED_EVT: + p_data->hdr.layer_specific = rcb_idx; + p_data->hdr.event = BTA_AV_CA_GOEP_CONNECT_EVT; + p_data->ca_connect.max_rx = p_msg->opened.our_mtu; + break; + case GOEPC_CLOSED_EVT: + p_data->hdr.layer_specific = rcb_idx; + p_data->hdr.event = BTA_AV_CA_GOEP_DISCONNECT_EVT; + p_data->ca_disconnect.reason = BT_STATUS_FAIL; + break; + case GOEPC_RESPONSE_EVT: + p_data->hdr.layer_specific = rcb_idx; + p_data->ca_response.pkt = p_msg->response.pkt; + p_data->ca_response.opcode = p_msg->response.opcode; + p_data->ca_response.srm_en = p_msg->response.srm_en; + p_data->ca_response.srm_wait = p_msg->response.srm_wait; + if (p_msg->response.final) { + p_data->hdr.event = BTA_AV_CA_RESPONSE_FINAL_EVT; + } + else { + p_data->hdr.event = BTA_AV_CA_RESPONSE_EVT; + } + break; + case GOEPC_MTU_CHANGED_EVT: + case GOEPC_CONGEST_EVT: + case GOEPC_UNCONGEST_EVT: + /* ignore these event */ + break; + default: + goto error; + break; + } + if (p_data) { + bta_sys_sendmsg(p_data); + } + return; + +error: + /* can not find rcb, just free resource and disconnect */ + if (p_data != NULL) { + osi_free(p_data); + } + if (event == GOEPC_RESPONSE_EVT && p_msg->response.pkt != NULL) { + osi_free(p_msg->response.pkt); + } + if (event != GOEPC_CLOSED_EVT) { + GOEPC_Close(handle); + } +} + +void bta_av_ca_api_open(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) +{ + tOBEX_SVR_INFO svr = {0}; + svr.tl = OBEX_OVER_L2CAP; + svr.l2cap.psm = p_rcb->cover_art_l2cap_psm; + svr.l2cap.pref_mtu = p_data->api_ca_open.mtu; + /* reuse the security mask store in bta_av_cb, when support multi connection, this may need change */ + svr.l2cap.sec_mask = bta_av_cb.sec_mask; + p_rcb->cover_art_max_rx = p_data->api_ca_open.mtu; + get_peer_bd_addr(p_rcb, svr.l2cap.addr); + + if (GOEPC_Open(&svr, bta_av_ca_goep_event_handler, &p_rcb->cover_art_goep_hdl) != GOEP_SUCCESS) { + /* open failed */ + if ((p_data = (tBTA_AV_DATA *) osi_malloc(sizeof(tBTA_AV_DATA))) == NULL) { + assert(0); + } + p_data->hdr.event = BTA_AV_CA_GOEP_DISCONNECT_EVT; + p_data->hdr.layer_specific = get_rcb_idx(p_rcb); + p_data->ca_disconnect.reason = BT_STATUS_FAIL; + bta_sys_sendmsg(p_data); + } +} + +void bta_av_ca_api_close(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) +{ + /* this is a normal disconnect, just build and send OBEX disconnect request */ + build_and_send_disconnect_req(p_rcb); +} + +void bta_av_ca_api_get(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) +{ + tOBEX_PARSE_INFO info = {0}; + UINT8 image_handle_utf16[BTA_AV_CA_IMG_HDL_UTF16_LEN]; + info.opcode = OBEX_OPCODE_GET_FINAL; + /* limit the tx buff size to BT_DEFAULT_BUFFER_SIZE */ + UINT16 tx_buff_size = BT_DEFAULT_BUFFER_SIZE < p_rcb->cover_art_max_tx ? BT_DEFAULT_BUFFER_SIZE : p_rcb->cover_art_max_tx; + if (GOEPC_PrepareRequest(p_rcb->cover_art_goep_hdl, &info, tx_buff_size) != GOEP_SUCCESS) { + /* something error */ + goto error; + } + + GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_rcb->cover_art_cid), 4); + switch (p_data->api_ca_get.type) + { + case BTA_AV_CA_GET_IMAGE_PROPERTIES: + GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_TYPE, (const UINT8 *)cover_art_img_type_prop, COVER_ART_IMG_TYPE_PROP_LEN); + break; + case BTA_AV_CA_GET_IMAGE: + GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_TYPE, (const UINT8 *)cover_art_img_type_img, COVER_ART_IMG_TYPE_IMG_LEN); + break; + case BTA_AV_CA_GET_LINKED_THUMBNAIL: + GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_TYPE, (const UINT8 *)cover_art_img_type_thm, COVER_ART_IMG_TYPE_THM_LEN); + break; + default: + /* should not go here */ + assert(0); + break; + } + image_handle_to_utf16(p_data->api_ca_get.image_handle, image_handle_utf16); + 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); + } + /* always request to enable srm */ + GOEPC_RequestSetSRM(p_rcb->cover_art_goep_hdl, TRUE, FALSE); + + if (GOEPC_SendRequest(p_rcb->cover_art_goep_hdl) != GOEP_SUCCESS) { + goto error; + } + return; +error: + close_goepc_and_disconnect(p_rcb); +} + +void bta_av_ca_response(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) +{ + tOBEX_PARSE_INFO info; + OBEX_ParseResponse(p_data->ca_response.pkt, p_data->ca_response.opcode, &info); + /* we always use a final get */ + if (p_data->ca_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; + while((header = OBEX_GetNextHeader(p_data->ca_response.pkt, &info)) != NULL) { + switch (*header) + { + case OBEX_HEADER_ID_BODY: + /* actually,END_OF_BODY should not in this continue response */ + 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(NULL, body_data, body_data_len, FALSE); + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + break; + default: + break; + } + } + if (body_data != NULL) { + /* the only one or the last body data */ + report_data_event(p_data->ca_response.pkt, body_data, body_data_len, FALSE); + } + else { + /* not any body data */ + osi_free(p_data->ca_response.pkt); + } + + /* if SRM not enable, we need to send a empty get request */ + if (!p_data->ca_response.srm_en || p_data->ca_response.srm_wait) { + build_and_send_empty_get_req(p_rcb); + } + } + else { + osi_free(p_data->ca_response.pkt); + goto error; + } + return; + +error: + close_goepc_and_disconnect(p_rcb); +} + +void bta_av_ca_response_final(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) +{ + tOBEX_PARSE_INFO info; + OBEX_ParseResponse(p_data->ca_response.pkt, p_data->ca_response.opcode, &info); + UINT8 *header = NULL; + if (p_data->ca_response.opcode == OBEX_OPCODE_CONNECT) { + /* we expect a success response code with final bit set */ + if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + if (info.max_packet_length < 255) { + p_rcb->cover_art_max_tx = 255; + } + else { + p_rcb->cover_art_max_tx = info.max_packet_length; + } + BOOLEAN cid_found = false; + while((header = OBEX_GetNextHeader(p_data->ca_response.pkt, &info)) != NULL) { + if (*header == OBEX_HEADER_ID_CONNECTION_ID) { + cid_found = true; + memcpy((UINT8 *)(&p_rcb->cover_art_cid), header + 1, 4); + break; + } + } + if (!cid_found) { + goto error; + } + tBTA_AV_CA_STATUS ca_status; + ca_status.connected = TRUE; + ca_status.reason = BT_STATUS_SUCCESS; + (*bta_av_cb.p_cback)(BTA_AV_CA_STATUS_EVT, (tBTA_AV *) &ca_status); + /* done, free response packet */ + osi_free(p_data->ca_response.pkt); + } + else { + osi_free(p_data->ca_response.pkt); + goto error; + } + } + else if (p_data->ca_response.opcode == OBEX_OPCODE_GET_FINAL) { + UINT8 *body_data = NULL; + UINT16 body_data_len = 0; + /* check response code is success */ + if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + while((header = OBEX_GetNextHeader(p_data->ca_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(NULL, body_data, body_data_len, FALSE); + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + break; + default: + break; + } + } + if (body_data != NULL) { + /* the only one or the last body data, packet will be free by upper layer */ + report_data_event(p_data->ca_response.pkt, body_data, body_data_len, TRUE); + } + else { + /* not any body data */ + osi_free(p_data->ca_response.pkt); + } + } + else { + report_error_data_event(BT_STATUS_FAIL); + osi_free(p_data->ca_response.pkt); + } + } + else if (p_data->ca_response.opcode == OBEX_OPCODE_DISCONNECT) { + /* received disconnect response, close l2cap channel and reset cover art value */ + bta_av_ca_force_disconnect(p_rcb, p_data); + osi_free(p_data->ca_response.pkt); + } + else { + osi_free(p_data->ca_response.pkt); + goto error; + } + return; + +error: + close_goepc_and_disconnect(p_rcb); +} + +void bta_av_ca_goep_connect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) +{ + /* goep connection open, use a smaller value as max_rx */ + if (p_rcb->cover_art_max_rx > p_data->ca_connect.max_rx) { + p_rcb->cover_art_max_rx = p_data->ca_connect.max_rx; + } + build_and_send_connect_req(p_rcb); +} + +void bta_av_ca_goep_disconnect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) +{ + p_rcb->cover_art_goep_hdl = 0; + p_rcb->cover_art_max_rx = 0; + p_rcb->cover_art_max_tx = 0; + p_rcb->cover_art_cid = 0; + + tBTA_AV_CA_STATUS ca_status; + ca_status.connected = FALSE; + ca_status.reason = p_data->ca_disconnect.reason; + (*bta_av_cb.p_cback)(BTA_AV_CA_STATUS_EVT, (tBTA_AV *) &ca_status); +} + +void bta_av_ca_force_disconnect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) +{ + if (p_rcb->cover_art_goep_hdl) { + GOEPC_Close(p_rcb->cover_art_goep_hdl); + } + /* dont reset p_rcb->cover_art_l2cap_psm */ + p_rcb->cover_art_goep_hdl = 0; + p_rcb->cover_art_cid = 0; + p_rcb->cover_art_max_tx = 0; + p_rcb->cover_art_max_rx = 0; + tBTA_AV_CA_STATUS ca_status; + ca_status.connected = FALSE; + /* force disconnect by upper, set reason to success */ + ca_status.reason = BT_STATUS_SUCCESS; + (*bta_av_cb.p_cback)(BTA_AV_CA_STATUS_EVT, (tBTA_AV *) &ca_status); +} + +void bta_av_ca_reset(tBTA_AV_RCB *p_rcb) +{ + if (p_rcb->cover_art_goep_hdl) { + GOEPC_Close(p_rcb->cover_art_goep_hdl); + } + p_rcb->cover_art_l2cap_psm = 0; + p_rcb->cover_art_goep_hdl = 0; + p_rcb->cover_art_state = 0; + p_rcb->cover_art_cid = 0; + p_rcb->cover_art_max_tx = 0; + p_rcb->cover_art_max_rx = 0; +} + +#endif /* BTA_AV_CA_INCLUDED */ diff --git a/components/bt/host/bluedroid/bta/av/bta_av_ca_sm.c b/components/bt/host/bluedroid/bta/av/bta_av_ca_sm.c new file mode 100644 index 000000000000..4a9d4e7c58a3 --- /dev/null +++ b/components/bt/host/bluedroid/bta/av/bta_av_ca_sm.c @@ -0,0 +1,170 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "common/bt_target.h" +#if BTA_AV_CA_INCLUDED + +#include +#include "bta/bta_av_api.h" +#include "bta_av_int.h" +#include "stack/avdt_api.h" +#include "bta/utl.h" +#include "stack/l2c_api.h" +#include "osi/allocator.h" +#include "osi/list.h" + +#include "common/bt_trace.h" + +/* state machine states */ +enum { + BTA_AV_CA_INIT_ST, + BTA_AV_CA_OPENING_ST, + BTA_AV_CA_CONNECTING_ST, + BTA_AV_CA_CONNECTED_ST, + BTA_AV_CA_GETTING_ST, + BTA_AV_CA_CLOSING_ST +}; + +/* state machine action enumeration list */ +enum { + BTA_AV_API_CA_OPEN, + BTA_AV_API_CA_CLOSE, + BTA_AV_API_CA_GET, + BTA_AV_CA_RESPONSE, + BTA_AV_CA_RESPONSE_FINAL, + BTA_AV_CA_GOEP_CONNECT, + BTA_AV_CA_GOEP_DISCONNECT, + BTA_AV_CA_FORCE_DISCONNECT, + BTA_AV_CA_NUM_ACTIONS +}; + +#define BTA_AV_CA_IGNORE BTA_AV_CA_NUM_ACTIONS + +/* type for action functions */ +typedef void (*tBTA_AV_CA_ACTION)(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); + +/* action functions */ +const tBTA_AV_CA_ACTION bta_av_ca_action[] = { + bta_av_ca_api_open, + bta_av_ca_api_close, + bta_av_ca_api_get, + bta_av_ca_response, + bta_av_ca_response_final, + bta_av_ca_goep_connect, + bta_av_ca_goep_disconnect, + bta_av_ca_force_disconnect, + NULL +}; + +/* state table information */ +#define BTA_AV_CA_ACTION_COL 0 /* position of actions */ +#define BTA_AV_CA_NEXT_STATE 1 /* position of next state */ +#define BTA_AV_CA_NUM_COLS 2 /* number of columns in state tables */ + +/* state table for init state */ +static const UINT8 bta_av_ca_st_init[][BTA_AV_CA_NUM_COLS] = { + /* Event Action 1 Next state */ + /* API_CA_OPEN_EVT */ {BTA_AV_API_CA_OPEN, BTA_AV_CA_OPENING_ST }, + /* API_CA_CLOSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST }, + /* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST }, + /* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST }, + /* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST }, + /* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST }, + /* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST }, +}; + +/* state table for opening state */ +static const UINT8 bta_av_ca_st_opening[][BTA_AV_CA_NUM_COLS] = { + /* Event Action 1 Next state */ + /* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_OPENING_ST }, + /* API_CA_CLOSE_EVT */ {BTA_AV_CA_FORCE_DISCONNECT, BTA_AV_CA_INIT_ST }, + /* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_OPENING_ST }, + /* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_OPENING_ST }, + /* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_OPENING_ST }, + /* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_GOEP_CONNECT, BTA_AV_CA_CONNECTING_ST }, + /* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST }, +}; + +/* state table for connecting state */ +static const UINT8 bta_av_ca_st_connecting[][BTA_AV_CA_NUM_COLS] = { + /* Event Action 1 Next state */ + /* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTING_ST }, + /* API_CA_CLOSE_EVT */ {BTA_AV_CA_FORCE_DISCONNECT, BTA_AV_CA_INIT_ST }, + /* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTING_ST }, + /* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTING_ST }, + /* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_RESPONSE_FINAL, BTA_AV_CA_CONNECTED_ST }, + /* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTING_ST }, + /* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST }, +}; + +/* state table for connected state */ +static const UINT8 bta_av_ca_st_connected[][BTA_AV_CA_NUM_COLS] = { + /* Event Action 1 Next state */ + /* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTED_ST }, + /* API_CA_CLOSE_EVT */ {BTA_AV_API_CA_CLOSE, BTA_AV_CA_CLOSING_ST }, + /* API_CA_GET_EVT */ {BTA_AV_API_CA_GET, BTA_AV_CA_GETTING_ST }, + /* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTED_ST }, + /* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTED_ST }, + /* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTED_ST }, + /* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST }, +}; + +/* state table for getting state */ +static const UINT8 bta_av_ca_st_getting[][BTA_AV_CA_NUM_COLS] = { + /* Event Action 1 Next state */ + /* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_GETTING_ST }, + /* API_CA_CLOSE_EVT */ {BTA_AV_CA_FORCE_DISCONNECT, BTA_AV_CA_INIT_ST }, + /* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_GETTING_ST }, + /* CA_RESPONSE_EVT */ {BTA_AV_CA_RESPONSE, BTA_AV_CA_GETTING_ST }, + /* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_RESPONSE_FINAL, BTA_AV_CA_CONNECTED_ST }, + /* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_GETTING_ST }, + /* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST }, +}; + +/* state table for closing state */ +static const UINT8 bta_av_ca_st_closing[][BTA_AV_CA_NUM_COLS] = { + /* Event Action 1 Next state */ + /* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CLOSING_ST }, + /* API_CA_CLOSE_EVT */ {BTA_AV_CA_FORCE_DISCONNECT, BTA_AV_CA_INIT_ST }, + /* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CLOSING_ST }, + /* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CLOSING_ST }, + /* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_RESPONSE_FINAL, BTA_AV_CA_INIT_ST }, + /* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CLOSING_ST }, + /* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST }, +}; + +/* type for state table */ +typedef const UINT8 (*tBTA_AV_CA_ST_TBL)[BTA_AV_CA_NUM_COLS]; + +/* state table */ +static const tBTA_AV_CA_ST_TBL bta_av_ca_st_tbl[] = { + bta_av_ca_st_init, + bta_av_ca_st_opening, + bta_av_ca_st_connecting, + bta_av_ca_st_connected, + bta_av_ca_st_getting, + bta_av_ca_st_closing +}; + +void bta_av_ca_sm_execute(tBTA_AV_RCB *p_rcb, UINT16 event, tBTA_AV_DATA *p_data) +{ + tBTA_AV_CA_ST_TBL state_table; + UINT8 action; + /* look up the state table for the current state */ + state_table = bta_av_ca_st_tbl[p_rcb->cover_art_state]; + + event -= BTA_AV_CA_FIRST_SM_EVT; + + /* set next state */ + p_rcb->cover_art_state = state_table[event][BTA_AV_CA_NEXT_STATE]; + + /* execute action functions */ + if ((action = state_table[event][BTA_AV_CA_ACTION_COL]) != BTA_AV_CA_IGNORE) { + (*bta_av_ca_action[action])(p_rcb, p_data); + } +} + +#endif /* BTA_AV_CA_INCLUDED */ diff --git a/components/bt/host/bluedroid/bta/av/bta_av_main.c b/components/bt/host/bluedroid/bta/av/bta_av_main.c index 2fde06f61be5..56dc5970a335 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_main.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_main.c @@ -461,7 +461,7 @@ static void bta_av_a2dp_report_cback(UINT8 handle, AVDT_REPORT_TYPE type, ** ** Function bta_av_api_sink_enable ** -** Description activate, deactive A2DP Sink, +** Description activate, deactivate A2DP Sink, ** ** Returns void ** @@ -593,7 +593,7 @@ static void bta_av_api_register(tBTA_AV_DATA *p_data) #endif } - /* Set the Calss of Device (Audio & Capturing/Rendering service class bit) */ + /* Set the Class of Device (Audio & Capturing/Rendering service class bit) */ if (p_data->api_reg.tsep == AVDT_TSEP_SRC) { cod.service = BTM_COD_SERVICE_CAPTURING | BTM_COD_SERVICE_AUDIO; cod.major = BTM_COD_MAJOR_AUDIO; @@ -1248,6 +1248,13 @@ BOOLEAN bta_av_hdl_event(BT_HDR *p_msg) APPL_TRACE_VERBOSE("AV sm event=0x%x(%s)\n", event, bta_av_evt_code(event)); /* state machine events */ bta_av_sm_execute(&bta_av_cb, p_msg->event, (tBTA_AV_DATA *) p_msg); +#if BTA_AV_CA_INCLUDED + } else if (event >= BTA_AV_CA_FIRST_SM_EVT && event <= BTA_AV_CA_LAST_SM_EVT) { + if (p_msg->layer_specific < BTA_AV_NUM_RCB) { + tBTA_AV_RCB *p_rcb = &bta_av_cb.rcb[p_msg->layer_specific]; + bta_av_ca_sm_execute(p_rcb, p_msg->event, (tBTA_AV_DATA *) p_msg); + } +#endif } else { APPL_TRACE_VERBOSE("handle=0x%x\n", p_msg->layer_specific); tBTA_AV_SCB *p_scb = bta_av_hndl_to_scb(p_msg->layer_specific); diff --git a/components/bt/host/bluedroid/bta/av/include/bta_av_int.h b/components/bt/host/bluedroid/bta/av/include/bta_av_int.h index 8c2554560d0b..4dba1461d468 100644 --- a/components/bt/host/bluedroid/bta/av/include/bta_av_int.h +++ b/components/bt/host/bluedroid/bta/av/include/bta_av_int.h @@ -85,7 +85,16 @@ enum { BTA_AV_ROLE_CHANGE_EVT, BTA_AV_AVDT_DELAY_RPT_EVT, BTA_AV_ACP_CONNECT_EVT, - +#if BTA_AV_CA_INCLUDED + /* these events are handled by the Cover Art Client state machine */ + BTA_AV_API_CA_OPEN_EVT, + BTA_AV_API_CA_CLOSE_EVT, + BTA_AV_API_CA_GET_EVT, + BTA_AV_CA_RESPONSE_EVT, + BTA_AV_CA_RESPONSE_FINAL_EVT, + BTA_AV_CA_GOEP_CONNECT_EVT, + BTA_AV_CA_GOEP_DISCONNECT_EVT, +#endif /* these events are handled outside of the state machine */ BTA_AV_API_ENABLE_EVT, BTA_AV_API_REGISTER_EVT, @@ -115,6 +124,12 @@ enum { #define BTA_AV_FIRST_SM_EVT BTA_AV_API_DISABLE_EVT #define BTA_AV_LAST_SM_EVT BTA_AV_AVRC_NONE_EVT +#if BTA_AV_CA_INCLUDED +/* events for AVRC Cover Art state machine */ +#define BTA_AV_CA_FIRST_SM_EVT BTA_AV_API_CA_OPEN_EVT +#define BTA_AV_CA_LAST_SM_EVT BTA_AV_CA_GOEP_DISCONNECT_EVT +#endif + /* events for AV stream control block state machine */ #define BTA_AV_FIRST_SSM_EVT BTA_AV_API_OPEN_EVT @@ -348,6 +363,52 @@ typedef struct { BT_HDR hdr; } tBTA_AV_API_GET_DELAY_VALUE; +#if BTA_AV_CA_INCLUDED + +/* data type for BTA_AV_API_CA_OPEN_EVT */ +typedef struct { + BT_HDR hdr; + UINT16 mtu; +} tBTA_AV_API_CA_OPEN; + +/* data type for BTA_AV_API_CA_CLOSE_EVT */ +typedef struct { + BT_HDR hdr; +} tBTA_AV_API_CA_CLOSE; + +/* data type for BTA_AV_API_CA_GET_EVT */ +typedef struct { + BT_HDR hdr; + tBTA_AV_GET_TYPE type; + UINT8 image_handle[7]; + /* Image descriptor used in get image function */ + UINT16 image_descriptor_len; + UINT8 *image_descriptor; +} tBTA_AV_API_CA_GET; + +/* data type for BTA_AV_CA_RESPONSE_EVT and BTA_AV_CA_RESPONSE_FINAL_EVT */ +typedef struct { + BT_HDR hdr; + BT_HDR *pkt; + UINT8 opcode; + BOOLEAN srm_en; + BOOLEAN srm_wait; +} tBTA_AV_CA_RESPONSE; + +/* data type for BTA_AV_CA_CONNECT_EVT */ +typedef struct { + BT_HDR hdr; + UINT16 max_rx; +} tBTA_AV_CA_CONNECT; + +/* data type for BTA_AV_CA_DISCONNECT_EVT */ +typedef struct { + BT_HDR hdr; + UINT16 reason; +} tBTA_AV_CA_DISCONNECT; + +#endif /* BTA_AV_CA_INCLUDED */ + /* initiator/acceptor role for adaption */ #define BTA_AV_ROLE_AD_INT 0x00 /* initiator */ #define BTA_AV_ROLE_AD_ACP 0x01 /* acceptor */ @@ -382,6 +443,14 @@ typedef union { tBTA_AV_API_META_RSP api_meta_rsp; tBTA_AV_API_SET_DELAY_VALUE api_set_delay_vlaue; tBTA_AV_API_GET_DELAY_VALUE api_get_delay_value; +#if BTA_AV_CA_INCLUDED + tBTA_AV_API_CA_OPEN api_ca_open; + tBTA_AV_API_CA_CLOSE api_ca_close; + tBTA_AV_API_CA_GET api_ca_get; + tBTA_AV_CA_RESPONSE ca_response; + tBTA_AV_CA_CONNECT ca_connect; + tBTA_AV_CA_DISCONNECT ca_disconnect; +#endif } tBTA_AV_DATA; typedef void (tBTA_AV_VDP_DATA_ACT)(void *p_scb); @@ -405,8 +474,8 @@ typedef union { #define BTA_AV_Q_TAG_START 0x02 /* before start sending media packets */ #define BTA_AV_Q_TAG_STREAM 0x03 /* during streaming */ -#define BTA_AV_WAIT_ACP_CAPS_ON 0x01 /* retriving the peer capabilities */ -#define BTA_AV_WAIT_ACP_CAPS_STARTED 0x02 /* started while retriving peer capabilities */ +#define BTA_AV_WAIT_ACP_CAPS_ON 0x01 /* retrieving the peer capabilities */ +#define BTA_AV_WAIT_ACP_CAPS_STARTED 0x02 /* started while retrieving peer capabilities */ #define BTA_AV_WAIT_ROLE_SW_RES_OPEN 0x04 /* waiting for role switch result after API_OPEN, before STR_OPENED */ #define BTA_AV_WAIT_ROLE_SW_RES_START 0x08 /* waiting for role switch result before streaming */ #define BTA_AV_WAIT_ROLE_SW_STARTED 0x10 /* started while waiting for role switch result */ @@ -462,7 +531,7 @@ typedef struct { BOOLEAN use_rc; /* TRUE if AVRCP is allowed */ BOOLEAN started; /* TRUE if stream started */ UINT8 co_started; /* non-zero, if stream started from call-out perspective */ - BOOLEAN recfg_sup; /* TRUE if the first attempt to reconfigure the stream was successfull, else False if command fails */ + BOOLEAN recfg_sup; /* TRUE if the first attempt to reconfigure the stream was successful, else False if command fails */ BOOLEAN suspend_sup; /* TRUE if Suspend stream is supported, else FALSE if suspend command fails */ BOOLEAN deregistring; /* TRUE if deregistering */ BOOLEAN sco_suspend; /* TRUE if SUSPEND is issued automatically for SCO */ @@ -471,7 +540,7 @@ typedef struct { UINT8 wait; /* set 0x1, when getting Caps as ACP, set 0x2, when started */ UINT8 q_tag; /* identify the associated q_info union member */ BOOLEAN no_rtp_hdr; /* TRUE if add no RTP header*/ - UINT8 disc_rsn; /* disconenction reason */ + UINT8 disc_rsn; /* disconnection reason */ UINT16 uuid_int; /*intended UUID of Initiator to connect to */ } tBTA_AV_SCB; @@ -481,6 +550,13 @@ typedef struct { #define BTA_AV_RC_CONN_MASK 0x20 +#define BTA_AV_CA_IMG_HDL_UTF16_LEN 16 /* Cover Art image handle in utf-16 format, fixed to 16 */ + +#define BTA_AV_CA_SRM_DISABLE 0x00 +#define BTA_AV_CA_SRM_ENABLE_REQ 0x01 +#define BTA_AV_CA_SRM_WAIT 0x02 +#define BTA_AV_CA_SRM_ENABLE 0x03 + /* type for AV RCP control block */ /* index to this control block is the rc handle */ typedef struct { @@ -491,6 +567,14 @@ typedef struct { tBTA_AV_FEAT peer_features; /* peer features mask */ UINT16 peer_ct_features; UINT16 peer_tg_features; +#if BTA_AV_CA_INCLUDED + UINT16 cover_art_l2cap_psm; /* OBEX over L2CAP PSM */ + UINT16 cover_art_goep_hdl; /* Cover Art client GOEP connection handle */ + UINT8 cover_art_state; /* Cover Art client state machine */ + UINT32 cover_art_cid; /* Cover Art client connection id */ + UINT16 cover_art_max_tx; /* max packet length peer device can receive */ + UINT16 cover_art_max_rx; /* max packet length we can receive */ +#endif } tBTA_AV_RCB; #define BTA_AV_NUM_RCB (BTA_AV_NUM_STRS + 2) @@ -705,6 +789,19 @@ extern void bta_av_do_disc_vdp (tBTA_AV_SCB *p_scb, tBTA_AV_DATA *p_data); extern void bta_av_vdp_str_opened (tBTA_AV_SCB *p_scb, tBTA_AV_DATA *p_data); extern void bta_av_reg_vdp (tAVDT_CS *p_cs, char *p_service_name, void *p_data); +#if BTA_AV_CA_INCLUDED +extern void bta_av_ca_api_open(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); +extern void bta_av_ca_api_close(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); +extern void bta_av_ca_api_get(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); +extern void bta_av_ca_response(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); +extern void bta_av_ca_response_final(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); +extern void bta_av_ca_goep_connect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); +extern void bta_av_ca_goep_disconnect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); +extern void bta_av_ca_force_disconnect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data); +extern void bta_av_ca_sm_execute(tBTA_AV_RCB *p_rcb, UINT16 event, tBTA_AV_DATA *p_data); +extern void bta_av_ca_reset(tBTA_AV_RCB *p_rcb); +#endif + #endif ///BTA_AV_INCLUDED == TRUE #endif /* BTA_AV_INT_H */ diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_av_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_av_api.h index ddf72b96372c..89f2c5c6dc59 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_av_api.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_av_api.h @@ -43,14 +43,14 @@ #define BTA_AV_FAIL_STREAM 3 /* stream connection failed */ #define BTA_AV_FAIL_RESOURCES 4 /* no resources */ #define BTA_AV_FAIL_ROLE 5 /* failed due to role management related issues */ -#define BTA_AV_FAIL_GET_CAP 6 /* get capability failed due to no SEP availale on the peer */ +#define BTA_AV_FAIL_GET_CAP 6 /* get capability failed due to no SEP available on the peer */ typedef UINT8 tBTA_AV_STATUS; /* AV features masks */ #define BTA_AV_FEAT_RCTG 0x0001 /* remote control target */ #define BTA_AV_FEAT_RCCT 0x0002 /* remote control controller */ -#define BTA_AV_FEAT_PROTECT 0x0004 /* streaming media contect protection */ +#define BTA_AV_FEAT_PROTECT 0x0004 /* streaming media context protection */ #define BTA_AV_FEAT_VENDOR 0x0008 /* remote control vendor dependent commands */ #define BTA_AV_FEAT_REPORT 0x0020 /* use reporting service for VDP */ #define BTA_AV_FEAT_METADATA 0x0040 /* remote control Metadata Transfer command/response */ @@ -60,6 +60,7 @@ typedef UINT8 tBTA_AV_STATUS; #define BTA_AV_FEAT_ADV_CTRL 0x0200 /* remote control Advanced Control command/response */ #define BTA_AV_FEAT_DELAY_RPT 0x0400 /* allow delay reporting */ #define BTA_AV_FEAT_ACP_START 0x0800 /* start stream when 2nd SNK was accepted */ +#define BTA_AV_FEAT_COVER_ART 0x1000 /* remote control target cover art */ /* Internal features */ #define BTA_AV_FEAT_NO_SCO_SSPD 0x8000 /* Do not suspend av streaming as to AG events(SCO or Call) */ @@ -107,6 +108,7 @@ typedef UINT8 tBTA_AV_HNDL; #define BTA_AV_MAX_VDP_MTU 1008 #endif +#define BTA_AV_CA_IMG_HDL_LEN 7 /* Cover Art image handle len, fixed to 7 */ /* codec type */ #define BTA_AV_CODEC_SBC A2D_MEDIA_CT_SBC /* SBC media codec type */ @@ -224,6 +226,12 @@ typedef UINT8 tBTA_AV_CODE; typedef UINT8 tBTA_AV_ERR; +/* type codes for BTA_AV_API_CA_GET */ +#define BTA_AV_CA_GET_IMAGE_PROPERTIES 0x01 +#define BTA_AV_CA_GET_IMAGE 0x02 +#define BTA_AV_CA_GET_LINKED_THUMBNAIL 0x03 + +typedef UINT8 tBTA_AV_GET_TYPE; /* AV callback events */ #define BTA_AV_ENABLE_EVT 0 /* AV enabled */ @@ -253,8 +261,13 @@ typedef UINT8 tBTA_AV_ERR; #define BTA_AV_SET_DELAY_VALUE_EVT 22 /* set delay reporting value */ #define BTA_AV_GET_DELAY_VALUE_EVT 23 /* get delay reporting value */ #define BTA_AV_SNK_PSC_CFG_EVT 24 /* Protocol service capabilities. */ + +/* still keep Cover Art event here if Cover Art feature not enabled */ +#define BTA_AV_CA_STATUS_EVT 25 /* Cover Art Client status event */ +#define BTA_AV_CA_DATA_EVT 26 /* Cover Art response body data */ + /* Max BTA event */ -#define BTA_AV_MAX_EVT 25 +#define BTA_AV_MAX_EVT 27 /* function types for call-out functions */ @@ -482,6 +495,25 @@ typedef struct { } tBTA_AV_SNK_PSC_CFG; +#if BTA_AV_CA_INCLUDED + +/* data associated with BTA_AV_CA_STATUS_EVT */ +typedef struct { + BOOLEAN connected; /* whether Cover Art connection is connected */ + UINT16 reason; /* connect failed or disconnect reason */ +} tBTA_AV_CA_STATUS; + +/* data associated with BTA_AV_CA_DATA_EVT */ +typedef struct { + UINT16 status; /* OBEX response status */ + BOOLEAN final; /* final data packet */ + UINT16 data_len; /* data len */ + UINT8 *p_data; /* point to the data in p_hdr */ + BT_HDR *p_hdr; /* after data pass to application, free this packet */ +} tBTA_AV_CA_DATA; + +#endif + /* union of data associated with AV callback */ typedef union { tBTA_AV_CHNL chnl; @@ -506,6 +538,10 @@ typedef union { tBTA_AV_RC_FEAT rc_feat; tBTA_AV_DELAY delay; tBTA_AV_SNK_PSC_CFG psc; +#if BTA_AV_CA_INCLUDED + tBTA_AV_CA_STATUS ca_status; + tBTA_AV_CA_DATA ca_data; +#endif } tBTA_AV; /* union of data associated with AV Media callback */ @@ -865,6 +901,47 @@ void BTA_AvMetaRsp(UINT8 rc_handle, UINT8 label, tBTA_AV_CODE rsp_code, *******************************************************************************/ void BTA_AvMetaCmd(UINT8 rc_handle, UINT8 label, tBTA_AV_CMD cmd_code, BT_HDR *p_pkt); +#if BTA_AV_CA_INCLUDED + +/******************************************************************************* +** +** Function BTA_AvCaOpen +** +** Description Open a Cover Art OBEX connection to peer device. This function +** can only be used if peer device TG support Cover Art feature and +** AV is enabled with feature BTA_AV_FEAT_METADATA. +** +** Returns void +** +*******************************************************************************/ +void BTA_AvCaOpen(UINT8 rc_handle, UINT16 pref_packet_len); + +/******************************************************************************* +** +** Function BTA_AvCaClose +** +** Description Close a Cover Art OBEX connection. +** +** Returns void +** +*******************************************************************************/ +void BTA_AvCaClose(UINT8 rc_handle); + +/******************************************************************************* +** +** Function BTA_AvCaGet +** +** Description Start the process to get image properties, get image or get +** linked thumbnail. This function can only be used if Cover Art +** OBEX connection is established. +** +** Returns void +** +*******************************************************************************/ +void BTA_AvCaGet(UINT8 rc_handle, tBTA_AV_GET_TYPE type, UINT8 *image_handle, UINT8 *image_descriptor, UINT16 image_descriptor_len); + +#endif /* BTA_AV_CA_INCLUDED */ + #ifdef __cplusplus } #endif diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_sys.h b/components/bt/host/bluedroid/bta/include/bta/bta_sys.h index 54376de635a7..ecf4791995d6 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_sys.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_sys.h @@ -91,7 +91,8 @@ typedef UINT16 tBTA_SYS_HW_MODULE; #define BTA_ID_GATTC 31 /* GATT Client */ #define BTA_ID_GATTS 32 /* GATT Client */ #define BTA_ID_SDP 33 /* SDP Client */ -#define BTA_ID_BLUETOOTH_MAX 34 /* last BT profile */ +#define BTA_ID_GOEPC 34 /* GOEP Client */ +#define BTA_ID_BLUETOOTH_MAX 35 /* last BT profile */ /* GENERIC */ #define BTA_ID_PRM 38 @@ -142,7 +143,7 @@ typedef void (tBTA_SYS_CONN_CBACK)(tBTA_SYS_CONN_STATUS status, UINT8 id, UINT8 typedef void (tBTA_SYS_SSR_CFG_CBACK)(UINT8 id, UINT8 app_id, UINT16 latency, UINT16 tout); #if (BTA_EIR_CANNED_UUID_LIST != TRUE) -/* eir callback for adding/removeing UUID */ +/* eir callback for adding/removing UUID */ typedef void (tBTA_SYS_EIR_CBACK)(tBT_UUID uuid, BOOLEAN adding); #endif diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c index 4c237b1e5094..d26ff5e75758 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,7 +34,7 @@ #if BTC_AV_INCLUDED -// global variable to inidcate avrc is initialized with a2dp +// global variable to indicate avrc is initialized with a2dp bool g_av_with_rc; // global variable to indicate a2dp is initialized bool g_a2dp_on_init; @@ -127,6 +127,8 @@ static btc_av_cb_t *btc_av_cb_ptr = NULL; case BTA_AV_META_MSG_EVT: \ case BTA_AV_RC_FEAT_EVT: \ case BTA_AV_REMOTE_RSP_EVT: \ + case BTA_AV_CA_STATUS_EVT: \ + case BTA_AV_CA_DATA_EVT: \ { \ btc_rc_handler(e, d);\ }break; \ @@ -382,6 +384,8 @@ static BOOLEAN btc_av_state_idle_handler(btc_sm_event_t event, void *p_data) case BTA_AV_META_MSG_EVT: case BTA_AV_RC_FEAT_EVT: case BTA_AV_REMOTE_RSP_EVT: + case BTA_AV_CA_STATUS_EVT: + case BTA_AV_CA_DATA_EVT: btc_rc_handler(event, (tBTA_AV *)p_data); break; @@ -1355,7 +1359,7 @@ static void bte_av_media_callback(tBTA_AV_EVT event, tBTA_AV_MEDIA *p_data) /* send a command to BT Media Task */ btc_a2dp_sink_reset_decoder((UINT8 *)p_data); - /* currently only supportes SBC */ + /* currently only supports SBC */ a2d_status = A2D_ParsSbcInfo(&sbc_cie, (UINT8 *)p_data, FALSE); if (a2d_status == A2D_SUCCESS) { btc_msg_t msg; diff --git a/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c b/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c index 7e7f1b2bb6c6..159731e78372 100644 --- a/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c +++ b/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c @@ -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 */ @@ -40,7 +40,7 @@ static void btc_rc_upstreams_evt(UINT16 event, tAVRC_COMMAND *pavrc_cmd, UINT8 c ** Static variables ******************************************************************************/ -/* flag indicating wheter TG/CT is initialized */ +/* flag indicating whether TG/CT is initialized */ static uint32_t s_rc_ct_init; static uint32_t s_rc_tg_init; @@ -157,6 +157,11 @@ bool btc_avrc_ct_rn_evt_supported(uint8_t event_id) true : false; } +bool btc_avrc_ct_check_cover_art_support(void) +{ + return (btc_rc_cb.rc_features & BTA_AV_FEAT_COVER_ART); +} + bool btc_avrc_tg_init_p(void) { return (s_rc_tg_init == BTC_RC_TG_INIT_MAGIC); @@ -181,6 +186,52 @@ bool btc_avrc_ct_connected_p(void) (btc_rc_cb.rc_features & BTA_AV_FEAT_RCTG); } +void btc_avrc_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_avrc_args_t *dst = (btc_avrc_args_t *)p_dest; + btc_avrc_args_t *src = (btc_avrc_args_t *)p_src; + size_t len; + + switch (msg->act) { +#if BTC_AV_CA_INCLUDED + case BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT: + len = src->ca_get_img.image_descriptor_len; + dst->ca_get_img.image_descriptor = (uint8_t *)osi_malloc(len); + if (dst->ca_get_img.image_descriptor) { + memcpy(dst->ca_get_img.image_descriptor, src->ca_get_img.image_descriptor, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + break; +#endif + default: + BTC_TRACE_DEBUG("%s Unhandled deep copy %d\n", __FUNCTION__, msg->act); + UNUSED(dst); + UNUSED(src); + UNUSED(len); + break; + } +} + +void btc_avrc_arg_deep_free(btc_msg_t *msg) +{ + btc_avrc_args_t *arg = (btc_avrc_args_t *)msg->arg; + + switch (msg->act) { +#if BTC_AV_CA_INCLUDED + case BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT: + if (arg->ca_get_img.image_descriptor) { + osi_free(arg->ca_get_img.image_descriptor); + } + break; +#endif + default: + BTC_TRACE_DEBUG("%s Unhandled deep free %d\n", __FUNCTION__, msg->act); + UNUSED(arg); + break; + } +} + void btc_avrc_tg_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { btc_avrc_tg_args_t *dst = (btc_avrc_tg_args_t *) p_dest; @@ -499,14 +550,28 @@ static void handle_rc_disconnect (tBTA_AV_RC_CLOSE *p_rc_close) // clean up the state btc_rc_cb.rc_handle = 0; btc_rc_cb.rc_connected = FALSE; - +#if BTC_AV_CA_INCLUDED + bool cover_art_connected = btc_rc_cb.rc_cover_art_connected; + btc_rc_cb.rc_cover_art_connected = FALSE; +#endif btc_rc_cb.rc_features = 0; btc_rc_cb.rc_ct_features = 0; btc_rc_cb.rc_tg_features = 0; memset(btc_rc_cb.rc_addr, 0, sizeof(BD_ADDR)); memset(btc_rc_cb.rc_ntf, 0, sizeof(btc_rc_cb.rc_ntf)); +#if BTC_AV_CA_INCLUDED /* report connection state */ + if (cover_art_connected) { + /* if rc disconnect, cover art disconnect too */ + esp_avrc_ct_cb_param_t param; + memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); + param.cover_art_state.state = ESP_AVRC_COVER_ART_DISCONNECTED; + param.cover_art_state.reason = BT_STATUS_FAIL; + btc_avrc_ct_cb_to_app(ESP_AVRC_CT_COVER_ART_STATE_EVT, ¶m); + } +#endif + if (rc_features & BTA_AV_FEAT_RCTG) { esp_avrc_ct_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); @@ -751,7 +816,7 @@ static void btc_rc_upstreams_evt(UINT16 event, tAVRC_COMMAND *pavrc_cmd, UINT8 c btc_rc_cb.rc_ntf[event_id - 1].registered = TRUE; btc_rc_cb.rc_ntf[event_id - 1].label = label; - BTC_TRACE_EVENT("%s: New registerd notification: event_id:0x%x, label:0x%x", + BTC_TRACE_EVENT("%s: New registered notification: event_id:0x%x, label:0x%x", __FUNCTION__, event_id, label); // set up callback @@ -947,7 +1012,7 @@ void btc_rc_handler(tBTA_AV_EVT event, tBTA_AV *p_data) memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.conn_stat.connected = true; memcpy(param.conn_stat.remote_bda, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); - btc_avrc_tg_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); + btc_avrc_tg_cb_to_app(ESP_AVRC_TG_CONNECTION_STATE_EVT, ¶m); } } while (0); btc_rc_cb.rc_features = p_data->rc_feat.peer_features; @@ -967,6 +1032,36 @@ void btc_rc_handler(tBTA_AV_EVT event, tBTA_AV *p_data) handle_rc_passthrough_cmd(&p_data->remote_cmd); } break; +#if BTC_AV_CA_INCLUDED + case BTA_AV_CA_STATUS_EVT: { + btc_rc_cb.rc_cover_art_connected = p_data->ca_status.connected; + esp_avrc_ct_cb_param_t param; + memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); + if (p_data->ca_status.connected) { + param.cover_art_state.state = ESP_AVRC_COVER_ART_CONNECTED; + } + else { + param.cover_art_state.state = ESP_AVRC_COVER_ART_DISCONNECTED; + } + param.cover_art_state.reason = p_data->ca_status.reason; + btc_avrc_ct_cb_to_app(ESP_AVRC_CT_COVER_ART_STATE_EVT, ¶m); + } + break; + case BTA_AV_CA_DATA_EVT: { + esp_avrc_ct_cb_param_t param; + memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); + param.cover_art_data.status = p_data->ca_data.status; + param.cover_art_data.final = p_data->ca_data.final; + param.cover_art_data.data_len = p_data->ca_data.data_len; + param.cover_art_data.p_data = p_data->ca_data.p_data; + btc_avrc_ct_cb_to_app(ESP_AVRC_CT_COVER_ART_DATA_EVT, ¶m); + /* free the data packet now */ + if (p_data->ca_data.p_hdr != NULL) { + osi_free(p_data->ca_data.p_hdr); + } + } + break; +#endif /* BTC_AV_CA_INCLUDED */ default: BTC_TRACE_DEBUG("Unhandled RC event : 0x%x", event); } @@ -1041,7 +1136,7 @@ static void btc_avrc_ct_deinit(void) BTC_TRACE_API("## %s ##", __FUNCTION__); if (g_a2dp_on_deinit) { - BTC_TRACE_WARNING("A2DP already deinit, AVRC CT shuold deinit in advance of A2DP !!!"); + BTC_TRACE_WARNING("A2DP already deinit, AVRC CT should deinit in advance of A2DP !!!"); } if (s_rc_ct_init != BTC_RC_CT_INIT_MAGIC) { @@ -1255,7 +1350,7 @@ static bt_status_t btc_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code BTA_AvRemoteCmd(btc_rc_cb.rc_handle, tl, (tBTA_AV_RC)key_code, (tBTA_AV_STATE)key_state); status = BT_STATUS_SUCCESS; - BTC_TRACE_API("%s: succesfully sent passthrough command to BTA", __FUNCTION__); + BTC_TRACE_API("%s: successfully sent passthrough command to BTA", __FUNCTION__); } else { status = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); @@ -1267,6 +1362,64 @@ static bt_status_t btc_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code return status; } +#if BTC_AV_CA_INCLUDED + +static void btc_avrc_ct_cover_art_connect(UINT16 mtu) +{ + if (!btc_rc_cb.rc_cover_art_connected) { + BTA_AvCaOpen(btc_rc_cb.rc_handle, mtu); + } + else { + BTC_TRACE_WARNING("%s: cover art already connected", __FUNCTION__); + } + return; +} + +static void btc_avrc_ct_cover_art_disconnect(void) +{ + if (btc_rc_cb.rc_cover_art_connected) { + BTA_AvCaClose(btc_rc_cb.rc_handle); + } + else { + BTC_TRACE_WARNING("%s: cover art not connected", __FUNCTION__); + } + return; +} + +static void btc_avrc_ct_cover_art_get_image_properties(UINT8 *image_handle) +{ + if (btc_rc_cb.rc_cover_art_connected) { + BTA_AvCaGet(btc_rc_cb.rc_handle, BTA_AV_CA_GET_IMAGE_PROPERTIES, image_handle, NULL, 0); + } + else { + BTC_TRACE_WARNING("%s: cover art not connected", __FUNCTION__); + } + return; +} + +static void btc_avrc_ct_cover_art_get_image(UINT8 *image_handle, UINT8 *image_descriptor, UINT16 image_descriptor_len) +{ + if (btc_rc_cb.rc_cover_art_connected) { + BTA_AvCaGet(btc_rc_cb.rc_handle, BTA_AV_CA_GET_IMAGE, image_handle, image_descriptor, image_descriptor_len); + } + else { + BTC_TRACE_WARNING("%s: cover art not connected", __FUNCTION__); + } + return; +} + +static void btc_avrc_ct_cover_art_get_linked_thumbnail(UINT8 *image_handle) +{ + if (btc_rc_cb.rc_cover_art_connected) { + BTA_AvCaGet(btc_rc_cb.rc_handle, BTA_AV_CA_GET_LINKED_THUMBNAIL, image_handle, NULL, 0); + } + else { + BTC_TRACE_WARNING("%s: cover art not connected", __FUNCTION__); + } + return; +} + +#endif /* BTC_AV_CA_INCLUDED */ /******************************************************************************* ** @@ -1298,7 +1451,7 @@ static void btc_avrc_tg_init(void) } if (g_a2dp_on_init) { - BTC_TRACE_WARNING("AVRC Taget is expected to be initialized in advance of A2DP !!!"); + BTC_TRACE_WARNING("AVRC Target is expected to be initialized in advance of A2DP !!!"); } } @@ -1320,7 +1473,7 @@ static void btc_avrc_tg_deinit(void) BTC_TRACE_API("## %s ##", __FUNCTION__); if (g_a2dp_on_deinit) { - BTC_TRACE_WARNING("A2DP already deinit, AVRC TG shuold deinit in advance of A2DP !!!"); + BTC_TRACE_WARNING("A2DP already deinit, AVRC TG should deinit in advance of A2DP !!!"); } if (s_rc_tg_init != BTC_RC_TG_INIT_MAGIC) { @@ -1418,6 +1571,28 @@ void btc_avrc_ct_call_handler(btc_msg_t *msg) btc_avrc_ct_send_set_absolute_volume_cmd(arg->set_abs_vol_cmd.tl, arg->set_abs_vol_cmd.volume); break; } +#if BTC_AV_CA_INCLUDED + case BTC_AVRC_CT_API_COVER_ART_CONNECT_EVT: { + btc_avrc_ct_cover_art_connect(arg->ca_conn.mtu); + break; + } + case BTC_AVRC_CT_API_COVER_ART_DISCONNECT_EVT: { + btc_avrc_ct_cover_art_disconnect(); + break; + } + case BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_PROPERTIES_EVT: { + btc_avrc_ct_cover_art_get_image_properties(arg->ca_get_img_prop.image_handle); + break; + } + case BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT: { + btc_avrc_ct_cover_art_get_image(arg->ca_get_img.image_handle, arg->ca_get_img.image_descriptor, arg->ca_get_img.image_descriptor_len); + break; + } + case BTC_AVRC_CT_API_COVER_ART_GET_LINKED_THUMBNAIL_EVT: { + btc_avrc_ct_cover_art_get_linked_thumbnail(arg->ca_get_lk_thn.image_handle); + break; + } +#endif default: BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act); } diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_avrc.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_avrc.h index 83200ae15ed4..a78a7af1dbca 100644 --- a/components/bt/host/bluedroid/btc/profile/std/include/btc_avrc.h +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_avrc.h @@ -39,7 +39,14 @@ typedef enum { BTC_AVRC_STATUS_API_SND_GET_RN_CAPS_EVT, BTC_AVRC_NOTIFY_API_SND_REG_NOTIFY_EVT, BTC_AVRC_CTRL_API_SND_SET_PLAYER_SETTING_EVT, - BTC_AVRC_CTRL_API_SND_SET_ABSOLUTE_VOLUME_EVT + BTC_AVRC_CTRL_API_SND_SET_ABSOLUTE_VOLUME_EVT, +#if BTC_AV_CA_INCLUDED + BTC_AVRC_CT_API_COVER_ART_CONNECT_EVT, + BTC_AVRC_CT_API_COVER_ART_DISCONNECT_EVT, + BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_PROPERTIES_EVT, + BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT, + BTC_AVRC_CT_API_COVER_ART_GET_LINKED_THUMBNAIL_EVT, +#endif } btc_avrc_act_t; typedef struct { @@ -77,6 +84,28 @@ typedef struct { uint8_t volume; } set_abs_vol_cmd_t; +#if BTC_AV_CA_INCLUDED + +typedef struct { + uint16_t mtu; +} ca_conn_t; + +typedef struct { + uint8_t image_handle[7]; +} ca_get_img_prop_t; + +typedef struct { + uint8_t image_handle[7]; + uint16_t image_descriptor_len; + uint8_t *image_descriptor; +} ca_get_img_t; + +typedef struct { + uint8_t image_handle[7]; +} ca_get_lk_thn_t; + +#endif /* BTC_AV_CA_INCLUDED */ + /* btc_avrc_args_t */ typedef union { pt_cmd_t pt_cmd; @@ -85,6 +114,12 @@ typedef union { ps_cmd_t ps_cmd; get_caps_cmd_t get_caps_cmd; set_abs_vol_cmd_t set_abs_vol_cmd; +#if BTC_AV_CA_INCLUDED + ca_conn_t ca_conn; + ca_get_img_prop_t ca_get_img_prop; + ca_get_img_t ca_get_img; + ca_get_lk_thn_t ca_get_lk_thn; +#endif } btc_avrc_args_t; /* btc_avrc_tg_act_t */ @@ -124,6 +159,9 @@ typedef struct { typedef struct { BOOLEAN rc_connected; +#if BTC_AV_CA_INCLUDED + BOOLEAN rc_cover_art_connected; +#endif UINT8 rc_handle; tBTA_AV_FEAT rc_features; UINT16 rc_ct_features; @@ -162,6 +200,8 @@ BOOLEAN btc_rc_get_connected_peer(BD_ADDR peer_addr); ********************************************************************************/ void btc_avrc_ct_call_handler(btc_msg_t *msg); void btc_avrc_tg_call_handler(btc_msg_t *msg); +void btc_avrc_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); +void btc_avrc_arg_deep_free(btc_msg_t *msg); void btc_avrc_tg_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); void btc_avrc_tg_arg_deep_free(btc_msg_t *msg); @@ -179,6 +219,7 @@ uint16_t btc_avrc_tg_get_rn_supported_evt(void); bool btc_avrc_tg_check_rn_supported_evt(uint16_t evt_set); bool btc_avrc_tg_rn_evt_supported(uint8_t event_id); bool btc_avrc_ct_rn_evt_supported(uint8_t event_id); +bool btc_avrc_ct_check_cover_art_support(void); #endif ///BTC_AV_INCLUDED == TRUE 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 c92d7de2b053..482c1c9a457e 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 @@ -39,6 +39,19 @@ #define UC_BT_A2DP_ENABLED FALSE #endif +//AVRCP +#ifdef CONFIG_BT_AVRCP_ENABLED +#define UC_BT_AVRCP_ENABLED TRUE +#ifdef CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED +#define UC_BT_AVRCP_CT_COVER_ART_ENABLED CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED +#else +#define UC_BT_AVRCP_CT_COVER_ART_ENABLED FALSE +#endif +#else +#define UC_BT_AVRCP_ENABLED FALSE +#define UC_BT_AVRCP_CT_COVER_ART_ENABLED FALSE +#endif + //SPP #ifdef CONFIG_BT_SPP_ENABLED #define UC_BT_SPP_ENABLED CONFIG_BT_SPP_ENABLED @@ -111,6 +124,13 @@ #define UC_BT_ENC_KEY_SIZE_CTRL_MODE 0 #endif +//GOEPC (BT) +#ifdef CONFIG_BT_GOEPC_ENABLED +#define UC_BT_GOEPC_ENABLED CONFIG_BT_GOEPC_ENABLED +#else +#define UC_BT_GOEPC_ENABLED FALSE +#endif + //BLE #ifdef CONFIG_BT_BLE_ENABLED #define UC_BT_BLE_ENABLED CONFIG_BT_BLE_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 4227ef38e93a..27c86a5977cb 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -91,6 +91,11 @@ #define SBC_DEC_INCLUDED TRUE #define BTC_AV_SRC_INCLUDED TRUE #define SBC_ENC_INCLUDED TRUE +#if UC_BT_AVRCP_CT_COVER_ART_ENABLED +#define BTA_AV_CA_INCLUDED TRUE +#define BTC_AV_CA_INCLUDED TRUE +#define AVRC_CA_INCLUDED TRUE +#endif /* UC_BT_AVRCP_CT_COVER_ART_ENABLED */ #endif /* UC_BT_A2DP_ENABLED */ #if (UC_BT_SPP_ENABLED == TRUE) @@ -171,6 +176,13 @@ #define BTC_HD_INCLUDED TRUE #endif /* UC_BT_HID_DEVICE_ENABLED */ +#if UC_BT_GOEPC_ENABLED +#ifndef OBEX_INCLUDED +#define OBEX_INCLUDED TRUE +#endif +#define GOEPC_INCLUDED TRUE +#endif /* UC_BT_GOEPC_ENABLED */ + #endif /* UC_BT_CLASSIC_ENABLED */ /* This is set to enable use of GAP L2CAP connections. */ @@ -376,6 +388,10 @@ #define BTC_AV_INCLUDED FALSE #endif +#ifndef BTC_AV_CA_INCLUDED +#define BTC_AV_CA_INCLUDED FALSE +#endif + #ifndef BTC_AV_SINK_INCLUDED #define BTC_AV_SINK_INCLUDED FALSE #endif @@ -449,6 +465,10 @@ #define BTA_AV_INCLUDED FALSE #endif +#ifndef BTA_AV_CA_INCLUDED +#define BTA_AV_CA_INCLUDED FALSE +#endif + #ifndef BTA_AV_SINK_INCLUDED #define BTA_AV_SINK_INCLUDED FALSE #endif @@ -1809,6 +1829,26 @@ #define OBX_FCR_TX_POOL_ID 3 #endif +/* Maximum OBEX connection allowed */ +#ifndef OBEX_MAX_CONNECTION +#define OBEX_MAX_CONNECTION 3 +#endif + +/* Maximum OBEX server allowed */ +#ifndef OBEX_MAX_SERVER +#define OBEX_MAX_SERVER 2 +#endif + +/****************************************************************************** +** +** GOEP +** +******************************************************************************/ + +/* Maximum GOEP client connection allowed */ +#ifndef GOEPC_MAX_CONNECTION +#define GOEPC_MAX_CONNECTION 3 +#endif /****************************************************************************** ** @@ -2141,6 +2181,20 @@ #define HID_HOST_REPAGE_WIN (2) #endif +/************************************************************************* +** Definitions for OBEX +*/ +#ifndef OBEX_INCLUDED +#define OBEX_INCLUDED FALSE +#endif + +/************************************************************************* +** Definitions for OBEX +*/ +#ifndef GOEPC_INCLUDED +#define GOEPC_INCLUDED FALSE +#endif + /************************************************************************* * A2DP Definitions */ @@ -2179,6 +2233,10 @@ #define AVRC_INCLUDED FALSE #endif +#ifndef AVRC_CA_INCLUDED +#define AVRC_CA_INCLUDED FALSE +#endif + #ifndef AVRC_METADATA_INCLUDED #if AVRC_INCLUDED == TRUE #define AVRC_METADATA_INCLUDED TRUE 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 a4f1ca749a74..74b7f10574d7 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_trace.h +++ b/components/bt/host/bluedroid/common/include/common/bt_trace.h @@ -316,6 +316,27 @@ static inline void trc_dump_buffer(const char *prefix, uint8_t *data, uint16_t l #define AVRC_TRACE_EVENT(fmt, args...) {if (avrc_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("BT_AVRC", fmt, ## args);} #define AVRC_TRACE_DEBUG(fmt, args...) {if (avrc_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("BT_AVRC", fmt, ## args);} +/* Define tracing for OBEX */ +#define OBEX_TRACE_ERROR(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("BT_OBEX", fmt, ## args);} +#define OBEX_TRACE_WARNING(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("BT_OBEX", fmt, ## args);} +#define OBEX_TRACE_API(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_API && BT_LOG_LEVEL_CHECK(AVRC,API)) BT_PRINT_I("BT_OBEX", fmt, ## args);} +#define OBEX_TRACE_EVENT(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("BT_OBEX", fmt, ## args);} +#define OBEX_TRACE_DEBUG(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("BT_OBEX", fmt, ## args);} + +/* Define tracing for OBEX_TL_L2CAP */ +#define OBEX_TL_L2CAP_TRACE_ERROR(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("OBEX_TL_L2CAP", fmt, ## args);} +#define OBEX_TL_L2CAP_TRACE_WARNING(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("OBEX_TL_L2CAP", fmt, ## args);} +#define OBEX_TL_L2CAP_TRACE_API(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_API && BT_LOG_LEVEL_CHECK(AVRC,API)) BT_PRINT_I("OBEX_TL_L2CAP", fmt, ## args);} +#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 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);} +#define GOEPC_TRACE_API(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_API && BT_LOG_LEVEL_CHECK(AVRC,API)) BT_PRINT_I("BT_GOEPC", fmt, ## args);} +#define GOEPC_TRACE_EVENT(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("BT_GOEPC", fmt, ## args);} +#define GOEPC_TRACE_DEBUG(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("BT_GOEPC", fmt, ## args);} + /* MCAP */ #define MCA_TRACE_ERROR(fmt, args...) {if (mca_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(MCA, ERROR)) BT_PRINT_E("BT_MCA", fmt, ## args);} @@ -484,6 +505,26 @@ extern UINT8 btif_trace_level; #define AVRC_TRACE_DEBUG(fmt, args...) #define AVRC_TRACE_API(fmt, args...) +/* Define tracing for OBEX */ +#define OBEX_TRACE_ERROR(fmt, args...) +#define OBEX_TRACE_WARNING(fmt, args...) +#define OBEX_TRACE_API(fmt, args...) +#define OBEX_TRACE_EVENT(fmt, args...) +#define OBEX_TRACE_DEBUG(fmt, args...) + +#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 GOEPC */ +#define GOEPC_TRACE_ERROR(fmt, args...) +#define GOEPC_TRACE_WARNING(fmt, args...) +#define GOEPC_TRACE_API(fmt, args...) +#define GOEPC_TRACE_EVENT(fmt, args...) +#define GOEPC_TRACE_DEBUG(fmt, args...) + /* MCAP */ #define MCA_TRACE_ERROR(fmt, args...) diff --git a/components/bt/host/bluedroid/main/bte_init.c b/components/bt/host/bluedroid/main/bte_init.c index 80c4357b6a57..77508b37773e 100644 --- a/components/bt/host/bluedroid/main/bte_init.c +++ b/components/bt/host/bluedroid/main/bte_init.c @@ -27,8 +27,8 @@ #include -/* Stack Configuation Related Init Definaton - * TODO: Now Just Unmask these defination until stack layer is OK +/* Stack Configuration Related Init Definaton + * TODO: Now Just Unmask these definition until stack layer is OK */ #ifndef BTA_INCLUDED @@ -91,6 +91,15 @@ #endif #endif +#if (defined(OBEX_INCLUDED) && OBEX_INCLUDED == TRUE) +#include "stack/obex_api.h" +#endif + +#if (defined(GOEPC_INCLUDED) && GOEPC_INCLUDED == TRUE) +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#endif + //BTA Modules #if BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE #include "bta/bta_api.h" @@ -267,6 +276,14 @@ void BTE_DeinitStack(void) } #endif // BTA_INCLUDED == TRUE +#if (defined(GOEPC_INCLUDED) && GOEPC_INCLUDED == TRUE) + GOEPC_Deinit(); +#endif + +#if (defined(OBEX_INCLUDED) && OBEX_INCLUDED == TRUE) + OBEX_Deinit(); +#endif + #if (defined(HID_DEV_INCLUDED) && HID_DEV_INCLUDED == TRUE) HID_DevDeinit(); #endif @@ -388,6 +405,18 @@ bt_status_t BTE_InitStack(void) MCA_Init(); #endif +#if (defined(OBEX_INCLUDED) && OBEX_INCLUDED == TRUE) + if (OBEX_Init() != OBEX_SUCCESS) { + goto error_exit; + } +#endif + +#if (defined(GOEPC_INCLUDED) && GOEPC_INCLUDED == TRUE) + if (GOEPC_Init() != GOEP_SUCCESS) { + goto error_exit; + } +#endif + //BTA Modules #if (BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE) if ((bta_sys_cb_ptr = (tBTA_SYS_CB *)osi_malloc(sizeof(tBTA_SYS_CB))) == NULL) { diff --git a/components/bt/host/bluedroid/stack/avct/avct_lcb.c b/components/bt/host/bluedroid/stack/avct/avct_lcb.c index 61f6c9a556e6..84a0f0212585 100644 --- a/components/bt/host/bluedroid/stack/avct/avct_lcb.c +++ b/components/bt/host/bluedroid/stack/avct/avct_lcb.c @@ -430,10 +430,10 @@ BOOLEAN avct_lcb_last_ccb(tAVCT_LCB *p_lcb, tAVCT_CCB *p_ccb_last) tAVCT_CCB *p_ccb = &avct_cb.ccb[0]; int i; - AVCT_TRACE_WARNING("avct_lcb_last_ccb"); + AVCT_TRACE_DEBUG("avct_lcb_last_ccb"); for (i = 0; i < AVCT_NUM_CONN; i++, p_ccb++) { - AVCT_TRACE_WARNING("%x: aloc:%d, lcb:%p/%p, ccb:%p/%p", - i, p_ccb->allocated, p_ccb->p_lcb, p_lcb, p_ccb, p_ccb_last); + AVCT_TRACE_DEBUG("%x: aloc:%d, lcb:%p/%p, ccb:%p/%p", + i, p_ccb->allocated, p_ccb->p_lcb, p_lcb, p_ccb, p_ccb_last); if (p_ccb->allocated && (p_ccb->p_lcb == p_lcb) && (p_ccb != p_ccb_last)) { return FALSE; } diff --git a/components/bt/host/bluedroid/stack/avrc/avrc_sdp.c b/components/bt/host/bluedroid/stack/avrc/avrc_sdp.c index fa98082e86b5..0b624b3f0c7a 100644 --- a/components/bt/host/bluedroid/stack/avrc/avrc_sdp.c +++ b/components/bt/host/bluedroid/stack/avrc/avrc_sdp.c @@ -29,8 +29,8 @@ #if (defined(AVRC_INCLUDED) && AVRC_INCLUDED == TRUE) -#ifndef SDP_AVRCP_1_5 -#define SDP_AVRCP_1_5 TRUE +#ifndef SDP_AVRCP_1_6 +#define SDP_AVRCP_1_6 TRUE #endif #ifndef SDP_AVCTP_1_4 @@ -52,7 +52,7 @@ const tSDP_PROTOCOL_ELEM avrc_proto_list [] = { #if SDP_AVCTP_1_4 == TRUE {UUID_PROTOCOL_AVCTP, 1, {AVCT_REV_1_4, 0} } #else -#if (SDP_AVRCP_1_4 == TRUE || SDP_AVRCP_1_5 == TRUE) +#if SDP_AVRCP_1_6 == TRUE {UUID_PROTOCOL_AVCTP, 1, {AVCT_REV_1_3, 0} } #else #if AVRC_METADATA_INCLUDED == TRUE @@ -64,7 +64,7 @@ const tSDP_PROTOCOL_ELEM avrc_proto_list [] = { #endif }; -#if SDP_AVRCP_1_5 == TRUE +#if SDP_AVRCP_1_6 == TRUE const tSDP_PROTO_LIST_ELEM avrc_add_proto_list [] = { { AVRC_NUM_PROTO_ELEMS, @@ -251,7 +251,7 @@ UINT16 AVRC_AddRecord(UINT16 service_uuid, char *p_service_name, char *p_provide /* add service class id list */ class_list[0] = service_uuid; -#if (SDP_AVCTP_1_4 == TRUE || SDP_AVRCP_1_5 == TRUE) +#if (SDP_AVCTP_1_4 == TRUE || SDP_AVRCP_1_6 == TRUE) if ( service_uuid == UUID_SERVCLASS_AV_REMOTE_CONTROL ) { class_list[1] = UUID_SERVCLASS_AV_REM_CTRL_CONTROL; count = 2; @@ -263,7 +263,7 @@ UINT16 AVRC_AddRecord(UINT16 service_uuid, char *p_service_name, char *p_provide result &= SDP_AddProtocolList(sdp_handle, AVRC_NUM_PROTO_ELEMS, (tSDP_PROTOCOL_ELEM *)avrc_proto_list); /* add profile descriptor list */ -#if SDP_AVRCP_1_5 == TRUE +#if SDP_AVRCP_1_6 == TRUE if (browsing_en) { add_additional_protocol_list = TRUE; } else if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET && @@ -277,7 +277,7 @@ UINT16 AVRC_AddRecord(UINT16 service_uuid, char *p_service_name, char *p_provide result &= SDP_AddAdditionProtoLists( sdp_handle, 1, (tSDP_PROTO_LIST_ELEM *)avrc_add_proto_list); } - result &= SDP_AddProfileDescriptorList(sdp_handle, UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_5); + result &= SDP_AddProfileDescriptorList(sdp_handle, UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_6); #else #if AVRC_METADATA_INCLUDED == TRUE result &= SDP_AddProfileDescriptorList(sdp_handle, UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_3); @@ -292,6 +292,13 @@ UINT16 AVRC_AddRecord(UINT16 service_uuid, char *p_service_name, char *p_provide } else if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET && media_player_virtual_filesystem_supported) { supported_feature |= AVRC_SUPF_TG_BROWSE; } +#if AVRC_CA_INCLUDED + if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_CONTROL || service_uuid == UUID_SERVCLASS_AV_REMOTE_CONTROL) { + supported_feature |= AVRC_SUPF_CT_COVER_ART_GIP; + supported_feature |= AVRC_SUPF_CT_COVER_ART_GI; + supported_feature |= AVRC_SUPF_CT_COVER_ART_GLT; + } +#endif /* add supported feature */ p = temp; UINT16_TO_BE_STREAM(p, supported_feature); @@ -383,7 +390,7 @@ bt_status_t AVRC_Init(void) ** ** Function AVRC_Deinit ** -** Description This function is called at stack shotdown to free the +** Description This function is called at stack shutdown to free the ** control block (if using dynamic memory), and deinitializes the ** control block and tracing level. ** diff --git a/components/bt/host/bluedroid/stack/goep/goepc_api.c b/components/bt/host/bluedroid/stack/goep/goepc_api.c new file mode 100644 index 000000000000..60badbaeec0c --- /dev/null +++ b/components/bt/host/bluedroid/stack/goep/goepc_api.c @@ -0,0 +1,376 @@ +/* + * 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/obex_api.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "goep_int.h" + +#if (GOEPC_INCLUDED == TRUE) + +/******************************************************************************* +** +** Function GOEPC_Init +** +** Description Initialize GOEP Client role, must call before using any +** other GOEPC APIs +** +** Returns GOEP_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 GOEPC_Init(void) +{ +#if (GOEP_DYNAMIC_MEMORY) + if (!goepc_cb_ptr) { + goepc_cb_ptr = (tGOEPC_CB *)osi_malloc(sizeof(tGOEPC_CB)); + if (!goepc_cb_ptr) { + return GOEP_NO_RESOURCES; + } + } +#endif /* #if (GOEP_DYNAMIC_MEMORY) */ + memset(&goepc_cb, 0, sizeof(tGOEPC_CB)); + goepc_cb.trace_level = BT_TRACE_LEVEL_ERROR; + return GOEP_SUCCESS; +} + +/******************************************************************************* +** +** Function GOEPC_Deinit +** +** Description Deinit GOEP Client role, once deinit, can not use any other +** GOEPC APIs until call GOEPC_Init again +** +*******************************************************************************/ +void GOEPC_Deinit(void) +{ +#if (GOEP_DYNAMIC_MEMORY) + if (goepc_cb_ptr) { + osi_free(goepc_cb_ptr); + goepc_cb_ptr = NULL; + } +#endif /* #if (GOEP_DYNAMIC_MEMORY) */ +} + +/******************************************************************************* +** +** Function GOEPC_Open +** +** Description Start the progress to establish a GOEP connection to server +** +** Returns GOEP_SUCCESS if successful, otherwise failed, when the +** connection is established, GOEPC_OPENED_EVT will come +** +*******************************************************************************/ +UINT16 GOEPC_Open(tOBEX_SVR_INFO *svr, tGOEPC_EVT_CBACK callback, UINT16 *out_handle) +{ + UINT16 ret = GOEP_SUCCESS; + tGOEPC_CCB *p_ccb = NULL; + + do { + /* check parameter, allow out_handle to be NULL */ + if (svr == NULL || callback == NULL) { + ret = GOEP_INVALID_PARAM; + break; + } + + p_ccb = goepc_allocate_ccb(); + if (p_ccb == NULL) { + ret = GOEP_NO_RESOURCES; + break; + } + + if (OBEX_CreateConn(svr, goepc_obex_callback, &p_ccb->obex_handle) != OBEX_SUCCESS) { + ret = GOEP_TL_ERROR; + break; + } + + /* success */ + p_ccb->callback = callback; + p_ccb->state = GOEPC_STATE_OPENING; + if (out_handle) { + *out_handle = p_ccb->allocated; + } + } while (0); + + if (ret != GOEP_SUCCESS && p_ccb != NULL) { + goepc_free_ccb(p_ccb); + } + return ret; +} + +/******************************************************************************* +** +** Function GOEPC_Close +** +** Description Close a GOEP connection immediately +** +** Returns GOEP_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 GOEPC_Close(UINT16 handle) +{ + tGOEPC_CCB *p_ccb = NULL; + + UINT16 ccb_idx = handle - 1; + if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) { + return GOEP_BAD_HANDLE; + } + + p_ccb = &goepc_cb.ccb[ccb_idx]; + if (p_ccb->obex_handle) { + OBEX_RemoveConn(p_ccb->obex_handle); + } + goepc_free_ccb(p_ccb); + + return GOEP_SUCCESS; +} + +/******************************************************************************* +** +** Function GOEPC_SendRequest +** +** Description Send the prepared request packet to server +** +** Returns GOEP_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 GOEPC_SendRequest(UINT16 handle) +{ + UINT16 ret = GOEP_SUCCESS; + tGOEPC_CCB *p_ccb = NULL; + BOOLEAN final = FALSE; + + do { + UINT16 ccb_idx = handle - 1; + if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) { + ret = GOEP_BAD_HANDLE; + break; + } + p_ccb = &goepc_cb.ccb[ccb_idx]; + + if (p_ccb->pkt == NULL) { + ret = GOEP_INVALID_STATE; + break; + } + + final = OBEX_CheckFinalBit(p_ccb->pkt); + /* check whether state machine allow this operation */ + if (!goepc_check_obex_req_allow(p_ccb->state, final)) { + ret = GOEP_INVALID_STATE; + break; + } + + if (p_ccb->congest) { + ret = GOEP_CONGEST; + break; + } + + /* execute srm state machine */ + goepc_srm_sm_execute(p_ccb, TRUE, p_ccb->pkt_srm_en, p_ccb->pkt_srm_wait); + + tGOEPC_DATA data; + data.pkt = p_ccb->pkt; + + p_ccb->last_pkt_opcode = p_ccb->curr_pkt_opcode; + p_ccb->pkt = NULL; + p_ccb->pkt_srm_en = FALSE; + p_ccb->pkt_srm_wait = FALSE; + + /* execute main state machine */ + if (final) { + goepc_sm_execute(p_ccb, GOEPC_SM_EVENT_REQ_FB, &data); + } + else { + goepc_sm_execute(p_ccb, GOEPC_SM_EVENT_REQ, &data); + } + /* since goepc_sm_execute may free ccb, can not access ccb here */ + } while (0); + + return ret; +} + +/******************************************************************************* +** +** Function GOEPC_PrepareRequest +** +** Description Prepare a request packet, packet will be store internally +** +** Returns GOEP_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 GOEPC_PrepareRequest(UINT16 handle, tOBEX_PARSE_INFO *info, UINT16 buff_size) +{ + UINT16 ret = GOEP_SUCCESS; + tGOEPC_CCB *p_ccb = NULL; + BT_HDR *pkt = NULL; + + do { + UINT16 ccb_idx = handle - 1; + if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) { + ret = GOEP_BAD_HANDLE; + break; + } + p_ccb = &goepc_cb.ccb[ccb_idx]; + + if (info == NULL || buff_size < OBEX_MIN_PACKET_SIZE) { + ret = GOEP_INVALID_PARAM; + break; + } + + if (p_ccb->pkt != NULL) { + ret = GOEP_INVALID_STATE; + break; + } + + if (!goepc_check_obex_req_param(info)) { + ret = GOEP_INVALID_PARAM; + break; + } + + if (OBEX_BuildRequest(info, buff_size, &pkt) != OBEX_SUCCESS) { + ret = GOEP_NO_RESOURCES; + break; + } + + p_ccb->curr_pkt_opcode = info->opcode; + p_ccb->pkt = pkt; + } while (0); + + return ret; +} + +/******************************************************************************* +** +** Function GOEPC_DropRequest +** +** Description Drop the prepared internal request packet +** +** Returns GOEP_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 GOEPC_DropRequest(UINT16 handle) +{ + UINT16 ccb_idx = handle - 1; + if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) { + return GOEP_BAD_HANDLE; + } + + tGOEPC_CCB *p_ccb = &goepc_cb.ccb[ccb_idx]; + if (p_ccb->pkt == NULL) { + return GOEP_INVALID_STATE; + } + + osi_free(p_ccb->pkt); + p_ccb->pkt = NULL; + p_ccb->pkt_srm_en = FALSE; + p_ccb->pkt_srm_wait = FALSE; + return GOEP_SUCCESS; +} + +/******************************************************************************* +** +** Function GOEPC_RequestSetSRM +** +** Description Modify the prepared internal request packet, append SRM header +** or SRMP header +** +** Returns GOEP_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 GOEPC_RequestSetSRM(UINT16 handle, BOOLEAN srm_en, BOOLEAN srm_wait) +{ + UINT16 ret = GOEP_SUCCESS; + tGOEPC_CCB *p_ccb = NULL; + + do { + UINT16 ccb_idx = handle - 1; + if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) { + ret = GOEP_BAD_HANDLE; + break; + } + p_ccb = &goepc_cb.ccb[ccb_idx]; + + if (!srm_en && !srm_wait) { + ret = GOEP_INVALID_PARAM; + break; + } + + if (p_ccb->pkt == NULL) { + ret = GOEP_INVALID_STATE; + break; + } + + if (srm_en) { + if (OBEX_AppendHeaderSRM(p_ccb->pkt, OBEX_SRM_ENABLE) == OBEX_SUCCESS) { + p_ccb->pkt_srm_en = TRUE; + } + else { + ret = GOEP_NO_RESOURCES; + break; + } + } + if (srm_wait) { + if (OBEX_AppendHeaderSRMP(p_ccb->pkt, OBEX_SRMP_WAIT) == OBEX_SUCCESS) { + p_ccb->pkt_srm_wait = TRUE; + } + else { + ret = GOEP_NO_RESOURCES; + break; + } + } + } while (0); + + return ret; +} + +/******************************************************************************* +** +** Function GOEPC_RequestAddHeader +** +** Description Modify the prepared internal request packet, append header +** +** Returns GOEP_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 GOEPC_RequestAddHeader(UINT16 handle, UINT8 header_id, const UINT8 *data, UINT16 data_len) +{ + UINT16 ret = GOEP_SUCCESS; + tGOEPC_CCB *p_ccb = NULL; + + do { + UINT16 ccb_idx = handle - 1; + if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) { + ret = GOEP_BAD_HANDLE; + break; + } + p_ccb = &goepc_cb.ccb[ccb_idx]; + + if (p_ccb->pkt == NULL) { + ret = GOEP_INVALID_STATE; + break; + } + + if (data == NULL || data_len == 0) { + ret = GOEP_INVALID_PARAM; + break; + } + + if (OBEX_AppendHeaderRaw(p_ccb->pkt, header_id, data, data_len) != OBEX_SUCCESS) { + ret = GOEP_NO_RESOURCES; + break; + } + } while (0); + + return ret; +} + +#endif /* #if (GOEPC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/goep/goepc_main.c b/components/bt/host/bluedroid/stack/goep/goepc_main.c new file mode 100644 index 000000000000..e0859580ec60 --- /dev/null +++ b/components/bt/host/bluedroid/stack/goep/goepc_main.c @@ -0,0 +1,528 @@ +/* + * 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/obex_api.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "goep_int.h" + +#if (GOEPC_INCLUDED == TRUE) + +#if GOEP_DYNAMIC_MEMORY == FALSE +tGOEPC_CB goepc_cb; +#else +tGOEPC_CB *goepc_cb_ptr = NULL; +#endif + +tGOEPC_CCB *goepc_allocate_ccb(void) +{ + tGOEPC_CCB *p_ccb = NULL; + for (int i = 0; i < GOEPC_MAX_CONNECTION; ++i) { + if (!goepc_cb.ccb[i].allocated) { + goepc_cb.ccb[i].allocated = i + 1; + p_ccb = &goepc_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +void goepc_free_ccb(tGOEPC_CCB *p_ccb) +{ + if (p_ccb->pkt != NULL) { + osi_free(p_ccb->pkt); + } + memset(p_ccb, 0, sizeof(tGOEPC_CCB)); +} + +BOOLEAN goepc_check_obex_req_param(tOBEX_PARSE_INFO *info) +{ + BOOLEAN ret = TRUE; + switch (info->opcode) + { + case OBEX_OPCODE_CONNECT: + if (info->max_packet_length < 255 || info->obex_version_number == 0) { + ret = FALSE; + } + break; + case OBEX_OPCODE_DISCONNECT: + case OBEX_OPCODE_PUT: + case OBEX_OPCODE_PUT_FINAL: + case OBEX_OPCODE_GET: + case OBEX_OPCODE_GET_FINAL: + case OBEX_OPCODE_SETPATH: + case OBEX_OPCODE_ACTION: + case OBEX_OPCODE_SESSION: + /* opcode allowed */ + break; + case OBEX_OPCODE_ABORT: + default: + ret = FALSE; + /* opcode not allowed */ + break; + } + + return ret; +} + +static tGOEPC_CCB *find_ccb_by_obex_handle(UINT16 obex_handle) +{ + tGOEPC_CCB *p_ccb = NULL; + for (int i = 0; i < GOEPC_MAX_CONNECTION; ++i) { + if (goepc_cb.ccb[i].allocated && goepc_cb.ccb[i].obex_handle == obex_handle) { + p_ccb = &goepc_cb.ccb[i]; + } + } + return p_ccb; +} + +static void goepc_extra_srm_rsp(UINT8 opcode, BT_HDR *pkt, BOOLEAN *srm_en, BOOLEAN *srm_wait) +{ + tOBEX_PARSE_INFO info; + BOOLEAN srm_found = FALSE; + BOOLEAN srmp_found = FALSE; + if (OBEX_ParseResponse(pkt, opcode, &info) == OBEX_SUCCESS) { + UINT8 *header = NULL; + while((header = OBEX_GetNextHeader(pkt, &info)) != NULL) { + switch (*header) + { + case OBEX_HEADER_ID_SRM: + if (header[1] == OBEX_SRM_ENABLE) { + *srm_en = TRUE; + } + srm_found = TRUE; + break; + case OBEX_HEADER_ID_SRM_PARAM: + switch (header[1]) + { + case OBEX_SRMP_ADD_PKT: + /* goep should not use this */ + break; + case OBEX_SRMP_WAIT: + *srm_wait = TRUE; + break; + case OBEX_SRMP_ADD_PKT_WAIT: + /* goep should not use this */ + break; + default: + break; + } + srmp_found = TRUE; + break; + default: + break; + } + if (srm_found && srmp_found) { + break; + } + } + } +} + +static void goepc_act_congest(tGOEPC_CCB *p_ccb) +{ + p_ccb->congest = TRUE; + p_ccb->callback(p_ccb->allocated, GOEPC_CONGEST_EVT, NULL); +} + +static void goepc_act_uncongest(tGOEPC_CCB *p_ccb) +{ + p_ccb->congest = FALSE; + p_ccb->callback(p_ccb->allocated, GOEPC_UNCONGEST_EVT, NULL); +} + +static void goepc_act_mtu_chg(tGOEPC_CCB *p_ccb, tGOEPC_MTU_CHG *mtu_chg) +{ + tGOEPC_MSG msg; + msg.mtu_changed.peer_mtu = mtu_chg->peer_mtu; + msg.mtu_changed.our_mtu = mtu_chg->our_mtu; + p_ccb->peer_mtu = mtu_chg->peer_mtu; + p_ccb->our_mtu = mtu_chg->our_mtu; + p_ccb->callback(p_ccb->allocated, GOEPC_MTU_CHANGED_EVT, &msg); +} + +void goepc_obex_callback(UINT16 handle, UINT8 event, tOBEX_MSG *msg) +{ + tGOEPC_DATA data; + UINT8 goepc_sm_event = GOEPC_SM_EVENT_DISCONNECT; + BOOLEAN exec_sm = FALSE; + tGOEPC_CCB *p_ccb = find_ccb_by_obex_handle(handle); + if (p_ccb == NULL) { + GOEPC_TRACE_ERROR("goepc_obex_callback can not find a ccb\n"); + /* can not find a ccb in goepc, free resource and remove this connection */ + if (event == OBEX_DATA_EVT && msg->data.pkt) { + osi_free(msg->data.pkt); + } + OBEX_RemoveConn(handle); + return; + } + + switch (event) + { + case OBEX_CONNECT_EVT: + data.connected.peer_mtu = msg->connect.peer_mtu; + data.connected.our_mtu = msg->connect.our_mtu; + goepc_sm_event = GOEPC_SM_EVENT_CONNECT; + exec_sm = TRUE; + break; + case OBEX_MTU_CHANGE_EVT: + data.mtu_chg.peer_mtu = msg->mtu_change.peer_mtu; + data.mtu_chg.our_mtu = msg->mtu_change.our_mtu; + goepc_act_mtu_chg(p_ccb, &data.mtu_chg); + break; + case OBEX_DISCONNECT_EVT: + /* when we received this event, obex connection already disconnect */ + p_ccb->obex_handle = 0; + goepc_sm_event = GOEPC_SM_EVENT_DISCONNECT;; + exec_sm = TRUE; + break; + case OBEX_CONGEST_EVT: + goepc_act_congest(p_ccb); + break; + case OBEX_UNCONGEST_EVT: + goepc_act_uncongest(p_ccb); + break; + case OBEX_DATA_EVT: + data.pkt = msg->data.pkt; + if (OBEX_CheckContinueResponse(data.pkt)) { + /* in OBEX 1.0, final bit of response code will always set, we need to check this */ + goepc_sm_event = GOEPC_SM_EVENT_RSP; + } + else if (OBEX_CheckFinalBit(data.pkt)) { + goepc_sm_event = GOEPC_SM_EVENT_RSP_FB; + } + else { + goepc_sm_event = GOEPC_SM_EVENT_RSP; + } + exec_sm = TRUE; + break; + default: + /* other event, ignore */ + break; + } + + if (exec_sm) { + goepc_sm_execute(p_ccb, goepc_sm_event, &data); + } +} + +static void goepc_sm_act_connect(tGOEPC_CCB *p_ccb, tGOEPC_CONNECTED *connected) +{ + tGOEPC_MSG msg; + msg.opened.peer_mtu = connected->peer_mtu; + msg.opened.our_mtu = connected->our_mtu; + p_ccb->peer_mtu = connected->peer_mtu; + p_ccb->our_mtu = connected->our_mtu; + + /* main state machine transfer to OPENED_IDLE */ + p_ccb->state = GOEPC_STATE_OPENED_IDLE; + p_ccb->callback(p_ccb->allocated, GOEPC_OPENED_EVT, &msg); +} + +static void goepc_sm_act_disconnect(tGOEPC_CCB *p_ccb) +{ + tGOEPC_MSG msg; + if (p_ccb->obex_handle) { + OBEX_RemoveConn(p_ccb->obex_handle); + } + msg.closed.reason = GOEP_TL_ERROR; + p_ccb->callback(p_ccb->allocated, GOEPC_CLOSED_EVT, &msg); + /* free ccb, main state machine end */ + goepc_free_ccb(p_ccb); +} + +static void goepc_sm_act_send_req(tGOEPC_CCB *p_ccb, BT_HDR *pkt) +{ + UINT16 ret = OBEX_SendPacket(p_ccb->obex_handle, pkt); + if (ret == OBEX_SUCCESS) { + /* main state machine transfer to OPENED_REQ */ + p_ccb->state = GOEPC_STATE_OPENED_REQ; + } + else { + /* send failed, something error in transport layer, disconnect */ + goepc_sm_act_disconnect(p_ccb); + } +} + +static void goepc_sm_act_send_req_fb(tGOEPC_CCB *p_ccb, BT_HDR *pkt) +{ + UINT16 ret = OBEX_SendPacket(p_ccb->obex_handle, pkt); + if (ret == OBEX_SUCCESS) { + /* main state machine transfer to OPENED_RSP */ + p_ccb->state = GOEPC_STATE_OPENED_RSP; + } + else { + /* send failed, something error in transport layer, disconnect */ + goepc_sm_act_disconnect(p_ccb); + } +} + +static void goepc_sm_act_rsp(tGOEPC_CCB *p_ccb, BT_HDR *pkt) +{ + /* handle srm state transfer */ + BOOLEAN srm_en = FALSE; + BOOLEAN srm_wait = FALSE; + goepc_extra_srm_rsp(p_ccb->last_pkt_opcode, pkt, &srm_en, &srm_wait); + goepc_srm_sm_execute(p_ccb, FALSE, srm_en, srm_wait); + /* main state machine not change */ + + tGOEPC_MSG msg; + msg.response.opcode = p_ccb->last_pkt_opcode; + msg.response.final = FALSE; + msg.response.srm_en = (p_ccb->srm_state == GOEPC_SRM_STATE_ENABLE_WAIT || p_ccb->srm_state == GOEPC_SRM_STATE_ENABLE); + msg.response.srm_wait = (p_ccb->srm_state == GOEPC_SRM_STATE_ENABLE_WAIT); + msg.response.pkt = pkt; + p_ccb->callback(p_ccb->allocated, GOEPC_RESPONSE_EVT, &msg); +} + +static void goepc_sm_act_rsp_fb(tGOEPC_CCB *p_ccb, BT_HDR *pkt) +{ + tGOEPC_MSG msg; + msg.response.opcode = p_ccb->last_pkt_opcode; + msg.response.final = TRUE; + msg.response.srm_en = FALSE; + msg.response.srm_wait = FALSE; + msg.response.pkt = pkt; + /* operation complete, reset srm state */ + p_ccb->srm_state = GOEPC_SRM_STATE_IDLE; + /* main state machine transfer to OPENED_IDLE */ + p_ccb->state = GOEPC_STATE_OPENED_IDLE; + p_ccb->callback(p_ccb->allocated, GOEPC_RESPONSE_EVT, &msg); +} + + +static void goepc_sm_state_opening(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data) +{ + switch (event) + { + case GOEPC_SM_EVENT_CONNECT: + goepc_sm_act_connect(p_ccb, &p_data->connected); + break; + case GOEPC_SM_EVENT_DISCONNECT: + goepc_sm_act_disconnect(p_ccb); + break; + case GOEPC_SM_EVENT_RSP: + case GOEPC_SM_EVENT_RSP_FB: + GOEPC_TRACE_ERROR("goepc_sm_state_opening received unexpected response from peer\n"); + if (p_data->pkt != NULL) { + osi_free(p_data->pkt); + } + goepc_sm_act_disconnect(p_ccb); + break; + default: + GOEPC_TRACE_ERROR("goepc_sm_state_opening unexpected event: 0x%x\n", event); + break; + } +} + +static void goepc_sm_state_opened_idle(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data) +{ + switch (event) + { + case GOEPC_SM_EVENT_DISCONNECT: + goepc_sm_act_disconnect(p_ccb); + break; + case GOEPC_SM_EVENT_REQ: + goepc_sm_act_send_req(p_ccb, p_data->pkt); + break; + case GOEPC_SM_EVENT_REQ_FB: + goepc_sm_act_send_req_fb(p_ccb, p_data->pkt); + break; + case GOEPC_SM_EVENT_RSP: + case GOEPC_SM_EVENT_RSP_FB: + GOEPC_TRACE_ERROR("goepc_sm_state_opened_idle received unexpected response from peer\n"); + /* peer sent a packet to us when we didn't request */ + if (p_data->pkt != NULL) { + osi_free(p_data->pkt); + } + goepc_sm_act_disconnect(p_ccb); + break; + default: + GOEPC_TRACE_ERROR("goepc_sm_state_opened_idle unexpected event: 0x%x\n", event); + break; + } +} + +static void goepc_sm_state_opened_req(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data) +{ + switch (event) + { + case GOEPC_SM_EVENT_DISCONNECT: + goepc_sm_act_disconnect(p_ccb); + break; + case GOEPC_SM_EVENT_REQ: + goepc_sm_act_send_req(p_ccb, p_data->pkt); + break; + case GOEPC_SM_EVENT_REQ_FB: + goepc_sm_act_send_req_fb(p_ccb, p_data->pkt); + break; + case GOEPC_SM_EVENT_RSP: + goepc_sm_act_rsp(p_ccb, p_data->pkt); + break; + case GOEPC_SM_EVENT_RSP_FB: + goepc_sm_act_rsp_fb(p_ccb, p_data->pkt); + break; + default: + GOEPC_TRACE_ERROR("goepc_sm_state_opened_req unexpected event: 0x%x\n", event); + break; + } +} + +static void goepc_sm_state_opened_rsp(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data) +{ + switch (event) + { + case GOEPC_SM_EVENT_DISCONNECT: + goepc_sm_act_disconnect(p_ccb); + break; + case GOEPC_SM_EVENT_REQ_FB: + goepc_sm_act_send_req_fb(p_ccb, p_data->pkt); + break; + case GOEPC_SM_EVENT_RSP: + goepc_sm_act_rsp(p_ccb, p_data->pkt); + break; + case GOEPC_SM_EVENT_RSP_FB: + goepc_sm_act_rsp_fb(p_ccb, p_data->pkt); + break; + default: + GOEPC_TRACE_ERROR("goepc_sm_state_opened_rsp unexpected event: 0x%x\n", event); + break; + } +} + +BOOLEAN goepc_check_obex_req_allow(UINT8 state, BOOLEAN final) +{ + BOOLEAN ret = FALSE; + if (final) { + switch (state) + { + case GOEPC_STATE_OPENED_IDLE: + case GOEPC_STATE_OPENED_REQ: + case GOEPC_STATE_OPENED_RSP: + ret = TRUE; + break; + default: + break; + } + } + else { + switch (state) + { + case GOEPC_STATE_OPENED_IDLE: + case GOEPC_STATE_OPENED_REQ: + ret = TRUE; + break; + default: + break; + } + } + return ret; +} + +void goepc_sm_execute(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data) +{ + switch (p_ccb->state) + { + case GOEPC_STATE_INIT: + /* do nothing */ + break; + case GOEPC_STATE_OPENING: + goepc_sm_state_opening(p_ccb, event, p_data); + break; + case GOEPC_STATE_OPENED_IDLE: + goepc_sm_state_opened_idle(p_ccb, event, p_data); + break; + case GOEPC_STATE_OPENED_REQ: + goepc_sm_state_opened_req(p_ccb, event, p_data); + break; + case GOEPC_STATE_OPENED_RSP: + goepc_sm_state_opened_rsp(p_ccb, event, p_data); + break; + default: + GOEPC_TRACE_ERROR("goepc_sm_execute unexpected state: 0x%x\n", p_ccb->state); + break; + } +} + +static void goepc_srm_sm_act_req(tGOEPC_CCB *p_ccb, BOOLEAN srm_en, BOOLEAN srm_wait) +{ + switch (p_ccb->srm_state) + { + case GOEPC_SRM_STATE_IDLE: + if (srm_en) { + p_ccb->srm_state = GOEPC_SRM_STATE_REQ; + p_ccb->srm_wait = srm_wait; + } + else { + p_ccb->srm_state = GOEPC_SRM_STATE_DISABLE; + } + break; + case GOEPC_SRM_STATE_ENABLE_WAIT: + if (!srm_wait) { + p_ccb->srm_wait = FALSE; + } + if (!p_ccb->srm_wait && !p_ccb->srm_peer_wait) { + /* no more wait, transfer to ENABLE */ + p_ccb->srm_state = GOEPC_SRM_STATE_ENABLE; + } + break; + default: + break; + } +} + +static void goepc_srm_sm_act_rsp(tGOEPC_CCB *p_ccb, BOOLEAN srm_en, BOOLEAN srm_wait) +{ + switch (p_ccb->srm_state) + { + case GOEPC_SRM_STATE_IDLE: + /* peer can not request to enable srm, ignore */ + break; + case GOEPC_SRM_STATE_REQ: + if (srm_en) { + p_ccb->srm_peer_wait = srm_wait; + if (p_ccb->srm_wait || p_ccb->srm_peer_wait) { + p_ccb->srm_state = GOEPC_SRM_STATE_ENABLE_WAIT; + } + else { + p_ccb->srm_state = GOEPC_SRM_STATE_ENABLE; + } + } + else { + p_ccb->srm_state = GOEPC_SRM_STATE_DISABLE; + } + break; + case GOEPC_SRM_STATE_ENABLE_WAIT: + if (!srm_wait) { + p_ccb->srm_peer_wait = FALSE; + } + if (!p_ccb->srm_wait && !p_ccb->srm_peer_wait) { + /* no more wait, transfer to ENABLE */ + p_ccb->srm_state = GOEPC_SRM_STATE_ENABLE; + } + break; + default: + break; + } +} + +void goepc_srm_sm_execute(tGOEPC_CCB *p_ccb, BOOLEAN is_req, BOOLEAN srm_en, BOOLEAN srm_wait) +{ + if (is_req) { + goepc_srm_sm_act_req(p_ccb, srm_en, srm_wait); + } + else { + goepc_srm_sm_act_rsp(p_ccb, srm_en, srm_wait); + } +} + +#endif /* #if (GOEPC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/goep/include/goep_int.h b/components/bt/host/bluedroid/stack/goep/include/goep_int.h new file mode 100644 index 000000000000..0c1915f6d6be --- /dev/null +++ b/components/bt/host/bluedroid/stack/goep/include/goep_int.h @@ -0,0 +1,103 @@ +/* + * 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 "stack/goep_common.h" +#include "stack/goepc_api.h" + +#if (GOEPC_INCLUDED == TRUE) + +/* GOEPC state machine events */ +enum { + GOEPC_SM_EVENT_CONNECT = 0, + GOEPC_SM_EVENT_DISCONNECT, + GOEPC_SM_EVENT_REQ, + GOEPC_SM_EVENT_REQ_FB, + GOEPC_SM_EVENT_RSP, + GOEPC_SM_EVENT_RSP_FB, +}; + +/* GOEPC state machine states */ +enum { + GOEPC_STATE_INIT = 0, + GOEPC_STATE_OPENING, + GOEPC_STATE_OPENED_IDLE, + GOEPC_STATE_OPENED_REQ, + GOEPC_STATE_OPENED_RSP, +}; + +/* GOEPC srm state machine states */ +enum { + GOEPC_SRM_STATE_IDLE = 0, + GOEPC_SRM_STATE_REQ, + GOEPC_SRM_STATE_ENABLE_WAIT, + GOEPC_SRM_STATE_ENABLE, + GOEPC_SRM_STATE_DISABLE, +}; + +/* GOEPC Connection Control block */ +typedef struct { + tGOEPC_EVT_CBACK *callback; /* GOEP event callback function */ + UINT16 obex_handle; /* OBEX connection handle */ + UINT16 peer_mtu; /* lower layer connection peer MTU */ + UINT16 our_mtu; /* lower layer connection our MTU */ + BOOLEAN congest; /* lower layer connection congestion status */ + + BT_HDR *pkt; /* packet prepared in this GOEP client */ + BOOLEAN pkt_srm_en; /* whether prepared packet had set SRM to enable */ + BOOLEAN pkt_srm_wait; /* whether prepared packet had set SRMP to wait */ + UINT8 curr_pkt_opcode; /* prepared packet opcode */ + + UINT8 last_pkt_opcode; /* last sent packet opcode */ + BOOLEAN srm_wait; /* whether we had set SRMP to wait */ + BOOLEAN srm_peer_wait; /* whether peer had set SRMP to wait */ + UINT8 srm_state; /* SRM state machine */ + UINT8 state; /* main state machine */ + UINT8 allocated; /* 0, not allocated. index+1, otherwise. equal to api handle */ +} tGOEPC_CCB; + +/* GOEPC Control block */ +typedef struct { + tGOEPC_CCB ccb[GOEPC_MAX_CONNECTION]; /* connection control blocks */ + UINT8 trace_level; /* trace level */ +} tGOEPC_CB; + +#if GOEP_DYNAMIC_MEMORY == FALSE +extern tGOEPC_CB goepc_cb; +#else +extern tGOEPC_CB *goepc_cb_ptr; +#define goepc_cb (*goepc_cb_ptr) +#endif + +typedef struct { + UINT16 peer_mtu; + UINT16 our_mtu; +} tGOEPC_CONNECTED; + +typedef struct { + UINT16 peer_mtu; + UINT16 our_mtu; +} tGOEPC_MTU_CHG; + +typedef union { + tGOEPC_CONNECTED connected; + tGOEPC_MTU_CHG mtu_chg; + BT_HDR *pkt; +} tGOEPC_DATA; + +tGOEPC_CCB *goepc_allocate_ccb(void); +void goepc_free_ccb(tGOEPC_CCB *p_ccb); +void goepc_obex_callback(UINT16 handle, UINT8 event, tOBEX_MSG *msg); +BOOLEAN goepc_check_obex_req_allow(UINT8 state, BOOLEAN final); +BOOLEAN goepc_check_obex_req_param(tOBEX_PARSE_INFO *info); +void goepc_sm_execute(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data); +void goepc_srm_sm_execute(tGOEPC_CCB *p_ccb, BOOLEAN is_req, BOOLEAN srm_en, BOOLEAN srm_wait); + +#endif /* #if (GOEPC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/include/stack/avrc_api.h b/components/bt/host/bluedroid/stack/include/stack/avrc_api.h index 21f4bee5766d..2985c65ca55f 100644 --- a/components/bt/host/bluedroid/stack/include/stack/avrc_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/avrc_api.h @@ -99,6 +99,9 @@ #define AVRC_SUPF_CT_CAT3 0x0004 /* Category 3 */ #define AVRC_SUPF_CT_CAT4 0x0008 /* Category 4 */ #define AVRC_SUPF_CT_BROWSE 0x0040 /* Browsing */ +#define AVRC_SUPF_CT_COVER_ART_GIP 0x0080 /* Cover Art GetImageProperties */ +#define AVRC_SUPF_CT_COVER_ART_GI 0x0100 /* Cover Art GetImage */ +#define AVRC_SUPF_CT_COVER_ART_GLT 0x0200 /* Cover Art GetLinkedThumbnail */ #define AVRC_SUPF_TG_CAT1 0x0001 /* Category 1 */ #define AVRC_SUPF_TG_CAT2 0x0002 /* Category 2 */ @@ -107,7 +110,8 @@ #define AVRC_SUPF_TG_APP_SETTINGS 0x0010 /* Player Application Settings */ #define AVRC_SUPF_TG_GROUP_NAVI 0x0020 /* Group Navigation */ #define AVRC_SUPF_TG_BROWSE 0x0040 /* Browsing */ -#define AVRC_SUPF_TG_MULTI_PLAYER 0x0080 /* Muliple Media Player */ +#define AVRC_SUPF_TG_MULTI_PLAYER 0x0080 /* Multiple Media Player */ +#define AVRC_SUPF_TG_COVER_ART 0x0100 /* Cover Art */ #define AVRC_META_SUCCESS AVRC_SUCCESS #define AVRC_META_FAIL AVRC_FAIL @@ -561,7 +565,7 @@ extern bt_status_t AVRC_Init(void); ** ** Function AVRC_Deinit ** -** Description This function is called at stack shotdown to free the +** Description This function is called at stack shutdown to free the ** control block (if using dynamic memory), and deinitializes the ** control block and tracing level. ** diff --git a/components/bt/host/bluedroid/stack/include/stack/avrc_defs.h b/components/bt/host/bluedroid/stack/include/stack/avrc_defs.h index c90fa6cfeb3f..06f73c29695b 100644 --- a/components/bt/host/bluedroid/stack/include/stack/avrc_defs.h +++ b/components/bt/host/bluedroid/stack/include/stack/avrc_defs.h @@ -35,6 +35,7 @@ #define AVRC_REV_1_3 0x0103 #define AVRC_REV_1_4 0x0104 #define AVRC_REV_1_5 0x0105 +#define AVRC_REV_1_6 0x0106 #define AVRC_PACKET_LEN 512 /* Per the spec, you must support 512 byte RC packets */ @@ -185,7 +186,7 @@ #define AVRC_PKT_END 3 #define AVRC_PKT_TYPE_MASK 3 -/* Define the PDUs carried in the vendor dependant data +/* Define the PDUs carried in the vendor dependent data */ #define AVRC_PDU_GET_CAPABILITIES 0x10 #define AVRC_PDU_LIST_PLAYER_APP_ATTR 0x11 @@ -297,7 +298,7 @@ typedef UINT8 tAVRC_BATTERY_STATUS; #define AVRC_MEDIA_ATTR_ID_TRACK_NUM 0x00000004 #define AVRC_MEDIA_ATTR_ID_NUM_TRACKS 0x00000005 #define AVRC_MEDIA_ATTR_ID_GENRE 0x00000006 -#define AVRC_MEDIA_ATTR_ID_PLAYING_TIME 0x00000007 /* in miliseconds */ +#define AVRC_MEDIA_ATTR_ID_PLAYING_TIME 0x00000007 /* in milliseconds */ #define AVRC_MAX_NUM_MEDIA_ATTR_ID 7 /* Define the possible values of play state diff --git a/components/bt/host/bluedroid/stack/include/stack/dyn_mem.h b/components/bt/host/bluedroid/stack/include/stack/dyn_mem.h index fa1ed4982a33..9f7eae207600 100644 --- a/components/bt/host/bluedroid/stack/include/stack/dyn_mem.h +++ b/components/bt/host/bluedroid/stack/include/stack/dyn_mem.h @@ -45,6 +45,8 @@ #define HCRP_DYNAMIC_MEMORY TRUE #define HFP_DYNAMIC_MEMORY TRUE #define HID_DYNAMIC_MEMORY TRUE +#define OBEX_DYNAMIC_MEMORY TRUE +#define GOEP_DYNAMIC_MEMORY TRUE #define HSP2_DYNAMIC_MEMORY TRUE #define ICP_DYNAMIC_MEMORY TRUE #define OPP_DYNAMIC_MEMORY TRUE @@ -79,6 +81,8 @@ #define HCRP_DYNAMIC_MEMORY FALSE #define HFP_DYNAMIC_MEMORY FALSE #define HID_DYNAMIC_MEMORY FALSE +#define OBEX_DYNAMIC_MEMORY FALSE +#define GOEP_DYNAMIC_MEMORY FALSE #define HSP2_DYNAMIC_MEMORY FALSE #define ICP_DYNAMIC_MEMORY FALSE #define OPP_DYNAMIC_MEMORY FALSE @@ -191,6 +195,14 @@ #define HID_DYNAMIC_MEMORY FALSE #endif +#ifndef OBEX_DYNAMIC_MEMORY +#define OBEX_DYNAMIC_MEMORY FALSE +#endif + +#ifndef GOEP_DYNAMIC_MEMORY +#define GOEP_DYNAMIC_MEMORY FALSE +#endif + #ifndef HSP2_DYNAMIC_MEMORY #define HSP2_DYNAMIC_MEMORY FALSE #endif diff --git a/components/bt/host/bluedroid/stack/include/stack/goep_common.h b/components/bt/host/bluedroid/stack/include/stack/goep_common.h new file mode 100644 index 000000000000..82b315691e8d --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/goep_common.h @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#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 */ diff --git a/components/bt/host/bluedroid/stack/include/stack/goepc_api.h b/components/bt/host/bluedroid/stack/include/stack/goepc_api.h new file mode 100644 index 000000000000..f2b93f86b0ee --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/goepc_api.h @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "common/bt_target.h" + +#include "stack/goep_common.h" +#include "stack/obex_api.h" + +#if (GOEPC_INCLUDED == TRUE) + +enum { + GOEPC_OPENED_EVT, /* connection open */ + GOEPC_CLOSED_EVT, /* disconnect unexpected */ + GOEPC_MTU_CHANGED_EVT, /* lower layer MTU change */ + GOEPC_CONGEST_EVT, /* lower layer connection congest */ + GOEPC_UNCONGEST_EVT, /* lower layer connection uncongest */ + GOEPC_RESPONSE_EVT /* response from server */ +}; + +typedef struct { + UINT16 peer_mtu; /* peer mtu of lower level connection */ + UINT16 our_mtu; /* our mtu of lower level connection */ +} tGOEPC_MSG_OPENED; + +typedef struct { + UINT8 reason; /* connection close reason */ +} tGOEPC_MSG_CLOSED; + +typedef struct { + UINT16 peer_mtu; /* peer mtu of lower level connection */ + UINT16 our_mtu; /* our mtu of lower level connection */ +} tGOEPC_MSG_MTU_CHANGED; + +typedef struct { + UINT8 opcode; /* which opcode that this packet response to */ + BOOLEAN final; /* whether this is a final packet */ + BOOLEAN srm_en; /* whether srm is enable */ + BOOLEAN srm_wait; /* whether srm wait is set, set by peer or by us */ + BT_HDR *pkt; /* pointer to response packet */ +} tGOEPC_MSG_RESPONSE; + +typedef union { + tGOEPC_MSG_OPENED opened; + tGOEPC_MSG_CLOSED closed; + tGOEPC_MSG_MTU_CHANGED mtu_changed; + tGOEPC_MSG_RESPONSE response; +} tGOEPC_MSG; + +typedef void (tGOEPC_EVT_CBACK)(UINT16 handle, UINT8 event, tGOEPC_MSG *msg); + +/******************************************************************************* +* The following APIs are called by bluetooth stack automatically +*******************************************************************************/ + +extern UINT16 GOEPC_Init(void); + +extern void GOEPC_Deinit(void); + +/******************************************************************************* +* The following APIs must be executed in btu task +*******************************************************************************/ + +extern UINT16 GOEPC_Open(tOBEX_SVR_INFO *p_svr, tGOEPC_EVT_CBACK callback, UINT16 *out_handle); + +extern UINT16 GOEPC_Close(UINT16 handle); + +extern UINT16 GOEPC_SendRequest(UINT16 handle); + +extern UINT16 GOEPC_PrepareRequest(UINT16 handle, tOBEX_PARSE_INFO *info, UINT16 buff_size); + +extern UINT16 GOEPC_DropRequest(UINT16 handle); + +extern UINT16 GOEPC_RequestSetSRM(UINT16 handle, BOOLEAN srm_en, BOOLEAN srm_wait); + +extern UINT16 GOEPC_RequestAddHeader(UINT16 handle, UINT8 header_id, const UINT8 *data, UINT16 data_len); + +#endif /* #if (GOEPC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/include/stack/obex_api.h b/components/bt/host/bluedroid/stack/include/stack/obex_api.h new file mode 100644 index 000000000000..e130a48734ed --- /dev/null +++ b/components/bt/host/bluedroid/stack/include/stack/obex_api.h @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "common/bt_target.h" + +#if (OBEX_INCLUDED == TRUE) + +/* API function return value result codes. */ +#define OBEX_SUCCESS 0 /* Operation successful */ +#define OBEX_FAILURE 1 /* Operation failed */ +#define OBEX_NO_RESOURCES 3 /* Not enough resources */ +#define OBEX_BAD_HANDLE 4 /* Bad handle */ +#define OBEX_INVALID_PARAM 5 /* Invalid parameter */ +#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 +*/ +#define OBEX_OPCODE_CONNECT 0x80 +#define OBEX_OPCODE_DISCONNECT 0x81 +#define OBEX_OPCODE_PUT 0x02 +#define OBEX_OPCODE_PUT_FINAL 0x82 +#define OBEX_OPCODE_GET 0x03 +#define OBEX_OPCODE_GET_FINAL 0x83 +#define OBEX_OPCODE_SETPATH 0x85 +#define OBEX_OPCODE_ACTION 0x06 +#define OBEX_OPCODE_SESSION 0x87 +#define OBEX_OPCODE_ABORT 0xFF + +#define OBEX_RESPONSE_CODE_CONTINUE 0x10 +#define OBEX_RESPONSE_CODE_OK 0x20 +#define OBEX_RESPONSE_CODE_CREATED 0x21 +#define OBEX_RESPONSE_CODE_ACCEPTED 0x22 +#define OBEX_RESPONSE_CODE_NON_AUTHORITATIVE_INFO 0x23 +#define OBEX_RESPONSE_CODE_NO_CONTENT 0x24 +#define OBEX_RESPONSE_CODE_RESET_CONTENT 0x25 +#define OBEX_RESPONSE_CODE_PARTIAL_CONTENT 0x26 +#define OBEX_RESPONSE_CODE_MULTIPLE_CHOICES 0x30 +#define OBEX_RESPONSE_CODE_MOVED_PERMANENTLY 0x31 +#define OBEX_RESPONSE_CODE_MOVED_TEMPORARILY 0x31 +#define OBEX_RESPONSE_CODE_SEE_OHTER 0x33 +#define OBEX_RESPONSE_CODE_NOT_MODIFIED 0x34 +#define OBEX_RESPONSE_CODE_USE_PROXY 0x35 +#define OBEX_RESPONSE_CODE_BAD_REQUEST 0x40 +#define OBEX_RESPONSE_CODE_UNAUTHORIZED 0x41 +#define OBEX_RESPONSE_CODE_PAYMENT_REQUIRED 0x42 +#define OBEX_RESPONSE_CODE_FORBIDDEN 0x43 +#define OBEX_RESPONSE_CODE_NOT_FOUND 0x44 +#define OBEX_RESPONSE_CODE_METHOD_NOT_ALLOWED 0x45 +#define OBEX_RESPONSE_CODE_NOT_ACCEPTABLE 0x46 +#define OBEX_RESPONSE_CODE_PROXY_AUTHENTICATION_REQUIRED 0x47 +#define OBEX_RESPONSE_CODE_REQUEST_TIME_OUT 0x48 +#define OBEX_RESPONSE_CODE_CONFLICT 0x49 +#define OBEX_RESPONSE_CODE_GONE 0x4A +#define OBEX_RESPONSE_CODE_LENGTH_REQUIRED 0x4B +#define OBEX_RESPONSE_CODE_PRECONDITION_FAILED 0x4C +#define OBEX_RESPONSE_CODE_REQUESTED_ENTITY_TOO_LARGE 0x4D +#define OBEX_RESPONSE_CODE_REQUEST_URL_TOO_LARGE 0x4E +#define OBEX_RESPONSE_CODE_UNSUPPORTED_MEDIA_TYPE 0x4F +#define OBEX_RESPONSE_CODE_INTERNAL_SERVER_ERROR 0x50 +#define OBEX_RESPONSE_CODE_NOT_IMPLEMENTED 0x51 +#define OBEX_RESPONSE_CODE_BAD_GATEWAY 0x52 +#define OBEX_RESPONSE_CODE_SERVICE_UNAVAILABLE 0x53 +#define OBEX_RESPONSE_CODE_GATEWAY_TIMEOUT 0x54 +#define OBEX_RESPONSE_CODE_HTTP_VERSION_NOT_SUPPORTED 0x55 +#define OBEX_RESPONSE_CODE_DATABASE_FULL 0x60 +#define OBEX_RESPONSE_CODE_DATABASE_LOCKED 0x61 + +#define OBEX_FINAL_BIT_MASK 0x80 + +#define OBEX_CONNECT_FLAGS 0x01 /* support multiple link */ +#define OBEX_SETPATH_FLAGS 0x03 /* default flags */ + +#define OBEX_PACKET_LENGTH_MAX (0xFFFF-1) +#define OBEX_PACKET_LENGTH_MIN 255 + +#define OBEX_VERSION_NUMBER 0x15 + +/* Header identifiers */ +#define OBEX_HEADER_ID_U2B_MASK 0xC0 /* upper 2 bits of header ID are user to indicate the header encoding */ +#define OBEX_HEADER_ID_U2B_TYPE1 0x00 /* null terminated Unicode text, length prefixed with 2 byte unsigned integer */ +#define OBEX_HEADER_ID_U2B_TYPE2 0x40 /* byte sequence, length prefixed with 2 byte unsigned integer */ +#define OBEX_HEADER_ID_U2B_TYPE3 0x80 /* 1 byte quantity */ +#define OBEX_HEADER_ID_U2B_TYPE4 0xC0 /* 4 byte quantity - transmitted in network byte order (high byte first) */ + +#define OBEX_HEADER_ID_COUNT 0xC0 +#define OBEX_HEADER_ID_NAME 0x01 +#define OBEX_HEADER_ID_TYPE 0x42 +#define OBEX_HEADER_ID_LENGTH 0xC3 +#define OBEX_HEADER_ID_TIME_ISO8601 0x44 +#define OBEX_HEADER_ID_TIME_4BYTE 0xC4 +#define OBEX_HEADER_ID_DESCRIPTION 0x05 +#define OBEX_HEADER_ID_TARGET 0x46 +#define OBEX_HEADER_ID_HTTP 0x47 +#define OBEX_HEADER_ID_BODY 0x48 +#define OBEX_HEADER_ID_END_OF_BODY 0x49 +#define OBEX_HEADER_ID_WHO 0x4A +#define OBEX_HEADER_ID_CONNECTION_ID 0xCB +#define OBEX_HEADER_ID_APP_PARAM 0x4C +#define OBEX_HEADER_ID_AUTH_CHALLENGE 0x4D +#define OBEX_HEADER_ID_AUTH_RESPONSE 0x4E +#define OBEX_HEADER_ID_CREATOR_ID 0xCF +#define OBEX_HEADER_ID_WAN_UUID 0x50 +#define OBEX_HEADER_ID_OBJECT_CLASS 0x51 +#define OBEX_HEADER_ID_SESSION_PARAM 0x52 +#define OBEX_HEADER_ID_SESSION_SEQ_NUM 0x93 +#define OBEX_HEADER_ID_ACTION_ID 0x94 +#define OBEX_HEADER_ID_DESTNAME 0x15 +#define OBEX_HEADER_ID_PERMISSIONS 0xD6 +#define OBEX_HEADER_ID_SRM 0x97 +#define OBEX_HEADER_ID_SRM_PARAM 0x98 +/* Reserved for future use: 0x19 to 0x2F */ +/* User defined: 0x30 to 0x3F */ + +#define OBEX_ACTION_ID_COPY 0x00 +#define OBEX_ACTION_ID_MOVE_RENAME 0x01 +#define OBEX_ACTION_ID_SET_PERMISSIONS 0x02 + +#define OBEX_SRM_DISABLE 0x00 +#define OBEX_SRM_ENABLE 0x01 +#define OBEX_SRM_SUPPORT 0x02 + +#define OBEX_SRMP_ADD_PKT 0x00 +#define OBEX_SRMP_WAIT 0x01 +#define OBEX_SRMP_ADD_PKT_WAIT 0x02 + +#define OBEX_MIN_PACKET_SIZE 3 + +enum { + /* client event */ + OBEX_CONNECT_EVT, /* connection opened */ + OBEX_DISCONNECT_EVT, /* connection disconnected */ + /* server event */ + OBEX_CONN_INCOME_EVT, /* an incoming connection */ + /* client or server event */ + OBEX_MTU_CHANGE_EVT, /* connection mtu changed */ + OBEX_CONGEST_EVT, /* connection congested */ + OBEX_UNCONGEST_EVT, /* connection is not congested */ + OBEX_DATA_EVT /* data received */ +}; + +enum { + OBEX_OVER_L2CAP = 0, + OBEX_OVER_RFCOMM, + OBEX_NUM_TL +}; + +typedef struct +{ + UINT16 psm; /* l2cap psm */ + UINT16 sec_mask; /* security mask */ + UINT16 pref_mtu; /* preferred mtu, limited by L2CAP_MTU_SIZE */ + BD_ADDR addr; /* peer bluetooth device address */ +} tOBEX_OVER_L2CAP_SVR; + +typedef struct +{ + UINT8 tl; /* transport type, OBEX_OVER_L2CAP or OBEX_OVER_RFCOMM */ + union + { + tOBEX_OVER_L2CAP_SVR l2cap; + }; +} tOBEX_SVR_INFO; + +typedef struct { + UINT8 opcode; + UINT8 response_code; + /* Connect */ + UINT8 obex_version_number; + UINT16 max_packet_length; + /* Connect or SetPath */ + UINT8 flags; + /* Internal use */ + UINT16 next_header_pos; +} tOBEX_PARSE_INFO; + +typedef union { + struct { + UINT16 peer_mtu; + UINT16 our_mtu; + } connect; + + struct { + UINT16 svr_handle; + UINT16 peer_mtu; + UINT16 our_mtu; + } conn_income; + + struct { + UINT16 peer_mtu; + UINT16 our_mtu; + } mtu_change; + + struct { + BT_HDR *pkt; + } data; +} tOBEX_MSG; + +typedef void (tOBEX_MSG_CBACK)(UINT16 handle, UINT8 event, tOBEX_MSG *msg); + +/******************************************************************************* +* The following APIs are called by bluetooth stack automatically +*******************************************************************************/ + +extern UINT16 OBEX_Init(void); + +extern void OBEX_Deinit(void); + +/******************************************************************************* +* The following APIs must be executed in btu task +*******************************************************************************/ + +extern UINT16 OBEX_CreateConn(tOBEX_SVR_INFO *server, tOBEX_MSG_CBACK callback, UINT16 *out_handle); + +extern UINT16 OBEX_RemoveConn(UINT16 handle); + +extern UINT16 OBEX_SendPacket(UINT16 handle, BT_HDR *pkt); + +extern UINT16 OBEX_RegisterServer(tOBEX_SVR_INFO *server, tOBEX_MSG_CBACK callback, UINT16 *out_svr_handle); + +extern UINT16 OBEX_DeregisterServer(UINT16 svr_handle); + +/******************************************************************************* +* The following APIs are util function, can be executed in btu or btc task +*******************************************************************************/ + +extern UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_pkt); + +extern UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_pkt); + +extern UINT16 OBEX_AppendHeader(BT_HDR *pkt, const UINT8 *header); + +extern UINT16 OBEX_AppendHeaderRaw(BT_HDR *pkt, UINT8 header_id, const UINT8 *data, UINT16 data_len); + +extern UINT16 OBEX_AppendHeaderSRM(BT_HDR *pkt, UINT8 value); + +extern UINT16 OBEX_AppendHeaderSRMP(BT_HDR *pkt, UINT8 value); + +extern UINT16 OBEX_GetPacketFreeSpace(BT_HDR *pkt); + +extern UINT16 OBEX_GetPacketLength(BT_HDR *pkt); + +extern UINT16 OBEX_ParseRequest(BT_HDR *pkt, tOBEX_PARSE_INFO *info); + +extern UINT16 OBEX_ParseResponse(BT_HDR *pkt, UINT8 opcode, tOBEX_PARSE_INFO *info); + +extern BOOLEAN OBEX_CheckFinalBit(BT_HDR *pkt); + +extern BOOLEAN OBEX_CheckContinueResponse(BT_HDR *pkt); + +extern UINT8 *OBEX_GetNextHeader(BT_HDR *pkt, tOBEX_PARSE_INFO *info); + +extern UINT16 OBEX_GetHeaderLength(UINT8 *header); + +#endif /* #if (OBEX_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/include/stack/sdp_api.h b/components/bt/host/bluedroid/stack/include/stack/sdp_api.h index 70da3182a579..7075e8bbf37b 100644 --- a/components/bt/host/bluedroid/stack/include/stack/sdp_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/sdp_api.h @@ -365,6 +365,20 @@ extern BOOLEAN SDP_FindProtocolListElemInRec (tSDP_DISC_REC *p_rec, UINT16 layer_uuid, tSDP_PROTOCOL_ELEM *p_elem); +/******************************************************************************* +** +** Function SDP_FindProtocolListElem +** +** Description This function looks at the protocol list for a specific protocol +** list element. +** +** Returns TRUE if found, FALSE if not +** If found, the passed protocol list element is filled in. +** +*******************************************************************************/ +extern BOOLEAN SDP_FindProtocolListElem (tSDP_DISC_ATTR *p_protocol_list, + UINT16 layer_uuid, + tSDP_PROTOCOL_ELEM *p_elem); /******************************************************************************* ** @@ -409,7 +423,7 @@ extern BOOLEAN SDP_FindProfileVersionInRec (tSDP_DISC_REC *p_rec, ** ** Description This function is called to create a record in the database. ** This would be through the SDP database maintenance API. The -** record is created empty, teh application should then call +** record is created empty, the application should then call ** "add_attribute" to add the record's attributes. ** ** Returns Record handle if OK, else 0. diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_int.h b/components/bt/host/bluedroid/stack/obex/include/obex_int.h new file mode 100644 index 000000000000..4512e89c2a7f --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/include/obex_int.h @@ -0,0 +1,73 @@ +/* + * 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 "obex_tl.h" +#include "obex_tl_l2cap.h" + +#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 */ + +#define OBEX_ROLE_CLIENT 0x01 +#define OBEX_ROLE_SERVER 0x02 + +/* OBEX connection state */ +#define OBEX_STATE_IDLE 0 /* No connection */ +#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 */ + UINT16 tl_hdl; /* Transport layer non-zeros connection handle*/ + UINT16 tl_peer_mtu; /* Transport layer peer mtu */ + UINT16 tl_our_mtu; /* Transport layer our mtu */ + UINT8 tl_cong; /* 1 if transport layer congestion, otherwise 0 */ + UINT8 tl; /* OBEX_OVER_L2CAP or OBEX_OVER_RFCOMM */ + UINT8 allocated; /* 0, not allocated. index+1, otherwise. equal to api handle */ + UINT8 state; /* This OBEX connection state */ + UINT8 role; /* This OBEX connection role */ +} tOBEX_CCB; + +/* OBEX Server Control block */ +typedef struct { + tOBEX_MSG_CBACK *callback; /* Connection msg callback function */ + UINT16 tl_hdl; /* Transport layer non-zeros server handle*/ + UINT8 tl; /* OBEX_OVER_L2CAP or OBEX_OVER_RFCOMM */ + UINT8 allocated; /* 0, not allocated. index+1, otherwise. */ +} tOBEX_SCB; + +/* OBEX Control block */ +typedef struct { + tOBEX_CCB ccb[OBEX_MAX_CONNECTION]; /* connection control blocks */ + tOBEX_SCB scb[OBEX_MAX_SERVER]; /* server control blocks */ + tOBEX_TL_OPS *tl_ops[OBEX_NUM_TL]; /* transport operation function pointer */ + UINT8 trace_level; /* trace level */ +} tOBEX_CB; + +#if OBEX_DYNAMIC_MEMORY == FALSE +extern tOBEX_CB obex_cb; +#else +extern tOBEX_CB *obex_cb_ptr; +#define obex_cb (*obex_cb_ptr) +#endif + +void obex_tl_l2cap_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); +void obex_free_scb(tOBEX_SCB *p_scb); + +#endif /* #if (OBEX_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_tl.h b/components/bt/host/bluedroid/stack/obex/include/obex_tl.h new file mode 100644 index 000000000000..6a60ed51dc61 --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/include/obex_tl.h @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "common/bt_target.h" + +#if (OBEX_INCLUDED == TRUE) + +/* Return code of obex_tl_send_data */ +#define OBEX_TL_FAILED FALSE +#define OBEX_TL_SUCCESS TRUE +#define OBEX_TL_CONGESTED 2 + +typedef enum { + OBEX_TL_CONN_OPEN_EVT, + OBEX_TL_CONN_INCOME_EVT, + OBEX_TL_DIS_CONN_EVT, + OBEX_TL_CONGEST_EVT, + OBEX_TL_UNCONGEST_EVT, + OBEX_TL_MTU_CHANGE_EVT, + OBEX_TL_DATA_EVT +} tOBEX_TL_EVT; + +typedef union { + /* general struct, used to retrieve handle */ + struct { + UINT16 hdl; + } any; + + /* struct for OBEX_TL_CONN_OPEN_EVT */ + struct { + UINT16 hdl; + UINT16 peer_mtu; + UINT16 our_mtu; + } conn_open; + + /* struct for OBEX_TL_CONN_INCOME_EVT */ + struct { + UINT16 hdl; + UINT16 peer_mtu; + UINT16 our_mtu; + UINT16 svr_hdl; + } conn_income; + + /* struct for OBEX_TL_MTU_CHANGE_EVT */ + struct { + UINT16 hdl; + UINT16 peer_mtu; + UINT16 our_mtu; + } mtu_chg; + + /* struct for OBEX_TL_DATA_EVT */ + struct { + UINT16 hdl; + BT_HDR *p_buf; + } data; +} tOBEX_TL_MSG; + +typedef struct +{ + UINT16 psm; /* l2cap psm */ + UINT16 sec_mask; /* security mask */ + UINT16 pref_mtu; /* preferred mtu, limited by L2CAP_MTU_SIZE */ + BD_ADDR addr; /* peer bluetooth device address */ +} tOBEX_TL_L2CAP_SVR; + +typedef union +{ + tOBEX_TL_L2CAP_SVR l2cap; +} tOBEX_TL_SVR_INFO; + +typedef void (tOBEX_TL_CBACK)(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg); + +typedef struct { + void (*init)(tOBEX_TL_CBACK *callback); + void (*deinit)(void); + UINT16 (*connect)(tOBEX_TL_SVR_INFO *server); + void (*disconnect)(UINT16 tl_hdl); + UINT16 (*send)(UINT16 tl_hdl, BT_HDR *p_buf); + UINT16 (*bind)(tOBEX_TL_SVR_INFO *server); + void (*unbind)(UINT16 tl_hdl); +} tOBEX_TL_OPS; + +#endif /* #if (OBEX_INCLUDED == TRUE) */ 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 new file mode 100644 index 000000000000..83f062bb6966 --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "obex_tl.h" + +#if (OBEX_INCLUDED == TRUE) + +#define OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET 13 /* L2CAP_MIN_OFFSET */ + +tOBEX_TL_OPS *obex_tl_l2cap_ops_get(void); + +#endif /* #if (OBEX_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/obex/obex_api.c b/components/bt/host/bluedroid/stack/obex/obex_api.c new file mode 100644 index 000000000000..befabb0e92b3 --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/obex_api.c @@ -0,0 +1,764 @@ +/* + * 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/obex_api.h" +#include "obex_int.h" +#include "obex_tl.h" +#include "obex_tl_l2cap.h" + +#if (OBEX_INCLUDED == TRUE) + +static inline void obex_server_to_tl_server(tOBEX_SVR_INFO *server, tOBEX_TL_SVR_INFO *tl_server) +{ + if (server->tl == OBEX_OVER_L2CAP) { + tl_server->l2cap.psm = server->l2cap.psm; + tl_server->l2cap.sec_mask = server->l2cap.sec_mask; + tl_server->l2cap.pref_mtu = server->l2cap.pref_mtu; + bdcpy(tl_server->l2cap.addr, server->l2cap.addr); + } + else { + OBEX_TRACE_ERROR("Unsupported OBEX transport type\n"); + assert(0); + } +} + +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); +} + +/******************************************************************************* +** +** Function OBEX_Init +** +** Description Initialize OBEX Profile, must call before using any other +** OBEX APIs +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_Init(void) +{ +#if (OBEX_DYNAMIC_MEMORY) + if (!obex_cb_ptr) { + obex_cb_ptr = (tOBEX_CB *)osi_malloc(sizeof(tOBEX_CB)); + if (!obex_cb_ptr) { + return OBEX_NO_RESOURCES; + } + } +#endif /* #if (OBEX_DYNAMIC_MEMORY) */ + memset(&obex_cb, 0, sizeof(tOBEX_CB)); + obex_cb.tl_ops[OBEX_OVER_L2CAP] = obex_tl_l2cap_ops_get(); + 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 */ + /* + 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); + } + */ + obex_cb.trace_level = BT_TRACE_LEVEL_ERROR; + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_Deinit +** +** Description Deinit OBEX profile, once deinit, can not use any other +** APIs until call OBEX_Init again +** +*******************************************************************************/ +void OBEX_Deinit(void) +{ + if (obex_cb.tl_ops[OBEX_OVER_L2CAP]->deinit != NULL) { + obex_cb.tl_ops[OBEX_OVER_L2CAP]->deinit(); + } + /* + if (obex_cb.tl_ops[OBEX_OVER_RFCOMM]->deinit != NULL) { + obex_cb.tl_ops[OBEX_OVER_RFCOMM]->deinit(); + } + */ +#if (OBEX_DYNAMIC_MEMORY) + if (obex_cb_ptr) { + osi_free(obex_cb_ptr); + obex_cb_ptr = NULL; + } +#endif /* #if (OBEX_DYNAMIC_MEMORY) */ +} + +/******************************************************************************* +** +** Function OBEX_CreateConn +** +** Description Start the progress of creating an OBEX connection +** +** Returns OBEX_SUCCESS if successful, otherwise failed, when the +** connection is opened, an OBEX_CONNECT_EVT will come +** +*******************************************************************************/ +UINT16 OBEX_CreateConn(tOBEX_SVR_INFO *server, tOBEX_MSG_CBACK callback, UINT16 *out_handle) +{ + UINT16 ret = OBEX_SUCCESS; + tOBEX_CCB *p_ccb = NULL; + + do { + if (server->tl >= OBEX_NUM_TL) { + ret = OBEX_INVALID_PARAM; + break; + } + + p_ccb = obex_allocate_ccb(); + if (p_ccb == NULL) { + ret = OBEX_NO_RESOURCES; + break; + } + + tOBEX_TL_SVR_INFO tl_server = {0}; + obex_server_to_tl_server(server, &tl_server); + p_ccb->tl = server->tl; + p_ccb->tl_hdl = obex_cb.tl_ops[p_ccb->tl]->connect(&tl_server); + if (p_ccb->tl_hdl == 0) { + ret = OBEX_ERROR_TL; + break; + } + + p_ccb->callback = callback; + p_ccb->role = OBEX_ROLE_CLIENT; + p_ccb->state = OBEX_STATE_OPENING; + *out_handle = p_ccb->allocated; + } while (0); + + if (ret != OBEX_SUCCESS && p_ccb != NULL) { + obex_free_ccb(p_ccb); + } + return ret; +} + +/******************************************************************************* +** +** Function OBEX_RemoveConn +** +** Description Remove an OBEX connection +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_RemoveConn(UINT16 handle) +{ + tOBEX_CCB *p_ccb = NULL; + + UINT16 ccb_idx = handle - 1; + if (ccb_idx >= OBEX_MAX_CONNECTION || !obex_cb.ccb[ccb_idx].allocated) { + return OBEX_BAD_HANDLE; + } + + p_ccb = &obex_cb.ccb[ccb_idx]; + obex_cb.tl_ops[p_ccb->tl]->disconnect(p_ccb->tl_hdl); + obex_free_ccb(p_ccb); + + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_RegisterServer +** +** Description Register an OBEX server and listen the incoming connection +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_RegisterServer(tOBEX_SVR_INFO *server, tOBEX_MSG_CBACK callback, UINT16 *out_svr_handle) +{ + UINT8 ret = OBEX_SUCCESS; + tOBEX_SCB *p_scb = NULL; + + do { + if (server->tl >= OBEX_NUM_TL) { + ret = OBEX_INVALID_PARAM; + break; + } + + p_scb = obex_allocate_scb(); + if (p_scb == NULL) { + ret = OBEX_NO_RESOURCES; + break; + } + + tOBEX_TL_SVR_INFO tl_server = {0}; + obex_server_to_tl_server(server, &tl_server); + p_scb->tl = server->tl; + p_scb->tl_hdl = obex_cb.tl_ops[p_scb->tl]->bind(&tl_server); + if (p_scb->tl_hdl == 0) { + ret = OBEX_ERROR_TL; + break; + } + p_scb->callback = callback; + + if (out_svr_handle) { + /* To avoid confuse with connection handle, left shift 8 bit */ + *out_svr_handle = p_scb->allocated << 8; + } + } while (0); + + if (ret != OBEX_SUCCESS && p_scb != NULL) { + obex_free_scb(p_scb); + } + return ret; +} + +/******************************************************************************* +** +** Function OBEX_DeregisterServer +** +** Description Deregister an OBEX server, if there are still a connection +** alive, the behavior depend on the implementation of transport +** layer +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_DeregisterServer(UINT16 svr_handle) +{ + tOBEX_SCB *p_scb = NULL; + + UINT16 scb_idx = (svr_handle >> 8) - 1; + if (scb_idx >= OBEX_MAX_SERVER || !obex_cb.scb[scb_idx].allocated) { + return OBEX_BAD_HANDLE; + } + + p_scb = &obex_cb.scb[scb_idx]; + obex_cb.tl_ops[p_scb->tl]->unbind(p_scb->tl_hdl); + obex_free_scb(p_scb); + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_SendPacket +** +** Description Send a packet to peer OBEX server or client, once call +** this function, the ownership of pkt is lost, do not free +** or modify the pkt any more +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_SendPacket(UINT16 handle, BT_HDR *pkt) +{ + UINT16 ret = OBEX_SUCCESS; + BOOLEAN free_pkt = true; + tOBEX_CCB *p_ccb = NULL; + do { + if (pkt == NULL) { + ret = OBEX_INVALID_PARAM; + break; + } + + UINT16 ccb_idx = handle - 1; + if (ccb_idx >= OBEX_MAX_CONNECTION || !obex_cb.ccb[ccb_idx].allocated) { + ret = OBEX_BAD_HANDLE; + break; + } + + p_ccb = &obex_cb.ccb[ccb_idx]; + if (p_ccb->state != OBEX_STATE_OPENED) { + ret = OBEX_NOT_OPEN; + break; + } + + if (pkt->len > p_ccb->tl_peer_mtu) { + ret = OBEX_PACKET_TOO_LARGE; + break; + } + + ret = obex_cb.tl_ops[p_ccb->tl]->send(p_ccb->tl_hdl, pkt); + /* packet has pass to lower layer, do not free it */ + free_pkt = false; + if (ret == OBEX_TL_SUCCESS || ret == OBEX_TL_CONGESTED) { + ret = OBEX_SUCCESS; + } + else { + ret = OBEX_ERROR_TL; + } + } while (0); + + if (free_pkt) { + osi_free(pkt); + } + return ret; +} + +/******************************************************************************* +** +** Function OBEX_BuildRequest +** +** Description Build a request packet with opcode and additional info, +** packet can free by osi_free +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_pkt) +{ + 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; + + BT_HDR *p_buf= (BT_HDR *)osi_malloc(buff_size); + if (p_buf == NULL) { + return OBEX_NO_RESOURCES; + } + + 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; + UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; + /* byte 0: opcode */ + *p_data++ = info->opcode; + /* byte 1, 2: packet length, skip, we will update at last */ + UINT8 *p_pkt_len = p_data; + p_data += 2; + + switch (info->opcode) + { + case OBEX_OPCODE_CONNECT: + /* byte 3: OBEX version number */ + *p_data++ = info->obex_version_number; + /* 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); + pkt_len += 4; + break; + case OBEX_OPCODE_SETPATH: + /* byte 3: flags */ + *p_data++ = info->flags; + /* byte 4: constants, reserved, must be zero */ + *p_data++ = 0; + pkt_len += 2; + break; + default: + break; + } + + STORE16BE(p_pkt_len, pkt_len); + p_buf->len = pkt_len; + *out_pkt = p_buf; + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_BuildResponse +** +** Description Build a response packet with opcode and response and additional +** info, packet can by free by osi_free +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_pkt) +{ + 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; + + BT_HDR *p_buf= (BT_HDR *)osi_malloc(buff_size); + if (p_buf == NULL) { + return OBEX_NO_RESOURCES; + } + + 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; + UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; + /* byte 0: response code */ + *p_data++ = info->response_code; + /* byte 1, 2: packet length, skip, we will update at last */ + UINT8 *p_pkt_len = p_data; + p_data += 2; + + /* we need to use opcode to decide the response format */ + switch (info->opcode) + { + case OBEX_OPCODE_CONNECT: + /* byte 3: OBEX version number */ + *p_data++ = info->obex_version_number; + /* 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); + pkt_len += 4; + break; + default: + break; + } + + STORE16BE(p_pkt_len, pkt_len); + p_buf->len = pkt_len; + *out_pkt = p_buf; + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_AppendHeader +** +** Description Append a header to specific packet, packet can be request +** or response, the format of header must be valid +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_AppendHeader(BT_HDR *pkt, const UINT8 *header) +{ + if (pkt == NULL || header == NULL) { + return OBEX_INVALID_PARAM; + } + + UINT16 header_len = 0; + UINT8 header_id = *header; + switch (header_id & OBEX_HEADER_ID_U2B_MASK) + { + case OBEX_HEADER_ID_U2B_TYPE1: + case OBEX_HEADER_ID_U2B_TYPE2: + header_len = (header[1] << 8) + header[2]; + break; + case OBEX_HEADER_ID_U2B_TYPE3: + header_len = 2; + break; + case OBEX_HEADER_ID_U2B_TYPE4: + header_len = 5; + break; + default: + break; + } + if (header_len == 0) { + return OBEX_INVALID_PARAM; + } + + if (pkt->layer_specific - pkt->len < header_len) { + /* the packet can not hold this header */ + return OBEX_NO_RESOURCES; + } + UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; + UINT8 *p_start = p_data + pkt->len; + memcpy(p_start, header, header_len); + pkt->len += header_len; + /* point to packet len */ + p_data++; + STORE16BE(p_data, pkt->len); + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_AppendHeaderRaw +** +** Description Append a header to specific packet, packet can be request +** or response, data not include 2 byte length prefixed +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_AppendHeaderRaw(BT_HDR *pkt, UINT8 header_id, const UINT8 *data, UINT16 data_len) +{ + if (pkt == NULL || data == NULL) { + return OBEX_INVALID_PARAM; + } + + UINT16 header_len = 0; + BOOLEAN store_header_len = FALSE; + switch (header_id & OBEX_HEADER_ID_U2B_MASK) + { + case OBEX_HEADER_ID_U2B_TYPE1: + case OBEX_HEADER_ID_U2B_TYPE2: + /* header id + 2 byte length prefixed + data */ + header_len = data_len + 3; + store_header_len = TRUE; + break; + case OBEX_HEADER_ID_U2B_TYPE3: + header_len = 2; + if (data_len != 1) { + return OBEX_INVALID_PARAM; + } + break; + case OBEX_HEADER_ID_U2B_TYPE4: + header_len = 5; + if (data_len != 4) { + return OBEX_INVALID_PARAM; + } + break; + default: + break; + } + if (header_len == 0) { + return OBEX_INVALID_PARAM; + } + + if (pkt->layer_specific - pkt->len < header_len) { + /* the packet can not hold this header */ + return OBEX_NO_RESOURCES; + } + UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; + UINT8 *p_start = p_data + pkt->len; + /* store header id */ + *p_start++ = header_id; + if (store_header_len) { + /* store header length */ + STORE16BE(p_start, header_len); + p_start+= 2; + } + /* store data */ + memcpy(p_start, data, data_len); + pkt->len += header_len; + /* point to packet len */ + p_data++; + STORE16BE(p_data, pkt->len); + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_AppendHeaderSRM +** +** Description Append a Single Response Mode header +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_AppendHeaderSRM(BT_HDR *pkt, UINT8 value) +{ + return OBEX_AppendHeaderRaw(pkt, OBEX_HEADER_ID_SRM, &value, 1); +} + +/******************************************************************************* +** +** Function OBEX_AppendHeaderSRMP +** +** Description Append a Single Response Mode Parameters header +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_AppendHeaderSRMP(BT_HDR *pkt, UINT8 value) +{ + return OBEX_AppendHeaderRaw(pkt, OBEX_HEADER_ID_SRM_PARAM, &value, 1); +} + +/******************************************************************************* +** +** Function OBEX_GetPacketFreeSpace +** +** Description Get the current free space of a packet, use this to check +** if a packet can hold a header +** +** Returns Current free space of a packet, in bytes +** +*******************************************************************************/ +UINT16 OBEX_GetPacketFreeSpace(BT_HDR *pkt) +{ + if (pkt == NULL) { + return 0; + } + return pkt->layer_specific - pkt->len; +} + +/******************************************************************************* +** +** Function OBEX_GetPacketLength +** +** Description Get the current packet length +** +** Returns Current packet length, in bytes +** +*******************************************************************************/ +UINT16 OBEX_GetPacketLength(BT_HDR *pkt) +{ + if (pkt == NULL) { + return 0; + } + return pkt->len; +} + +/******************************************************************************* +** +** Function OBEX_ParseRequest +** +** Description Parse a request packet +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_ParseRequest(BT_HDR *pkt, tOBEX_PARSE_INFO *info) +{ + if (pkt == NULL || info == NULL) { + return OBEX_INVALID_PARAM; + } + + UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; + info->opcode = *p_data; + switch (info->opcode) + { + case OBEX_OPCODE_CONNECT: + info->obex_version_number = p_data[3]; + info->flags = p_data[4]; + info->max_packet_length = (p_data[5] << 8) + p_data[6]; + info->next_header_pos = 7; + break; + case OBEX_OPCODE_SETPATH: + info->flags = p_data[3]; + info->next_header_pos = 5; + break; + default: + info->next_header_pos = 3; + break; + } + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_ParseResponse +** +** Description Parse a request response packet +** +** Returns OBEX_SUCCESS if successful, otherwise failed +** +*******************************************************************************/ +UINT16 OBEX_ParseResponse(BT_HDR *pkt, UINT8 opcode, tOBEX_PARSE_INFO *info) +{ + if (pkt == NULL || info == NULL) { + return OBEX_INVALID_PARAM; + } + + UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; + info->opcode = opcode; + info->response_code = *p_data; + switch (opcode) + { + case OBEX_OPCODE_CONNECT: + info->obex_version_number = p_data[3]; + info->flags = p_data[4]; + info->max_packet_length = (p_data[5] << 8) + p_data[6]; + info->next_header_pos = 7; + break; + default: + info->next_header_pos = 3; + break; + } + return OBEX_SUCCESS; +} + +/******************************************************************************* +** +** Function OBEX_CheckFinalBit +** +** Description Check whether a packet had set the final bit +** +** Returns TRUE if final bit set, otherwise, false +** +*******************************************************************************/ +BOOLEAN OBEX_CheckFinalBit(BT_HDR *pkt) +{ + if (pkt == NULL) { + return FALSE; + } + UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; + return (*p_data) & OBEX_FINAL_BIT_MASK; +} + +/******************************************************************************* +** +** Function OBEX_CheckContinueResponse +** +** Description Check whether a packet is continue response +** +** Returns TRUE if continue response, otherwise, false +** +*******************************************************************************/ +BOOLEAN OBEX_CheckContinueResponse(BT_HDR *pkt) +{ + if (pkt == NULL) { + return FALSE; + } + UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; + return (*p_data == 0x90) || (*p_data == 0x10); +} + +/******************************************************************************* +** +** Function OBEX_GetHeaderLength +** +** Description Get header length +** +** Returns header length +** +*******************************************************************************/ +UINT16 OBEX_GetHeaderLength(UINT8 *header) +{ + UINT16 header_len = 0; + UINT8 header_id = *header; + switch (header_id & OBEX_HEADER_ID_U2B_MASK) + { + case OBEX_HEADER_ID_U2B_TYPE1: + case OBEX_HEADER_ID_U2B_TYPE2: + header_len = (header[1] << 8) + header[2]; + break; + case OBEX_HEADER_ID_U2B_TYPE3: + header_len = 2; + break; + case OBEX_HEADER_ID_U2B_TYPE4: + header_len = 5; + break; + default: + /* unreachable */ + break; + } + return header_len; +} + +/******************************************************************************* +** +** Function OBEX_GetNextHeader +** +** Description Get next header pointer from a packet +** +** Returns Pointer to start address of a header, NULL if no more header +** or failed +** +*******************************************************************************/ +UINT8 *OBEX_GetNextHeader(BT_HDR *pkt, tOBEX_PARSE_INFO *info) +{ + if (pkt == NULL || info == NULL) { + return NULL; + } + UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; + if (info->next_header_pos == 0 || info->next_header_pos >= pkt->len) { + return NULL; + } + UINT8 *header = p_data + info->next_header_pos; + UINT16 header_len = OBEX_GetHeaderLength(header); + info->next_header_pos += header_len; + return header; +} + +#endif /* #if (OBEX_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/obex/obex_main.c b/components/bt/host/bluedroid/stack/obex/obex_main.c new file mode 100644 index 000000000000..c5f04539021d --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/obex_main.c @@ -0,0 +1,211 @@ +/* + * 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/obex_api.h" +#include "obex_int.h" +#include "obex_tl.h" + +#if (OBEX_INCLUDED == TRUE) + +#if OBEX_DYNAMIC_MEMORY == FALSE +tOBEX_CB obex_cb; +#else +tOBEX_CB *obex_cb_ptr = NULL; +#endif + +static tOBEX_CCB *obex_find_ccb_by_tl_hdl(UINT8 tl, UINT16 tl_hdl) +{ + tOBEX_CCB *p_ccb = NULL; + for (int i = 0; i < OBEX_MAX_CONNECTION; ++i) { + if (obex_cb.ccb[i].allocated && obex_cb.ccb[i].tl == tl && obex_cb.ccb[i].tl_hdl == tl_hdl) { + p_ccb = &obex_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tOBEX_SCB *obex_find_scb_by_tl_hdl(UINT8 tl, UINT16 tl_hdl) +{ + tOBEX_SCB *p_scb = NULL; + for (int i = 0; i < OBEX_MAX_SERVER; ++i) { + if (obex_cb.scb[i].allocated && obex_cb.scb[i].tl == tl && obex_cb.scb[i].tl_hdl == tl_hdl) { + p_scb = &obex_cb.scb[i]; + break; + } + } + return p_scb; +} + +tOBEX_CCB *obex_allocate_ccb(void) +{ + tOBEX_CCB *p_ccb = NULL; + for (int i = 0; i < OBEX_MAX_CONNECTION; ++i) { + if (!obex_cb.ccb[i].allocated) { + obex_cb.ccb[i].allocated = i + 1; + p_ccb = &obex_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +void obex_free_ccb(tOBEX_CCB *p_ccb) +{ + assert(p_ccb->allocated != 0); + memset(p_ccb, 0, sizeof(tOBEX_CCB)); +} + +tOBEX_SCB *obex_allocate_scb(void) +{ + tOBEX_SCB *p_scb = NULL; + for (int i = 0; i < OBEX_MAX_SERVER; ++i) { + if (!obex_cb.scb[i].allocated) { + obex_cb.scb[i].allocated = i + 1; + p_scb = &obex_cb.scb[i]; + break; + } + } + return p_scb; +} + +void obex_free_scb(tOBEX_SCB *p_scb) +{ + assert(p_scb->allocated != 0); + memset(p_scb, 0, sizeof(tOBEX_SCB)); +} + +/* check whether connection mtu is valid, if not, disconnect it */ +static bool check_conn_mtu_valid(tOBEX_CCB *p_ccb, BOOLEAN call_cb) +{ + if (p_ccb->tl_our_mtu < 255 || p_ccb->tl_peer_mtu < 255) { + if (call_cb && p_ccb->callback) { + p_ccb->callback(p_ccb->allocated, OBEX_DISCONNECT_EVT, NULL); + } + OBEX_TRACE_ERROR("Check OBEX transport layer MTU failed, disconnect"); + obex_cb.tl_ops[p_ccb->tl]->disconnect(p_ccb->tl_hdl); + obex_free_ccb(p_ccb); + return false; + } + return true; +} + +void obex_tl_evt_handler(UINT8 tl, tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg) +{ + UINT16 tl_hdl = msg->any.hdl; + tOBEX_CCB *p_ccb = obex_find_ccb_by_tl_hdl(tl, tl_hdl); + tOBEX_SCB *p_scb = NULL; + tOBEX_MSG cb_msg = {0}; + + switch (evt) + { + case OBEX_TL_CONN_OPEN_EVT: + assert(p_ccb != NULL); + p_ccb->tl_peer_mtu = msg->conn_open.peer_mtu; + p_ccb->tl_our_mtu = msg->conn_open.our_mtu; + if (!check_conn_mtu_valid(p_ccb, TRUE)) { + break; + } + p_ccb->state = OBEX_STATE_OPENED; + if (p_ccb->callback) { + cb_msg.connect.peer_mtu = msg->conn_open.peer_mtu; + cb_msg.connect.our_mtu = msg->conn_open.our_mtu; + p_ccb->callback(p_ccb->allocated, OBEX_CONNECT_EVT, &cb_msg); + } + break; + case OBEX_TL_DIS_CONN_EVT: + if (p_ccb != NULL) { + if (p_ccb->callback) { + p_ccb->callback(p_ccb->allocated, OBEX_DISCONNECT_EVT, NULL); + } + obex_free_ccb(p_ccb); + } + break; + case OBEX_TL_CONGEST_EVT: + assert(p_ccb != NULL); + if (p_ccb->callback) { + p_ccb->callback(p_ccb->allocated, OBEX_CONGEST_EVT, NULL); + } + break; + case OBEX_TL_UNCONGEST_EVT: + assert(p_ccb != NULL); + if (p_ccb->callback) { + p_ccb->callback(p_ccb->allocated, OBEX_UNCONGEST_EVT, NULL); + } + break; + case OBEX_TL_MTU_CHANGE_EVT: + assert(p_ccb != NULL); + p_ccb->tl_peer_mtu = msg->mtu_chg.peer_mtu; + p_ccb->tl_our_mtu = msg->mtu_chg.our_mtu; + if (!check_conn_mtu_valid(p_ccb, TRUE)) { + break; + } + if (p_ccb->callback) { + cb_msg.mtu_change.peer_mtu = msg->mtu_chg.peer_mtu; + cb_msg.mtu_change.our_mtu = msg->mtu_chg.our_mtu; + p_ccb->callback(p_ccb->allocated, OBEX_MTU_CHANGE_EVT, &cb_msg); + } + break; + case OBEX_TL_DATA_EVT: + assert(p_ccb != NULL); + if (p_ccb->callback) { + cb_msg.data.pkt = msg->data.p_buf; + p_ccb->callback(p_ccb->allocated, OBEX_DATA_EVT, &cb_msg); + } + else { + /* No callback, just free the packet */ + osi_free(msg->data.p_buf); + } + break; + case OBEX_TL_CONN_INCOME_EVT: + /* New connection, p_ccb should be NULL */ + assert(p_ccb == NULL); + p_scb = obex_find_scb_by_tl_hdl(tl, msg->conn_income.svr_hdl); + if (p_scb == NULL) { + obex_cb.tl_ops[tl]->disconnect(tl_hdl); + break; + } + + if ((p_ccb = obex_allocate_ccb()) == NULL) { + obex_cb.tl_ops[tl]->disconnect(tl_hdl); + break; + } + + p_ccb->tl = tl; + p_ccb->tl_hdl = tl_hdl; + p_ccb->role = OBEX_ROLE_SERVER; + p_ccb->tl_peer_mtu = msg->conn_income.peer_mtu; + p_ccb->tl_our_mtu = msg->conn_income.our_mtu; + if (!check_conn_mtu_valid(p_ccb, FALSE)) { + break; + } + p_ccb->state = OBEX_STATE_OPENED; + p_ccb->callback = p_scb->callback; + if (p_ccb->callback) { + cb_msg.conn_income.svr_handle = p_scb->allocated << 8; + cb_msg.conn_income.peer_mtu = msg->conn_income.peer_mtu; + cb_msg.conn_income.our_mtu = msg->conn_income.our_mtu; + p_ccb->callback(p_ccb->allocated, OBEX_CONN_INCOME_EVT, &cb_msg); + } + break; + default: + OBEX_TRACE_ERROR("Unknown OBEX transport event: 0x%x\n", evt); + break; + } +} + +void obex_tl_l2cap_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg) +{ + obex_tl_evt_handler(OBEX_OVER_L2CAP, 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 new file mode 100644 index 000000000000..dfd423c73d9a --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c @@ -0,0 +1,807 @@ +/* + * 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/l2c_api.h" +#include "stack/l2cdefs.h" +#include "stack/btm_api.h" +#include "btm_int.h" +#include "obex_tl.h" +#include "obex_tl_l2cap.h" + +#if (OBEX_INCLUDED == TRUE) + +#define OBEX_TL_L2CAP_NUM_CONN 4 +#define OBEX_TL_L2CAP_NUM_SERVER 2 + +#define OBEX_TL_L2CAP_STATUS_FLAG_CFG_DONE 0x01 +#define OBEX_TL_L2CAP_STATUS_FLAG_PEER_CFG_DONE 0x02 +#define OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED 0x80 + +/* ERTM Tx window size */ +#define OBEX_TL_L2CAP_FCR_OPT_TX_WINDOW_SIZE 10 + +/* ERTM Maximum transmissions before disconnecting */ +#define OBEX_TL_L2CAP_FCR_OPT_MAX_TX_B4_DISCNT 20 + +/* ERTM Retransmission timeout (2 secs) */ +#define OBEX_TL_L2CAP_FCR_OPT_RETX_TOUT 2000 + +/* ERTM Monitor timeout (12 secs) */ +#define OBEX_TL_L2CAP_FCR_OPT_MONITOR_TOUT 12000 + +/* ERTM ERTM MPS segment size */ +#define OBEX_TL_L2CAP_FCR_OPT_MAX_PDU_SIZE 1010 + +typedef struct { + UINT16 vpsm; /* psm in our side */ + UINT16 lcid; /* local channel id */ + UINT16 peer_mtu; /* MTU that peer device set */ + UINT16 our_mtu; /* MTU that we set to peer device */ + BOOLEAN initiator; /* TRUE if is initiator, otherwise FALSE */ + UINT8 id; /* remote channel id */ + UINT8 status_flag; /* status flag used in config procedure */ + UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, equal to handle */ + BD_ADDR addr; /* peer bluetooth device address */ +} tOBEX_TL_L2CAP_CCB; + +typedef struct { + UINT16 psm; /* psm in our side */ + UINT16 pref_mtu; /* preferred mtu */ + UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, handle of server will left shift 8 bits */ +} tOBEX_TL_L2CAP_SCB; + +typedef struct { + tOBEX_TL_CBACK *callback; /* Upper layer callback */ + tOBEX_TL_L2CAP_CCB ccb[OBEX_TL_L2CAP_NUM_CONN]; + tOBEX_TL_L2CAP_SCB scb[OBEX_TL_L2CAP_NUM_SERVER]; + tL2CAP_APPL_INFO l2cap_reg_info; /* Default L2CAP Registration info */ + UINT8 trace_level; /* trace level */ +} tOBEX_TL_L2CAP_CB; + +#if OBEX_DYNAMIC_MEMORY == FALSE +static tOBEX_TL_L2CAP_CB obex_tl_l2cap_cb; +#else +static tOBEX_TL_L2CAP_CB *obex_tl_l2cap_cb_ptr = NULL; +#define obex_tl_l2cap_cb (*obex_tl_l2cap_cb_ptr) +#endif + +static tL2CAP_ERTM_INFO obex_tl_l2cap_etm_opts = +{ + L2CAP_FCR_ERTM_MODE, + L2CAP_FCR_CHAN_OPT_ERTM | L2CAP_FCR_CHAN_OPT_BASIC, /* Some devices do not support ERTM */ + L2CAP_USER_RX_BUF_SIZE, + L2CAP_USER_TX_BUF_SIZE, + L2CAP_FCR_RX_BUF_SIZE, + L2CAP_FCR_TX_BUF_SIZE +}; + +static tL2CAP_FCR_OPTS obex_tl_l2cap_fcr_opts = +{ + L2CAP_FCR_ERTM_MODE, + OBEX_TL_L2CAP_FCR_OPT_TX_WINDOW_SIZE, /* Tx window size */ + OBEX_TL_L2CAP_FCR_OPT_MAX_TX_B4_DISCNT, /* Maximum transmissions before disconnecting */ + OBEX_TL_L2CAP_FCR_OPT_RETX_TOUT, /* Retransmission timeout (2 secs) */ + OBEX_TL_L2CAP_FCR_OPT_MONITOR_TOUT, /* Monitor timeout (12 secs) */ + OBEX_TL_L2CAP_FCR_OPT_MAX_PDU_SIZE /* MPS segment size */ +}; + +static tOBEX_TL_L2CAP_CCB *allocate_ccb(void) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = NULL; + for(int i = 0; i < OBEX_TL_L2CAP_NUM_CONN; ++i) { + if (obex_tl_l2cap_cb.ccb[i].allocated == 0) { + obex_tl_l2cap_cb.ccb[i].allocated = i + 1; + p_ccb = &obex_tl_l2cap_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static void free_ccb(tOBEX_TL_L2CAP_CCB *p_ccb) +{ + memset(p_ccb, 0, sizeof(tOBEX_TL_L2CAP_CCB)); +} + +static tOBEX_TL_L2CAP_CCB *find_ccb_by_lcid(UINT16 lcid) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = NULL; + for (int i = 0; i < OBEX_TL_L2CAP_NUM_CONN; ++i) { + if (obex_tl_l2cap_cb.ccb[i].allocated && obex_tl_l2cap_cb.ccb[i].lcid == lcid) { + p_ccb = &obex_tl_l2cap_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tOBEX_TL_L2CAP_CCB *find_ccb_by_hdl(UINT16 hdl) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = NULL; + if (hdl > 0 && hdl <= OBEX_TL_L2CAP_NUM_CONN) { + if (obex_tl_l2cap_cb.ccb[hdl-1].allocated == hdl) { + p_ccb = &obex_tl_l2cap_cb.ccb[hdl-1]; + } + } + return p_ccb; +} + +static tOBEX_TL_L2CAP_CCB *find_ccb_by_psm(UINT16 psm) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = NULL; + for(int i = 0; i < OBEX_TL_L2CAP_NUM_CONN; ++i) { + if (obex_tl_l2cap_cb.ccb[i].allocated && obex_tl_l2cap_cb.ccb[i].vpsm == psm) { + p_ccb = &obex_tl_l2cap_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tOBEX_TL_L2CAP_SCB *allocate_scb(void) +{ + tOBEX_TL_L2CAP_SCB *p_scb = NULL; + for(int i = 0; i < OBEX_TL_L2CAP_NUM_SERVER; ++i) { + if (obex_tl_l2cap_cb.scb[i].allocated == 0) { + obex_tl_l2cap_cb.scb[i].allocated = i + 1; + p_scb = &obex_tl_l2cap_cb.scb[i]; + break; + } + } + return p_scb; +} + +static tOBEX_TL_L2CAP_SCB *find_scb_by_psm(UINT16 psm) +{ + tOBEX_TL_L2CAP_SCB *p_scb = NULL; + for(int i = 0; i < OBEX_TL_L2CAP_NUM_SERVER; ++i) { + if (obex_tl_l2cap_cb.scb[i].allocated && obex_tl_l2cap_cb.scb[i].psm == psm) { + p_scb = &obex_tl_l2cap_cb.scb[i]; + break; + } + } + return p_scb; +} + +static void free_scb(tOBEX_TL_L2CAP_SCB *p_scb) +{ + memset(p_scb, 0, sizeof(tOBEX_TL_L2CAP_SCB)); +} + +static tOBEX_TL_L2CAP_SCB *find_scb_by_hdl(UINT16 hdl) +{ + tOBEX_TL_L2CAP_SCB *p_scb = NULL; + hdl = hdl >> 8; + if (hdl > 0 && hdl <= OBEX_TL_L2CAP_NUM_SERVER) { + if (obex_tl_l2cap_cb.scb[hdl-1].allocated == hdl) { + p_scb = &obex_tl_l2cap_cb.scb[hdl-1]; + } + } + return p_scb; +} + +/******************************************************************************* + * + * Function obex_tl_l2cap_sec_check_complete_term + * + * Description OBEX over L2CAP security check complete callback function + * (terminated). + * + ******************************************************************************/ +static void l2cap_sec_check_complete_term(BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_data, UINT8 res) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = (tOBEX_TL_L2CAP_CCB *)p_ref_data; + + if (res == BTM_SUCCESS) { + L2CA_ErtmConnectRsp(p_ccb->addr, p_ccb->id, p_ccb->lcid, L2CAP_CONN_OK, 0, &obex_tl_l2cap_etm_opts); + tL2CAP_CFG_INFO cfg = {0}; + cfg.mtu_present = TRUE; + cfg.mtu = p_ccb->our_mtu; + cfg.fcr_present = TRUE; + cfg.fcr = obex_tl_l2cap_fcr_opts; + L2CA_ConfigReq(p_ccb->lcid, &cfg); + } + else{ + L2CA_ErtmConnectRsp(p_ccb->addr, p_ccb->id, p_ccb->lcid, L2CAP_CONN_SECURITY_BLOCK, 0, &obex_tl_l2cap_etm_opts); + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + } +} + +/******************************************************************************* + * + * Function obex_tl_l2cap_sec_check_complete_orig + * + * Description OBEX over L2CAP security check complete callback function + * (originated). + * + ******************************************************************************/ +static void l2cap_sec_check_complete_orig(BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_data, UINT8 res) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = (tOBEX_TL_L2CAP_CCB *)p_ref_data; + + if (res == BTM_SUCCESS) { + tL2CAP_CFG_INFO cfg = {0}; + cfg.mtu_present = TRUE; + cfg.mtu = p_ccb->our_mtu; + cfg.fcr_present = TRUE; + cfg.fcr = obex_tl_l2cap_fcr_opts; + L2CA_ConfigReq(p_ccb->lcid, &cfg); + } else { + L2CA_DisconnectReq(p_ccb->lcid); + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + } +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_connect_ind +** +** Description This is a callback function called by L2CAP when +** L2CA_ConnectInd received. +** +*******************************************************************************/ +void obex_tl_l2cap_connect_ind(BD_ADDR addr, UINT16 lcid, UINT16 psm, UINT8 id) +{ + tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_psm(psm); + if (p_scb == NULL) { + /* An incoming connection, but no corresponding server found, reject */ + L2CA_ErtmConnectRsp (addr, id, lcid, L2CAP_CONN_NO_PSM, 0, &obex_tl_l2cap_etm_opts); + OBEX_TL_L2CAP_TRACE_WARNING("No corresponding server found, reject incoming connection\n"); + return; + } + + tOBEX_TL_L2CAP_CCB *p_ccb = allocate_ccb(); + if (p_ccb == NULL) { + /* No resource, can not allocate ccb, reject */ + L2CA_ErtmConnectRsp (addr, id, lcid, L2CAP_CONN_NO_RESOURCES, 0, &obex_tl_l2cap_etm_opts); + OBEX_TL_L2CAP_TRACE_WARNING("Can not allocate a ccb for new connection\n"); + return; + } + + bdcpy(p_ccb->addr, addr); + p_ccb->initiator = FALSE; + p_ccb->lcid = lcid; + p_ccb->id = id; + p_ccb->vpsm = psm; + p_ccb->our_mtu = p_scb->pref_mtu; + if (btm_sec_mx_access_request(p_ccb->addr, p_ccb->vpsm, + FALSE, BTM_SEC_PROTO_OBEX, + OBEX_TL_L2CAP_NUM_CONN + p_scb->allocated - 1, + &l2cap_sec_check_complete_term, p_ccb) == BTM_CMD_STARTED) { + L2CA_ErtmConnectRsp(addr, id, lcid, L2CAP_CONN_PENDING, 0, &obex_tl_l2cap_etm_opts); + } +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_connect_cfm +** +** Description This is a callback function called by L2CAP when +** ConnectCfm received. +** +*******************************************************************************/ +void obex_tl_l2cap_connect_cfm(UINT16 lcid, UINT16 result) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid); + if (p_ccb == NULL) { + OBEX_TL_L2CAP_TRACE_ERROR("l2cap_connect_cfm but no ccb found\n"); + return; + } + + if (result == L2CAP_CONN_OK) { + btm_sec_mx_access_request(p_ccb->addr, p_ccb->vpsm, + TRUE, BTM_SEC_PROTO_OBEX, + p_ccb->allocated - 1, + &l2cap_sec_check_complete_orig, p_ccb); + } else { + OBEX_TL_L2CAP_TRACE_WARNING("l2cap_connect_cfm result != L2CAP_CONN_OK: result: 0x%x\n", result); + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + } +} + + +/******************************************************************************* +** +** Function obex_tl_l2cap_config_ind +** +** Description This is a callback function called by L2CAP when +** L2CA_ConfigInd received. +** +*******************************************************************************/ +void obex_tl_l2cap_config_ind(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid); + if (p_ccb == NULL) { + OBEX_TL_L2CAP_TRACE_ERROR("l2cap_config_ind but no ccb found\n"); + return; + } + + tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_psm(p_ccb->vpsm); + if (p_ccb->initiator == FALSE && p_scb == NULL) { + /* not a initiator, but can not find corresponding server */ + OBEX_TL_L2CAP_TRACE_ERROR("l2cap_config_ind, not a initiator, but can not find corresponding server\n"); + return; + } + + /* save peer mtu if present */ + UINT16 peer_mtu = L2CAP_DEFAULT_MTU; + if (p_cfg->mtu_present) { + peer_mtu = p_cfg->mtu; + } + + p_cfg->mtu_present = FALSE; + p_cfg->flush_to_present = FALSE; + p_cfg->qos_present = FALSE; + p_cfg->result = L2CAP_CFG_OK; + L2CA_ConfigRsp(p_ccb->lcid, p_cfg); + + if (p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED) { + /* reconfig l2cap channel */ + if (p_ccb->peer_mtu != peer_mtu) { + /* only report to upper if mtu change */ + p_ccb->peer_mtu = peer_mtu; + tOBEX_TL_MSG msg = {0}; + msg.mtu_chg.hdl = p_ccb->allocated; + msg.mtu_chg.peer_mtu = peer_mtu; + msg.mtu_chg.our_mtu = p_ccb->our_mtu; + obex_tl_l2cap_cb.callback(OBEX_TL_MTU_CHANGE_EVT, &msg); + } + return; + } + + p_ccb->peer_mtu = peer_mtu; + p_ccb->status_flag |= OBEX_TL_L2CAP_STATUS_FLAG_PEER_CFG_DONE; + if (p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_CFG_DONE) { + p_ccb->status_flag |= OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED; + } + + if ((p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED)) { + tOBEX_TL_MSG msg = {0}; + if (p_ccb->initiator) { + msg.conn_open.hdl = p_ccb->allocated; + msg.conn_open.peer_mtu = p_ccb->peer_mtu; + msg.conn_open.our_mtu = p_ccb->our_mtu; + obex_tl_l2cap_cb.callback(OBEX_TL_CONN_OPEN_EVT, &msg); + } + else { + msg.conn_income.hdl = p_ccb->allocated; + msg.conn_income.peer_mtu = p_ccb->peer_mtu; + msg.conn_income.our_mtu = p_ccb->our_mtu; + msg.conn_income.svr_hdl = p_scb->allocated << 8; + obex_tl_l2cap_cb.callback(OBEX_TL_CONN_INCOME_EVT, &msg); + } + } +} + + +/******************************************************************************* +** +** Function obex_tl_l2cap_config_cfm +** +** Description This is a callback function called by L2CAP when +** L2CA_ConfigCnf received. +** +*******************************************************************************/ +void obex_tl_l2cap_config_cfm(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid); + if (p_ccb == NULL) { + OBEX_TL_L2CAP_TRACE_ERROR("l2cap_config_cfm but no ccb found\n"); + return; + } + + tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_psm(p_ccb->vpsm); + if (p_ccb->initiator == FALSE && p_scb == NULL) { + /* not a initiator, but can not find corresponding server */ + OBEX_TL_L2CAP_TRACE_ERROR("l2cap_config_cfm, not a initiator, but can not find corresponding server\n"); + return; + } + + if (p_cfg->result != L2CAP_CFG_OK) { + OBEX_TL_L2CAP_TRACE_WARNING("l2cap_config_cfm result != L2CAP_CFG_OK: result: 0x%x\n", p_cfg->result); + L2CA_DisconnectReq(p_ccb->lcid); + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + return; + } + + p_ccb->status_flag |= OBEX_TL_L2CAP_STATUS_FLAG_CFG_DONE; + if (p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_PEER_CFG_DONE) { + p_ccb->status_flag |= OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED; + } + + if (p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED) { + tOBEX_TL_MSG msg = {0}; + if (p_ccb->initiator) { + msg.conn_open.hdl = p_ccb->allocated; + msg.conn_open.peer_mtu = p_ccb->peer_mtu; + msg.conn_open.our_mtu = p_ccb->our_mtu; + obex_tl_l2cap_cb.callback(OBEX_TL_CONN_OPEN_EVT, &msg); + } + else { + msg.conn_income.hdl = p_ccb->allocated; + msg.conn_income.peer_mtu = p_ccb->peer_mtu; + msg.conn_income.our_mtu = p_ccb->our_mtu; + msg.conn_income.svr_hdl = p_scb->allocated << 8; + obex_tl_l2cap_cb.callback(OBEX_TL_CONN_INCOME_EVT, &msg); + } + } +} + + +/******************************************************************************* +** +** Function obex_tl_l2cap_qos_violation_ind +** +** Description This is a callback function called by L2CAP when +** L2CA_QoSViolationIndInd received. +** +*******************************************************************************/ +void obex_tl_l2cap_qos_violation_ind(BD_ADDR addr) +{ + UNUSED(addr); +} + + +/******************************************************************************* +** +** Function obex_tl_l2cap_disconnect_ind +** +** Description This is a callback function called by L2CAP when +** L2CA_DisconnectInd received. +** +*******************************************************************************/ +void obex_tl_l2cap_disconnect_ind(UINT16 lcid, BOOLEAN is_conf_needed) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid); + + if (is_conf_needed) { + L2CA_DisconnectRsp(lcid); + } + + if (p_ccb == NULL) { + OBEX_TL_L2CAP_TRACE_ERROR("l2cap_disconnect_ind but no ccb found\n"); + return; + } + + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); +} + + +/******************************************************************************* +** +** Function obex_tl_l2cap_buf_data_ind +** +** Description This is a callback function called by L2CAP when +** data frame is received. +** +*******************************************************************************/ +void obex_tl_l2cap_buf_data_ind(UINT16 lcid, BT_HDR *p_buf) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid); + if (p_ccb == NULL) { + OBEX_TL_L2CAP_TRACE_ERROR("l2cap_buf_data_ind but no ccb found\n"); + osi_free(p_buf); + return; + } + + tOBEX_TL_MSG msg = {0}; + msg.data.hdl = p_ccb->allocated; + msg.data.p_buf = p_buf; + obex_tl_l2cap_cb.callback(OBEX_TL_DATA_EVT, &msg); +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_congestion_status_ind +** +** Description This is a callback function called by L2CAP when +** L2CAP channel congestion status changes +** +*******************************************************************************/ +void obex_tl_l2cap_congestion_status_ind(UINT16 lcid, BOOLEAN is_congested) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid); + + if (p_ccb == NULL) { + OBEX_TL_L2CAP_TRACE_ERROR("l2cap_congestion_status_ind but no ccb found\n"); + return; + } + + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + if (is_congested) { + obex_tl_l2cap_cb.callback(OBEX_TL_CONGEST_EVT, &msg); + } + else { + obex_tl_l2cap_cb.callback(OBEX_TL_UNCONGEST_EVT, &msg); + } +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_init +** +** Description Initialize OBEX over L2CAP transport layer, callback +** can not be NULL, must be called once before using any +** other APIs +** +*******************************************************************************/ +void obex_tl_l2cap_init(tOBEX_TL_CBACK callback) +{ + assert(callback != NULL); +#if (OBEX_DYNAMIC_MEMORY) + if (!obex_tl_l2cap_cb_ptr) { + obex_tl_l2cap_cb_ptr = (tOBEX_TL_L2CAP_CB *)osi_malloc(sizeof(tOBEX_TL_L2CAP_CB)); + if (!obex_tl_l2cap_cb_ptr) { + OBEX_TL_L2CAP_TRACE_ERROR("OBEX over L2CAP transport layer initialize failed, no memory\n"); + assert(0); + } + } +#endif /* #if (OBEX_DYNAMIC_MEMORY) */ + memset(&obex_tl_l2cap_cb, 0, sizeof(tOBEX_TL_L2CAP_CB)); + obex_tl_l2cap_cb.callback = callback; + obex_tl_l2cap_cb.trace_level = BT_TRACE_LEVEL_ERROR; + + tL2CAP_APPL_INFO *p_reg_info = &obex_tl_l2cap_cb.l2cap_reg_info; + + p_reg_info->pL2CA_ConnectInd_Cb = NULL; /* obex_tl_l2cap_connect_ind or NULL, depend on server or not */ + p_reg_info->pL2CA_ConnectCfm_Cb = obex_tl_l2cap_connect_cfm; + p_reg_info->pL2CA_ConnectPnd_Cb = NULL; + p_reg_info->pL2CA_ConfigInd_Cb = obex_tl_l2cap_config_ind; + p_reg_info->pL2CA_ConfigCfm_Cb = obex_tl_l2cap_config_cfm; + p_reg_info->pL2CA_DisconnectInd_Cb = obex_tl_l2cap_disconnect_ind; + p_reg_info->pL2CA_DisconnectCfm_Cb = NULL; + p_reg_info->pL2CA_QoSViolationInd_Cb = obex_tl_l2cap_qos_violation_ind; + p_reg_info->pL2CA_DataInd_Cb = obex_tl_l2cap_buf_data_ind; + p_reg_info->pL2CA_CongestionStatus_Cb = obex_tl_l2cap_congestion_status_ind; + p_reg_info->pL2CA_TxComplete_Cb = NULL; +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_init +** +** Description Deinitialize OBEX over L2CAP transport layer +** +*******************************************************************************/ +void obex_tl_l2cap_deinit(void) +{ +#if (OBEX_DYNAMIC_MEMORY) + if (obex_tl_l2cap_cb_ptr) { + osi_free(obex_tl_l2cap_cb_ptr); + obex_tl_l2cap_cb_ptr = NULL; + } +#endif /* #if (OBEX_DYNAMIC_MEMORY) */ +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_connect +** +** Description Start the process of establishing a L2CAP connection +** +** Returns Non-zeros handle, 0 while failed +** +*******************************************************************************/ +UINT16 obex_tl_l2cap_connect(tOBEX_TL_SVR_INFO *server) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = allocate_ccb(); + if (p_ccb == NULL) { + /* can not allocate a ccb */ + return 0; + } + + if (find_scb_by_psm(server->l2cap.psm) == NULL) { + /* if the psm not register by a server, we register it as outgoing only record */ + tL2CAP_APPL_INFO *p_reg_info = &obex_tl_l2cap_cb.l2cap_reg_info; + p_reg_info->pL2CA_ConnectInd_Cb = NULL; + p_ccb->vpsm = L2CA_Register(server->l2cap.psm, p_reg_info); + if (p_ccb->vpsm == 0) { + free_ccb(p_ccb); + return 0; + } + /* set security level */ + BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_OBEX, server->l2cap.sec_mask, p_ccb->vpsm, BTM_SEC_PROTO_OBEX, p_ccb->allocated - 1); + } + else { + p_ccb->vpsm = server->l2cap.psm; + } + + if (server->l2cap.pref_mtu == 0 || server->l2cap.pref_mtu > L2CAP_MTU_SIZE) { + p_ccb->our_mtu = L2CAP_MTU_SIZE; + } + else { + p_ccb->our_mtu = server->l2cap.pref_mtu; + } + p_ccb->initiator = TRUE; + p_ccb->lcid = L2CA_ErtmConnectReq(p_ccb->vpsm, server->l2cap.addr, &obex_tl_l2cap_etm_opts); + if (p_ccb->lcid == 0) { + free_ccb(p_ccb); + return 0; + } + + return p_ccb->allocated; +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_disconnect +** +** Description Disconnect a L2CAP connection +** +*******************************************************************************/ +void obex_tl_l2cap_disconnect(UINT16 hdl) +{ + tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_hdl(hdl); + if (p_ccb != NULL) { + L2CA_DisconnectReq(p_ccb->lcid); + if (p_ccb->initiator && find_scb_by_psm(p_ccb->vpsm) == NULL) { + L2CA_Deregister(p_ccb->vpsm); + } + free_ccb(p_ccb); + } +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_send_data +** +** Description Start the process of establishing a L2CAP connection +** +** Returns OBEX_TL_SUCCESS, if data accepted +** OBEX_TL_CONGESTED, if data accepted and the channel is congested +** OBEX_TL_FAILED, if error +** +*******************************************************************************/ +UINT16 obex_tl_l2cap_send_data(UINT16 hdl, BT_HDR *p_buf) +{ + UINT16 ret = OBEX_TL_FAILED; + tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_hdl(hdl); + if (p_ccb == NULL) { + osi_free(p_buf); + return ret; + } + + /* Can not send data size larger than peer MTU */ + /* Offset should not smaller than L2CAP_MIN_OFFSET */ + if (p_buf->len > p_ccb->peer_mtu || p_buf->offset < L2CAP_MIN_OFFSET) { + osi_free(p_buf); + return ret; + } + + UINT16 status = L2CA_DataWrite(p_ccb->lcid, p_buf); + switch (status) + { + case L2CAP_DW_SUCCESS: + ret = OBEX_TL_SUCCESS; + break; + case L2CAP_DW_CONGESTED: + ret = OBEX_TL_CONGESTED; + break; + default: + ret = OBEX_TL_FAILED; + break; + } + return ret; +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_bind +** +** Description Register a server in L2CAP module +** +** Returns Non-zeros handle, 0 while failed +** +*******************************************************************************/ +UINT16 obex_tl_l2cap_bind(tOBEX_TL_SVR_INFO *server) +{ + tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_psm(server->l2cap.psm); + + if (p_scb != NULL) { + /* psm already used */ + return 0; + } + + p_scb = allocate_scb(); + if (p_scb == NULL) { + /* can not allocate a new scb */ + return 0; + } + + if (server->l2cap.pref_mtu == 0 || server->l2cap.pref_mtu > L2CAP_MTU_SIZE) { + p_scb->pref_mtu= L2CAP_MTU_SIZE; + } + else { + p_scb->pref_mtu = server->l2cap.pref_mtu; + } + + tL2CAP_APPL_INFO *p_reg_info = &obex_tl_l2cap_cb.l2cap_reg_info; + p_reg_info->pL2CA_ConnectInd_Cb = obex_tl_l2cap_connect_ind; + /* Register a l2cap server, in this case, L2CA_Register always return the same psm as input */ + p_scb->psm = L2CA_Register(server->l2cap.psm, p_reg_info); + if (p_scb->psm == 0) { + free_scb(p_scb); + return 0; + } + /* set security level */ + BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_OBEX, server->l2cap.sec_mask, p_scb->psm, BTM_SEC_PROTO_OBEX, OBEX_TL_L2CAP_NUM_CONN + p_scb->allocated - 1); + BTM_SetSecurityLevel(FALSE, "", BTM_SEC_SERVICE_OBEX, server->l2cap.sec_mask, p_scb->psm, BTM_SEC_PROTO_OBEX, OBEX_TL_L2CAP_NUM_CONN + p_scb->allocated - 1); + /* left shift 8 bits to avoid confuse with connection handle */ + return p_scb->allocated << 8; +} + +/******************************************************************************* +** +** Function obex_tl_l2cap_unbind +** +** Description Deregister a server in L2CAP module +** +*******************************************************************************/ +void obex_tl_l2cap_unbind(UINT16 tl_hdl) +{ + tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_hdl(tl_hdl); + if (p_scb) { + tOBEX_TL_L2CAP_CCB *p_ccb = NULL; + while ((p_ccb = find_ccb_by_psm(p_scb->psm)) != NULL) { + L2CA_DisconnectReq(p_ccb->lcid); + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + } + L2CA_Deregister(p_scb->psm); + free_scb(p_scb); + } +} + +static tOBEX_TL_OPS obex_tl_l2cap_ops = { + .init = obex_tl_l2cap_init, + .deinit = obex_tl_l2cap_deinit, + .connect = obex_tl_l2cap_connect, + .disconnect = obex_tl_l2cap_disconnect, + .bind = obex_tl_l2cap_bind, + .unbind = obex_tl_l2cap_unbind, + .send = obex_tl_l2cap_send_data +}; + +/******************************************************************************* +** +** Function obex_tl_l2cap_ops_get +** +** Description Get the operation function structure pointer of OBEX over +** L2CAP transport layer +** +** Returns Pointer to operation function structure +** +*******************************************************************************/ +tOBEX_TL_OPS *obex_tl_l2cap_ops_get(void) +{ + return &obex_tl_l2cap_ops; +} + +#endif /* #if (OBEX_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/sdp/sdp_api.c b/components/bt/host/bluedroid/stack/sdp/sdp_api.c index 0103e4de0177..9b477a1c85cf 100644 --- a/components/bt/host/bluedroid/stack/sdp/sdp_api.c +++ b/components/bt/host/bluedroid/stack/sdp/sdp_api.c @@ -372,7 +372,7 @@ BOOLEAN SDP_FindServiceUUIDInRec(tSDP_DISC_REC *p_rec, tBT_UUID *p_uuid) if (SDP_DISC_ATTR_TYPE(p_sattr->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE) { /* Look through data element sequence until no more UUIDs */ for (p_extra_sattr = p_sattr->attr_value.v.p_sub_attr; p_extra_sattr; p_extra_sattr = p_extra_sattr->p_next_attr) { - /* Increment past this to see if the next attribut is UUID */ + /* Increment past this to see if the next attribute is UUID */ if ((SDP_DISC_ATTR_TYPE(p_extra_sattr->attr_len_type) == UUID_DESC_TYPE) /* only support 16 bits UUID for now */ && (SDP_DISC_ATTR_LEN(p_extra_sattr->attr_len_type) == 2)) { @@ -522,7 +522,7 @@ tSDP_DISC_REC *SDP_FindServiceInDb (tSDP_DISCOVERY_DB *p_db, UINT16 service_uuid if (SDP_DISC_ATTR_TYPE(p_sattr->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE) { /* Look through data element sequence until no more UUIDs */ for (p_extra_sattr = p_sattr->attr_value.v.p_sub_attr; p_extra_sattr; p_extra_sattr = p_extra_sattr->p_next_attr) { - /* Increment past this to see if the next attribut is UUID */ + /* Increment past this to see if the next attribute is UUID */ if ((SDP_DISC_ATTR_TYPE(p_extra_sattr->attr_len_type) == UUID_DESC_TYPE) && (SDP_DISC_ATTR_LEN(p_extra_sattr->attr_len_type) == 2) /* for a specific uuid, or any one */ @@ -768,6 +768,28 @@ BOOLEAN SDP_FindProtocolListElemInRec (tSDP_DISC_REC *p_rec, UINT16 layer_uuid, return (FALSE); } +/******************************************************************************* +** +** Function SDP_FindProtocolListElem +** +** Description This function looks at the protocol list for a specific protocol +** list element. +** +** Returns TRUE if found, FALSE if not +** If found, the passed protocol list element is filled in. +** +*******************************************************************************/ +BOOLEAN SDP_FindProtocolListElem (tSDP_DISC_ATTR *p_protocol_list, UINT16 layer_uuid, tSDP_PROTOCOL_ELEM *p_elem) +{ +#if SDP_CLIENT_ENABLED == TRUE + /* don't check the input protocol descriptor list id, this api may be use in additional protocol descriptor list */ + if ((SDP_DISC_ATTR_TYPE(p_protocol_list->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE)) { + return sdp_fill_proto_elem(p_protocol_list, layer_uuid, p_elem); + } +#endif + /* If here, no match found */ + return (FALSE); +} /******************************************************************************* ** diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md index 63d6e11bc865..072cacd12c83 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md @@ -36,7 +36,7 @@ idf.py menuconfig * Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Example Configuration -* Enable Classic Bluetooth and A2DP under **Component config --> Bluetooth --> Bluedroid Enable** +* For AVRCP CT Cover Art feature, is enabled by default, we can disable it by unselecting menuconfig option `Component config --> Bluetooth --> Bluedroid Options --> Classic Bluetooth --> AVRCP Features --> AVRCP CT Cover Art`. This example will try to use AVRCP CT Cover Art feature, get cover art image and count the image size if peer device support, this can be disable in `A2DP Example Configuration --> Use AVRCP CT Cover Art Feature`. ### Build and Flash @@ -66,7 +66,13 @@ I (122697) BT_AV: Audio packet count 100 I (124697) BT_AV: Audio packet count 200 I (126697) BT_AV: Audio packet count 300 I (128697) BT_AV: Audio packet count 400 +``` + +The output when receiving a cover art image: +``` +I (53349) RC_CT: AVRC metadata rsp: attribute id 0x80, 1000748 +I (53639) RC_CT: Cover Art Client final data event, image size: 14118 bytes ``` Also, the sound will be heard if a loudspeaker is connected and possible external I2S codec is correctly configured. For ESP32 A2DP source example, the sound is noise as the audio source generates the samples with a random sequence. diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild index 7edc3ead9de0..c276f7282a9c 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild @@ -53,4 +53,12 @@ menu "A2DP Example Configuration" default "ESP_SPEAKER" help Use this option to set local device name. + + config EXAMPLE_AVRCP_CT_COVER_ART_ENABLE + bool "Use AVRCP CT Cover Art Feature" + depends on BT_CLASSIC_ENABLED + default y + help + This enables the AVRCP Cover Art feature in example and try to get cover art image from peer device. + endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c index ceb10b3b6f0e..47c76babf2d8 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -93,6 +93,13 @@ i2s_chan_handle_t tx_chan = NULL; dac_continuous_handle_t tx_chan; #endif +#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE +static bool cover_art_connected = false; +static bool cover_art_getting = false; +static uint32_t cover_art_image_size = 0; +static uint8_t image_handle_old[7]; +#endif + /******************************** * STATIC FUNCTION DEFINITIONS *******************************/ @@ -107,6 +114,18 @@ static void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param) rc->meta_rsp.attr_text = attr_text; } +#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE +static bool image_handle_check(uint8_t *image_handle, int len) +{ + /* Image handle length must be 7 */ + if (len == 7 && memcmp(image_handle_old, image_handle, 7) != 0) { + memcpy(image_handle_old, image_handle, 7); + return true; + } + return false; +} +#endif + static void bt_av_new_track(void) { /* request metadata */ @@ -114,6 +133,11 @@ static void bt_av_new_track(void) ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_ALBUM | ESP_AVRC_MD_ATTR_GENRE; +#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE + if (cover_art_connected) { + attr_mask |= ESP_AVRC_MD_ATTR_COVER_ART; + } +#endif esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask); /* register notification if peer support the event_id */ @@ -364,7 +388,7 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) if (a2d->a2d_psc_cfg_stat.psc_mask & ESP_A2D_PSC_DELAY_RPT) { ESP_LOGI(BT_AV_TAG, "Peer device support delay reporting"); } else { - ESP_LOGI(BT_AV_TAG, "Peer device unsupport delay reporting"); + ESP_LOGI(BT_AV_TAG, "Peer device unsupported delay reporting"); } break; } @@ -415,15 +439,24 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) } break; } - /* when passthrough responsed, this event comes */ + /* when passthrough response, this event comes */ case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d, rsp_code %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state, rc->psth_rsp.rsp_code); break; } - /* when metadata responsed, this event comes */ + /* when metadata response, this event comes */ case ESP_AVRC_CT_METADATA_RSP_EVT: { ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); +#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE + if(rc->meta_rsp.attr_id == 0x80 && cover_art_connected && cover_art_getting == false) { + /* check image handle is valid and different with last one, wo dont want to get an image repeatedly */ + if(image_handle_check(rc->meta_rsp.attr_text, rc->meta_rsp.attr_length)) { + esp_avrc_ct_cover_art_get_linked_thumbnail(rc->meta_rsp.attr_text); + cover_art_getting = true; + } + } +#endif free(rc->meta_rsp.attr_text); break; } @@ -436,6 +469,13 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) /* when feature of remote device indicated, this event comes */ case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %"PRIx32", TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); +#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE + if ((rc->rmt_feats.tg_feat_flag & ESP_AVRC_FEAT_FLAG_TG_COVER_ART) && !cover_art_connected) { + ESP_LOGW(BT_RC_CT_TAG, "Peer support Cover Art feature, start connection..."); + /* set mtu to zero to use a default value */ + esp_avrc_ct_cover_art_connect(0); + } +#endif break; } /* when notification capability of peer device got, this event comes */ @@ -448,6 +488,36 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) bt_av_play_pos_changed(); break; } + case ESP_AVRC_CT_COVER_ART_STATE_EVT: { +#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE + if (rc->cover_art_state.state == ESP_AVRC_COVER_ART_CONNECTED) { + cover_art_connected = true; + ESP_LOGW(BT_RC_CT_TAG, "Cover Art Client connected"); + } + else { + cover_art_connected = false; + ESP_LOGW(BT_RC_CT_TAG, "Cover Art Client disconnected, reason:%d", rc->cover_art_state.reason); + } +#endif + break; + } + case ESP_AVRC_CT_COVER_ART_DATA_EVT: { +#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE + /* when rc->cover_art_data.final is true, it means we have received the entire image or get operation failed */ + if (rc->cover_art_data.final) { + if(rc->cover_art_data.status == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BT_RC_CT_TAG, "Cover Art Client final data event, image size: %lu bytes", cover_art_image_size); + } + else { + ESP_LOGE(BT_RC_CT_TAG, "Cover Art Client get operation failed"); + } + cover_art_image_size = 0; + /* set the getting state to false, we can get next image now */ + cover_art_getting = false; + } +#endif + break; + } /* others */ default: ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event); @@ -545,6 +615,14 @@ void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) { +#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE + /* we must handle ESP_AVRC_CT_COVER_ART_DATA_EVT in this callback, copy image data to other buff before return if need */ + if (event == ESP_AVRC_CT_COVER_ART_DATA_EVT && param->cover_art_data.status == ESP_BT_STATUS_SUCCESS) { + cover_art_image_size += param->cover_art_data.data_len; + /* copy image data to other place */ + /* memcpy(p_buf, param->cover_art_data.p_data, param->cover_art_data.data_len); */ + } +#endif switch (event) { case ESP_AVRC_CT_METADATA_RSP_EVT: bt_app_alloc_meta_buffer(param); @@ -553,7 +631,9 @@ void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: case ESP_AVRC_CT_REMOTE_FEATURES_EVT: - case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: + case ESP_AVRC_CT_COVER_ART_STATE_EVT: + case ESP_AVRC_CT_COVER_ART_DATA_EVT: { bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); break; } diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults index 63d63358d2fc..3d17667a4c26 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults @@ -7,4 +7,5 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_CLASSIC_ENABLED=y CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=y CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n