mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-15 22:44:19 +00:00
812 lines
27 KiB
C
812 lines
27 KiB
C
/*
|
|
* 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) */
|