summaryrefslogtreecommitdiff
path: root/lib/bt/host/bluedroid/stack/obex
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2025-07-25 13:33:07 +1000
committerjacqueline <me@jacqueline.id.au>2025-07-25 13:33:07 +1000
commitc8e79a926620e48830778714cfe4b2ea2453fcaf (patch)
tree8c756e08e01b8e147cf72bec128026f46bd854c5 /lib/bt/host/bluedroid/stack/obex
parent237136f3e93cb6b5be24670d7520adb17cc0fa36 (diff)
downloadtangara-fw-c8e79a926620e48830778714cfe4b2ea2453fcaf.tar.gz
Update forked idf components
Diffstat (limited to 'lib/bt/host/bluedroid/stack/obex')
-rw-r--r--lib/bt/host/bluedroid/stack/obex/include/obex_int.h76
-rw-r--r--lib/bt/host/bluedroid/stack/obex/include/obex_tl.h97
-rw-r--r--lib/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h18
-rw-r--r--lib/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h18
-rw-r--r--lib/bt/host/bluedroid/stack/obex/obex_api.c776
-rw-r--r--lib/bt/host/bluedroid/stack/obex/obex_main.c216
-rw-r--r--lib/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c811
-rw-r--r--lib/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c439
8 files changed, 2451 insertions, 0 deletions
diff --git a/lib/bt/host/bluedroid/stack/obex/include/obex_int.h b/lib/bt/host/bluedroid/stack/obex/include/obex_int.h
new file mode 100644
index 00000000..28c4eab4
--- /dev/null
+++ b/lib/bt/host/bluedroid/stack/obex/include/obex_int.h
@@ -0,0 +1,76 @@
+/*
+ * 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)
+
+#if (RFCOMM_INCLUDED == TRUE)
+#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET /* should set to max value of all transport layer */
+#define OBEX_BT_HDR_RESERVE_LEN OBEX_TL_RFCOMM_BT_HDR_RESERVE_LEN /* should set to max value of all transport layer */
+#else
+#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_L2CAP_BT_HDR_OFFSET_MIN
+#define OBEX_BT_HDR_RESERVE_LEN OBEX_TL_L2CAP_BT_HDR_RESERVE_LEN
+#endif
+
+#define OBEX_ROLE_CLIENT 0x01
+#define OBEX_ROLE_SERVER 0x02
+
+/* 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 */
+
+/* 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);
+void obex_tl_rfcomm_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg);
+tOBEX_CCB *obex_allocate_ccb(void);
+tOBEX_SCB *obex_allocate_scb(void);
+void obex_free_ccb(tOBEX_CCB *p_ccb);
+void obex_free_scb(tOBEX_SCB *p_scb);
+
+#endif /* #if (OBEX_INCLUDED == TRUE) */
diff --git a/lib/bt/host/bluedroid/stack/obex/include/obex_tl.h b/lib/bt/host/bluedroid/stack/obex/include/obex_tl.h
new file mode 100644
index 00000000..9211b9b0
--- /dev/null
+++ b/lib/bt/host/bluedroid/stack/obex/include/obex_tl.h
@@ -0,0 +1,97 @@
+/*
+ * 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 struct
+{
+ UINT8 scn; /* service channel number */
+ UINT16 sec_mask; /* security mask */
+ UINT16 pref_mtu; /* preferred mtu, limited by rfcomm mtu */
+ BD_ADDR addr; /* peer bluetooth device address */
+} tOBEX_TL_RFCOMM_SVR;
+
+typedef union
+{
+ tOBEX_TL_L2CAP_SVR l2cap;
+ tOBEX_TL_RFCOMM_SVR rfcomm;
+} tOBEX_TL_SVR_INFO;
+
+typedef void (tOBEX_TL_CBACK)(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg);
+
+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/lib/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h b/lib/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h
new file mode 100644
index 00000000..32667766
--- /dev/null
+++ b/lib/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include "obex_tl.h"
+
+#if (OBEX_INCLUDED == TRUE)
+
+#define OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET 13 /* equal to L2CAP_MIN_OFFSET */
+#define OBEX_TL_L2CAP_BT_HDR_RESERVE_LEN 0 /* not require any additional byte */
+
+tOBEX_TL_OPS *obex_tl_l2cap_ops_get(void);
+
+#endif /* #if (OBEX_INCLUDED == TRUE) */
diff --git a/lib/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h b/lib/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h
new file mode 100644
index 00000000..61b9d295
--- /dev/null
+++ b/lib/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include "obex_tl.h"
+
+#if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE)
+
+#define OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET 18 /* RFCOMM_MIN_OFFSET + L2CAP_MIN_OFFSET */
+#define OBEX_TL_RFCOMM_BT_HDR_RESERVE_LEN 1 /* reserve 1 byte for rfcomm fcs */
+
+tOBEX_TL_OPS *obex_tl_rfcomm_ops_get(void);
+
+#endif /* #if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) */
diff --git a/lib/bt/host/bluedroid/stack/obex/obex_api.c b/lib/bt/host/bluedroid/stack/obex/obex_api.c
new file mode 100644
index 00000000..99276a93
--- /dev/null
+++ b/lib/bt/host/bluedroid/stack/obex/obex_api.c
@@ -0,0 +1,776 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <string.h>
+
+#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"
+#include "obex_tl_rfcomm.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 if (server->tl == OBEX_OVER_RFCOMM) {
+ tl_server->rfcomm.scn = server->rfcomm.scn;
+ tl_server->rfcomm.sec_mask = server->rfcomm.sec_mask;
+ tl_server->rfcomm.pref_mtu = server->rfcomm.pref_mtu;
+ bdcpy(tl_server->rfcomm.addr, server->rfcomm.addr);
+ }
+ else {
+ OBEX_TRACE_ERROR("Unsupported OBEX transport type\n");
+ assert(0);
+ }
+}
+
+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;
+ UINT16_TO_BE_FIELD(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);
+ }
+#if (RFCOMM_INCLUDED == TRUE)
+ obex_cb.tl_ops[OBEX_OVER_RFCOMM] = obex_tl_rfcomm_ops_get();
+ if (obex_cb.tl_ops[OBEX_OVER_RFCOMM]->init != NULL) {
+ obex_cb.tl_ops[OBEX_OVER_RFCOMM]->init(obex_tl_rfcomm_callback);
+ }
+#endif
+ obex_cb.trace_level = BT_TRACE_LEVEL_ERROR;
+ return OBEX_SUCCESS;
+}
+
+/*******************************************************************************
+**
+** 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 (RFCOMM_INCLUDED == TRUE)
+ if (obex_cb.tl_ops[OBEX_OVER_RFCOMM]->deinit != NULL) {
+ obex_cb.tl_ops[OBEX_OVER_RFCOMM]->deinit();
+ }
+#endif
+#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 + OBEX_BT_HDR_RESERVE_LEN;
+
+ 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 - OBEX_BT_HDR_RESERVE_LEN;
+ 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*/
+ UINT16_TO_BE_FIELD(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;
+ }
+
+ UINT16_TO_BE_FIELD(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 + OBEX_BT_HDR_RESERVE_LEN;
+
+ 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 - OBEX_BT_HDR_RESERVE_LEN;
+ 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 */
+ UINT16_TO_BE_FIELD(p_data, info->max_packet_length);
+ pkt_len += 4;
+ break;
+ default:
+ break;
+ }
+
+ UINT16_TO_BE_FIELD(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++;
+ UINT16_TO_BE_FIELD(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) {
+ return OBEX_INVALID_PARAM;
+ }
+
+ if ((data == NULL && data_len != 0) || (data != NULL && data_len == 0)) {
+ 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 */
+ UINT16_TO_BE_FIELD(p_start, header_len);
+ p_start+= 2;
+ }
+ if (data != NULL) {
+ /* store data */
+ memcpy(p_start, data, data_len);
+ }
+ pkt->len += header_len;
+ /* point to packet len */
+ p_data++;
+ UINT16_TO_BE_FIELD(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/lib/bt/host/bluedroid/stack/obex/obex_main.c b/lib/bt/host/bluedroid/stack/obex/obex_main.c
new file mode 100644
index 00000000..7810ff3c
--- /dev/null
+++ b/lib/bt/host/bluedroid/stack/obex/obex_main.c
@@ -0,0 +1,216 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <string.h>
+
+#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);
+}
+
+void obex_tl_rfcomm_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg)
+{
+ obex_tl_evt_handler(OBEX_OVER_RFCOMM, evt, msg);
+}
+
+#endif /* #if (OBEX_INCLUDED == TRUE) */
diff --git a/lib/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c b/lib/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c
new file mode 100644
index 00000000..d509b4cf
--- /dev/null
+++ b/lib/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c
@@ -0,0 +1,811 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <string.h>
+
+#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;
+ }
+
+ if (p_ccb->initiator && find_scb_by_psm(p_ccb->vpsm) == NULL) {
+ L2CA_Deregister(p_ccb->vpsm);
+ }
+
+ tOBEX_TL_MSG msg = {0};
+ msg.any.hdl = p_ccb->allocated;
+ obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
+ 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/lib/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c b/lib/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c
new file mode 100644
index 00000000..ce7799c8
--- /dev/null
+++ b/lib/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c
@@ -0,0 +1,439 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <string.h>
+
+#include "osi/osi.h"
+#include "osi/allocator.h"
+#include "common/bt_target.h"
+
+#include "stack/port_api.h"
+#include "stack/btm_api.h"
+#include "stack/sdpdefs.h"
+#include "obex_tl.h"
+#include "obex_tl_rfcomm.h"
+
+#if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE)
+
+#define OBEX_TL_RFCOMM_NUM_CONN 4
+#define OBEX_TL_RFCOMM_NUM_SERVER 2
+
+#define OBEX_TL_RFCOMM_EVENT_MARK (PORT_EV_FC | PORT_EV_FCS)
+
+typedef struct {
+ UINT16 rfc_handle; /* rfcomm handle */
+ UINT16 mtu; /* rfcomm mtu */
+ BOOLEAN initiator; /* TRUE if is initiator, otherwise FALSE */
+ UINT8 scn; /* service channel number */
+ BD_ADDR addr; /* peer bluetooth device address */
+ UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, equal to handle */
+} tOBEX_TL_RFCOMM_CCB;
+
+typedef struct {
+ UINT16 rfc_handle; /* rfcomm handle */
+ UINT8 scn; /* service channel number */
+ UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, handle of server will left shift 8 bits */
+} tOBEX_TL_RFCOMM_SCB;
+
+typedef struct {
+ tOBEX_TL_CBACK *callback; /* Upper layer callback */
+ tOBEX_TL_RFCOMM_CCB ccb[OBEX_TL_RFCOMM_NUM_CONN];
+ tOBEX_TL_RFCOMM_SCB scb[OBEX_TL_RFCOMM_NUM_SERVER];
+ UINT8 trace_level; /* trace level */
+} tOBEX_TL_RFCOMM_CB;
+
+#if OBEX_DYNAMIC_MEMORY == FALSE
+static tOBEX_TL_RFCOMM_CB obex_tl_rfcomm_cb;
+#else
+static tOBEX_TL_RFCOMM_CB *obex_tl_rfcomm_cb_ptr = NULL;
+#define obex_tl_rfcomm_cb (*obex_tl_rfcomm_cb_ptr)
+#endif
+
+static tOBEX_TL_RFCOMM_CCB *allocate_ccb(void)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = NULL;
+ for(int i = 0; i < OBEX_TL_RFCOMM_NUM_CONN; ++i) {
+ if (obex_tl_rfcomm_cb.ccb[i].allocated == 0) {
+ obex_tl_rfcomm_cb.ccb[i].allocated = i + 1;
+ p_ccb = &obex_tl_rfcomm_cb.ccb[i];
+ break;
+ }
+ }
+ return p_ccb;
+}
+
+static tOBEX_TL_RFCOMM_SCB *allocate_scb(void)
+{
+ tOBEX_TL_RFCOMM_SCB *p_scb = NULL;
+ for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) {
+ if (obex_tl_rfcomm_cb.scb[i].allocated == 0) {
+ obex_tl_rfcomm_cb.scb[i].allocated = i + 1;
+ p_scb = &obex_tl_rfcomm_cb.scb[i];
+ break;
+ }
+ }
+ return p_scb;
+}
+
+static void free_ccb(tOBEX_TL_RFCOMM_CCB *p_ccb)
+{
+ memset(p_ccb, 0, sizeof(tOBEX_TL_RFCOMM_CCB));
+}
+
+static void free_scb(tOBEX_TL_RFCOMM_SCB *p_scb)
+{
+ memset(p_scb, 0, sizeof(tOBEX_TL_RFCOMM_SCB));
+}
+
+static tOBEX_TL_RFCOMM_CCB *find_ccb_by_handle(UINT16 handle)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = NULL;
+ if (handle > 0 && handle <= OBEX_TL_RFCOMM_NUM_CONN) {
+ if (obex_tl_rfcomm_cb.ccb[handle-1].allocated == handle) {
+ p_ccb = &obex_tl_rfcomm_cb.ccb[handle-1];
+ }
+ }
+ return p_ccb;
+}
+
+static tOBEX_TL_RFCOMM_CCB *find_ccb_by_rfc_handle(UINT16 rfc_handle)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = NULL;
+ for(int i = 0; i < OBEX_TL_RFCOMM_NUM_CONN; ++i) {
+ if (obex_tl_rfcomm_cb.ccb[i].allocated && obex_tl_rfcomm_cb.ccb[i].rfc_handle == rfc_handle) {
+ p_ccb = &obex_tl_rfcomm_cb.ccb[i];
+ break;
+ }
+ }
+ return p_ccb;
+}
+
+static tOBEX_TL_RFCOMM_SCB *find_scb_by_handle(UINT16 handle)
+{
+ tOBEX_TL_RFCOMM_SCB *p_scb = NULL;
+ handle = handle >> 8;
+ if (handle > 0 && handle <= OBEX_TL_RFCOMM_NUM_SERVER) {
+ if (obex_tl_rfcomm_cb.scb[handle-1].allocated == handle) {
+ p_scb = &obex_tl_rfcomm_cb.scb[handle-1];
+ }
+ }
+ return p_scb;
+}
+
+static tOBEX_TL_RFCOMM_SCB *find_scb_by_rfc_handle(UINT16 rfc_handle)
+{
+ tOBEX_TL_RFCOMM_SCB *p_scb = NULL;
+ for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) {
+ if (obex_tl_rfcomm_cb.scb[i].allocated && obex_tl_rfcomm_cb.scb[i].rfc_handle == rfc_handle) {
+ p_scb = &obex_tl_rfcomm_cb.scb[i];
+ break;
+ }
+ }
+ return p_scb;
+}
+
+static tOBEX_TL_RFCOMM_SCB *find_scb_by_scn(UINT16 scn)
+{
+ tOBEX_TL_RFCOMM_SCB *p_scb = NULL;
+ for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) {
+ if (obex_tl_rfcomm_cb.scb[i].allocated && obex_tl_rfcomm_cb.scb[i].scn == scn) {
+ p_scb = &obex_tl_rfcomm_cb.scb[i];
+ break;
+ }
+ }
+ return p_scb;
+}
+
+static void rfcomm_mgmt_event_handler(tOBEX_TL_RFCOMM_CCB *p_ccb, UINT32 code)
+{
+ tOBEX_TL_MSG msg = {0};
+ msg.any.hdl = p_ccb->allocated;
+ switch (code)
+ {
+ case PORT_SUCCESS:
+ /* event already handled, do nothing */
+ break;
+ default:
+ /* other event, disconnect */
+ obex_tl_rfcomm_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
+ free_ccb(p_ccb);
+ break;
+ }
+}
+
+static void rfcomm_client_mgmt_callback(UINT32 code, UINT16 rfc_handle, void* data)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle);
+ if (p_ccb == NULL) {
+ OBEX_TL_RFCOMM_TRACE_DEBUG("No ccb to handle rfcomm event\n");
+ return;
+ }
+ /* connection opened, handle event here */
+ if (code == PORT_SUCCESS) {
+ assert(data != NULL);
+ tPORT_MGMT_CL_CALLBACK_ARG *cl_mgmt_cb_arg = (tPORT_MGMT_CL_CALLBACK_ARG *)data;
+ p_ccb->mtu = cl_mgmt_cb_arg->peer_mtu;
+
+ tOBEX_TL_MSG msg = {0};
+ msg.conn_open.hdl = p_ccb->allocated;
+ msg.conn_open.peer_mtu = p_ccb->mtu;
+ msg.conn_open.our_mtu = p_ccb->mtu;
+ obex_tl_rfcomm_cb.callback(OBEX_TL_CONN_OPEN_EVT, &msg);
+ }
+ rfcomm_mgmt_event_handler(p_ccb, code);
+}
+
+static void rfcomm_server_mgmt_callback(UINT32 code, UINT16 rfc_handle, void* data)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = NULL;
+ /* incoming connection, handle event here */
+ if (code == PORT_SUCCESS) {
+ assert(data != NULL);
+ tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_rfc_handle(rfc_handle);
+ tPORT_MGMT_SR_CALLBACK_ARG *sr_mgmt_cb_arg = (tPORT_MGMT_SR_CALLBACK_ARG *)data;
+ if (p_scb == NULL) {
+ OBEX_TL_RFCOMM_TRACE_WARNING("No scb to this rfcomm connection\n");
+ /* tell rfcomm to reject this connection */
+ sr_mgmt_cb_arg->accept = FALSE;
+ return;
+ }
+
+ /* try to find p_ccb with this rfc_handle, we expect to get a NULL */
+ p_ccb = find_ccb_by_rfc_handle(rfc_handle);
+ if (p_ccb == NULL) {
+ p_ccb = allocate_ccb();
+ if (p_ccb == NULL) {
+ OBEX_TL_RFCOMM_TRACE_WARNING("can not allocate a ccb for new connection\n");
+ sr_mgmt_cb_arg->accept = FALSE;
+ return;
+ }
+ }
+ else {
+ OBEX_TL_RFCOMM_TRACE_WARNING("found duplicate rfcomm connection\n");
+ }
+
+ p_ccb->initiator = FALSE;
+ p_ccb->rfc_handle = rfc_handle;
+ p_ccb->scn = p_scb->scn;
+ p_ccb->mtu = sr_mgmt_cb_arg->peer_mtu;
+ /* get peer bd_addr */
+ PORT_CheckConnection(rfc_handle, FALSE, p_ccb->addr, NULL);
+
+ tOBEX_TL_MSG msg = {0};
+ msg.conn_income.hdl = p_ccb->allocated;
+ msg.conn_income.peer_mtu = p_ccb->mtu;
+ msg.conn_income.our_mtu = p_ccb->mtu;
+ msg.conn_income.svr_hdl = (p_scb->allocated << 8);
+ obex_tl_rfcomm_cb.callback(OBEX_TL_CONN_INCOME_EVT, &msg);
+ }
+ else {
+ /* other event, it means server is connected */
+ p_ccb = find_ccb_by_rfc_handle(rfc_handle);
+ if (p_ccb == NULL) {
+ OBEX_TL_RFCOMM_TRACE_DEBUG("No ccb to handle rfcomm event\n");
+ return;
+ }
+ }
+ rfcomm_mgmt_event_handler(p_ccb, code);
+}
+
+static int rfcomm_data_callback(UINT16 rfc_handle, UINT8 *p_buf, UINT16 len, int type)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle);
+ if (p_ccb != NULL && type == DATA_CO_CALLBACK_TYPE_INCOMING) {
+ tOBEX_TL_MSG msg = {0};
+ msg.data.hdl = p_ccb->allocated;
+ msg.data.p_buf = (BT_HDR *)p_buf;
+ obex_tl_rfcomm_cb.callback(OBEX_TL_DATA_EVT, &msg);
+ PORT_FlowControl_GiveCredit(rfc_handle, TRUE, 1);
+ }
+ else if(p_buf != NULL) {
+ osi_free(p_buf);
+ }
+ return 1;
+}
+
+static void rfcomm_event_callback(UINT32 code, UINT16 rfc_handle)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle);
+ if (p_ccb == NULL) {
+ OBEX_TL_RFCOMM_TRACE_WARNING("No ccb to handle rfcomm event\n");
+ return;
+ }
+
+ if (code & PORT_EV_FC) {
+ tOBEX_TL_MSG msg = {0};
+ msg.any.hdl = p_ccb->allocated;
+ if (code & PORT_EV_FCS) {
+ obex_tl_rfcomm_cb.callback(OBEX_TL_UNCONGEST_EVT, &msg);
+ }
+ else {
+ obex_tl_rfcomm_cb.callback(OBEX_TL_CONGEST_EVT, &msg);
+ }
+ }
+}
+
+void obex_tl_rfcomm_init(tOBEX_TL_CBACK *callback)
+{
+ assert(callback != NULL);
+#if (OBEX_DYNAMIC_MEMORY)
+ if (!obex_tl_rfcomm_cb_ptr) {
+ obex_tl_rfcomm_cb_ptr = (tOBEX_TL_RFCOMM_CB *)osi_malloc(sizeof(tOBEX_TL_RFCOMM_CB));
+ if (!obex_tl_rfcomm_cb_ptr) {
+ OBEX_TL_RFCOMM_TRACE_ERROR("OBEX over RFCOMM transport layer initialize failed, no memory\n");
+ assert(0);
+ }
+ }
+#endif /* #if (OBEX_DYNAMIC_MEMORY) */
+ memset(&obex_tl_rfcomm_cb, 0, sizeof(tOBEX_TL_RFCOMM_CB));
+ obex_tl_rfcomm_cb.callback = callback;
+ obex_tl_rfcomm_cb.trace_level = BT_TRACE_LEVEL_ERROR;
+}
+
+void obex_tl_rfcomm_deinit(void)
+{
+#if (OBEX_DYNAMIC_MEMORY)
+ if (obex_tl_rfcomm_cb_ptr) {
+ osi_free(obex_tl_rfcomm_cb_ptr);
+ obex_tl_rfcomm_cb_ptr = NULL;
+ }
+#endif /* #if (OBEX_DYNAMIC_MEMORY) */
+}
+
+UINT16 obex_tl_rfcomm_connect(tOBEX_TL_SVR_INFO *server)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = allocate_ccb();
+ if (p_ccb == NULL) {
+ return 0;
+ }
+
+ BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_OBEX, server->rfcomm.sec_mask, BT_PSM_RFCOMM, BTM_SEC_PROTO_RFCOMM, server->rfcomm.scn);
+ if (RFCOMM_CreateConnection(UUID_PROTOCOL_OBEX, server->rfcomm.scn, FALSE, server->rfcomm.pref_mtu,
+ server->rfcomm.addr, &p_ccb->rfc_handle, rfcomm_client_mgmt_callback) != PORT_SUCCESS) {
+ free_ccb(p_ccb);
+ return 0;
+ }
+
+ /* set up data callback, event mask and event callback */
+ PORT_SetDataCOCallback(p_ccb->rfc_handle, rfcomm_data_callback);
+ PORT_SetEventMask(p_ccb->rfc_handle, OBEX_TL_RFCOMM_EVENT_MARK);
+ PORT_SetEventCallback(p_ccb->rfc_handle, rfcomm_event_callback);
+
+ bdcpy(p_ccb->addr, server->rfcomm.addr);
+ p_ccb->scn = server->rfcomm.scn;
+ p_ccb->initiator = TRUE;
+
+ return p_ccb->allocated;
+}
+
+void obex_tl_rfcomm_disconnect(UINT16 handle)
+{
+ tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_handle(handle);
+ if (p_ccb != NULL) {
+ RFCOMM_RemoveConnection(p_ccb->rfc_handle);
+ free_ccb(p_ccb);
+ }
+}
+
+UINT16 obex_tl_rfcomm_send(UINT16 handle, BT_HDR *p_buf)
+{
+ UINT16 ret = OBEX_TL_FAILED;
+ tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_handle(handle);
+ do {
+ if (p_ccb == NULL) {
+ osi_free(p_buf);
+ break;
+ }
+
+ /* Can not send data size larger than MTU */
+ /* Offset should not smaller than OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET */
+ if (p_buf->len > p_ccb->mtu || p_buf->offset < OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET) {
+ osi_free(p_buf);
+ break;
+ }
+
+ if (PORT_Write(p_ccb->rfc_handle, p_buf) == PORT_SUCCESS) {
+ ret = OBEX_TL_SUCCESS;
+ }
+ } while (0);
+ return ret;
+}
+
+UINT16 obex_tl_rfcomm_bind(tOBEX_TL_SVR_INFO *server)
+{
+ tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_scn(server->rfcomm.scn);
+ if (p_scb != NULL) {
+ /* scn already used */
+ return 0;
+ }
+
+ p_scb = allocate_scb();
+ if (p_scb == NULL) {
+ OBEX_TL_RFCOMM_TRACE_WARNING("Can not allocate scb, out of number\n");
+ return 0;
+ }
+
+ BTM_SetSecurityLevel(FALSE, "", BTM_SEC_SERVICE_OBEX, server->rfcomm.sec_mask, BT_PSM_RFCOMM, BTM_SEC_PROTO_RFCOMM, server->rfcomm.scn);
+ if (RFCOMM_CreateConnection(UUID_PROTOCOL_OBEX, server->rfcomm.scn, TRUE, server->rfcomm.pref_mtu,
+ server->rfcomm.addr, &p_scb->rfc_handle, rfcomm_server_mgmt_callback) != PORT_SUCCESS) {
+ free_scb(p_scb);
+ return 0;
+ }
+
+ /* set up data callback, event mask and event callback */
+ PORT_SetDataCOCallback(p_scb->rfc_handle, rfcomm_data_callback);
+ PORT_SetEventMask(p_scb->rfc_handle, OBEX_TL_RFCOMM_EVENT_MARK);
+ PORT_SetEventCallback(p_scb->rfc_handle, rfcomm_event_callback);
+
+ p_scb->scn = server->rfcomm.scn;
+
+ /* left shift 8 bits as server handle, avoid confuse with connection handle */
+ return (p_scb->allocated << 8);
+}
+
+void obex_tl_rfcomm_unbind(UINT16 handle)
+{
+ tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_handle(handle);
+ if (p_scb) {
+ tOBEX_TL_RFCOMM_CCB *p_ccb = NULL;
+ while ((p_ccb = find_ccb_by_rfc_handle(p_scb->rfc_handle)) != NULL) {
+ RFCOMM_RemoveConnection(p_ccb->rfc_handle);
+ tOBEX_TL_MSG msg = {0};
+ msg.any.hdl = p_ccb->allocated;
+ obex_tl_rfcomm_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
+ free_ccb(p_ccb);
+ }
+ RFCOMM_RemoveServer(p_scb->rfc_handle);
+ free_scb(p_scb);
+ }
+}
+
+static tOBEX_TL_OPS obex_tl_rfcomm_ops = {
+ .init = obex_tl_rfcomm_init,
+ .deinit = obex_tl_rfcomm_deinit,
+ .connect = obex_tl_rfcomm_connect,
+ .disconnect = obex_tl_rfcomm_disconnect,
+ .bind = obex_tl_rfcomm_bind,
+ .unbind = obex_tl_rfcomm_unbind,
+ .send = obex_tl_rfcomm_send
+};
+
+/*******************************************************************************
+**
+** Function obex_tl_rfcomm_ops_get
+**
+** Description Get the operation function structure pointer of OBEX over
+** RFCOMM transport layer
+**
+** Returns Pointer to operation function structure
+**
+*******************************************************************************/
+tOBEX_TL_OPS *obex_tl_rfcomm_ops_get(void)
+{
+ return &obex_tl_rfcomm_ops;
+}
+
+#endif /* #if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) */