From da858edb7ab54d0de38569bf6498ffc6208e1081 Mon Sep 17 00:00:00 2001 From: linruihao Date: Fri, 2 Aug 2024 12:01:27 +0800 Subject: [PATCH] feat(bt/bluedroid): Support OBEX over L2CAP --- .../common/include/common/bt_target.h | 9 + .../bluedroid/stack/include/stack/obex_api.h | 264 ++++++ .../bluedroid/stack/obex/include/obex_int.h | 73 ++ .../bluedroid/stack/obex/include/obex_tl.h | 88 ++ .../stack/obex/include/obex_tl_l2cap.h | 17 + .../bt/host/bluedroid/stack/obex/obex_api.c | 764 +++++++++++++++++ .../bt/host/bluedroid/stack/obex/obex_main.c | 211 +++++ .../host/bluedroid/stack/obex/obex_tl_l2cap.c | 807 ++++++++++++++++++ 8 files changed, 2233 insertions(+) create mode 100644 components/bt/host/bluedroid/stack/include/stack/obex_api.h create mode 100644 components/bt/host/bluedroid/stack/obex/include/obex_int.h create mode 100644 components/bt/host/bluedroid/stack/obex/include/obex_tl.h create mode 100644 components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h create mode 100644 components/bt/host/bluedroid/stack/obex/obex_api.c create mode 100644 components/bt/host/bluedroid/stack/obex/obex_main.c create mode 100644 components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c 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 4227ef38e9..4310bd162a 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -1809,6 +1809,15 @@ #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 /****************************************************************************** ** 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 0000000000..e130a48734 --- /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/obex/include/obex_int.h b/components/bt/host/bluedroid/stack/obex/include/obex_int.h new file mode 100644 index 0000000000..4512e89c2a --- /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 0000000000..6a60ed51dc --- /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 0000000000..83f062bb69 --- /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 0000000000..befabb0e92 --- /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 0000000000..c5f0453902 --- /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 0000000000..dfd423c73d --- /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) */