/* * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "osi/osi.h" #include "osi/allocator.h" #include "common/bt_target.h" #include "stack/obex_api.h" #include "obex_int.h" #include "obex_tl.h" #include "obex_tl_l2cap.h" #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) */