| #include <stdlib.h> |
| #include <string.h> |
| #include "workQueue.h" |
| #include "persist.h" |
| #include "config.h" |
| #include "timer.h" |
| #include "l2cap.h" |
| #include "types.h" |
| #include "uuid.h" |
| #include "uniq.h" |
| #include "util.h" |
| #include "log.h" |
| #include "att.h" |
| #include "bt.h" |
| #include "sg.h" |
| #include "mt.h" |
| #include "sm.h" |
| |
| |
| //TODO: proper error when reading chars that need encr! |
| |
| /* ERRORS */ |
| #define ATT_OPCODE_ERROR 0x01 /* struct attPduError */ |
| |
| /* REQUESTS */ |
| #define ATT_OPCODE_MTU_EXCH_REQ 0x02 /* struct attPduMtu */ |
| #define ATT_OPCODE_FIND_INFO_REQ 0x04 /* struct attPduFindInfoReq */ |
| #define ATT_OPCODE_FIND_BY_TYPE_VAL_REQ 0x06 /* struct attPduFindByTypeValReq */ |
| #define ATT_OPCODE_READ_BY_TYPE_REQ 0x08 /* struct attPduReadByTypeReq */ |
| #define ATT_OPCODE_READ_REQ 0x0A /* struct attPduReadReq */ |
| #define ATT_OPCODE_READ_BLOB_REQ 0x0C /* struct attPduReadBlobReq */ |
| #define ATT_OPCODE_READ_MULT_REQ 0x0E /* uint16_t[] - at least two handles required */ |
| #define ATT_OPCODE_READ_BY_GRP_TYPE_REQ 0x10 /* struct attPduReadByGrpTypeReq */ |
| #define ATT_OPCODE_WRITE_REQ 0x12 /* struct attPduWriteReq */ |
| #define ATT_OPCODE_PREPARE_WRITE_REQ 0x16 /* struct attPduPrepareWriteReq */ |
| #define ATT_OPCODE_EXECUTE_WRITE_REQ 0x18 /* struct attPduExecuteWriteReq */ |
| |
| /* RESPONSES */ |
| #define ATT_OPCODE_MTU_EXCH_RSP 0x03 /* struct attPduMtu */ |
| #define ATT_OPCODE_FIND_INFO_RSP 0x05 /* struct attPduFindInfoRsp */ |
| #define ATT_OPCODE_FIND_BY_TYPE_VAL_RSP 0x07 /* struct attPduFindByTypeValRspItem[] */ |
| #define ATT_OPCODE_READ_BY_TYPE_RSP 0x09 /* struct attPduReadByTypeRsp */ |
| #define ATT_OPCODE_READ_RSP 0x0B /* uint8_t[] */ |
| #define ATT_OPCODE_READ_BLOB_RSP 0x0D /* uint8_t[] */ |
| #define ATT_OPCODE_READ_MULT_RSP 0x0F /* uint8_t[] - values concatenated together */ |
| #define ATT_OPCODE_READ_BY_GRP_TYPE_RSP 0x11 /* struct attPduReadByGrpTypeRsp */ |
| #define ATT_OPCODE_WRITE_RSP 0x13 /* NO ADDITIONAL DATA SENT */ |
| #define ATT_OPCODE_PREPARE_WRITE_RSP 0x17 /* struct attPduPrepareWriteRsp */ |
| #define ATT_OPCODE_EXECUTE_WRITE_RSP 0x19 /* NO ADDITIONAL DATA SENT */ |
| |
| /* NOTIFICATIONS */ |
| #define ATT_OPCODE_HANDLE_VALUE_NOTIF 0x1B /* struct attPduHandleValueNotif */ |
| |
| /* INDICATIONS */ |
| #define ATT_OPCODE_HANDLE_VALUE_IND 0x1D /* struct attPduHandleValueInd */ |
| |
| /* CONFIRMATION */ |
| #define ATT_OPCODE_HANDLE_VALUE_CONF 0x1E /* NO ADDITIONAL DATA SENT */ |
| |
| /* COMMANDS */ |
| #define ATT_OPCODE_WRITE_CMD 0x52 /* struct attPduWriteCmd */ |
| #define ATT_OPCODE_SIGNED_WRITE_CMD 0xD2 /* struct attPduSignedWriteCmd */ |
| |
| |
| |
| #define ATT_FIND_INFO_RSP_FMT_16 0x01 /* attPduFindInfoData16 */ |
| #define ATT_FIND_INFO_RSP_FMT_128 0x02 /* attPduFindInfoData128 */ |
| |
| #define ATT_EXECUTE_WRITE_FLG_CANCEL 0x00 /* cancel all prepared writes */ |
| #define ATT_EXECUTE_WRITE_FLG_EXECUTE 0x01 /* perform all prepared writes */ |
| |
| |
| |
| struct attHdr { |
| uint8_t opcode; /* ATT_OPCODE_* */ |
| } __packed; |
| |
| struct attPduError { |
| uint8_t reqOpcode; |
| uint16_t handle; |
| uint8_t err; /* ATT_ERROR_* */ |
| } __packed; |
| |
| struct attPduMtu { |
| uint16_t mtu; |
| } __packed; |
| |
| struct attPduFindInfoReq { |
| uint16_t startHandle; |
| uint16_t endHandle; |
| }__packed; |
| |
| struct attPduFindInfoData16 { |
| uint16_t handle; |
| uint16_t uuid16; |
| } __packed; |
| |
| struct attPduFindInfoData128 { |
| uint16_t handle; |
| struct uuid uuid128; |
| } __packed; |
| |
| struct attPduFindInfoRsp { |
| uint8_t format; /* ATT_FIND_INFO_RSP_FMT_* */ |
| /* data here: attPduFindInfoData16[] or attPduFindInfoData128[] */ |
| } __packed; |
| |
| struct attPduFindByTypeValReq { |
| uint16_t startHandle; |
| uint16_t endHandle; |
| uint16_t uuid16; |
| /* value follows as a byte array */ |
| } __packed; |
| |
| struct attPduFindByTypeValRspItem { |
| uint16_t firstHandle; |
| uint16_t groupEndHandle; /* meaning defined by higher layer */ |
| } __packed; |
| |
| struct attPduReadByTypeReq { |
| uint16_t startHandle; |
| uint16_t endHandle; |
| /* either a 16 or a 128 bit uuid follows */ |
| } __packed; |
| |
| struct attHandleValuePair { |
| uint16_t handle; |
| /* value bytes here */ |
| } __packed; |
| |
| struct attPduReadByTypeRsp { |
| uint8_t handleValuePairSz; |
| /* attHandleValuePair array follows */ |
| } __packed; |
| |
| struct attPduReadReq { |
| uint16_t handle; |
| } __packed; |
| |
| struct attPduReadBlobReq { |
| uint16_t handle; |
| uint16_t offset; |
| } __packed; |
| |
| struct attPduReadByGrpTypeReq { |
| uint16_t startHandle; |
| uint16_t endHandle; |
| /* either a 16 or a 128 bit uuid follows */ |
| } __packed; |
| |
| struct attGrpValuePair { |
| uint16_t handle; |
| uint16_t groupEndHadle; |
| /* data here */ |
| } __packed; |
| |
| struct attPduReadByGrpTypeRsp { |
| uint8_t attGrpValuePairSz; |
| /* attGrpValuePair array follows */ |
| } __packed; |
| |
| struct attPduWriteReq { |
| uint16_t handle; |
| /* data follows */ |
| } __packed; |
| |
| struct attPduPrepareWriteReq { |
| uint16_t handle; |
| uint16_t offset; |
| /* data */ |
| } __packed; |
| |
| struct attPduPrepareWriteRsp { |
| uint16_t handle; |
| uint16_t offset; |
| /* data echo */ |
| } __packed; |
| |
| struct attPduExecuteWriteReq { |
| uint8_t flags; /* ATT_EXECUTE_WRITE_FLG_* */ |
| } __packed; |
| |
| struct attPduHandleValueNotif { |
| uint16_t handle; |
| /* data */ |
| } __packed; |
| |
| struct attPduHandleValueInd { |
| uint16_t handle; |
| /* data */ |
| } __packed; |
| |
| struct attPduWriteCmd { |
| uint16_t handle; |
| /* data */ |
| } __packed; |
| |
| struct attPduSignedWriteCmd { |
| uint16_t handle; |
| /* data */ |
| /* 12 bytes of signature follow from pdu type byte to last data byte */ |
| } __packed; |
| |
| |
| #define ATT_CONN_LIST_GROW_BY 16 |
| #define ATT_FIRST_CONTINUE_CALL 0xFF |
| |
| struct attSrvPendingOp { /* an op we have an outstanding callback for */ |
| |
| uint8_t origOpcode; /* set to 0 when nothing is in progress, since all possible in-progress opcodes are nonzero */ |
| sg replyInProgress; |
| uint16_t origStartHandle; |
| uint16_t startHandle; |
| uint16_t endHandle; |
| uint16_t mtu; |
| |
| union { |
| struct { |
| struct uuid uuid; |
| void *wantedVal; |
| uint16_t wantedLen; |
| uint16_t valGrpLen; |
| } findByTypeValue; |
| struct { |
| struct uuid uuid; |
| bool grpType; |
| uint16_t valGrpLen; |
| uint16_t itemLen; |
| } readByType; |
| struct { |
| uint16_t offset; |
| } read; |
| struct { |
| sg handles; |
| uint16_t lastReadHandle; |
| } readMult; |
| }; |
| }; |
| |
| struct attConn { |
| |
| /* list */ |
| struct attConn *next; |
| struct attConn *prev; |
| |
| /* comon for client and server */ |
| l2c_handle_t conn; |
| uint32_t mtu; |
| bool isEncr; |
| bool isMitmProtected; |
| bool isLE; |
| |
| /* used if we're acting as client on this connection */ |
| struct { |
| uniq_t ongoingTrans; /* is a timer ID */ |
| } cli; |
| |
| /* used if we're acting as server */ |
| struct { |
| att_range_t indRangeRef; |
| uniq_t indTimer; |
| uint64_t indRef; |
| uint16_t indOffset; |
| |
| att_cid_t cid; /* for higher layer */ |
| att_trans_t expectedTransId; |
| |
| struct attSrvPendingOp penOp; |
| } srv; |
| }; |
| |
| struct attConnListNode { |
| struct attConnListNode *next; |
| l2c_handle_t conn; |
| }; |
| |
| struct attSrvVal { |
| struct attSrvVal *next; |
| struct attSrvVal *prev; |
| struct uuid type; |
| uint16_t offset; |
| uint16_t grpLen; |
| uint8_t perms; |
| }; |
| |
| struct attSrvRange { |
| att_range_t rangeRef; |
| struct attSrvRange *next; |
| struct attSrvRange *prev; |
| uint16_t start; |
| uint16_t len; |
| struct attSrvVal *valsHead; |
| struct attSrvVal *valsTail; |
| struct attConnListNode *seenBy; |
| bool allowLE; |
| bool allowEDR; |
| }; |
| |
| struct attWorkItem { |
| uint8_t type; |
| l2c_handle_t who; |
| union { |
| struct { |
| att_cid_t cid; /* having both this and "who" here makes life a bit easier */ |
| } srv; |
| struct { |
| uniq_t trans; |
| } cli; |
| }; |
| |
| union { |
| struct { |
| att_range_t range; |
| uint16_t ofst; |
| att_trans_t transId; |
| uint16_t byteOfst; |
| uint16_t maxLen; |
| uint8_t reason; |
| } srvRead; |
| struct { |
| att_range_t range; |
| uint16_t ofst; |
| att_trans_t transId; |
| uint16_t byteOfst; |
| uint16_t len; |
| uint8_t reason; |
| /* data follows */ |
| } srvWrite; |
| struct { |
| att_trans_t transId; |
| bool exec; |
| } srvExecWrite; |
| struct { |
| att_range_t range; |
| uint16_t ofst; |
| uint64_t ref; |
| uint8_t evt; |
| } srvIndResult; |
| struct { |
| uint16_t mtu; |
| bool asServer; |
| } mtuInfo; |
| struct { |
| sem_t *sem; |
| } sync; |
| struct { |
| sg data; |
| } cliJustData; |
| struct { |
| uint16_t handle; |
| uint8_t err; |
| uint8_t errOpcode; |
| bool isLocalErr; |
| } cliErr; |
| struct { |
| uint16_t handle; |
| bool needsAck; |
| sg data; |
| } cliIndNotif; |
| }; |
| }; |
| |
| #define WORK_ATT_MTU_INFO 0 |
| #define WORK_ATT_SYNC 1 |
| #define WORK_SRV_READ_CALL 2 |
| #define WORK_SRV_WRITE_CALL 3 |
| #define WORK_SRV_EXEC_WRITE_CALL 4 |
| #define WORK_SRV_IND_RESULT 5 |
| #define WORK_CLI_FIND_INFO_CALL 6 |
| #define WORK_CLI_FIND_BY_TYPE_VAL_CALL 7 |
| #define WORK_CLI_READ_BY_TYPE_CALL 8 |
| #define WORK_CLI_READ_CALL 9 /* or read blob */ |
| #define WORK_CLI_READ_BY_GRP_TYPE_CALL 10 |
| #define WORK_CLI_WRITE_CALL 11 |
| #define WORK_CLI_ERR_CALL 12 |
| #define WORK_CLI_IND_NOTIF_CALL 13 |
| |
| |
| /* our state */ |
| static pthread_mutex_t mAttLock = PTHREAD_MUTEX_INITIALIZER; |
| static struct attSrvRange *mRangesHead; |
| static struct attSrvRange *mRangesTail; |
| |
| static attSrvTxCbk mTxCbk; |
| static attMtuCbk mSrvMtuCbk; |
| static attSrvIndResultCbk mIndCbk; |
| static attSrvValueExecWriteCbk mExecCbk; |
| static attSrvValueReadCbk mReadCbk; |
| static attSrvValueWriteCbk mWriteCbk; |
| |
| static attMtuCbk mCliMtuCbk; |
| static attCliReadCbk mCliReadF; |
| static attCliWriteCbk mCliWriteF; |
| static attCliError mCliErrF; |
| static attCliIndNotif mCliIndNotifF; |
| static attCliFindInfoCbk mCliFindInfoCbk; |
| static attCliFindByTypeValCbk mCliFindByTypeValCbk; |
| |
| static struct attConn *mConns; |
| static bool mInTeardown; |
| static struct workQueue *mWorkQ; |
| static pthread_t mWorker; |
| static unsigned mNumUsers = 0; /* of ATT itself */ |
| |
| |
| |
| |
| /* |
| * FUNCTION: attConnFindByL2cConn |
| * USE: Find a per-connection state given an l2cap connection handle |
| * PARAMS: who - l2cap connection handle |
| * RETURN: struct attConn or NULL if not found |
| * NOTES: plase call while holding mAttLock |
| */ |
| static struct attConn* attConnFindByL2cConn(l2c_handle_t who) |
| { |
| struct attConn *conn = mConns; |
| |
| while (conn && conn->conn != who) |
| conn = conn->next; |
| |
| return conn; |
| } |
| |
| /* |
| * FUNCTION: attConnFindBySrvCid |
| * USE: Find a per-connection state given a server "cid" value (used by android if sadly) |
| * PARAMS: srvCid - the cid |
| * RETURN: struct attConn or NULL if not found |
| * NOTES: plase call while holding mAttLock |
| */ |
| static struct attConn* attConnFindBySrvCid(att_cid_t srvCid) |
| { |
| struct attConn *conn = mConns; |
| |
| while (conn && conn->srv.cid != srvCid) |
| conn = conn->next; |
| |
| return conn; |
| } |
| |
| /* |
| * FUNCTION: attConnListFree |
| * USE: Free a conn-list |
| * PARAMS: listP - where list head is stored |
| * RETURN: NONE |
| * NOTES: call while holding mAttLock |
| */ |
| static void attConnListFree(struct attConnListNode **listP) |
| { |
| while (*listP) { |
| struct attConnListNode *t = (*listP)->next; |
| free(*listP); |
| *listP = t; |
| } |
| } |
| |
| /* |
| * FUNCTION: attConnListIsEmpty |
| * USE: See if a conn-list has items |
| * PARAMS: listP - where list head is stored |
| * RETURN: true if an item is found |
| * NOTES: call while holding mAttLock |
| */ |
| static bool attConnListIsEmpty(struct attConnListNode **listP) |
| { |
| return !*listP; |
| } |
| |
| /* |
| * FUNCTION: attConnListDel |
| * USE: Delete a given connection from a conn-list |
| * PARAMS: listP - where list head is stored |
| * conn - the connection to remove |
| * RETURN: NONE |
| * NOTES: call while holding mAttLock |
| */ |
| static void attConnListDel(struct attConnListNode **listP, l2c_handle_t conn) |
| { |
| struct attConnListNode *c = *listP, *p = NULL; |
| |
| while (c && c->conn != conn) { |
| p = c; |
| c = c->next; |
| } |
| |
| if (c) { |
| if (p) |
| p->next = c->next; |
| else |
| *listP = c->next; |
| |
| free(c); |
| } |
| } |
| |
| /* |
| * FUNCTION: attWorkItemAlloc |
| * USE: Allocate a work item of a given type |
| * PARAMS: type - the type |
| * extraSz - the extra size to allocate |
| * RETURN: success |
| * NOTES: |
| */ |
| static struct attWorkItem* attWorkItemAlloc(uint8_t type, uint32_t extraSz) |
| { |
| struct attWorkItem *wi = (struct attWorkItem*)calloc(1, sizeof(struct attWorkItem) + extraSz); |
| if (wi) |
| wi->type = type; |
| |
| return wi; |
| } |
| |
| /* |
| * FUNCTION: attSrvSchedCbkGetNextTid |
| * USE: Get the transaction ID to use |
| * PARAMS: conn - the per-connection data |
| * RETURN: the transaction ID to use |
| * NOTES: call with mAttLock held |
| */ |
| static att_trans_t attSrvSchedCbkGetNextTid(struct attConn* conn) |
| { |
| return conn->srv.expectedTransId = uniqGetNext(); |
| } |
| |
| /* |
| * FUNCTION: attCliScheduleErrorCbk |
| * USE: Schedule a call to the error callback |
| * PARAMS: trans - the transaction |
| * who - the peer connection handle |
| * handle - the handle value (ignored if local stack error) |
| * err - error number (ignored if local stack error) |
| * errOpcode - the opcode that caused the error |
| * isLocalErr - local error? |
| * RETURN: false on immediate failure |
| * NOTES: |
| */ |
| static bool attCliScheduleErrorCbk(uniq_t trans, l2c_handle_t who, uint16_t handle, uint8_t err, uint8_t errOpcode, bool isLocalErr) |
| { |
| struct attWorkItem *wi; |
| int tid; |
| |
| wi = attWorkItemAlloc(WORK_CLI_ERR_CALL, 0); |
| if (!wi) |
| return false; |
| |
| wi->who = who; |
| wi->cli.trans = trans; |
| wi->cliErr.handle = handle; |
| wi->cliErr.err = err; |
| wi->cliErr.errOpcode = errOpcode; |
| wi->cliErr.isLocalErr = isLocalErr; |
| |
| if (workQueuePut(mWorkQ, wi)) |
| return true; |
| |
| free(wi); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSrvSchedReadCbk |
| * USE: Schedule a call to the read callback |
| |
| * PARAMS: range - the handle range |
| * val - the value struct |
| * conn - the per-connection structure |
| * reason - reason value to pass to read callback |
| * byteOfst - byte offset to pass to read callback |
| * maxLen - maxLength to read |
| * RETURN: true if we should expect callback. false on immediate failute |
| * NOTES: call with mAttLock held Do not release the lock until all your state in conn->penOp is set up |
| */ |
| static bool attSrvSchedReadCbk(struct attSrvRange *range, struct attSrvVal* val, struct attConn* conn, uint8_t reason, uint16_t byteOfst, uint16_t maxLen) |
| { |
| struct attWorkItem *wi; |
| |
| wi = attWorkItemAlloc(WORK_SRV_READ_CALL, 0); |
| if (!wi) |
| return false; |
| |
| wi->who = conn->conn; |
| wi->srv.cid = conn->srv.cid; |
| wi->srvRead.range = range->rangeRef; |
| wi->srvRead.ofst = val->offset; |
| wi->srvRead.transId = attSrvSchedCbkGetNextTid(conn); |
| wi->srvRead.byteOfst = byteOfst; |
| wi->srvRead.maxLen = maxLen; |
| wi->srvRead.reason = reason; |
| |
| if (workQueuePut(mWorkQ, wi)) |
| return true; |
| |
| free(wi); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSrvSchedWriteCbk |
| * USE: Schedule a call to the write callback |
| * PARAMS: range - the handle range |
| * val - the value struct |
| * conn - the per-connection structure |
| * reason - reason value to pass to write callback |
| * replace - repalce the existing slot (else will fail if slot not empty) |
| * byteOfst - byte offset to pass to write callback |
| * data - the sg with the data to write |
| * RETURN: true if we should expect callback. false on immediate failute |
| * NOTES: call with mAttLock held Do not release the lock until all state in conn->penOp is set up. WILL NOT FREE "data". Caller must! |
| */ |
| static bool attSrvSchedWriteCbk(struct attSrvRange *range, struct attSrvVal* val, struct attConn* conn, uint8_t reason, uint16_t byteOfst, sg data) |
| { |
| uint16_t len = sgLength(data); |
| struct attWorkItem *wi; |
| const void *dataPtr = NULL; |
| bool needFree = false; |
| void *iter; |
| bool ret; |
| |
| wi = attWorkItemAlloc(WORK_SRV_WRITE_CALL, len); |
| if (!wi) |
| return false; |
| |
| sgSerialize(data, 0, len, wi + 1); |
| |
| wi->who = conn->conn; |
| wi->srv.cid = conn->srv.cid; |
| wi->srvWrite.range = range->rangeRef; |
| wi->srvWrite.ofst = val->offset; |
| wi->srvWrite.transId = attSrvSchedCbkGetNextTid(conn); |
| wi->srvWrite.byteOfst = byteOfst; |
| wi->srvWrite.len = len; |
| wi->srvWrite.reason = reason; |
| |
| if (workQueuePut(mWorkQ, wi)) { |
| sgFree(data); |
| return true; |
| } |
| |
| free(wi); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSrvSchedExecCbk |
| * USE: Schedule a call to the execute-write callback |
| * PARAMS: conn - the per-connection structure |
| * exec - true to execute, false to clear state |
| * RETURN: true if we should expect callback. false on immediate failute |
| * NOTES: call with mAttLock held Do not release the lock until all state in conn->penOp is set up. WILL NOT FREE "data". Caller must! |
| */ |
| static bool attSrvSchedExecCbk(struct attConn* conn, bool exec) |
| { |
| struct attWorkItem *wi; |
| |
| wi = attWorkItemAlloc(WORK_SRV_EXEC_WRITE_CALL, 0); |
| if (!wi) |
| return false; |
| |
| wi->who = conn->conn; |
| wi->srv.cid = conn->srv.cid; |
| wi->srvExecWrite.transId = attSrvSchedCbkGetNextTid(conn); |
| wi->srvExecWrite.exec = exec; |
| |
| if (workQueuePut(mWorkQ, wi)) |
| return true; |
| |
| free(wi); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSrvSchedIndResultCbk |
| * USE: Schedule a call to the indication result callback |
| * PARAMS: rangeRef - the range reference |
| * offst - the value offset in the range |
| * conn - the per-connection structure |
| * evt - what happened? |
| * ref - the reference value provided by the higher layer |
| * RETURN: success |
| * NOTES: call with mAttLock held Do not release the lock until all state in conn->penOp is set up |
| */ |
| static bool attSrvSchedIndResultCbk(att_range_t rangeRef, uint16_t offst, struct attConn* conn, uint8_t evt, uint64_t ref) |
| { |
| struct attWorkItem *wi; |
| |
| wi = attWorkItemAlloc(WORK_SRV_IND_RESULT, 0); |
| if (!wi) |
| return false; |
| |
| wi->who = conn->conn; |
| wi->srv.cid = conn->srv.cid; |
| wi->srvIndResult.range = rangeRef; |
| wi->srvIndResult.ofst = offst; |
| wi->srvIndResult.ref = ref; |
| wi->srvIndResult.evt = evt; |
| |
| if (workQueuePut(mWorkQ, wi)) |
| return true; |
| |
| free(wi); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSrvSchedMtuCbk |
| * USE: Schedule a call to the MTU callback |
| * PARAMS: conn - the per-connection structure |
| * mtu - the new mtu |
| * asServer - are we acting as server for this negotiation |
| * RETURN: success |
| * NOTES: call with mAttLock held Do not release the lock until all state in conn->penOp is set up |
| */ |
| static bool attSrvSchedMtuCbk(struct attConn* conn, uint32_t mtu, bool asServer) |
| { |
| struct attWorkItem *wi; |
| |
| wi = attWorkItemAlloc(WORK_ATT_MTU_INFO, 0); |
| if (!wi) |
| return false; |
| |
| wi->who = conn->conn; |
| wi->srv.cid = conn->srv.cid; |
| wi->mtuInfo.mtu = mtu; |
| wi->mtuInfo.asServer = asServer; |
| |
| if (workQueuePut(mWorkQ, wi)) |
| return true; |
| |
| free(wi); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSync |
| * USE: Verifies all pending att work before now is finished. Use this after deleting things to make sure all refs to them are gone |
| * PARAMS: NONE |
| * RETURN: success |
| * NOTES: blocks till done |
| */ |
| bool attSync(void) |
| { |
| struct attWorkItem *wi; |
| sem_t sem; |
| |
| wi = attWorkItemAlloc(WORK_ATT_SYNC, 0); |
| if (!wi) |
| goto fail_alloc; |
| |
| if (sem_init(&sem, 0, 0)) |
| goto fail_sem; |
| |
| wi->sync.sem = &sem; |
| |
| if (!workQueuePut(mWorkQ, wi)) |
| goto fail_enq; |
| |
| r_sem_wait(&sem); |
| sem_destroy(&sem); |
| return true; |
| |
| fail_enq: |
| sem_destroy(&sem); |
| |
| fail_sem: |
| free(wi); |
| |
| fail_alloc: |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attConnListAddIfNew |
| * USE: Add a given connection to a connection list, if it is not yet present there |
| * PARAMS: l2cConn - the l2c connection handle |
| * RETURN: false on error |
| * NOTES: call with mAttLock held |
| */ |
| static bool attConnListAddIfNew(l2c_handle_t l2cConn) |
| { |
| struct attConn *conn; |
| struct bt_addr peer; |
| uint32_t i; |
| |
| /* check for existence & alloc as needed */ |
| if (attConnFindByL2cConn(l2cConn)) |
| return true; |
| conn = calloc(1, sizeof(struct attConn)); |
| if (!conn) |
| return false; |
| |
| /* grab the peer address */ |
| conn->conn = l2cConn; |
| if (!l2cApiGetBtAddr(l2cConn, &peer)) { |
| loge("Failed to get peer address\n"); |
| free(conn); |
| return false; |
| } |
| |
| /* get initial encr and mitm state */ |
| conn->isEncr = l2cApiIsConnEncrypted(conn->conn); |
| conn->isMitmProtected = l2cApiIsConnMitmSafe(conn->conn); |
| conn->isLE = BT_ADDR_IS_LE(peer); |
| |
| /* find a free server CID */ |
| conn->srv.cid = uniqGetNext(); |
| |
| /* link it in */ |
| conn->next = mConns; |
| if (mConns) |
| mConns->prev = conn; |
| mConns = conn; |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: attEncrAuthUpdate |
| * USE: Update the ATT state about a change in a connection to a client pertaining to auth/encr |
| * PARAMS: who - the connection to the client |
| * encr - is it now encrypted? |
| * mitmSafe - is it now mitm-safe? |
| * RETURN: success |
| * NOTES: |
| */ |
| bool attEncrAuthUpdate(l2c_handle_t who, bool encr, bool mitmSafe) |
| { |
| struct attConn *conn; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAttLock); |
| attConnListAddIfNew(who); |
| conn = attConnFindByL2cConn(who); |
| if (conn) { |
| conn->isEncr = encr; |
| conn->isMitmProtected = mitmSafe; |
| ret = true; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: attSrvInit |
| * USE: Init our ATT server |
| * PARAMS: txF - callback to use to TX data to clients |
| * srvMtuF - callback to tell higher-leyer of new MTU |
| * indF - callback to tell higher-layer of ind TX progress |
| * execF - callback to execute or cancel pending writes |
| * readF - read callback |
| * writeF - write callback |
| * RETURN: NONE |
| * NOTES: |
| */ |
| void attSrvInit(attSrvTxCbk txF, attMtuCbk srvMtuF, attSrvIndResultCbk indF, attSrvValueExecWriteCbk execF, attSrvValueReadCbk readF, attSrvValueWriteCbk writeF) |
| { |
| pthread_mutex_lock(&mAttLock); |
| mTxCbk = txF; |
| mSrvMtuCbk = srvMtuF; |
| mIndCbk = indF; |
| mExecCbk = execF; |
| mReadCbk = readF; |
| mWriteCbk = writeF; |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| /* |
| * FUNCTION: attSrvDeinit |
| * USE: Deinit our ATT server |
| * PARAMS: NONE |
| * RETURN: NONE |
| * NOTES: |
| */ |
| void attSrvDeinit(void) |
| { |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| struct attConn *conn; |
| uint32_t i; |
| |
| pthread_mutex_lock(&mAttLock); |
| for (conn = mConns; conn; conn = conn->next) { |
| if (conn->srv.indTimer) { |
| if (timerCancel(conn->srv.indTimer)) |
| attSrvSchedIndResultCbk(conn->srv.indRangeRef, conn->srv.indOffset, conn, ATT_SRV_EVT_IND_SRV_DESTROYED, conn->srv.indRef); |
| conn->srv.indTimer = 0; |
| } |
| } |
| /* all uncancelled timers need to be run before we destroy state, so we let them */ |
| pthread_mutex_unlock(&mAttLock); |
| timerSync(); |
| pthread_mutex_lock(&mAttLock); |
| |
| for (range = mRangesHead; range; range = range->next) { |
| for (val = range->valsHead; val; ) { |
| struct attSrvVal *t = val; |
| val = val->next; |
| free(t); |
| } |
| attConnListFree(&range->seenBy); |
| } |
| |
| pthread_mutex_unlock(&mAttLock); |
| |
| mTxCbk = NULL; |
| mSrvMtuCbk = NULL; |
| mIndCbk = NULL; |
| } |
| |
| /* |
| * FUNCTION: attSrvRangeDelete |
| * USE: delete all values in a given range |
| * PARAMS: range - the range |
| * RETURN: NONE |
| * NOTES: call with server's write lock held |
| */ |
| static void attSrvRangeDeleteAllVals(struct attSrvRange *range) |
| { |
| struct attSrvVal *val, *t; |
| |
| val = range->valsHead; |
| while (val) { |
| t = val; |
| val = val->next; |
| free(t); |
| } |
| range->valsHead = NULL; |
| range->valsTail = NULL; |
| } |
| |
| /* |
| * FUNCTION: attSrvRangeDelete |
| * USE: delete a given att range struct |
| * PARAMS: range - the range |
| * RETURN: NONE |
| * NOTES: call with server's write lock held |
| */ |
| static void attSrvRangeDelete(struct attSrvRange *range) |
| { |
| |
| if (range->prev) |
| range->prev->next = range->next; |
| else |
| mRangesHead = range->next; |
| |
| if (range->next) |
| range->next->prev = range->prev; |
| else |
| mRangesTail = range->prev; |
| |
| attSrvRangeDeleteAllVals(range); |
| if (!attConnListIsEmpty(&range->seenBy)) |
| logw("Deleted range 0x%04x+0x%04x has been seen\n", range->start, range->len); |
| attConnListFree(&range->seenBy); |
| free(range); |
| } |
| |
| /* |
| * FUNCTION: attNotifConnClose |
| * USE: tell the ATT code about a closed connection (in case it cares) |
| * PARAMS: l2cConn - the l2c connection handle |
| * RETURN: NONE |
| * NOTES: if said connection substribed to notifications, allows us to unsubstribe |
| * them proactively instead of waiting for TX errors |
| */ |
| void attNotifConnClose(l2c_handle_t l2cConn) |
| { |
| struct attSrvRange *range, *t; |
| struct attSrvVal *val; |
| struct attConn *conn; |
| uint32_t i; |
| |
| pthread_mutex_lock(&mAttLock); |
| for (range = mRangesHead; range;) { |
| attConnListDel(&range->seenBy, l2cConn); |
| t = range; |
| range = range->next; |
| if (attConnListIsEmpty(&t->seenBy) && !t->rangeRef) /* delete a disused range after all who knew about it are gone */ |
| attSrvRangeDelete(t); |
| } |
| |
| conn = attConnFindByL2cConn(l2cConn); |
| if (conn) { |
| if (conn->prev) |
| conn->prev->next = conn->next; |
| else |
| mConns = conn->next; |
| if (conn->next) |
| conn->next->prev = conn->prev; |
| |
| //TODO: XXX: what other cleanup is needed? |
| free(conn); |
| } |
| else |
| logi("ATT: notified of a connection closure for a connection we never knew about"); |
| |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| /* |
| * FUNCTION: attSrvHandleRangeReserve |
| * USE: reserve a range of handles in the ATT server |
| * PARAMS: len - the wanted handle range length |
| * RETURN: a reference to the handle range or 0 on failure |
| * NOTES: by default both LE and EDR accesses are disabled until a call to aapiSrvHandleRangeSetAllowedTransports() is amde |
| */ |
| att_range_t attSrvHandleRangeReserve(uint16_t len) |
| { |
| uint32_t searchH = ATT_FIRST_VALID_HANDLE; |
| struct attSrvRange *next, *prev = NULL, *newRange; |
| att_range_t ret = 0; |
| |
| pthread_mutex_lock(&mAttLock); |
| next = mRangesHead; |
| while (searchH <= ATT_LAST_VALID_HANDLE && next) { |
| if (next->start - searchH >= len) |
| break; |
| else { |
| searchH = next->start + next->len; |
| prev = next; |
| next = next->next; |
| } |
| } |
| if (searchH + len - 1 > ATT_LAST_VALID_HANDLE) |
| goto out; |
| |
| /* next is currently the next item for us, or NULL if we're the new last */ |
| /* prev is currently the prev item for us, or NULL if we're the new first */ |
| |
| newRange = (struct attSrvRange*)calloc(1, sizeof(struct attSrvRange)); |
| if (!newRange) |
| goto out; |
| |
| newRange->start = searchH; |
| newRange->len = len; |
| newRange->rangeRef = uniqGetNext(); |
| newRange->prev = prev; |
| newRange->next = next; |
| if (next) |
| next->prev = newRange; |
| else |
| mRangesTail = newRange; |
| if (prev) |
| prev->next = newRange; |
| else |
| mRangesHead = newRange; |
| |
| ret = newRange->rangeRef; |
| out: |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvRangeFind |
| * USE: find a range structure for a given range reference in a given ATT server |
| * PARAMS: rangeRef - the range reference |
| * RETURN: the range struct or NULL if not found |
| * NOTES: call with mAttLock held |
| |
| */ |
| static struct attSrvRange* attSrvRangeFind(att_range_t rangeRef) |
| { |
| struct attSrvRange *range; |
| |
| for (range = mRangesHead; range && range->rangeRef != rangeRef; range = range->next); |
| |
| return range; |
| } |
| |
| /* |
| * FUNCTION: aapiSrvHandleRangeSetAllowedTransports |
| * USE: Set which transports may access a range |
| * PARAMS: rangeRef - the range reference |
| * allowLE - accessible by LE? |
| * allowEDR - accessible by EDR? |
| * RETURN: a reference to the handle range or 0 on failure |
| * NOTES: |
| */ |
| bool aapiSrvHandleRangeSetAllowedTransports(att_range_t rangeRef, bool allowLE, bool allowEDR) |
| { |
| struct attSrvRange *range; |
| |
| pthread_mutex_lock(&mAttLock); |
| range = attSrvRangeFind(rangeRef); |
| if (!range) |
| logw("Attempt to delete a non-existent range\n"); |
| else { |
| range->allowLE = allowLE; |
| range->allowEDR = allowEDR; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return !!range; |
| } |
| |
| /* |
| * FUNCTION: attSrvValFind |
| * USE: find a value structure for a given range structure and a given offset |
| * PARAMS: range - the range |
| * offset - the offset |
| * RETURN: the value struct or NULL if not found |
| * NOTES: call with mAttLock held |
| */ |
| static struct attSrvVal* attSrvValFind(struct attSrvRange *range, uint16_t offset) |
| { |
| struct attSrvVal *val; |
| |
| for (val = range->valsHead; val && val->offset != offset; val = val->next); |
| |
| return val; |
| } |
| |
| /* |
| * FUNCTION: attSrvValRangeFind |
| * USE: find a value structure and a range structure for a ATT server, a range handle, and an offset |
| * PARAMS: rangeRef - the range reference |
| * offset - the offset |
| * rangeP - range gets stored here |
| * RETURN: the value struct or NULL if not found |
| * NOTES: call with mAttLock held |
| */ |
| static struct attSrvVal* attSrvValRangeFind(att_range_t rangeRef, uint16_t offset, struct attSrvRange **rangeP) |
| { |
| *rangeP = attSrvRangeFind(rangeRef); |
| if (*rangeP) |
| return attSrvValFind(*rangeP, offset); |
| return NULL; |
| } |
| |
| /* |
| * FUNCTION: attSrvHandleRangeRelease |
| * USE: release a range of handles in the ATT server |
| * PARAMS: rangeRef - the range reference |
| * RETURN: NONE |
| * NOTES: |
| */ |
| void attSrvHandleRangeRelease(att_range_t rangeRef) |
| { |
| struct attSrvRange *range; |
| |
| pthread_mutex_lock(&mAttLock); |
| range = attSrvRangeFind(rangeRef); |
| if (!range) |
| logw("Attempt to delete a non-existent range\n"); |
| else { |
| if (attConnListIsEmpty(&range->seenBy)) |
| attSrvRangeDelete(range); |
| else { |
| attSrvRangeDeleteAllVals(range); |
| range->rangeRef = 0; |
| } |
| } |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| /* |
| * FUNCTION: attSrvHandleRangeGetBase |
| * USE: Get the actual handle value of the first handle in a range |
| * PARAMS: rangeRef - the range reference |
| * RETURN: handle value or 0 on error |
| * NOTES: |
| */ |
| uint16_t attSrvHandleRangeGetBase(att_range_t rangeRef) |
| { |
| struct attSrvRange *range; |
| uint16_t ret; |
| |
| pthread_mutex_lock(&mAttLock); |
| range = attSrvRangeFind(rangeRef); |
| ret = range ? range->start : 0; |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvHandleRangeGetLen |
| * USE: Get the actual length (in handled) of a range |
| * PARAMS: rangeRef - the range reference |
| * RETURN: length or 0 on error |
| * NOTES: |
| */ |
| uint16_t attSrvHandleRangeGetLen(att_range_t rangeRef) |
| { |
| struct attSrvRange *range; |
| uint16_t ret; |
| |
| pthread_mutex_lock(&mAttLock); |
| range = attSrvRangeFind(rangeRef); |
| ret = range ? range->len : 0; |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvValueRegister |
| * USE: Register a given value in a given range |
| * PARAMS: rangeRef - the range reference |
| * offset - handle's offset from start of the range |
| * type - the type of the registered value |
| * perms - permissions (ATT_PERM_*) |
| * RETURN: success |
| * NOTES: |
| */ |
| bool attSrvValueRegister(att_range_t rangeRef, uint16_t offset, const struct uuid *type, uint8_t perms) |
| { |
| struct attSrvVal *next, *prev = NULL, *val; |
| struct attSrvRange *range; |
| bool ret = false; |
| |
| |
| pthread_mutex_lock(&mAttLock); |
| range = attSrvRangeFind(rangeRef); |
| if (!range) { |
| logw("Attempt to add a value to a non-existent range\n"); |
| goto out; |
| } |
| |
| if (offset >= range->len) { |
| logw("Value or group end out of range\n"); |
| goto out; |
| } |
| |
| for (next = range->valsHead; next && next->offset < offset; prev = next, next = next->next); |
| /* |
| * prev is our prev or NULL if we're first |
| * next is our next or NULL if we're last |
| * If this is a duplication, next is the item we're trying to duplicate |
| */ |
| |
| if (next && next->offset == offset) { |
| logw("Attempt to create duplicate-offset value (0x%04X+0x%04X)\n", range->start, offset); |
| goto out; |
| } |
| |
| val = (struct attSrvVal*)calloc(1, sizeof(struct attSrvVal)); |
| if (!val) |
| goto out; |
| |
| memcpy(&val->type, type, sizeof(val->type)); |
| val->offset = offset; |
| val->perms = perms; |
| val->next = next; |
| val->prev = prev; |
| if (next) |
| next->prev = val; |
| else |
| range->valsTail = val; |
| if (prev) |
| prev->next = val; |
| else |
| range->valsHead = val; |
| ret = true; |
| |
| out: |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvValueGetGrpLen |
| * USE: Get a given value's "group length" parameter |
| * PARAMS: rangeRef - the range reference |
| * offset - handle's offset from start of the range) |
| * grpLenP - the group length gets stored here |
| * RETURN: success |
| * NOTES: |
| */ |
| bool attSrvValueGetGrpLen(att_range_t rangeRef, uint16_t offset, uint16_t *grpLenP) |
| { |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAttLock); |
| val = attSrvValRangeFind(rangeRef, offset, &range); |
| if (!range) { |
| logw("Attempt to set group len on a value from a non-existent range\n"); |
| goto out; |
| } |
| if (!val) { |
| logw("Attempt to set group len on a non-existent value from a non-existent range (0x%04X+0x%04X)\n", range->start, offset); |
| goto out; |
| } |
| |
| if (grpLenP) |
| *grpLenP = val->grpLen; |
| ret = true; |
| |
| out: |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| /* |
| * FUNCTION: attSrvValueSetGrpLen |
| * USE: Set a given value's "group length" parameter |
| * PARAMS: rangeRef - the range reference |
| * offset - handle's offset from start of the range) |
| * grpLen - the group length to set |
| * RETURN: success |
| * NOTES: |
| */ |
| bool attSrvValueSetGrpLen(att_range_t rangeRef, uint16_t offset, uint16_t grpLen) |
| { |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAttLock); |
| val = attSrvValRangeFind(rangeRef, offset, &range); |
| if (!range) { |
| logw("Attempt to set group len on a value from a non-existent range\n"); |
| goto out; |
| } |
| if (!val) { |
| logw("Attempt to set group len on a non-existent value from a non-existent range (0x%04X+0x%04X)\n", range->start, offset); |
| goto out; |
| } |
| |
| if (offset + grpLen > range->len) { |
| logw("Group end out of range\n"); |
| goto out; |
| } |
| |
| val->grpLen = grpLen; |
| ret = true; |
| |
| out: |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvValueUnregister |
| * USE: Unregister a given value in a given range |
| * PARAMS: rangeRef - the range reference |
| * offset - handle's offset from start of the range) |
| * RETURN: success |
| * NOTES: |
| */ |
| bool attSrvValueUnregister(att_range_t rangeRef, uint16_t offset) |
| { |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAttLock); |
| val = attSrvValRangeFind(rangeRef, offset, &range); |
| if (!range) { |
| logw("Attempt to remove a value from a non-existent range\n"); |
| goto out; |
| } |
| if (!val) { |
| logw("Attempt to remove a non-existent value from a non-existent range (0x%04X+0x%04X)\n", range->start, offset); |
| goto out; |
| } |
| |
| if (val->next) |
| val->next->prev = val->prev; |
| else |
| range->valsTail = val->prev; |
| if (val->prev) |
| val->prev->next = val->next; |
| else |
| range->valsHead = val->next; |
| free(val); |
| ret = true; |
| |
| out: |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attConnMtuGetByConn |
| * USE: Get the MTU to someone |
| * PARAMS: conn - per-connection structure |
| * tryDefaults - try to find the default to use? |
| * RETURN: the mtu to use or 0 on error |
| * NOTES: call with mAttLock held |
| */ |
| static uint32_t attConnMtuGetByConn(struct attConn *conn, bool tryDefaults) |
| { |
| uint32_t mtu = 0; |
| |
| if (!(mtu = conn->mtu) && tryDefaults) { |
| struct bt_addr peer; |
| |
| if (!l2cApiGetBtAddr(conn->conn, &peer)) { |
| logw("Failed to get peer address\n"); |
| return 0; |
| } |
| mtu = BT_ADDR_IS_EDR(peer) ? ATT_MTU_MIN_EDR : ATT_MTU_MIN_LE; |
| } |
| |
| return mtu; |
| } |
| /* |
| * FUNCTION: attConnMtuGetByL2c |
| * USE: Get the MTU to someone |
| * PARAMS: to - whom to |
| * tryDefaults - try to find the default to use? |
| * RETURN: the mtu to use or 0 on error |
| * NOTES: call with mAttLock held |
| */ |
| static uint32_t attConnMtuGetByL2c(l2c_handle_t to, bool tryDefaults) |
| { |
| struct attConn *conn = attConnFindByL2cConn(to); |
| uint32_t mtu = 0; |
| |
| if ((!conn || !(mtu = conn->mtu)) && tryDefaults) { |
| struct bt_addr peer; |
| |
| if (!l2cApiGetBtAddr(to, &peer)) { |
| logw("Failed to get peer address\n"); |
| return 0; |
| } |
| mtu = BT_ADDR_IS_EDR(peer) ? ATT_MTU_MIN_EDR : ATT_MTU_MIN_LE; |
| } |
| |
| return mtu; |
| } |
| |
| /* |
| * FUNCTION: attConnMtuSet |
| * USE: Set the MTU to someone |
| * PARAMS: to - whom to |
| * mtu - the MTU to set |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attConnMtuSet(l2c_handle_t to, uint32_t mtu) |
| { |
| struct attConn *conn = attConnFindByL2cConn(to); |
| |
| if (!conn) { |
| logw("Setting MTU for unknown connection\n"); |
| return false; |
| } |
| |
| conn->mtu = mtu; |
| return true; |
| } |
| |
| /* |
| * FUNCTION: attSrvTimerCbk |
| * USE: Called when a timeout for a confirmation for a sent notification comes |
| * PARAMS: which - the timer |
| * userData - unused |
| * RETURN: NONE |
| * NOTES: XXX: penOp interaction with this should be explored |
| */ |
| static void attSrvTimerCbk(uniq_t which, uint64_t userData) |
| { |
| struct attConn *conn = NULL; |
| |
| pthread_mutex_lock(&mAttLock); |
| |
| /* find the relant conn */ |
| for (conn = mConns; conn && conn->srv.indTimer != which; conn = conn->next); |
| |
| /* if we found it, send the callback */ |
| if (conn) |
| attSrvSchedIndResultCbk(conn->srv.indRangeRef, conn->srv.indOffset, conn, ATT_SRV_EVT_IND_TIMEOUT, conn->srv.indRef); |
| |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| |
| /* |
| * FUNCTION: attSendPacketRaw |
| * USE: Sends an assembled packet to the peer |
| * PARAMS: to - whom the data is to be sent to |
| * preData - first part of the packet or NULL |
| * preLen - length of fais data |
| * data - the middle of the packet or NULL |
| * postData - last part of the packet or NULL |
| * postLen - length of said data |
| * RETURN: ATT_TX_RET_* |
| * NOTES: |
| */ |
| static uint8_t attSendPacketRaw(l2c_handle_t to, const void *preData, uint32_t preLen, sg data, const void *postData, uint32_t postLen) |
| { |
| uint8_t ret; |
| sg tmpData = NULL; |
| |
| if (!data) { |
| tmpData = data = sgNew(); |
| if (!tmpData) |
| return ATT_TX_RET_OOM; |
| } |
| |
| if (preLen && !sgConcatFrontCopy(data, preData, preLen)) { |
| if (tmpData) |
| sgFree(tmpData); |
| return ATT_TX_RET_OOM; |
| } |
| |
| if (postLen && !sgConcatBackCopy(data, postData, postLen)) { |
| if (tmpData) |
| sgFree(tmpData); |
| else |
| sgTruncFront(data, preLen); |
| return ATT_TX_RET_OOM; |
| } |
| |
| ret = mTxCbk(to, data); |
| if (ret == ATT_TX_RET_ACCEPTED) |
| return ret; |
| |
| if (tmpData) |
| sgFree(tmpData); |
| else { |
| sgTruncBack(data, postLen); |
| sgTruncFront(data, preLen); |
| } |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvSendPacketNotif |
| * USE: Send notifications to all clients who wanted them for this value |
| * PARAMS: range - the range |
| * val - the value struct |
| * conn - the per-connection structure |
| * data - the value |
| * notify - send notification (as opposed to indication) |
| * ref - pased to callbacks so you know which notif/ind they are tellking you about |
| * RETURN: L2C_TX_* |
| * NOTES: call with mAttLock held |
| */ |
| static uint8_t attSrvSendPacketNotif(struct attSrvRange *range, struct attSrvVal *val, struct attConn* conn, sg data, bool notify, uint64_t ref) |
| { |
| uint8_t hdrBuf[sizeof(struct attHdr) + sizeof(struct attPduHandleValueNotif) + sizeof(struct attPduHandleValueInd)]; /* definitely big enoguh to fit the fist and either second or third */ |
| struct attHdr *hdr = (struct attHdr*)hdrBuf; |
| struct attPduHandleValueNotif *notifH = (struct attPduHandleValueNotif*)(hdr + 1); |
| struct attPduHandleValueInd *indH = (struct attPduHandleValueInd*)(hdr + 1); |
| uint8_t hdrLen = sizeof(*hdr); |
| uint32_t mtu; |
| uint8_t ret; |
| |
| if (notify) { |
| utilSetLE8(&hdr->opcode, ATT_OPCODE_HANDLE_VALUE_NOTIF); |
| utilSetLE16(¬ifH->handle, val->offset + range->start); |
| hdrLen += sizeof(*notifH); |
| } else { |
| |
| if (conn->srv.indTimer) |
| return ATT_TX_RET_ATT_BUSY; |
| |
| utilSetLE8(&hdr->opcode, ATT_OPCODE_HANDLE_VALUE_IND); |
| utilSetLE16(&indH->handle, val->offset + range->start); |
| hdrLen += sizeof(*indH); |
| } |
| |
| mtu = attConnMtuGetByConn(conn, true); |
| if (!mtu) |
| return ATT_TX_RET_ATT_ERROR; |
| |
| mtu -= hdrLen; |
| if (sgLength(data) > mtu) { |
| logd("Truncating wanted notif/ind data to %ub\n", mtu); |
| sgTruncBack(data, sgLength(data) - mtu); |
| } |
| |
| if (!notify) { |
| conn->srv.indRangeRef = range->rangeRef; |
| conn->srv.indOffset = val->offset; |
| conn->srv.indTimer = timerSet(ATT_IND_TIMEOUT, attSrvTimerCbk, 0); |
| } |
| |
| return attSendPacketRaw(conn->conn, hdrBuf, hdrLen, data, NULL, 0); |
| } |
| |
| /* |
| * FUNCTION: attSrvSendPacketErrorReply |
| * USE: Sends an error to the client |
| * PARAMS: to - whom to send to |
| * err - the error |
| * opcode - opcode for which the error happened |
| * handle - the pertinent handle |
| * RETURN: success |
| * NOTES: |
| */ |
| static bool attSrvSendPacketErrorReply(l2c_handle_t to, uint8_t errNo, uint8_t opcode, uint16_t handle) |
| { |
| struct attPduError err; |
| struct attHdr hdr; |
| |
| utilSetLE8(&hdr.opcode, ATT_OPCODE_ERROR); |
| utilSetLE8(&err.reqOpcode, opcode); |
| utilSetLE8(&err.err, errNo); |
| utilSetLE16(&err.handle, handle); |
| |
| return attSendPacketRaw(to, &hdr, sizeof(hdr), NULL, &err, sizeof(err)) == ATT_TX_RET_ACCEPTED; |
| } |
| |
| /* |
| * FUNCTION: attSendPacket |
| * USE: Sends a packet to a client |
| * PARAMS: to - whom to send to |
| * opcode - opcode to send |
| * hdr - a header to prepend |
| * hdrLen - length of said header |
| * data - the data to send (may be NULL) |
| * RETURN: success |
| * NOTES: |
| */ |
| static bool attSendPacket(l2c_handle_t to, uint8_t opcode, const void *hdr, uint32_t hdrLen, sg data) |
| { |
| struct attHdr attHdr; |
| bool freeData = false; |
| |
| if (!data) { |
| data = sgNew(); |
| if (!data) |
| return false; |
| freeData = true; |
| } |
| |
| utilSetLE8(&attHdr.opcode, opcode); |
| |
| if (hdr && !sgConcatFrontCopy(data, hdr, hdrLen)) |
| goto out; |
| |
| if (attSendPacketRaw(to, &attHdr, sizeof(attHdr), data, NULL, 0) == ATT_TX_RET_ACCEPTED) |
| return true; |
| |
| sgTruncFront(data, hdrLen); |
| /* fallthough */ |
| |
| out: |
| if (freeData) |
| sgFree(data); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSendPacketEmptyPacket |
| * USE: Sends an empy packet to a client |
| * PARAMS: to - whom to send to |
| * opcode - opcode to send |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSendPacketEmptyPacket(l2c_handle_t to, uint8_t opcode) |
| { |
| return attSendPacket(to, opcode, NULL, 0, NULL); |
| } |
| |
| /* |
| * FUNCTION: attSrvValueNotifyChanged |
| * USE: Sen a notification or an indication |
| * PARAMS: rangeRef - the range reference |
| * offset - handle's offset from start of the range |
| * cid - the connection id |
| * data - the data to send |
| * notify - send notification (as opposed to indication) |
| * ref - pased to callbacks so you know which notif/ind they are tellking you about |
| * RETURN: ATT_TX_RET_* |
| * NOTES: |
| */ |
| uint8_t attSrvValueNotifyChanged(att_range_t rangeRef, uint16_t offset, att_cid_t cid, sg data, bool notify, uint64_t ref) |
| { |
| uint8_t ret = ATT_TX_RET_ATT_ERROR; |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| struct attConn *conn; |
| uint32_t i; |
| |
| pthread_mutex_lock(&mAttLock); |
| conn = attConnFindBySrvCid(cid); |
| if (!conn) { |
| loge("Got notif request reply for unknown connection\n"); |
| goto out; |
| } |
| |
| val = attSrvValRangeFind(rangeRef, offset, &range); |
| if (!range) { |
| logw("Attempt to notify on a value from a non-existent range\n"); |
| goto out; |
| } |
| if (!val) { |
| logw("Attempt to notify on a non-existent value from an existent range (0x%04X+0x%04X)\n", range->start, offset); |
| goto out; |
| } |
| |
| ret = attSrvSendPacketNotif(range, val, conn, data, notify, ref); |
| |
| out: |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvCidResolve |
| * USE: Attempt to resolve a CID to a L2C connection handle |
| * PARAMS: srvCid - the server cid |
| * RETURN: L2C connection handle or 0 on failure |
| * NOTES: this is used externally |
| */ |
| l2c_handle_t attSrvCidResolve(att_cid_t srvCid) |
| { |
| struct attConn* conn; |
| l2c_handle_t ret = 0; |
| |
| pthread_mutex_lock(&mAttLock); |
| conn = attConnFindBySrvCid(srvCid); |
| if (conn) |
| ret = conn->conn; |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSetMtu |
| * USE: Called to set the MTU for an ATT server to a given client directly and with no negotiation |
| * PARAMS: to - whom the MTU is to |
| * mtu - the mtu |
| * RETURN: NONE |
| * NOTES: used by EDR only. LE does its own negotiation of this over special ATT packets |
| */ |
| void attSetMtu(l2c_handle_t to, uint32_t mtu) |
| { |
| pthread_mutex_lock(&mAttLock); |
| attConnListAddIfNew(to); |
| attConnMtuSet(to, mtu); |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| /* |
| * FUNCTION: attReadUuid |
| * USE: Read a UUID-128 or a UUID16 from an sg, if it is the only thing there |
| * PARAMS: dst - we store the read UUID here |
| * s - the data |
| * RETURN: success |
| * NOTES: |
| */ |
| bool attReadUuid(struct uuid *dst, sg s) |
| { |
| uint8_t buf[16]; |
| struct uuid *ru = (struct uuid*)buf; |
| |
| if (sgLength(s) == sizeof(uint16_t)) { |
| sgSerializeCutFront(s, buf, sizeof(uint16_t)); |
| uuidFromUuid16(dst, utilGetLE16(buf)); |
| } else if (sgLength(s) == sizeof(struct uuid)) { |
| sgSerializeCutFront(s, buf, sizeof(struct uuid)); |
| uuidReadLE(dst, ru); |
| } else |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: attSrvIsValReadableBy |
| * USE: Decide if a given value is readable by a given connection |
| * PARAMS: range -the value's range |
| * val - the value |
| * conn - per-connection state for this connection |
| * RETURN: ATT_ERROR_* |
| * NOTES: call with mAttLock held |
| */ |
| static uint8_t attSrvIsValReadableBy(struct attSrvRange *range, struct attSrvVal *val, struct attConn *conn) |
| { |
| if (val->perms & ATT_PERM_READ) |
| return ATT_ERROR_NONE; |
| if (val->perms & ATT_PERM_READ_ENCR) |
| return conn->isEncr ? ATT_ERROR_NONE : ATT_ERROR_INSUFFICIENT_ENCR; |
| if (val->perms & ATT_PERM_READ_ENCR_MITM) { |
| if (!conn->isEncr) |
| return ATT_ERROR_INSUFFICIENT_ENCR; |
| else if (!conn->isMitmProtected) |
| return ATT_ERROR_INSUFFICIENT_AUTH; |
| else |
| return ATT_ERROR_NONE; |
| } |
| |
| return ATT_ERROR_UNLIKELY_ERROR; /* ?? */ |
| } |
| |
| /* |
| * FUNCTION: attSrvIsValWriteableBy |
| * USE: Decide if a given value is writeable by a given connection |
| * PARAMS: range -the value's range |
| * val - the value |
| * conn - per-connection state for this connection |
| * isSigned - is this a signed write? |
| * RETURN: ATT_ERROR_* |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvIsValWriteableBy(struct attSrvRange *range, struct attSrvVal *val, struct attConn *conn, bool isSigned) |
| { |
| if (val->perms & ATT_PERM_WRITE) |
| return ATT_ERROR_NONE; |
| if (isSigned) |
| return ((val->perms & ATT_PERM_WRITE_SIGNED) && !conn->isEncr) ? ATT_ERROR_NONE : ATT_ERROR_WRITE_NOT_ALLOWED; |
| if (val->perms & ATT_PERM_WRITE_ENCR) |
| return conn->isEncr ? ATT_ERROR_NONE : ATT_ERROR_INSUFFICIENT_ENCR; |
| if (val->perms & ATT_PERM_WRITE_ENCR_MITM) { |
| if (!conn->isEncr) |
| return ATT_ERROR_INSUFFICIENT_ENCR; |
| else if (!conn->isMitmProtected) |
| return ATT_ERROR_INSUFFICIENT_AUTH; |
| else |
| return ATT_ERROR_NONE; |
| } |
| |
| return ATT_ERROR_WRITE_NOT_ALLOWED; |
| } |
| |
| /* |
| * FUNCTION: attRangeIsVisibleBy |
| * USE: Decide if a given range is visibile to a given connection |
| * PARAMS: range - the range |
| * conn - per-connection state for this connection |
| * RETURN: true if visible, false else |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvIsRangeVisibleTo(struct attSrvRange *range, struct attConn *conn) |
| { |
| return (conn->isLE && range->allowLE) || (!conn->isLE && range->allowEDR); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpFindInfo |
| * USE: Perform a find info request |
| * PARAMS: to - whom to reply to |
| * mtu - the mtu to use |
| * startHandle - first handle the client cares about |
| * endHandle - last handle the client cares about |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpFindInfo(l2c_handle_t to, uint32_t mtu, uint16_t startHandle, uint16_t endHandle) |
| { |
| struct attConn *conn = attConnFindByL2cConn(to); |
| struct attPduFindInfoRsp rsp; |
| bool allUuidsSmall = true; |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| sg results = NULL; |
| bool err = false; |
| |
| if (!conn) { |
| loge("No connection found\n"); |
| return attSrvSendPacketErrorReply(to, ATT_ERROR_UNLIKELY_ERROR, ATT_OPCODE_FIND_INFO_REQ, startHandle); |
| } |
| |
| if (!startHandle || startHandle > endHandle) |
| return attSrvSendPacketErrorReply(to, ATT_ERROR_INVALID_HANDLE, ATT_OPCODE_FIND_INFO_REQ, startHandle); |
| |
| results = sgNew(); |
| |
| if (!results) |
| return attSrvSendPacketErrorReply(to, ATT_ERROR_INSUFFICIENT_RSRCS, ATT_OPCODE_FIND_INFO_REQ, startHandle); |
| |
| mtu -= sizeof(struct attPduFindInfoRsp) + sizeof(struct attHdr); /* mtu is now how many bytes we can produce */ |
| |
| for (range = mRangesHead; range; range = range->next) { |
| if (!attSrvIsRangeVisibleTo(range, conn)) |
| continue; |
| for (val = range->valsHead; val; val = val->next) { |
| uint16_t uuid16, handle = range->start + val->offset; |
| |
| if (handle < startHandle) |
| continue; |
| if (handle > endHandle) |
| goto out; |
| |
| if (allUuidsSmall && uuidToUuid16(&uuid16, &val->type)) { |
| struct attPduFindInfoData16 result; |
| uint16_t uuid; |
| |
| utilSetLE16(&result.handle, handle); |
| utilSetLE16(&result.uuid16, uuid16); |
| if (!sgConcatBackCopy(results, &result, sizeof(result))) { |
| err = true; |
| goto out; |
| } |
| } else { |
| struct attPduFindInfoData128 result; |
| if (allUuidsSmall) { /* need to convert to all-large */ |
| uint32_t numResults = sgLength(results) / sizeof(struct attPduFindInfoData16); |
| struct attPduFindInfoData16 result16; |
| struct uuid uuid; |
| |
| while(numResults--) { |
| if (!sgSerializeCutFront(results, &result16, sizeof(result16))) |
| goto unrecoverable; |
| utilSetLE16(&result.handle, utilGetLE16(&result16.handle)); |
| uuidFromUuid16(&uuid, utilGetLE16(&result16.uuid16)); |
| uuidWriteLE(&result.uuid128, &uuid); |
| if (!sgConcatBackCopy(results, &result, sizeof(result))) |
| goto unrecoverable; |
| } |
| allUuidsSmall = false; |
| } |
| utilSetLE16(&result.handle, handle); |
| uuidWriteLE(&result.uuid128, &val->type); |
| if (!sgConcatBackCopy(results, &result, sizeof(result))) { |
| err = true; |
| goto out; |
| } |
| } |
| |
| /* keep length in check */ |
| if (sgLength(results) >= mtu) |
| goto out; |
| } |
| } |
| |
| out: |
| /* if we have results AND an error, favour sending results */ |
| if (!sgLength(results) && err) |
| goto unrecoverable; |
| |
| /* make sure we do not send too much */ |
| while (sgLength(results) > mtu) |
| sgTruncBack(results, allUuidsSmall ? sizeof(struct attPduFindInfoData16) : sizeof(struct attPduFindInfoData128)); |
| |
| /* a lack of results is reported thusly */ |
| if (!sgLength(results)) { |
| sgFree(results); |
| return attSrvSendPacketErrorReply(to, ATT_ERROR_ATTRIBUTE_NOT_FOUND, ATT_OPCODE_FIND_INFO_REQ, startHandle); |
| } |
| |
| /* we're ready to send a reply */ |
| utilSetLE8(&rsp.format, allUuidsSmall ? ATT_FIND_INFO_RSP_FMT_16 : ATT_FIND_INFO_RSP_FMT_128); |
| if (attSendPacket(to, ATT_OPCODE_FIND_INFO_RSP, &rsp, sizeof(rsp), results)) |
| return true; |
| |
| /* if we fall though to here, try to send an error (even if it is unlikely to work) */ |
| |
| unrecoverable: |
| if (results) |
| sgFree(results); |
| return attSrvSendPacketErrorReply(to, ATT_ERROR_UNLIKELY_ERROR, ATT_OPCODE_FIND_INFO_REQ, 0); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpFindByTypeValContinue |
| * USE: Continue a find by type value request |
| * PARAMS: conn - the per-connection struct |
| * lastReadData - return from the READ call |
| * leastReadLen - length last read result produced |
| * lastReadRet - last read's result |
| * RETURN: false to fail immediately, true if more work will be done |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpFindByTypeValContinue(struct attConn *conn, const void *lastReadData, uint16_t leastReadLen, uint8_t lastReadRet) |
| { |
| uint8_t err = ATT_ERROR_UNLIKELY_ERROR; |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| uint16_t startHandle; |
| uint16_t endHandle; |
| uint32_t mtu; |
| sg results; |
| |
| if (conn->srv.penOp.origOpcode != ATT_OPCODE_FIND_BY_TYPE_VAL_REQ) |
| return false; |
| |
| startHandle = conn->srv.penOp.startHandle; |
| endHandle = conn->srv.penOp.endHandle; |
| mtu = conn->srv.penOp.mtu; |
| results = conn->srv.penOp.replyInProgress; |
| |
| mtu -= sizeof(struct attHdr); /* mtu is now how many bytes we can produce */ |
| |
| if (lastReadRet == ATT_ERROR_NONE && leastReadLen == conn->srv.penOp.findByTypeValue.wantedLen && !memcmp(lastReadData, conn->srv.penOp.findByTypeValue.wantedVal, leastReadLen)) { |
| |
| /* if the last read succeeded, append the result if it is a result */ |
| struct attPduFindByTypeValRspItem result; |
| |
| utilSetLE16(&result.firstHandle, startHandle); |
| utilSetLE16(&result.groupEndHandle, startHandle + conn->srv.penOp.findByTypeValue.valGrpLen - 1); |
| |
| if (!sgConcatBackCopy(results, &result, sizeof(result))) |
| goto done; |
| |
| if (sgLength(results) >= mtu) |
| goto done; |
| |
| } else if (lastReadRet != ATT_FIRST_CONTINUE_CALL) { |
| /* if the last read failed - stop searching */ |
| goto done; |
| } |
| |
| if (startHandle == endHandle) |
| goto done; |
| startHandle++; |
| |
| for (range = mRangesHead; range; range = range->next) { |
| if (!attSrvIsRangeVisibleTo(range, conn)) |
| continue; |
| for (val = range->valsHead; val; val = val->next) { |
| uint16_t handle = range->start + val->offset; |
| uint16_t effectiveGrpLen = val->grpLen ? val->grpLen : 1; |
| |
| |
| /* range check */ |
| if (handle < startHandle) |
| continue; |
| if (handle > endHandle) |
| goto done; |
| |
| /* must be readable */ |
| if (ATT_ERROR_NONE != attSrvIsValReadableBy(range, val, conn)) |
| continue; |
| |
| /* must be of a given type */ |
| if (!uuidCmp(&val->type, &conn->srv.penOp.findByTypeValue.uuid)) |
| continue; |
| |
| /* we found a falue to read - schedule it */ |
| conn->srv.penOp.startHandle = handle; |
| conn->srv.penOp.findByTypeValue.valGrpLen = effectiveGrpLen; |
| if (!attSrvSchedReadCbk(range, val, conn, ATT_READ_FOR_FIND_BY_TYPE_VAL, 0, conn->srv.penOp.findByTypeValue.wantedLen + 1)) |
| break; |
| |
| return false; |
| } |
| } |
| |
| done: |
| conn->srv.penOp.origOpcode = 0; |
| free(conn->srv.penOp.findByTypeValue.wantedVal); |
| |
| /* make sure we do not send too much */ |
| while (sgLength(results) > mtu) |
| sgTruncBack(results, sizeof(struct attPduFindByTypeValRspItem)); |
| |
| /* a lack of results is reported via error */ |
| if (!sgLength(results)) { |
| err = ATT_ERROR_ATTRIBUTE_NOT_FOUND; |
| startHandle = conn->srv.penOp.origStartHandle; |
| } else if (attSendPacket(conn->conn, ATT_OPCODE_FIND_BY_TYPE_VAL_RSP, NULL, 0, results)) |
| return true; |
| |
| /* if we fall though to here, try to send an error */ |
| sgFree(results); |
| return attSrvSendPacketErrorReply(conn->conn, err, ATT_OPCODE_FIND_BY_TYPE_VAL_REQ, startHandle); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpFindByTypeValStart |
| * USE: Start a find by type value request |
| * PARAMS: to - whom to reply to |
| * mtu - the mtu to use |
| * startHandle - first handle the client cares about |
| * endHandle - last handle the client cares about |
| * uuid - the uuid to find |
| * s - the value to find |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpFindByTypeValStart(l2c_handle_t to, uint32_t mtu, uint16_t startHandle, uint16_t endHandle, const struct uuid *uuid, sg s) |
| { |
| struct attConn *conn = attConnFindByL2cConn(to); |
| uint8_t err = ATT_ERROR_INSUFFICIENT_RSRCS; |
| sg results = NULL; |
| bool ret; |
| |
| if (!startHandle || startHandle > endHandle) { |
| ret = attSrvSendPacketErrorReply(to, ATT_ERROR_INVALID_HANDLE, ATT_OPCODE_FIND_BY_TYPE_VAL_REQ, startHandle); |
| goto out; |
| } |
| |
| if (!conn) |
| loge("No connection found\n"); |
| else if (conn->srv.penOp.origOpcode) |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| else { |
| |
| results = sgNew(); |
| if (results) { |
| |
| conn->srv.penOp.findByTypeValue.wantedVal = malloc(sgLength(s)); |
| if (conn->srv.penOp.findByTypeValue.wantedVal) { |
| |
| sgSerialize(s, 0, sgLength(s), conn->srv.penOp.findByTypeValue.wantedVal); |
| |
| conn->srv.penOp.origOpcode = ATT_OPCODE_FIND_BY_TYPE_VAL_REQ; |
| conn->srv.penOp.replyInProgress = results; |
| conn->srv.penOp.origStartHandle = startHandle; |
| conn->srv.penOp.startHandle = startHandle - 1; |
| conn->srv.penOp.endHandle = endHandle; |
| conn->srv.penOp.mtu = mtu; |
| memcpy(&conn->srv.penOp.findByTypeValue.uuid, uuid, sizeof(conn->srv.penOp.findByTypeValue.uuid)); |
| conn->srv.penOp.findByTypeValue.wantedLen = sgLength(s); |
| conn->srv.penOp.findByTypeValue.valGrpLen = 0; |
| |
| if (attSrvOpFindByTypeValContinue(conn, NULL, 0, ATT_FIRST_CONTINUE_CALL)) { |
| sgFree(s); |
| return true; |
| } |
| |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| conn->srv.penOp.origOpcode = 0; |
| |
| free(conn->srv.penOp.findByTypeValue.wantedVal); |
| } |
| sgFree(results); |
| } |
| } |
| |
| ret = attSrvSendPacketErrorReply(to, err, ATT_OPCODE_FIND_BY_TYPE_VAL_REQ, startHandle); |
| |
| out: |
| if (ret) |
| sgFree(s); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvOpReadByTypeContinue |
| * USE: Continue a read by type / read by group type request |
| * PARAMS: conn - the per-connection structure |
| * lastReadData - return from the READ call |
| * lastReadLen - length last read result produced |
| * lastReadRet - last read's result |
| * RETURN: false to fail immediately, true if more work will be done |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpReadByTypeContinue(struct attConn *conn, const void *lastReadData, uint16_t lastReadLen, uint8_t lastReadRet) |
| { |
| uint8_t err = ATT_ERROR_NONE; |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| uint16_t packetHdrSz; |
| uint16_t startHandle; |
| uint16_t itemMaxLen; |
| uint16_t endHandle; |
| uint8_t readReason; |
| uint8_t itemHdrSz; |
| uint8_t reqOpcode; |
| uint8_t rspOpcode; |
| uint16_t mtu; |
| bool grpType; |
| sg results; |
| |
| |
| grpType = conn->srv.penOp.readByType.grpType; |
| packetHdrSz = grpType ? sizeof(struct attPduReadByGrpTypeRsp) : sizeof(struct attPduReadByTypeRsp); |
| itemHdrSz = grpType ? sizeof(struct attGrpValuePair) : sizeof(struct attHandleValuePair); |
| itemMaxLen = grpType ? ATT_MAX_LEN_FOR_RD_BY_GRP_TYP : ATT_MAX_LEN_FOR_RD_BY_TYPE; |
| readReason = grpType ? ATT_READ_FOR_READ_BY_GRP_TYPE : ATT_READ_FOR_READ_BY_TYPE; |
| reqOpcode = grpType ? ATT_OPCODE_READ_BY_GRP_TYPE_REQ : ATT_OPCODE_READ_BY_TYPE_REQ; |
| rspOpcode = grpType ? ATT_OPCODE_READ_BY_GRP_TYPE_RSP : ATT_OPCODE_READ_BY_TYPE_RSP; |
| |
| if (conn->srv.penOp.origOpcode != reqOpcode) |
| return false; |
| |
| startHandle = conn->srv.penOp.startHandle; |
| endHandle = conn->srv.penOp.endHandle; |
| mtu = conn->srv.penOp.mtu; |
| results = conn->srv.penOp.replyInProgress; |
| |
| mtu -= sizeof(struct attHdr) + packetHdrSz; /* mtu is now how many bytes we can produce */ |
| if (itemMaxLen > mtu - itemHdrSz) |
| itemMaxLen = mtu - itemHdrSz; |
| |
| if (lastReadRet == ATT_ERROR_NONE) { |
| sg thisResult; |
| uint16_t perResultSize; |
| |
| /* only items of the same size shall be provided, so the first result sets the size */ |
| if (sgLength(results) && lastReadLen != conn->srv.penOp.readByType.itemLen) |
| goto done; |
| conn->srv.penOp.readByType.itemLen = lastReadLen; |
| |
| /* prepare the per-item header */ |
| if (grpType) { |
| struct attGrpValuePair gvp; |
| utilSetLE16(&gvp.handle, startHandle); |
| utilSetLE16(&gvp.groupEndHadle, startHandle + conn->srv.penOp.readByType.valGrpLen - 1); |
| |
| thisResult = sgNewWithCopyData(&gvp, sizeof(gvp)); |
| } else { |
| struct attHandleValuePair hvp; |
| utilSetLE16(&hvp.handle, startHandle); |
| |
| thisResult = sgNewWithCopyData(&hvp, sizeof(hvp)); |
| } |
| |
| if (!thisResult) { |
| err = ATT_ERROR_INSUFFICIENT_RSRCS; |
| goto done; |
| } |
| |
| if (!sgConcatBackCopy(thisResult, lastReadData, lastReadLen)) { |
| sgFree(thisResult); |
| err = ATT_ERROR_INSUFFICIENT_RSRCS; |
| goto done; |
| } |
| |
| perResultSize = sgLength(thisResult); |
| |
| /* append */ |
| sgConcat(results, thisResult); |
| |
| /* |
| * If we cannot fit another result (since they are all the same size), get out. |
| * One result is guaranteed to fit! |
| */ |
| if (sgLength(results) + perResultSize > mtu) |
| goto done; |
| |
| } else if (lastReadRet != ATT_FIRST_CONTINUE_CALL) { |
| /* if the last read failed - stop searching */ |
| err = lastReadRet; |
| goto done; |
| } |
| |
| if (startHandle == endHandle) |
| goto done; |
| startHandle++; |
| |
| for (range = mRangesHead; range; range = range->next) { |
| if (!attSrvIsRangeVisibleTo(range, conn)) |
| continue; |
| for (val = range->valsHead; val; val = val->next) { |
| uint16_t handle = range->start + val->offset; |
| uint16_t effectiveGrpLen = val->grpLen ? val->grpLen : 1; |
| |
| /* range check */ |
| if (handle < startHandle) |
| continue; |
| if (handle > endHandle) |
| goto done; |
| |
| /* must be readable */ |
| err = attSrvIsValReadableBy(range, val, conn); |
| if (err != ATT_ERROR_NONE) { |
| startHandle = handle; |
| goto done; |
| } |
| |
| /* must be of a given type */ |
| if (!uuidCmp(&val->type, &conn->srv.penOp.readByType.uuid)) |
| continue; |
| |
| /* we found a falue to read - schedule it */ |
| conn->srv.penOp.startHandle = handle; |
| conn->srv.penOp.readByType.valGrpLen = effectiveGrpLen; |
| if (attSrvSchedReadCbk(range, val, conn, readReason, 0, itemMaxLen)) |
| return true; |
| |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| goto done; |
| } |
| } |
| |
| done: |
| conn->srv.penOp.origOpcode = 0; |
| |
| if (sgLength(results) > mtu) { |
| /* make sure we do not send too much */ |
| logw("Impossible result length %u over mtu %u in read by [grp] type\n", sgLength(results), mtu); |
| startHandle = conn->srv.penOp.origStartHandle; |
| } else if (!sgLength(results)) { |
| |
| /* a lack of results is reported thusly */ |
| if (err == ATT_ERROR_NONE) { |
| err = ATT_ERROR_ATTRIBUTE_NOT_FOUND; |
| startHandle = conn->srv.penOp.origStartHandle; |
| } |
| } else { |
| struct attPduReadByTypeRsp rbt; |
| struct attPduReadByGrpTypeRsp gtr; |
| void *hdr; |
| |
| if (grpType) { |
| hdr = >r; |
| utilSetLE8(>r.attGrpValuePairSz, conn->srv.penOp.readByType.itemLen + sizeof(struct attGrpValuePair)); |
| } else { |
| hdr = &rbt; |
| utilSetLE8(&rbt.handleValuePairSz, conn->srv.penOp.readByType.itemLen + sizeof(struct attHandleValuePair)); |
| } |
| |
| if (attSendPacket(conn->conn, rspOpcode, hdr, packetHdrSz, results)) |
| return true; |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| } |
| |
| /* if we fall though to here, try to send an error (even if it is unlikely to work) */ |
| sgFree(results); |
| return attSrvSendPacketErrorReply(conn->conn, err, reqOpcode, startHandle); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpReadByTypeStart |
| * USE: Start a read by type / read by group type request |
| * PARAMS: to - whom to reply to |
| * mtu - the mtu to use |
| * startHandle - first handle the client cares about |
| * endHandle - last handle the client cares about |
| * uuid - the uuid to find |
| * grpType - is this a "read by group type" request ? |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpReadByTypeStart(l2c_handle_t to, uint32_t mtu, uint16_t startHandle, uint16_t endHandle, const struct uuid *uuid, bool grpType) |
| { |
| const uint8_t reqOpcode = grpType ? ATT_OPCODE_READ_BY_GRP_TYPE_REQ : ATT_OPCODE_READ_BY_TYPE_REQ; |
| const uint8_t rspOpcode = grpType ? ATT_OPCODE_READ_BY_GRP_TYPE_RSP : ATT_OPCODE_READ_BY_TYPE_RSP; |
| const uint8_t readReason = grpType ? ATT_READ_FOR_READ_BY_GRP_TYPE : ATT_READ_FOR_READ_BY_TYPE; |
| struct attConn *conn = attConnFindByL2cConn(to); |
| uint8_t err = ATT_ERROR_INSUFFICIENT_RSRCS; |
| sg results = NULL; |
| |
| if (!startHandle || startHandle > endHandle) |
| return attSrvSendPacketErrorReply(to, ATT_ERROR_INVALID_HANDLE, reqOpcode, startHandle); |
| |
| if (!conn) |
| loge("No connection found\n"); |
| else if (conn->srv.penOp.origOpcode) |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| else { |
| results = sgNew(); |
| if (results) { |
| |
| conn->srv.penOp.origOpcode = reqOpcode; |
| conn->srv.penOp.replyInProgress = results; |
| conn->srv.penOp.origStartHandle = startHandle; |
| conn->srv.penOp.startHandle = startHandle - 1; |
| conn->srv.penOp.endHandle = endHandle; |
| conn->srv.penOp.mtu = mtu; |
| memcpy(&conn->srv.penOp.readByType.uuid, uuid, sizeof(conn->srv.penOp.readByType.uuid)); |
| conn->srv.penOp.readByType.grpType = grpType; |
| conn->srv.penOp.readByType.valGrpLen = 0; |
| conn->srv.penOp.readByType.itemLen = 0; |
| |
| if (attSrvOpReadByTypeContinue(conn, NULL, 0, ATT_FIRST_CONTINUE_CALL)) |
| return true; |
| |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| conn->srv.penOp.origOpcode = 0; |
| |
| sgFree(results); |
| } |
| } |
| |
| return attSrvSendPacketErrorReply(to, err, reqOpcode, startHandle); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpReadContinue |
| * USE: Continue a read / read blob request |
| * PARAMS: conn - the per-connection structure |
| * lastReadData - return from the READ call |
| * leastReadLen - length last read result produced |
| * lastReadRet - last read's result |
| * RETURN: false to fail immediately, true if more work will be done |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpReadContinue(struct attConn *conn, const void *lastReadData, uint16_t leastReadLen, uint8_t lastReadRet) |
| { |
| uint8_t err = lastReadRet; |
| uint16_t wantedHandle; |
| uint8_t opc, rspOpc; |
| sg result; |
| |
| |
| if (conn->srv.penOp.origOpcode == ATT_OPCODE_READ_REQ) |
| rspOpc = ATT_OPCODE_READ_RSP; |
| else if (conn->srv.penOp.origOpcode == ATT_OPCODE_READ_BLOB_REQ) |
| rspOpc = ATT_OPCODE_READ_BLOB_RSP; |
| else |
| return false; |
| |
| wantedHandle = conn->srv.penOp.startHandle; |
| opc = conn->srv.penOp.origOpcode; |
| conn->srv.penOp.origOpcode = 0; |
| |
| if (err == ATT_ERROR_NONE) { |
| result = sgNewWithCopyData(lastReadData, leastReadLen); |
| if (!result) |
| err = ATT_ERROR_INSUFFICIENT_RSRCS; |
| else if (!attSendPacket(conn->conn, rspOpc, NULL, 0, result)) { |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| sgFree(result); |
| } else |
| return true; |
| } |
| |
| return attSrvSendPacketErrorReply(conn->conn, err, opc, wantedHandle); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpReadStart |
| * USE: Start a read/readBlob request |
| * PARAMS: to - whom to reply to |
| * mtu - the mtu to use |
| * wantedHandle - the handle the client cares about |
| * offset - offset to start at (if needed for the opcode) |
| * opcode - the opcode we were called with |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpReadStart(l2c_handle_t to, uint32_t mtu, uint16_t wantedHandle, uint16_t offset, uint8_t opcode) |
| { |
| struct attConn *conn = attConnFindByL2cConn(to); |
| uint8_t err = ATT_ERROR_INSUFFICIENT_RSRCS; |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| |
| |
| if (!wantedHandle) |
| err = ATT_ERROR_INVALID_HANDLE; |
| else if (!conn) { |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| wantedHandle = 0; |
| loge("No connection found\n"); |
| } else if (conn->srv.penOp.origOpcode) |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| else { |
| for (range = mRangesHead; range; range = range->next) { |
| if (!attSrvIsRangeVisibleTo(range, conn)) |
| continue; |
| for (val = range->valsHead; val; val = val->next) { |
| uint8_t readReason = opcode == ATT_OPCODE_READ_REQ ? ATT_READ_FOR_READ : ATT_READ_FOR_READ_BLOB; |
| uint16_t handle = range->start + val->offset; |
| |
| /* range check */ |
| if (handle < wantedHandle) |
| continue; |
| if (handle > wantedHandle) { |
| err = ATT_ERROR_ATTRIBUTE_NOT_FOUND; |
| goto done; |
| } |
| |
| /* must be readable */ |
| err = attSrvIsValReadableBy(range, val, conn); |
| if (err != ATT_ERROR_NONE) |
| goto done; |
| |
| /* do a read */ |
| conn->srv.penOp.origOpcode = opcode; |
| conn->srv.penOp.mtu = mtu; |
| conn->srv.penOp.startHandle = wantedHandle; |
| conn->srv.penOp.read.offset = offset; |
| |
| if (attSrvSchedReadCbk(range, val, conn, readReason, offset, mtu - sizeof(struct attHdr))) |
| return true; |
| |
| conn->srv.penOp.origOpcode = 0; |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| goto done; |
| } |
| } |
| } |
| done: |
| |
| return attSrvSendPacketErrorReply(to, err, opcode, wantedHandle); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpReadMultContinue |
| * USE: Continue a read multiple request |
| * PARAMS: conn - per-connection structure |
| * lastReadData - return from the READ call |
| * leastReadLen - length last read result produced |
| * lastReadRet - last read's result |
| * RETURN: false to fail immediately, true if more work will be done |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpReadMultContinue(struct attConn *conn, const void *lastReadData, uint16_t leastReadLen, uint8_t lastReadRet) |
| { |
| uint8_t err = ATT_ERROR_NONE; |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| uint16_t wantedHandle = 0; |
| sg results, handles; |
| uint16_t mtu; |
| |
| |
| if (conn->srv.penOp.origOpcode != ATT_OPCODE_READ_MULT_REQ) |
| return false; |
| |
| mtu = conn->srv.penOp.mtu; |
| results = conn->srv.penOp.replyInProgress; |
| handles = conn->srv.penOp.readMult.handles; |
| |
| mtu -= sizeof(struct attHdr); /* mtu is now how many bytes we can produce */ |
| |
| if (lastReadRet == ATT_ERROR_NONE) { |
| |
| if (!sgConcatBackCopy(results, lastReadData, leastReadLen)) { |
| err = ATT_ERROR_INSUFFICIENT_RSRCS; |
| goto error; |
| } |
| |
| /* if no more handles to read, stop */ |
| if (!sgLength(handles)) |
| goto done; |
| |
| /* even if we go way over mtu, we keep reading because as per spec we need to generate errors correctly */ |
| |
| } else if (lastReadRet != ATT_FIRST_CONTINUE_CALL) { |
| /* if the last read failed - stop searching */ |
| err = lastReadRet; |
| wantedHandle = conn->srv.penOp.readMult.lastReadHandle; |
| goto error; |
| } |
| |
| if (!sgSerializeCutFront(handles, &wantedHandle, sizeof(wantedHandle))) |
| goto done; |
| wantedHandle = utilGetLE16(&wantedHandle); |
| |
| for (range = mRangesHead; range; range = range->next) { |
| if (!attSrvIsRangeVisibleTo(range, conn)) |
| continue; |
| for (val = range->valsHead; val; val = val->next) { |
| uint16_t handle = range->start + val->offset; |
| |
| /* range check */ |
| if (handle < wantedHandle) |
| continue; |
| if (handle > wantedHandle) { |
| err = ATT_ERROR_INVALID_HANDLE; |
| goto error; |
| } |
| |
| conn->srv.penOp.readMult.lastReadHandle = wantedHandle; |
| |
| /* must be readable */ |
| err = attSrvIsValReadableBy(range, val, conn); |
| if (err != ATT_ERROR_NONE) { |
| wantedHandle = handle; |
| goto error; |
| } |
| |
| /* we found the value to read - schedule it */ |
| conn->srv.penOp.startHandle = handle; |
| if (attSrvSchedReadCbk(range, val, conn, ATT_READ_FOR_READ_MULT, 0, mtu)) |
| return true; |
| |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| goto error; |
| } |
| } |
| /* if we get here, we did not find the value */ |
| err = ATT_ERROR_INVALID_HANDLE; |
| goto error; |
| |
| done: |
| sgFree(handles); |
| handles = NULL; |
| /* mind the length */ |
| if (sgLength(results) > mtu) |
| sgTruncBack(results, sgLength(results) - mtu); |
| |
| if (attSendPacket(conn->conn, ATT_OPCODE_READ_MULT_RSP, NULL, 0, results)) |
| return true; |
| |
| /* uh oh */ |
| err = ATT_ERROR_UNLIKELY_ERROR; |
| |
| error: |
| conn->srv.penOp.origOpcode = 0; |
| sgFree(results); |
| if (handles) |
| sgFree(handles); |
| return attSrvSendPacketErrorReply(conn->conn, err, ATT_OPCODE_READ_MULT_REQ, wantedHandle); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpReadMultStart |
| * USE: Start a read multiple request |
| * PARAMS: to - whom to reply to |
| * mtu - the mtu to use |
| * s - sg with uint16_t list of handles |
| * RETURN: success |
| * NOTES: call with mAttLock held. WILL FREE "handles" eventually, if true is returned |
| */ |
| static bool attSrvOpReadMultStart(l2c_handle_t to, uint32_t mtu, sg handles) |
| { |
| struct attConn *conn = attConnFindByL2cConn(to); |
| uint8_t err = ATT_ERROR_UNLIKELY_ERROR; |
| |
| if (!conn) |
| loge("No connection found\n"); |
| else if (!conn->srv.penOp.origOpcode) { |
| |
| conn->srv.penOp.replyInProgress = sgNew(); |
| if (!conn->srv.penOp.replyInProgress) |
| err = ATT_ERROR_INSUFFICIENT_RSRCS; |
| else { |
| |
| conn->srv.penOp.origOpcode = ATT_OPCODE_READ_MULT_REQ; |
| conn->srv.penOp.readMult.handles = handles; |
| conn->srv.penOp.mtu = mtu; |
| |
| if (attSrvOpReadContinue(conn, NULL, 0, ATT_FIRST_CONTINUE_CALL)) |
| return true; |
| |
| sgFree(conn->srv.penOp.replyInProgress); |
| } |
| } |
| |
| if (attSrvSendPacketErrorReply(to, err, ATT_OPCODE_READ_MULT_REQ, 0)) { |
| sgFree(handles); |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSrvWriteContinue |
| * USE: Continue a [signed] write request / command |
| * PARAMS: conn - per-connection structure |
| * lastWriteData - return from the READ call |
| * leastWriteLen - length last read result produced |
| * lastWriteRet - last read's result |
| * RETURN: false to fail immediately, true if more work will be done |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvWriteContinue(struct attConn *conn, const void *lastWriteData, uint16_t leastWriteLen, uint8_t lastWriteRet) |
| { |
| uint8_t err = ATT_ERROR_UNLIKELY_ERROR; |
| |
| if (conn->srv.penOp.origOpcode != ATT_OPCODE_WRITE_REQ && conn->srv.penOp.origOpcode != ATT_OPCODE_SIGNED_WRITE_CMD && |
| conn->srv.penOp.origOpcode != ATT_OPCODE_WRITE_REQ && conn->srv.penOp.origOpcode != ATT_OPCODE_PREPARE_WRITE_REQ) |
| return false; |
| |
| if (conn->srv.penOp.origOpcode == ATT_OPCODE_WRITE_REQ) { |
| conn->srv.penOp.origOpcode = 0; |
| return attSendPacketEmptyPacket(conn->conn, ATT_OPCODE_WRITE_RSP); |
| } |
| if (conn->srv.penOp.origOpcode == ATT_OPCODE_PREPARE_WRITE_REQ) { |
| conn->srv.penOp.origOpcode = 0; |
| return attSendPacket(conn->conn, ATT_OPCODE_PREPARE_WRITE_RSP, lastWriteData, leastWriteLen, NULL); |
| } |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: attSrvWriteStart |
| * USE: Start a [signed] write request / command |
| * PARAMS: to - whom to reply to |
| * mtu - the mtu to use |
| * wantedHandle - the handle to write |
| * data - sg with the data to write |
| * byteOfst - offset into the attribute to write |
| * opcode - the opcode the brought us here |
| * writeReason - the write reason to pass to write callback |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvWriteStart(l2c_handle_t to, uint32_t mtu, uint16_t wantedHandle, sg data, uint16_t byteOfst, uint8_t opcode, uint8_t writeReason) |
| { |
| struct attConn *conn = attConnFindByL2cConn(to); |
| uint8_t err = ATT_ERROR_UNLIKELY_ERROR; |
| struct attSrvRange *range; |
| struct attSrvVal *val; |
| |
| if (!conn) |
| loge("No connection found\n"); |
| else if (!conn->srv.penOp.origOpcode) { |
| |
| for (range = mRangesHead; range; range = range->next) { |
| if (!attSrvIsRangeVisibleTo(range, conn)) |
| continue; |
| for (val = range->valsHead; val; val = val->next) { |
| uint16_t handle = range->start + val->offset; |
| |
| if (handle > wantedHandle) |
| val = NULL; |
| if (handle < wantedHandle) |
| continue; |
| goto out; |
| } |
| } |
| out: |
| if (!val) |
| err = ATT_ERROR_INVALID_HANDLE; |
| else if ((err = attSrvIsValWriteableBy(range, val, conn, writeReason == ATT_WRITE_FOR_SIGNED_WRITE_CMD)) != ATT_ERROR_NONE) { |
| /* actully all is done in the if clause */ |
| } else { |
| conn->srv.penOp.origOpcode = opcode; |
| conn->srv.penOp.mtu = mtu; |
| conn->srv.penOp.startHandle = wantedHandle; |
| if (attSrvSchedWriteCbk(range, val, conn, writeReason, 0, data)) |
| return true; |
| conn->srv.penOp.origOpcode = 0; |
| } |
| } |
| |
| if (attSrvSendPacketErrorReply(to, err, ATT_OPCODE_WRITE_REQ, wantedHandle)) { |
| sgFree(data); |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * FUNCTION: attSrvOpSignedWrite |
| * USE: Perform a signed write command |
| * PARAMS: to - whom to reply to |
| * mtu - the mtu to use |
| * handle - the handle to write |
| * rxedSig - the SIG we got |
| * data - sg with the data to write |
| * RETURN: success |
| * NOTES: call with mAttLock held WILL NOT FREE "data". Caller must! |
| */ |
| static bool attSrvOpSignedWrite(l2c_handle_t to, uint32_t mtu, uint16_t handle, const uint8_t *rxedSig, sg data) |
| { |
| uint8_t hdr[sizeof(struct attHdr) + sizeof(struct attPduSignedWriteCmd)]; |
| struct attHdr *attHdr = (struct attHdr*)hdr; |
| struct attPduSignedWriteCmd *cmd = (struct attPduSignedWriteCmd*)(hdr + 1); |
| uint8_t calcedSig[ATT_SIG_LEN]; |
| uint8_t k[SM_BLOCK_LEN]; |
| struct bt_addr addr; |
| |
| if (!l2cApiGetBtAddr(to, &addr)) { |
| logw("Cannot get address for signed write. Dropping\n"); |
| goto out; |
| } |
| |
| /* XXX: may need to resolve address first */ |
| if (!persistGetDevKey(&addr, KEY_TYPE_CSRK, k)) { |
| logw("Cannot get CSRK for signed write. Dropping\n"); |
| goto out; |
| } |
| |
| /* Signature is over packet header too, but we stripped it. Luckily it is easy to reconstruct temporarily */ |
| utilSetLE8(&attHdr->opcode, ATT_OPCODE_SIGNED_WRITE_CMD); |
| utilSetLE16(&cmd->handle, handle); |
| if (!sgConcatFrontCopy(data, hdr, sizeof(hdr))) |
| return false; |
| |
| smSignatureCalc(data, k, calcedSig); |
| sgTruncFront(data, sizeof(hdr)); |
| |
| if (memcmp(calcedSig, rxedSig, ATT_SIG_LEN)) { |
| logw("Sig check failed. Dropping\n"); |
| goto out; |
| } |
| |
| return attSrvWriteStart(to, mtu, handle, data, 0, ATT_OPCODE_SIGNED_WRITE_CMD, ATT_WRITE_FOR_SIGNED_WRITE_CMD); |
| |
| out: |
| sgFree(data); |
| return true; |
| } |
| |
| |
| /* |
| * FUNCTION: attSrvOpExecuteWriteContinue |
| * USE: Continue an execute write request |
| * PARAMS: conn - per-connection structure |
| * to - the connection handle |
| * execResult - the result of the execution attempt |
| * errorHandle - in case of error stores the handle the caused it |
| * RETURN: false to fail immediately, true if more work will be done |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpExecuteWriteContinue(struct attConn *conn, uint8_t execResult, uint16_t errorHandle) |
| { |
| uint8_t err = ATT_ERROR_UNLIKELY_ERROR; |
| |
| |
| if (conn->srv.penOp.origOpcode != ATT_OPCODE_EXECUTE_WRITE_REQ) |
| return false; |
| |
| conn->srv.penOp.origOpcode = 0; |
| |
| if (!execResult) { |
| if (attSendPacketEmptyPacket(conn->conn, ATT_OPCODE_EXECUTE_WRITE_RSP)) |
| return true; |
| execResult = ATT_ERROR_UNLIKELY_ERROR; |
| } |
| |
| return attSrvSendPacketErrorReply(conn->conn, execResult, ATT_OPCODE_EXECUTE_WRITE_REQ, errorHandle); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpExecuteWriteStart |
| * USE: Start an execute write request |
| * PARAMS: to - whom to reply to |
| * mtu - the mtu to use |
| * flags - ATT_EXECUTE_WRITE_FLG_* |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpExecuteWriteStart(l2c_handle_t to, uint32_t mtu, uint8_t flags) |
| { |
| uint8_t err = ATT_ERROR_UNLIKELY_ERROR; |
| struct attConn* conn = attConnFindByL2cConn(to); |
| bool ret = ATT_ERROR_NONE; |
| |
| if (!conn) { |
| /* not much to do here - nothing we CAN do */ |
| } else if (flags == ATT_EXECUTE_WRITE_FLG_CANCEL || flags == ATT_EXECUTE_WRITE_FLG_EXECUTE) { |
| if (attSrvSchedExecCbk(conn, flags == ATT_EXECUTE_WRITE_FLG_EXECUTE)) |
| return true; |
| } else { |
| err = ATT_ERROR_INVALID_PDU; |
| goto err; |
| } |
| |
| if (ret == ATT_ERROR_NONE && attSendPacketEmptyPacket(to, ATT_OPCODE_EXECUTE_WRITE_RSP)) |
| return true; |
| /* if we fail to send, fall through and try to signal an error */ |
| |
| err: |
| return attSrvSendPacketErrorReply(to, ret, ATT_OPCODE_EXECUTE_WRITE_REQ, 0); |
| } |
| |
| /* |
| * FUNCTION: attSrvOpHandleValueConf |
| * USE: Handle a value confirmation |
| * PARAMS: to - whom to reply to (if any) |
| * mtu - the mtu to use |
| * RETURN: success |
| * NOTES: call with mAttLock held |
| */ |
| static bool attSrvOpHandleValueConf(l2c_handle_t to, uint32_t mtu) |
| { |
| struct attConn *conn; |
| |
| conn = attConnFindByL2cConn(to); |
| if (!conn) |
| loge("No connection found\n"); |
| else if (conn->srv.indTimer) { /* we're not late */ |
| attSrvSchedIndResultCbk(conn->srv.indRangeRef, conn->srv.indOffset, conn, ATT_SRV_EVT_IND_ACKED, conn->srv.indRef); |
| timerCancel(conn->srv.indTimer); |
| conn->srv.indTimer = 0; |
| } else /* we are late */ |
| logw("Confirmation is late\n"); |
| |
| pthread_mutex_lock(&mAttLock); |
| return true; |
| } |
| |
| /* |
| * FUNCTION: attCliTransTimeout |
| * USE: Timer callback for ATT trasnaction timer |
| * PARAMS: timer - the timer |
| * cbkData - the L2C connection handle |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void attCliTransTimeout(uniq_t timer, uint64_t cbkData) |
| { |
| l2c_handle_t to = (l2c_handle_t)cbkData; |
| struct attConn* conn; |
| pthread_mutex_lock(&mAttLock); |
| conn = attConnFindByL2cConn(to); |
| if (!conn) |
| logw("Timer for transaction to for unknown connection\n"); |
| else if (!conn->cli.ongoingTrans) |
| logw("Stale timer for transaction fired\n"); |
| else if (conn->cli.ongoingTrans != timer) |
| logw("Wrong timer for transaction fired\n"); |
| else { |
| if (!attCliScheduleErrorCbk(conn->cli.ongoingTrans, to, 0, 0, 0, true)) |
| loge("Failed to enqueue a timeout\n"); |
| conn->cli.ongoingTrans = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| /* |
| * FUNCTION: attCliReqStart |
| * USE: Prepare to send a request that we expect a response for |
| * PARAMS: to - whom to |
| * RETURN: non-zero transaction ID or 0 on failure |
| * NOTES: call with mAttLock held |
| */ |
| static uniq_t attCliReqStart(l2c_handle_t to) |
| { |
| struct attConn* conn; |
| |
| attConnListAddIfNew(to); |
| conn = attConnFindByL2cConn(to); |
| |
| if (!conn) |
| loge("Cannot find connection for new ATT transaction\n"); |
| else if (conn->cli.ongoingTrans) |
| logw("Refusing to start ATT transaction while another inflight\n"); |
| else { |
| conn->cli.ongoingTrans = timerSet(ATT_REQ_TIMOUT, attCliTransTimeout, (uint64_t)to); |
| return conn->cli.ongoingTrans; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * FUNCTION: attCliReqStop |
| * USE: Clean up state for a sent transaction for ATT client |
| * PARAMS: to - whom to |
| * RETURN: transaction ID that was in-progress or 0 if none |
| * NOTES: call with mAttLock held |
| */ |
| static uniq_t attCliReqStop(l2c_handle_t to) |
| { |
| struct attConn* conn; |
| uniq_t ret = 0; |
| |
| attConnListAddIfNew(to); |
| conn = attConnFindByL2cConn(to); |
| |
| if (!conn) |
| loge("Cannot find connection for enw ATT transaction\n"); |
| else if (!conn->cli.ongoingTrans) |
| logw("Refusing to stop ATT transaction while none inflight\n"); |
| else { |
| ret = conn->cli.ongoingTrans; |
| timerCancel(conn->cli.ongoingTrans); |
| conn->cli.ongoingTrans = 0; |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attSrvDataRx |
| * USE: Called when data arrives from a given connection |
| * PARAMS: from - whom the data arived from |
| * s - the data |
| * RETURN: NONE |
| * NOTES: |
| */ |
| void attSrvDataRx(l2c_handle_t from, sg s) |
| { |
| struct attPduFindByTypeValReq findByTypeValReq; |
| struct attPduPrepareWriteReq prepareWriteReq; |
| struct attPduReadByGrpTypeReq readByGrpType; |
| struct attPduSignedWriteCmd signedWriteCmd; |
| struct attPduExecuteWriteReq execWriteReq; |
| struct attPduReadByTypeReq readByTypeReq; |
| struct attPduFindInfoReq findInfoReq; |
| struct attPduReadBlobReq readBlobReq; |
| struct attPduHandleValueNotif notif; |
| struct attPduWriteReq writeReq; |
| struct attPduWriteCmd writeCmd; |
| struct attPduReadReq readReq; |
| uint8_t opcode, wiType = 0; |
| struct attPduError errRsp; |
| struct attPduMtu mtuExch; |
| struct attWorkItem *wi; |
| struct uuid uuid; |
| struct attHdr hdr; |
| bool ret = false; |
| uint32_t newMtu; |
| uint32_t mtu; |
| uniq_t trans; |
| |
| if (!sgSerializeCutFront(s, &hdr, sizeof(hdr))) { |
| loge("ATT cannot find header\n"); |
| sgFree(s); |
| return; |
| } |
| |
| opcode = utilGetLE8(&hdr.opcode); |
| |
| /* add to known client list */ |
| pthread_mutex_lock(&mAttLock); |
| attConnListAddIfNew(from); |
| |
| switch (opcode) { |
| case ATT_OPCODE_ERROR: |
| if (!sgSerializeCutFront(s, &errRsp, sizeof(errRsp))) |
| logw("Invalid error packet gotten: too short\n"); |
| else if (sgLength(s)) |
| logw("Invalid error packet gotten: too long (by %ub)\n", sgLength(s)); |
| else if (!(trans = attCliReqStop(from))) |
| logw("Got reply from no transaction in progress\n"); |
| else if (!attCliScheduleErrorCbk(trans, from, utilGetLE16(&errRsp.handle), utilGetLE16(&errRsp.err), utilGetLE16(&errRsp.reqOpcode), false)) |
| loge("Failed to schedule an error callback\n"); |
| break; |
| |
| case ATT_OPCODE_MTU_EXCH_REQ: |
| case ATT_OPCODE_MTU_EXCH_RSP: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &mtuExch, sizeof(mtuExch))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (sgLength(s)) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else { |
| struct attConn* conn = attConnFindByL2cConn(from); |
| |
| newMtu = utilGetLE16(&mtuExch.mtu); |
| mtu = attConnMtuGetByL2c(from, false); |
| if (!mtu || mtu > newMtu) |
| mtu = newMtu; |
| attConnMtuSet(from, mtu); |
| if (opcode == ATT_OPCODE_MTU_EXCH_REQ) { /* it was a request - we should reply */ |
| utilSetLE16(&mtuExch.mtu, mtu); /* we unconditionally accept */ |
| ret = attSendPacket(from, ATT_OPCODE_MTU_EXCH_RSP, &mtuExch, sizeof(mtuExch), NULL); |
| } |
| if (conn) |
| attSrvSchedMtuCbk(conn, mtu, opcode == ATT_OPCODE_MTU_EXCH_REQ); |
| } |
| break; |
| |
| case ATT_OPCODE_FIND_INFO_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &findInfoReq, sizeof(findInfoReq))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (sgLength(s)) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else |
| ret = attSrvOpFindInfo(from, mtu, utilGetLE16(&findInfoReq.startHandle), utilGetLE16(&findInfoReq.endHandle)); |
| break; |
| |
| case ATT_OPCODE_FIND_INFO_RSP: |
| if (!wiType) |
| wiType = WORK_CLI_FIND_INFO_CALL; |
| /* fall through */ |
| |
| case ATT_OPCODE_FIND_BY_TYPE_VAL_RSP: |
| if (!wiType) |
| wiType = WORK_CLI_FIND_BY_TYPE_VAL_CALL; |
| /* fall through */ |
| |
| case ATT_OPCODE_READ_BY_TYPE_RSP: |
| if (!wiType) |
| wiType = WORK_CLI_READ_BY_TYPE_CALL; |
| /* fall through */ |
| |
| case ATT_OPCODE_READ_RSP: |
| case ATT_OPCODE_READ_BLOB_RSP: |
| if (!wiType) |
| wiType = WORK_CLI_READ_CALL; |
| /* fall through */ |
| |
| case ATT_OPCODE_READ_BY_GRP_TYPE_RSP: |
| if (!wiType) |
| wiType = WORK_CLI_READ_BY_GRP_TYPE_CALL; |
| |
| /* common code for all client-mode response packets */ |
| if (!(trans = attCliReqStop(from))) |
| logw("Got reply from no transaction in progress\n"); |
| else if (!(wi = attWorkItemAlloc(wiType, 0))) |
| loge("Failed to make a work item\n"); |
| else { |
| wi->who = from; |
| wi->cli.trans = trans; |
| wi->cliJustData.data = s; |
| if (workQueuePut(mWorkQ, wi)) |
| s = NULL; |
| else { |
| free(wi); |
| loge("Failed to enqueue work item\n"); |
| } |
| } |
| break; |
| |
| case ATT_OPCODE_FIND_BY_TYPE_VAL_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &findByTypeValReq, sizeof(findByTypeValReq))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else { |
| uuidFromUuid16(&uuid, utilGetLE16(&findByTypeValReq.uuid16)); |
| if (attSrvOpFindByTypeValStart(from, mtu, utilGetLE16(&findByTypeValReq.startHandle), utilGetLE16(&findByTypeValReq.endHandle), &uuid, s)) { |
| ret = true; |
| s = NULL; |
| } |
| } |
| break; |
| |
| case ATT_OPCODE_READ_BY_TYPE_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &readByTypeReq, sizeof(readByTypeReq)) || !attReadUuid(&uuid, s)) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else |
| ret = attSrvOpReadByTypeStart(from, mtu, utilGetLE16(&readByTypeReq.startHandle), utilGetLE16(&readByTypeReq.endHandle), &uuid, false); |
| break; |
| |
| case ATT_OPCODE_READ_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &readReq, sizeof(readReq))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (sgLength(s)) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else |
| ret = attSrvOpReadStart(from, mtu, utilGetLE16(&readReq.handle), 0, opcode); |
| break; |
| |
| case ATT_OPCODE_READ_BLOB_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &readBlobReq, sizeof(readBlobReq))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (sgLength(s)) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else |
| ret = attSrvOpReadStart(from, mtu, utilGetLE16(&readBlobReq.handle), utilGetLE16(&readBlobReq.offset), opcode); |
| break; |
| |
| case ATT_OPCODE_READ_MULT_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if ((sgLength(s) % sizeof(uint16_t)) || sgLength(s) < sizeof(uint16_t[ATT_MIN_HANDLES_IN_READ_MULT])) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (attSrvOpReadMultStart(from, mtu, s)) { |
| ret = true; |
| s = NULL; |
| } |
| break; |
| |
| case ATT_OPCODE_READ_BY_GRP_TYPE_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &readByGrpType, sizeof(readByGrpType)) || !attReadUuid(&uuid, s)) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else |
| ret = attSrvOpReadByTypeStart(from, mtu, utilGetLE16(&readByGrpType.startHandle), utilGetLE16(&readByGrpType.endHandle), &uuid, true); |
| break; |
| |
| case ATT_OPCODE_WRITE_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &writeReq, sizeof(writeReq))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (attSrvWriteStart(from, mtu, utilGetLE16(&writeReq.handle), s, 0, opcode, ATT_WRITE_FOR_WRITE_REQ)) { |
| ret = true; |
| s = NULL; |
| } |
| break; |
| |
| case ATT_OPCODE_WRITE_RSP: |
| if (!(trans = attCliReqStop(from))) |
| logw("Got reply from no transaction in progress\n"); |
| else if (!(wi = attWorkItemAlloc(WORK_CLI_WRITE_CALL, 0))) |
| loge("Failed to make a work item\n"); |
| else { |
| wi->who = from; |
| wi->cli.trans = trans; |
| if (workQueuePut(mWorkQ, wi)) |
| s = NULL; |
| else { |
| free(wi); |
| loge("Failed to enqueue work item\n"); |
| } |
| } |
| break; |
| |
| case ATT_OPCODE_PREPARE_WRITE_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &prepareWriteReq, sizeof(prepareWriteReq))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (attSrvWriteStart(from, mtu, utilGetLE16(&prepareWriteReq.handle), s, utilGetLE16(&prepareWriteReq.offset), opcode, ATT_WRITE_FOR_PREPARE)) { |
| ret = true; |
| s = NULL; |
| } |
| break; |
| |
| case ATT_OPCODE_EXECUTE_WRITE_REQ: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &execWriteReq, sizeof(execWriteReq))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (sgLength(s)) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else |
| ret = attSrvOpExecuteWriteStart(from, mtu, utilGetLE16(&execWriteReq.flags)); |
| break; |
| |
| case ATT_OPCODE_HANDLE_VALUE_NOTIF: |
| case ATT_OPCODE_HANDLE_VALUE_IND: |
| if (!sgSerializeCutFront(s, ¬if, sizeof(notif))) |
| loge("Failed to get notification header\n"); |
| if (!(wi = attWorkItemAlloc(WORK_CLI_IND_NOTIF_CALL, 0))) |
| loge("Failed to make a work item\n"); |
| else { |
| wi->who = from; |
| wi->cliIndNotif.needsAck = opcode == ATT_OPCODE_HANDLE_VALUE_IND; |
| wi->cliIndNotif.data = s; |
| wi->cliIndNotif.handle = utilGetLE16(¬if.handle); |
| if (workQueuePut(mWorkQ, wi)) |
| s = NULL; |
| else { |
| free(wi); |
| loge("Failed to enqueue work item\n"); |
| } |
| } |
| break; |
| |
| case ATT_OPCODE_HANDLE_VALUE_CONF: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (sgLength(s)) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else |
| ret = attSrvOpHandleValueConf(from, mtu); |
| break; |
| |
| case ATT_OPCODE_WRITE_CMD: |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &writeCmd, sizeof(writeCmd))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (attSrvWriteStart(from, mtu, utilGetLE16(&writeReq.handle), s, 0, opcode, ATT_WRITE_FOR_WRITE_CMD)) { |
| ret = true; |
| s = NULL; |
| } |
| break; |
| |
| case ATT_OPCODE_SIGNED_WRITE_CMD: |
| if (l2cApiIsConnEncrypted(from)) { |
| /* ignore signed writes on encrypted connections as per spec */ |
| ret = true; |
| break; |
| } |
| mtu = attConnMtuGetByL2c(from, true); |
| if (!sgSerializeCutFront(s, &signedWriteCmd, sizeof(signedWriteCmd))) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else if (sgLength(s) < ATT_SIG_LEN) |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_INVALID_PDU, opcode, 0); |
| else { |
| uint8_t sig[ATT_SIG_LEN]; |
| sgSerialize(s, sgLength(s) - ATT_SIG_LEN, ATT_SIG_LEN, sig); |
| sgTruncBack(s, ATT_SIG_LEN); |
| if (attSrvOpSignedWrite(from, mtu, utilGetLE16(&signedWriteCmd.handle), sig, s)) { |
| ret = true; |
| s = NULL; |
| } |
| } |
| break; |
| |
| default: |
| logw("Unexpected ATT opcode 0x%02X\n", opcode); |
| ret = attSrvSendPacketErrorReply(from, ATT_ERROR_REQ_NOT_SUPPORTED, opcode, 0); |
| break; |
| } |
| |
| pthread_mutex_unlock(&mAttLock); |
| if (!ret) |
| loge("ATT failed to handle this message\n"); |
| if (s) |
| sgFree(s); |
| } |
| |
| /* |
| * FUNCTION: attSrvCbkReply |
| * USE: Called by higher layer when a callback we made to them completes |
| * PARAMS: cid - the connection ID in android-gatt terms |
| * transId - the transaction Id we originally passed to the higher layer |
| * handle - the handle value (if relevant -> only in exec write) |
| * err - ATT_ERROR_* |
| * data - the read data |
| * len - read length |
| * RETURN: unused |
| * NOTES: |
| */ |
| void attSrvCbkReply(att_cid_t cid, att_trans_t transId, uint16_t handle, uint8_t err, const void *data, uint16_t len) |
| { |
| struct attConn* conn; |
| uint8_t opcode; |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAttLock); |
| conn = attConnFindBySrvCid(cid); |
| if (!conn) |
| loge("Got callback reply for unknown connection\n"); |
| else if (conn->srv.expectedTransId != transId) |
| loge("Got callback reply for unknown transaction id "ATT_TRANS_FMT"u\n", ATT_TRANS_CONV(transId)); |
| else { |
| switch (conn->srv.penOp.origOpcode) { |
| case ATT_OPCODE_FIND_BY_TYPE_VAL_REQ: |
| ret = attSrvOpFindByTypeValContinue(conn, data, len, err); |
| break; |
| case ATT_OPCODE_READ_BY_TYPE_REQ: |
| case ATT_OPCODE_READ_BY_GRP_TYPE_REQ: |
| ret = attSrvOpReadByTypeContinue(conn, data, len, err); |
| break; |
| case ATT_OPCODE_READ_REQ: |
| case ATT_OPCODE_READ_BLOB_REQ: |
| ret = attSrvOpReadContinue(conn, data, len, err); |
| break; |
| case ATT_OPCODE_READ_MULT_REQ: |
| ret = attSrvOpReadMultContinue(conn, data, len, err); |
| break; |
| case ATT_OPCODE_WRITE_REQ: |
| case ATT_OPCODE_SIGNED_WRITE_CMD: |
| case ATT_OPCODE_WRITE_CMD: |
| case ATT_OPCODE_PREPARE_WRITE_REQ: |
| ret = attSrvWriteContinue(conn, data, len, err); |
| break; |
| case ATT_OPCODE_EXECUTE_WRITE_REQ: |
| ret = attSrvOpExecuteWriteContinue(conn, err, handle); |
| break; |
| default: |
| loge("Unknown original opcode\n"); |
| conn->srv.penOp.origOpcode = 0; |
| } |
| } |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| /* |
| * FUNCTION: attWorker |
| * USE: ATT worker thread |
| * PARAMS: unused - unused |
| * RETURN: unused |
| * NOTES: |
| */ |
| static void* attWorker(void *unused) |
| { |
| uint8_t format, length; |
| struct attWorkItem *wi; |
| bool keepGoing = true; |
| struct uuid uuid; |
| bool haveMore; |
| int status; |
| |
| pthread_setname_np(pthread_self(), "att_worker"); |
| |
| while(1) { |
| status = workQueueGet(mWorkQ, (void**)&wi); |
| |
| /* The only one thing we use the work queue status for - exiting */ |
| if (status) |
| break; |
| |
| switch (wi->type) { |
| case WORK_ATT_MTU_INFO: |
| (wi->mtuInfo.asServer ? mSrvMtuCbk : mCliMtuCbk)(wi->who, wi->mtuInfo.mtu); |
| break; |
| |
| case WORK_ATT_SYNC: |
| sem_post(wi->sync.sem); |
| break; |
| |
| case WORK_SRV_READ_CALL: |
| if (!mReadCbk(wi->who, wi->srv.cid, wi->srvRead.range, wi->srvRead.ofst, wi->srvRead.transId, wi->srvRead.byteOfst, wi->srvRead.reason, wi->srvRead.maxLen)) |
| attSrvCbkReply(wi->srv.cid, wi->srvRead.transId, 0, ATT_ERROR_UNLIKELY_ERROR, NULL, 0); |
| break; |
| |
| case WORK_SRV_WRITE_CALL: |
| if (!mWriteCbk(wi->who, wi->srv.cid, wi->srvWrite.range, wi->srvWrite.ofst, wi->srvWrite.transId, wi->srvWrite.byteOfst, wi->srvWrite.reason, wi->srvWrite.len, wi + 1)) |
| attSrvCbkReply(wi->srv.cid, wi->srvWrite.transId, 0, ATT_ERROR_UNLIKELY_ERROR, NULL, 0); |
| break; |
| |
| case WORK_SRV_EXEC_WRITE_CALL: |
| if (!mExecCbk(wi->who, wi->srv.cid, wi->srvExecWrite.transId, wi->srvExecWrite.exec)) |
| attSrvCbkReply(wi->srv.cid, wi->srvExecWrite.transId, 0, ATT_ERROR_UNLIKELY_ERROR, NULL, 0); |
| break; |
| |
| case WORK_SRV_IND_RESULT: |
| mIndCbk(wi->who, wi->srv.cid, wi->srvIndResult.range, wi->srvIndResult.ofst, wi->srvIndResult.evt, wi->srvIndResult.ref); |
| break; |
| |
| case WORK_CLI_FIND_INFO_CALL: |
| if (!sgSerializeCutFront(wi->cliJustData.data, &format, sizeof(format))) |
| logw("Failed to get format from received data for Find\n"); |
| else if (format != ATT_FIND_INFO_RSP_FMT_16 && format != ATT_FIND_INFO_RSP_FMT_128) |
| logw("Format %u unknown for Find\n", format); |
| else { |
| do { |
| uint16_t handle; |
| |
| if (format == ATT_FIND_INFO_RSP_FMT_16) { |
| uint8_t buf[sizeof(uint16_t)]; |
| |
| if (!sgSerializeCutFront(wi->cliJustData.data, buf, sizeof(handle))) |
| break; |
| handle = utilGetLE16(buf); |
| if (!sgSerializeCutFront(wi->cliJustData.data, buf, sizeof(buf))) |
| break; |
| uuidFromUuid16(&uuid, utilGetLE16(buf)); |
| haveMore = sgLength(wi->cliJustData.data) >= sizeof(buf) + sizeof(handle); |
| } else { |
| uint8_t buf[sizeof(struct uuid)]; |
| |
| if (!sgSerializeCutFront(wi->cliJustData.data, buf, sizeof(handle))) |
| break; |
| handle = utilGetLE16(buf); |
| if (!sgSerializeCutFront(wi->cliJustData.data, &buf, sizeof(buf))) |
| break; |
| uuidReadLE(&uuid, buf); |
| haveMore = sgLength(wi->cliJustData.data) >= sizeof(buf) + sizeof(handle); |
| } |
| keepGoing = mCliFindInfoCbk(wi->cli.trans, wi->who, handle, &uuid, haveMore); |
| } while (haveMore && keepGoing); |
| if (sgLength(wi->cliJustData.data) && keepGoing) |
| logw("Unexpected leftover data for Find: %ub\n", sgLength(wi->cliJustData.data)); |
| } |
| sgFree(wi->cliJustData.data); |
| break; |
| |
| case WORK_CLI_FIND_BY_TYPE_VAL_CALL: |
| do { |
| uint16_t attrH, grpEndH; |
| uint8_t buf[sizeof(uint16_t) * 2]; |
| |
| if (!sgSerializeCutFront(wi->cliJustData.data, buf, sizeof(buf))) |
| break; |
| |
| haveMore = sgLength(wi->cliJustData.data) >= sizeof(buf); |
| attrH = utilGetLE16(buf); |
| grpEndH = utilGetLE16(buf + sizeof(attrH)); |
| |
| keepGoing = mCliFindByTypeValCbk(wi->cli.trans, wi->who, attrH, grpEndH, haveMore); |
| } while (haveMore && keepGoing); |
| if (sgLength(wi->cliJustData.data) && keepGoing) |
| logw("Unexpected leftover data for FindByTypeValue: %ub\n", sgLength(wi->cliJustData.data)); |
| sgFree(wi->cliJustData.data); |
| break; |
| |
| |
| case WORK_CLI_READ_BY_TYPE_CALL: |
| if (!sgSerializeCutFront(wi->cliJustData.data, &length, sizeof(length))) |
| logw("Failed to get length from received data for ReadByType\n"); |
| else if (length < sizeof(uint16_t)) |
| logw("Length %u impossible for ReadByType\n", length); |
| else if (sgLength(wi->cliJustData.data) < length) |
| logw("Sg length %u impossible for ReadByType (expected %u+)\n", sgLength(wi->cliJustData.data), length); |
| else { |
| uint8_t buf[sizeof(uint16_t)]; |
| uint16_t handle; |
| sg s; |
| |
| do { |
| if (!sgSerializeCutFront(wi->cliJustData.data, buf, sizeof(buf))) |
| break; |
| handle = utilGetLE16(buf); |
| |
| s = sgSplit(wi->cliJustData.data, length - sizeof(buf)); |
| if (!s) |
| break; |
| sgSwap(wi->cliJustData.data, s); |
| |
| haveMore = sgLength(wi->cliJustData.data) >= length; |
| keepGoing = mCliReadF(wi->cli.trans, wi->who, &handle, NULL, s, haveMore); |
| } while(haveMore && keepGoing); |
| } |
| if (sgLength(wi->cliJustData.data) && keepGoing) |
| logw("Unexpected leftover data for ReadByGroupType: %ub\n", sgLength(wi->cliJustData.data)); |
| sgFree(wi->cliJustData.data); |
| break; |
| |
| case WORK_CLI_READ_CALL: |
| mCliReadF(wi->cli.trans, wi->who, NULL, NULL, wi->cliJustData.data, false); |
| break; |
| |
| case WORK_CLI_READ_BY_GRP_TYPE_CALL: |
| if (!sgSerializeCutFront(wi->cliJustData.data, &length, sizeof(length))) |
| logw("Failed to get length from received data for ReadByGroupType\n"); |
| else if (length < sizeof(uint16_t) * 2) |
| logw("Length %u impossible for ReadByGroupType\n", length); |
| else if (sgLength(wi->cliJustData.data) < length) |
| logw("Sg length %u impossible for ReadByGroupType (expected %u+)\n", sgLength(wi->cliJustData.data), length); |
| else { |
| uint8_t buf[sizeof(uint16_t)]; |
| uint16_t handle, grpEndHandle; |
| sg s; |
| |
| do { |
| if (!sgSerializeCutFront(wi->cliJustData.data, buf, sizeof(buf))) |
| break; |
| handle = utilGetLE16(buf); |
| |
| if (!sgSerializeCutFront(wi->cliJustData.data, buf, sizeof(buf))) |
| break; |
| grpEndHandle = utilGetLE16(buf); |
| |
| s = sgSplit(wi->cliJustData.data, length - sizeof(buf) * 2); |
| if (!s) |
| break; |
| sgSwap(wi->cliJustData.data, s); |
| |
| haveMore = sgLength(wi->cliJustData.data) >= length; |
| keepGoing = mCliReadF(wi->cli.trans, wi->who, &handle, &grpEndHandle, s, haveMore); |
| } while(haveMore && keepGoing); |
| } |
| if (sgLength(wi->cliJustData.data) && keepGoing) |
| logw("Unexpected leftover data for ReadByGroupType: %ub\n", sgLength(wi->cliJustData.data)); |
| sgFree(wi->cliJustData.data); |
| break; |
| |
| case WORK_CLI_WRITE_CALL: |
| mCliWriteF(wi->cli.trans, wi->who); |
| break; |
| |
| case WORK_CLI_ERR_CALL: |
| mCliErrF(wi->cli.trans, wi->who, wi->cliErr.isLocalErr ? NULL : &wi->cliErr.handle, wi->cliErr.isLocalErr ? NULL : &wi->cliErr.err, wi->cliErr.isLocalErr ? NULL : &wi->cliErr.errOpcode); |
| break; |
| |
| case WORK_CLI_IND_NOTIF_CALL: |
| mCliIndNotifF(wi->who, wi->cliIndNotif.needsAck, wi->cliIndNotif.handle, wi->cliIndNotif.data); |
| break; |
| |
| default: |
| loge("Unknown work type %d\n", wi->type); |
| break; |
| } |
| free(wi); |
| } |
| |
| logd("ATT worker exiting\n"); |
| return NULL; |
| } |
| |
| /* |
| * FUNCTION: attWorkItemFree |
| * USE: Free an ATT work item at time of work quque destruction |
| * PARAMS: NONE |
| * RETURN: success |
| * NOTES: |
| */ |
| static void attWorkItemFree(void *item) |
| { |
| struct attWorkItem *wi = (struct attWorkItem*)item; |
| |
| /* future clean steps here */ |
| |
| free(wi); |
| } |
| |
| /* |
| * FUNCTION: attInit |
| * USE: Called to init all of ATT |
| * PARAMS: NONE |
| * RETURN: success |
| * NOTES: internally ref-counted as multiple users exist |
| */ |
| bool attInit(void) |
| { |
| bool ret = false; |
| |
| pthread_mutex_lock(&mAttLock); |
| if (!mNumUsers) { |
| |
| mConns = NULL; |
| mWorkQ = workQueueAlloc(ATT_WORK_Q_SIZE); |
| if (!mWorkQ) |
| loge("Failed to create work Q\n"); |
| else if (pthread_create(&mWorker, NULL, attWorker, NULL)) { |
| loge("Failed to create worker\n"); |
| workQueueFree(mWorkQ, attWorkItemFree); |
| } else { |
| ret = true; |
| } |
| } else |
| ret = true; |
| |
| if (ret) |
| mNumUsers++; |
| |
| pthread_mutex_unlock(&mAttLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attDeinit |
| * USE: Called to deinit all of ATT |
| * PARAMS: NONE |
| * RETURN: NONE |
| * NOTES: internally ref-counted as multiple users exist |
| */ |
| void attDeinit(void) |
| { |
| pthread_mutex_lock(&mAttLock); |
| if (!mNumUsers) |
| loge("too many ATT closures!\n"); |
| else if (!--mNumUsers) { |
| workQueueWakeAll(mWorkQ, 1); |
| pthread_join(mWorker, NULL); |
| workQueueFree(mWorkQ, attWorkItemFree); |
| while (mConns) { |
| struct attConn *t = mConns->next; |
| free(mConns); |
| mConns = t; |
| } |
| } |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| /* |
| * FUNCTION: attCliRequestMtu |
| * USE: As a client request an MTU value from the server |
| * PARAMS: to - the server l2c connection |
| * mtu - the mtu to request |
| * RETURN: false on error, true if will try |
| * NOTES: |
| */ |
| bool attCliRequestMtu(l2c_handle_t to, uint32_t mtu) |
| { |
| struct attPduMtu mtuExch; |
| bool ret; |
| |
| utilSetLE16(&mtuExch.mtu, mtu); |
| return attSendPacket(to, ATT_OPCODE_MTU_EXCH_REQ, &mtuExch, sizeof(mtuExch), NULL); |
| } |
| |
| /* |
| * FUNCTION: attCliInit |
| * USE: Init ATT client code (by providing callbacks) |
| * PARAMS: cliMtuF - calback about MTU negotiation results |
| * cliReadF - read callback for clients |
| * cliWriteF - write callabck for clients |
| * cliErrF - error callback for clients |
| * RETURN: NONE |
| * NOTES: |
| */ |
| void attCliInit(attMtuCbk cliMtuF, attCliFindInfoCbk cliFindInfoCbk, attCliFindByTypeValCbk cliFindByTypeValCbk, attCliReadCbk cliReadF, attCliWriteCbk cliWriteF, attCliError cliErrF, attCliIndNotif cliIndNotifF) |
| { |
| pthread_mutex_lock(&mAttLock); |
| mCliMtuCbk = cliMtuF; |
| mCliFindInfoCbk = cliFindInfoCbk; |
| mCliFindByTypeValCbk = cliFindByTypeValCbk; |
| mCliReadF = cliReadF; |
| mCliWriteF = cliWriteF; |
| mCliErrF = cliErrF; |
| mCliIndNotifF = cliIndNotifF; |
| pthread_mutex_unlock(&mAttLock); |
| } |
| |
| /* |
| * FUNCTION: attCliDeinit |
| * USE: Deinit ATT client code |
| * PARAMS: NONE |
| * RETURN: |
| * NOTES: |
| */ |
| void attCliDeinit(void) |
| { |
| //TODO: maybe? |
| } |
| |
| /* |
| * FUNCTION: attCliSendIndicationAck |
| * USE: Send a value confirmation (an ack to a value indication) |
| * PARAMS: to - whom to |
| * RETURN: false on error |
| * NOTES: |
| */ |
| bool attCliSendIndicationAck(l2c_handle_t to) |
| { |
| return attSendPacketEmptyPacket(to, ATT_OPCODE_HANDLE_VALUE_CONF); |
| } |
| |
| /* |
| * FUNCTION: attCliFindInformation |
| * USE: Send a find-information request to the server |
| * PARAMS: to - whom to |
| * hStart - start handle |
| * hEnd - end handle |
| * RETURN: non-zero transaction ID or 0 on failure |
| * NOTES: find or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliFindInformation(l2c_handle_t to, uint16_t hStart, uint16_t hEnd) |
| { |
| struct attPduFindInfoReq req; |
| uniq_t ret; |
| |
| utilSetLE16(&req.startHandle, hStart); |
| utilSetLE16(&req.endHandle, hEnd); |
| |
| pthread_mutex_lock(&mAttLock); |
| ret = attCliReqStart(to); |
| if (ret && !attSendPacket(to, ATT_OPCODE_FIND_INFO_REQ, &req, sizeof(req), NULL)) { |
| attCliReqStop(to); |
| ret = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attCliFindByTypeValue |
| * USE: Send a find-by-type-value request to the server |
| * PARAMS: to - whom to |
| * hStart - start handle |
| * hEnd - end handle |
| * typeUuid16 - type (must be a uuid16) |
| * value - the value to look for |
| * RETURN: non-zero transaction ID or 0 on failure |
| * NOTES: find or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliFindByTypeValue(l2c_handle_t to, uint16_t hStart, uint16_t hEnd, uint16_t typeUuid16, sg value) |
| { |
| struct attPduFindByTypeValReq req; |
| uint32_t mtu; |
| uniq_t ret; |
| |
| utilSetLE16(&req.startHandle, hStart); |
| utilSetLE16(&req.endHandle, hEnd); |
| utilSetLE16(&req.uuid16, typeUuid16); |
| |
| pthread_mutex_lock(&mAttLock); |
| mtu = attConnMtuGetByL2c(to, true); |
| if (sizeof(req) + sizeof(struct attHdr) + sgLength(value) > mtu) { |
| logw("Truncating value in FindByTypeValue to fit into mtu %u -> %u\n", sgLength(value), mtu - sizeof(req) - sizeof(struct attHdr)); |
| sgTruncBack(value, sgLength(value) + sizeof(req) + sizeof(struct attHdr) - mtu); |
| } |
| |
| ret = attCliReqStart(to); |
| if (ret && !attSendPacket(to, ATT_OPCODE_FIND_BY_TYPE_VAL_REQ, &req, sizeof(req), value)) { |
| attCliReqStop(to); |
| ret = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attCliReadByType |
| * USE: Send a read-by-type request to the server |
| * PARAMS: to - whom to |
| * hStart - start handle |
| * hEnd - end handle |
| * type - the type of values to read |
| * RETURN: non-zero transaction ID or 0 on failure |
| * NOTES: read or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliReadByType(l2c_handle_t to, uint16_t hStart, uint16_t hEnd, const struct uuid *type) |
| { |
| uint8_t hdrBuf[sizeof(struct attPduReadByTypeReq) + sizeof(struct uuid) + sizeof(uint16_t)]; /* definitely big enoguh to fit both formats */ |
| struct attPduReadByTypeReq *req = (struct attPduReadByTypeReq*)hdrBuf; |
| struct uuid *uuid128 = (struct uuid*)(req + 1); |
| uint16_t *uuid16 = (uint16_t*)(req + 1); |
| uint16_t tmpUuid; |
| unsigned uuidSz; |
| uniq_t ret; |
| |
| utilSetLE16(&req->startHandle, hStart); |
| utilSetLE16(&req->endHandle, hEnd); |
| |
| if (uuidToUuid16(&tmpUuid, type)) { |
| utilSetLE16(uuid16, tmpUuid); |
| uuidSz = sizeof(uint16_t); |
| } else { |
| uuidWriteLE(uuid128, type); |
| uuidSz = sizeof(struct uuid); |
| } |
| |
| pthread_mutex_lock(&mAttLock); |
| ret = attCliReqStart(to); |
| if (ret && !attSendPacket(to, ATT_OPCODE_READ_BY_TYPE_REQ, hdrBuf, sizeof(struct attPduReadByTypeReq) + uuidSz, NULL)) { |
| attCliReqStop(to); |
| ret = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attCliReadByGroupType |
| * USE: Send a read-by-group-type request to the server |
| * PARAMS: to - whom to |
| * hStart - start handle |
| * hEnd - end handle |
| * type - the group type of values to read |
| * RETURN: non-zero transaction ID or 0 on failure |
| * NOTES: read or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliReadByGroupType(l2c_handle_t to, uint16_t hStart, uint16_t hEnd, const struct uuid *type) |
| { |
| uint8_t hdrBuf[sizeof(struct attPduReadByGrpTypeReq) + sizeof(struct uuid) + sizeof(uint16_t)]; /* definitely big enoguh to fit both formats */ |
| struct attPduReadByGrpTypeReq *req = (struct attPduReadByGrpTypeReq*)hdrBuf; |
| struct uuid *uuid128 = (struct uuid*)(req + 1); |
| uint16_t *uuid16 = (uint16_t*)(req + 1); |
| uint16_t tmpUuid; |
| unsigned uuidSz; |
| uniq_t ret; |
| |
| utilSetLE16(&req->startHandle, hStart); |
| utilSetLE16(&req->endHandle, hEnd); |
| |
| if (uuidToUuid16(&tmpUuid, type)) { |
| utilSetLE16(uuid16, tmpUuid); |
| uuidSz = sizeof(uint16_t); |
| } else { |
| uuidWriteLE(uuid128, type); |
| uuidSz = sizeof(struct uuid); |
| } |
| |
| pthread_mutex_lock(&mAttLock); |
| ret = attCliReqStart(to); |
| if (ret && !attSendPacket(to, ATT_OPCODE_READ_BY_GRP_TYPE_REQ, hdrBuf, sizeof(struct attPduReadByTypeReq) + uuidSz, NULL)) { |
| attCliReqStop(to); |
| ret = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attCliRead |
| * USE: Send a read request to the server |
| * PARAMS: to - whom to |
| * handle - the handle |
| * RETURN: non-zero transaction ID or 0 on failure |
| * NOTES: read or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliRead(l2c_handle_t to, uint16_t handle) |
| { |
| struct attPduReadReq req; |
| uniq_t ret; |
| |
| utilSetLE16(&req.handle, handle); |
| |
| pthread_mutex_lock(&mAttLock); |
| ret = attCliReqStart(to); |
| if (ret && !attSendPacket(to, ATT_OPCODE_READ_REQ, &req, sizeof(req), NULL)) { |
| attCliReqStop(to); |
| ret = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attCliReadBlob |
| * USE: Send a read-blob request to the server |
| * PARAMS: to - whom to |
| * handle - the handle |
| * offset - the offset to request into the value |
| * RETURN: non-zero transaction ID or 0 on failure |
| * NOTES: read or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliReadBlob(l2c_handle_t to, uint16_t handle, uint16_t offset) |
| { |
| struct attPduReadBlobReq req; |
| uniq_t ret; |
| |
| utilSetLE16(&req.handle, handle); |
| utilSetLE16(&req.offset, offset); |
| |
| pthread_mutex_lock(&mAttLock); |
| ret = attCliReqStart(to); |
| if (ret && !attSendPacket(to, ATT_OPCODE_READ_BLOB_REQ, &req, sizeof(req), NULL)) { |
| attCliReqStop(to); |
| ret = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| |
| /* |
| * FUNCTION: attCliWriteInt |
| * USE: Send a write request/command to the server |
| * PARAMS: to - whom to |
| * handle - the handle |
| * data - the data |
| * cmd - use a write command? (else we'll use request) |
| * RETURN: non-zero transaction ID or 0 on failure (transaction ID returned is fake for write command) |
| * NOTES: write or error callbacks passed to attCliInit may be called in case of request. |
| * none will be for command. Passed-in SG may be truncated to MTU-3 even in cases of failure |
| */ |
| uniq_t attCliWriteInt(l2c_handle_t to, uint16_t handle, sg data, bool cmd) |
| { |
| struct attPduWriteCmd req; |
| uint32_t mtu; |
| uniq_t ret; |
| |
| utilSetLE16(&req.handle, handle); |
| |
| pthread_mutex_lock(&mAttLock); |
| mtu = attConnMtuGetByL2c(to, true); |
| if (sizeof(req) + sizeof(struct attHdr) + sgLength(data) > mtu) { |
| logw("Truncating value in Write to fit into mtu %u -> %u\n", sgLength(data), mtu - sizeof(req) - sizeof(struct attHdr)); |
| sgTruncBack(data, sgLength(data) + sizeof(req) + sizeof(struct attHdr) - mtu); |
| } |
| |
| ret = cmd ? 1 : attCliReqStart(to); |
| if (ret && !attSendPacket(to, cmd ? ATT_OPCODE_WRITE_CMD : ATT_OPCODE_WRITE_REQ, &req, sizeof(req), data)) { |
| if (!cmd) |
| attCliReqStop(to); |
| ret = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attCliWrite |
| * USE: Send a write request to the server |
| * PARAMS: to - whom to |
| * handle - the handle |
| * data - the data |
| * RETURN: non-zero transaction ID or 0 on failure |
| * NOTES: write or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliWrite(l2c_handle_t to, uint16_t handle, sg data) |
| { |
| return attCliWriteInt(to, handle, data, false); |
| } |
| |
| /* |
| * FUNCTION: attCliWrite |
| * USE: Send a write command to the server |
| * PARAMS: to - whom to |
| * handle - the handle |
| * data - the data |
| * RETURN: false on failure |
| * NOTES: no callbacks will be called. |
| */ |
| bool attCliWriteNoAck(l2c_handle_t to, uint16_t handle, sg data) |
| { |
| return !!attCliWriteInt(to, handle, data, true); |
| } |
| |
| /* |
| * FUNCTION: attCliSignedWriteNoAck |
| * USE: Send a signed write command to the server |
| * PARAMS: to - whom to |
| * handle - the handle |
| * data - the data |
| * RETURN: false on failure |
| * NOTES: no callbacks will be called. |
| */ |
| bool attCliSignedWriteNoAck(l2c_handle_t to, uint16_t handle, sg data) |
| { |
| uint8_t hdr[sizeof(struct attHdr) + sizeof(struct attPduSignedWriteCmd)]; |
| struct attHdr *attHdr = (struct attHdr*)hdr; |
| struct attPduSignedWriteCmd *cmd = (struct attPduSignedWriteCmd*)(hdr + 1); |
| uint8_t sig[ATT_SIG_LEN]; |
| uint8_t k[SM_BLOCK_LEN]; |
| bool ret = false; |
| uint32_t mtu; |
| |
| utilSetLE8(&attHdr->opcode, ATT_OPCODE_SIGNED_WRITE_CMD); |
| utilSetLE16(&cmd->handle, handle); |
| |
| if (!persistGetDevKey(NULL, KEY_TYPE_CSRK, k)) { |
| logw("Cannot get ownCSRK for signed write\n"); |
| return false; |
| } |
| |
| pthread_mutex_lock(&mAttLock); |
| mtu = attConnMtuGetByL2c(to, true); |
| if (sizeof(hdr) + sizeof(sig) + sgLength(data) > mtu) { |
| logw("Truncating value in SignedWrite to fit into mtu %u -> %u\n", sgLength(data), mtu - sizeof(hdr) - sizeof(sig)); |
| sgTruncBack(data, sgLength(data) + sizeof(hdr) + sizeof(sig) - mtu); |
| } |
| |
| if (!sgConcatFrontCopy(data, hdr, sizeof(hdr))) |
| loge("Failed to concat SG for signed write\n"); |
| else { |
| smSignatureCalc(data, k, sig); |
| |
| ret = attSendPacketRaw(to, NULL, 0, data, sig, sizeof(sig)) == ATT_TX_RET_ACCEPTED; |
| if (!ret) |
| sgTruncFront(data, sizeof(hdr)); |
| } |
| |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attCliPrepareWrite |
| * USE: Send a prepare-write request to the server |
| * PARAMS: to - whom to |
| * handle - the handle |
| * offset - the offset into the data to write |
| * data - the data |
| * RETURN: transaction id or 0 on failure |
| * NOTES: write or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliPrepareWrite(l2c_handle_t to, uint16_t handle, uint16_t offset, sg data) |
| { |
| struct attPduPrepareWriteReq req; |
| uint32_t mtu; |
| uniq_t ret; |
| |
| utilSetLE16(&req.handle, handle); |
| utilSetLE16(&req.offset, offset); |
| |
| pthread_mutex_lock(&mAttLock); |
| mtu = attConnMtuGetByL2c(to, true); |
| if (sizeof(req) + sizeof(struct attHdr) + sgLength(data) > mtu) { |
| logw("Truncating value in PrepareWrite to fit into mtu %u -> %u\n", sgLength(data), mtu - sizeof(req) - sizeof(struct attHdr)); |
| sgTruncBack(data, sgLength(data) + sizeof(req) + sizeof(struct attHdr) - mtu); |
| } |
| |
| ret = attCliReqStart(to); |
| if (ret && !attSendPacket(to, ATT_OPCODE_PREPARE_WRITE_REQ, &req, sizeof(req), data)) { |
| attCliReqStop(to); |
| ret = 0; |
| } |
| |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attCliExecuteStagedWrites |
| * USE: Send a execute-write request to the server |
| * PARAMS: to - whom to |
| * commit - commit data or destroy it? |
| * RETURN: transaction id or 0 on failure |
| * NOTES: write or error callbacks passed to attCliInit may be called |
| */ |
| uniq_t attCliExecuteStagedWrites(l2c_handle_t to, bool commit) |
| { |
| struct attPduExecuteWriteReq req; |
| uniq_t ret; |
| |
| utilSetLE8(&req.flags, commit ? ATT_EXECUTE_WRITE_FLG_EXECUTE : ATT_EXECUTE_WRITE_FLG_CANCEL); |
| |
| pthread_mutex_lock(&mAttLock); |
| ret = attCliReqStart(to); |
| if (ret && !attSendPacket(to, ATT_OPCODE_EXECUTE_WRITE_REQ, &req, sizeof(req), NULL)) { |
| attCliReqStop(to); |
| ret = 0; |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: attClientInvalidateCache |
| * USE: Invalidate the cache for a given connection |
| * PARAMS: to - whom to |
| * RETURN: success? |
| * NOTES: |
| */ |
| bool attClientInvalidateCache(l2c_handle_t to) |
| { |
| struct attConn* conn; |
| bool ret = true; |
| |
| pthread_mutex_lock(&mAttLock); |
| conn = attConnFindByL2cConn(to); |
| if (!conn) |
| logw("Caches of nonexistent AATT connections are by definition always clean :)\n"); |
| else { |
| //TODO: this |
| } |
| pthread_mutex_unlock(&mAttLock); |
| |
| return ret; |
| } |
| |
| |
| |
| |
| |