feat(bt/bluedroid): Support OBEX over L2CAP

This commit is contained in:
linruihao 2024-08-02 12:01:27 +08:00 committed by BOT
parent 928addee19
commit da858edb7a
8 changed files with 2233 additions and 0 deletions

View File

@ -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
/******************************************************************************
**

View File

@ -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) */

View File

@ -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) */

View File

@ -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) */

View File

@ -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) */

View File

@ -0,0 +1,764 @@
/*
* 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"
#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) */

View File

@ -0,0 +1,211 @@
/*
* 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);
}
#endif /* #if (OBEX_INCLUDED == TRUE) */

View File

@ -0,0 +1,807 @@
/*
* 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;
}
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) */