#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; | |
int cid; /* for higher layer */ | |
int nextTransId; | |
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 { | |
int 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; | |
int transId; | |
uint16_t byteOfst; | |
uint16_t maxLen; | |
uint8_t reason; | |
} srvRead; | |
struct { | |
att_range_t range; | |
uint16_t ofst; | |
int transId; | |
uint16_t byteOfst; | |
uint16_t len; | |
uint8_t reason; | |
/* data follows */ | |
} srvWrite; | |
struct { | |
int 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(int 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 (positive int) | |
* NOTES: call with mAttLock held | |
*/ | |
static int attSrvSchedCbkGetNextTid(struct attConn* conn) | |
{ | |
int tid; | |
conn->srv.nextTransId++; | |
if (conn->srv.nextTransId < 0) | |
conn->srv.nextTransId = 1; | |
return conn->srv.nextTransId; | |
} | |
/* | |
* 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; | |
int tid; | |
wi = attWorkItemAlloc(WORK_SRV_READ_CALL, 0); | |
if (!wi) | |
return false; | |
tid = attSrvSchedCbkGetNextTid(conn); | |
wi->who = conn->conn; | |
wi->srv.cid = conn->srv.cid; | |
wi->srvRead.range = range->rangeRef; | |
wi->srvRead.ofst = val->offset; | |
wi->srvRead.transId = tid; | |
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; | |
int tid; | |
wi = attWorkItemAlloc(WORK_SRV_WRITE_CALL, len); | |
if (!wi) | |
return false; | |
tid = attSrvSchedCbkGetNextTid(conn); | |
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 = tid; | |
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; | |
int tid; | |
wi = attWorkItemAlloc(WORK_SRV_EXEC_WRITE_CALL, 0); | |
if (!wi) | |
return false; | |
tid = attSrvSchedCbkGetNextTid(conn); | |
wi->who = conn->conn; | |
wi->srv.cid = conn->srv.cid; | |
wi->srvExecWrite.transId = tid; | |
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 = mConns ? mConns->srv.cid : 1; | |
while (attConnFindBySrvCid(conn->srv.cid)) | |
if (++conn->srv.cid < 0) | |
conn->srv.cid = 1; | |
/* 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; | |
int newCid; | |
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, int 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(int 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 | |
* 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 attSrvOpReadByTypeContinue(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 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) && leastReadLen != conn->srv.penOp.readByType.itemLen) | |
goto done; | |
conn->srv.penOp.readByType.itemLen = leastReadLen; | |
/* 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, leastReadLen)) { | |
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 */ | |
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; | |
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; | |
conn->srv.penOp.origOpcode = 0; | |
if (conn->srv.penOp.origOpcode == ATT_OPCODE_WRITE_REQ) | |
return attSendPacketEmptyPacket(conn->conn, ATT_OPCODE_WRITE_RSP); | |
if (conn->srv.penOp.origOpcode == ATT_OPCODE_PREPARE_WRITE_REQ) | |
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; | |
} | |
out: | |
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(int cid, int 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.nextTransId != transId) | |
loge("Got callback reply for unknown transcation id %u\n", 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; | |
} | |