/*************************************************************************************************/ /*! * \file * * \brief Enhanced ATT (EATT) server. * * Copyright (c) 2019-2020 Packetcraft, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*************************************************************************************************/ #include <string.h> #include "wsf_types.h" #include "wsf_assert.h" #include "wsf_trace.h" #include "wsf_timer.h" #include "wsf_msg.h" #include "wsf_math.h" #include "wsf_os.h" #include "util/bstream.h" #include "l2c_api.h" #include "l2c_main.h" #include "dm_api.h" #include "att_api.h" #include "att_main.h" #include "atts_main.h" #include "eatt_api.h" #include "att_eatt.h" #include "svc_core.h" /************************************************************************************************** Function Prototypes **************************************************************************************************/ static void eattsL2cCocDataInd(l2cCocEvt_t *pEvt); static void eattsL2cCocDataCnf(l2cCocEvt_t *pEvt); static void eattsConnCback(attCcb_t *pCcb, dmEvt_t *pDmEvt); /************************************************************************************************** Local Variables **************************************************************************************************/ /* Interface to ATT */ static const eattFcnIf_t attsFcnIf = { eattsL2cCocDataInd, eattsL2cCocDataCnf, (attMsgHandler_t) attsMsgCback, eattsConnCback }; /************************************************************************************************** Global Variables **************************************************************************************************/ extern attsProcFcn_t attsProcFcnTbl[]; extern const uint8_t attsMinPduLen[]; /*************************************************************************************************/ /*! * \brief Get a channel for transmission of an EATT server message. * * \param connId DM connection ID. * \param priority Operation priority. * \param dataLen Length of value data. * * \return Slot ID */ /*************************************************************************************************/ static uint8_t eattsGetFreeSlot(dmConnId_t connId, uint8_t priority, uint16_t dataLen) { eattConnCb_t *pCcb = eattGetConnCb(connId); attCcb_t *pAttCcb = attCcbByConnId(connId); if (pCcb && pAttCcb) { uint8_t i; for (i = 0; i < EATT_CONN_CHAN_MAX; i++) { eattChanCb_t *pChanCb = &pCcb->pChanCb[i]; if (pChanCb->inUse && (pChanCb->priority >= priority) && (pChanCb->localMtu >= dataLen)) { if (!(pAttCcb->sccb[i].control & ATT_CCB_STATUS_RSP_PENDING)) { EATT_TRACE_INFO1("eattsGetFreeSlot: allocating slot: %#x", i + 1); return i + 1; } } } } return ATT_BEARER_SLOT_ID; } /*************************************************************************************************/ /*! * \brief L2CAP CoC data indication callback function. * * \param pEvt Pointer to event structure. * * \return None. */ /*************************************************************************************************/ static void eattsL2cCocDataInd(l2cCocEvt_t *pEvt) { l2cCocDataInd_t *pDataInd = &pEvt->dataInd; uint8_t opcode; uint8_t method; uint8_t err; uint16_t attHandle; attsProcFcn_t procFcn; dmConnId_t connId = (dmConnId_t) pDataInd->hdr.param; uint8_t slot = eattGetSlotId(connId, pDataInd->cid); attCcb_t *pAttCcb = attCcbByConnId(connId); attsCcb_t *pAttsCcb = attsCcbByConnId(connId, slot); uint16_t len = pEvt->dataInd.dataLen; if (slot != ATT_BEARER_SLOT_INVALID) { /* parse opcode */ opcode = *(pDataInd->pData); /* get method */ if ((opcode <= ATT_PDU_WRITE_REQ) || ((opcode >= ATT_PDU_PREP_WRITE_REQ) && (opcode <= ATT_PDU_VALUE_CNF))) { method = ATT_OPCODE_2_METHOD(opcode); } else if (opcode == ATT_PDU_WRITE_CMD) { method = ATT_METHOD_WRITE_CMD; } else if (opcode == ATT_PDU_READ_MULT_VAR_REQ) { method = ATT_METHOD_READ_MULT_VAR; } else if (opcode == ATT_PDU_SIGNED_WRITE_CMD) { method = ATT_METHOD_SIGNED_WRITE_CMD; } else { method = ATT_METHOD_ERR; } /* ignore packet if write response is pending. */ if (pAttCcb->sccb[slot].control & ATT_CCB_STATUS_RSP_PENDING) { if (method != ATT_METHOD_VALUE_CNF) { return; } } /* check client's status to see if server is allowed to process this PDU. */ err = attsCsfActClientState(connId - 1, opcode, pEvt->dataInd.pData - L2C_PAYLOAD_START); if (err) { BYTES_TO_UINT16(attHandle, pEvt->dataInd.pData + ATT_HDR_LEN); } else { attHandle = ATT_HANDLE_NONE; } #if defined(ATTS_ERROR_TEST) && (ATTS_ERROR_TEST == TRUE) if (attCb.errTest != ATT_SUCCESS) { attsErrRsp(pAttCcb, ATT_BEARER_SLOT_ID, opcode, attHandle, attCb.errTest); return; } #endif /* if no error process request */ if (!err) { /* look up processing function */ procFcn = attsProcFcnTbl[method]; /* if method is supported */ if (procFcn != NULL) { /* verify length */ if (len >= attsMinPduLen[method]) { /* execute processing function */ (*procFcn)(pAttsCcb, len, pEvt->dataInd.pData - L2C_PAYLOAD_START); err = 0; } else { /* invalid PDU length */ err = ATT_ERR_INVALID_PDU; } } else { /* PDU not supported */ err = ATT_ERR_NOT_SUP; } } /* if there's an error and an error response can be sent for this opcode */ if (err && (opcode != ATT_PDU_MTU_REQ) && (opcode != ATT_PDU_VALUE_CNF) && ((opcode & ATT_PDU_MASK_COMMAND) == 0)) { attsErrRsp(pAttCcb, slot, opcode, attHandle, err); } } } /*************************************************************************************************/ /*! * \brief L2CAP CoC data confirm callback function. * * \param pEvt Pointer to event structure. * * \return None. */ /*************************************************************************************************/ static void eattsL2cCocDataCnf(l2cCocEvt_t *pEvt) { dmConnId_t connId = (dmConnId_t) pEvt->dataCnf.hdr.param; uint8_t slot = eattGetSlotId(connId, pEvt->dataCnf.cid); attsCcb_t *pCcb; /* note this function is currently only called when flow is enabled */ if (slot != ATT_BEARER_SLOT_INVALID) { /* get CCB */ if ((pCcb = attsCcbByConnId(connId, slot)) != NULL) { /* call pending indication and notification callback */ attsIndNtfCallback(connId, pCcb, ATT_SUCCESS); } } } /*************************************************************************************************/ /*! * \brief Connection callback for ATTS. * * \param pCcb ATT control block. * \param pDmEvt DM callback event. * * \return None. */ /*************************************************************************************************/ static void eattsConnCback(attCcb_t *pCcb, dmEvt_t *pDmEvt) { (void) pCcb; (void) pDmEvt; /* take no action */ } /*************************************************************************************************/ /*! * \brief Calculate the length of a list of handle length value tupples. * * \param numTuples The number of tuples. * \param pTupleList Pointer to a list of tuples. * * \return length of tupples. */ /*************************************************************************************************/ static uint16_t eattMultiNtfLen(uint16_t numTuples, eattTuple_t *pTupleList) { uint8_t i; uint16_t len = 0; for (i = 0; i < numTuples; i++) { len += pTupleList[i].len + sizeof(uint16_t) * 2; } return len; } /*************************************************************************************************/ /*! * \brief Send multiple attribute protocol Handle Value Notification. * * \param connId DM connection ID. * \param priority Operation priority. * \param numTuples The number of tuples. * \param pTupleList Pointer to a list of tuples. * * \return None. */ /*************************************************************************************************/ void EattsMultiValueNtf(dmConnId_t connId, uint8_t priority, uint16_t numTuples, eattTuple_t *pTupleList) { uint16_t valueLen = eattMultiNtfLen(numTuples, pTupleList); uint8_t slot; bool_t msgSent = FALSE; WsfTaskLock(); slot = eattsGetFreeSlot(connId, priority, valueLen + ATT_MULT_VALUE_NTF_BUF_LEN); WsfTaskUnlock(); if (slot) { /* Only send notifications and indications if client is aware of any database changes. */ if (attsCsfIsClientChangeAware(connId, 0)) { attsApiMsg_t *pMsg; uint8_t *p; uint8_t i; /* allocate message buffer */ if ((pMsg = WsfMsgAlloc(sizeof(attsApiMsg_t))) != NULL) { /* set parameters */ pMsg->hdr.param = connId; pMsg->hdr.event = ATTS_MSG_API_VALUE_IND_NTF; pMsg->slot = slot; pMsg->pPkt = attMsgAlloc(ATT_MULT_VALUE_NTF_BUF_LEN + valueLen); if (pMsg->pPkt != NULL) { /* set data length and handle (ind and ntf have same header length) */ pMsg->pPkt->len = ATT_PDU_MULT_VALUE_NTF_LEN + valueLen; /* build packet */ p = (uint8_t *)pMsg->pPkt + L2C_PAYLOAD_START; UINT8_TO_BSTREAM(p, ATT_PDU_MULT_VALUE_NTF); for (i = 0; i < numTuples; i++) { UINT16_TO_BSTREAM(p, pTupleList[i].handle); UINT16_TO_BSTREAM(p, pTupleList[i].len); memcpy(p, pTupleList[i].pValue, pTupleList[i].len); p += pTupleList[i].len; } /* send message */ WsfMsgSend(attCb.handlerId, pMsg); msgSent = TRUE; } else { /* free message buffer if packet buffer alloc failed */ WsfMsgFree(pMsg); } } } if (!msgSent) { /* Failed to send the packet, release the slot. */ attExecCallback(connId, ATTS_MULT_VALUE_CNF, 0, ATT_ERR_MEMORY, 0); } } else { /* call callback with no channel available status */ attExecCallback(connId, ATTS_MULT_VALUE_CNF, 0, ATT_ERR_NO_CHANNEL, 0); } } /*************************************************************************************************/ /*! * \brief Send an attribute protocol Handle Value Indication. * * \param connId DM connection ID. * \param priority Operation priority. * \param handle Attribute handle. * \param valueLen Length of value data. * \param pValue Pointer to value data. * * \return None. */ /*************************************************************************************************/ void EattsHandleValueInd(dmConnId_t connId, uint8_t priority, uint16_t handle, uint16_t valueLen, uint8_t *pValue) { uint8_t slot = eattsGetFreeSlot(connId, priority, valueLen); attsHandleValueIndNtf(connId, handle, slot, valueLen, pValue, ATT_PDU_VALUE_IND, FALSE); } /*************************************************************************************************/ /*! * \brief Send an attribute protocol Handle Value Notification. * * \param connId DM connection ID. * \param priority Operation priority. * \param handle Attribute handle. * \param valueLen Length of value data. * \param pValue Pointer to value data. * * \return None. */ /*************************************************************************************************/ void EattsHandleValueNtf(dmConnId_t connId, uint8_t priority, uint16_t handle, uint16_t valueLen, uint8_t *pValue) { uint8_t slot = eattsGetFreeSlot(connId, priority, valueLen); attsHandleValueIndNtf(connId, handle, slot, valueLen, pValue, ATT_PDU_VALUE_NTF, FALSE); } /*************************************************************************************************/ /*! * \brief Send an attribute protocol Handle Value Indication without copying the attribute * value data. * * Note: attribute value buffer 'pValue' must be allocated with AttMsgAlloc(). * * \param connId DM connection ID. * \param priority Operation priority. * \param handle Attribute handle. * \param valueLen Length of value data. * \param pValue Pointer to value data. * * \return None. */ /*************************************************************************************************/ void EattsHandleValueIndZeroCpy(dmConnId_t connId, uint8_t priority, uint16_t handle, uint16_t valueLen, uint8_t *pValue) { uint8_t slot = eattsGetFreeSlot(connId, priority, valueLen); attsHandleValueIndNtf(connId, handle, slot, valueLen, pValue, ATT_PDU_VALUE_IND, TRUE); } /*************************************************************************************************/ /*! * \brief Send an attribute protocol Handle Value Notification without copying the attribute * value data. * * Note: attribute value buffer 'pValue' must be allocated with AttMsgAlloc(). * * \param connId DM connection ID. * \param priority Operation priority. * \param handle Attribute handle. * \param valueLen Length of value data. * \param pValue Pointer to value data. * * \return None. */ /*************************************************************************************************/ void EattsHandleValueNtfZeroCpy(dmConnId_t connId, uint8_t priority, uint16_t handle, uint16_t valueLen, uint8_t *pValue) { uint8_t slot = eattsGetFreeSlot(connId, priority, valueLen); attsHandleValueIndNtf(connId, handle, slot, valueLen, pValue, ATT_PDU_VALUE_NTF, TRUE); } /*************************************************************************************************/ /*! * \brief Send a response to a pending write request. For use with ATT_RSP_PENDING. * * \param connId Connection ID. * \param slot EATT channel slot ID. * \param handle Attribute handle. * \param status Status of the write request. * * \return None. * * \note When a higher layer returns ATT_RSP_PENDING to an ATT write callback indicating the * response status is pending, the higher layer must subsequently call this function * with the status of the write request. */ /*************************************************************************************************/ void EattsContinueWriteReq(dmConnId_t connId, uint8_t slot, uint16_t handle, uint8_t status) { attCcb_t *pCcb; uint8_t *pBuf; uint8_t *p; /* get connection cb for this handle */ if ((pCcb = attCcbByConnId(connId)) == NULL) { return; } if (pCcb->sccb[slot].control & ATT_CCB_STATUS_RSP_PENDING) { /* clear response pending */ pCcb->sccb[slot].control &= ~ATT_CCB_STATUS_RSP_PENDING; if (status) { attsErrRsp(pCcb, slot, ATT_PDU_WRITE_REQ, handle, status); } else { if ((pBuf = attMsgAlloc(L2C_PAYLOAD_START + ATT_WRITE_RSP_LEN)) != NULL) { /* build and send PDU */ p = pBuf + L2C_PAYLOAD_START; UINT8_TO_BSTREAM(p, ATT_PDU_WRITE_RSP); attL2cDataReq(pCcb, slot, ATT_WRITE_RSP_LEN, pBuf); } } } } /*************************************************************************************************/ /*! * \brief Initialize the Enhanced ATT Server. * * \return None */ /*************************************************************************************************/ void EattsInit() { /* set up callback interface */ attCb.pEnServer = &attsFcnIf; }