| #include <string.h> |
| #include <stdlib.h> |
| #include "l2cap.h" |
| #include "sendQ.h" |
| #include "uniq.h" |
| #include "uuid.h" |
| #include "util.h" |
| #include "sdp.h" |
| #include "log.h" |
| #include "sg.h" |
| #include "mt.h" |
| |
| #define SDP_SDP_SERVER_UUID 0x1000 |
| |
| #define SDP_PSM 0x0001 |
| |
| #define SDP_HANDLE_SELF 0 |
| #define SDP_HANDLE_FIRST 0x00010000 |
| #define SDP_HANDLE_LAST 0xFFFFFFFF |
| #define SDP_MAX_UUIDS_IN_SEARCH 12 |
| |
| #define SDP_PDU_Error_Response 1 |
| #define SDP_PDU_Service_Search_Request 2 |
| #define SDP_PDU_Service_Search_Response 3 |
| #define SDP_PDU_Service_Attribute_Request 4 |
| #define SDP_PDU_Service_Attribute_Response 5 |
| #define SDP_PDU_Service_Search_Attribute_Request 6 |
| #define SDP_PDU_Service_Search_Attribute_Response 7 |
| |
| #define SDP_ERR_Invalid_SDP_Version 0x0001 |
| #define SDP_ERR_Invalid_Service_Record_Handle 0x0002 |
| #define SDP_ERR_Invalid_Request_Syntax 0x0003 |
| #define SDP_ERR_Invalid_PDU_Size 0x0004 |
| #define SDP_ERR_Invalid_Continuation_State 0x0005 |
| #define SDP_ERR_Insufficient_Resources 0x0006 |
| |
| |
| |
| struct sdpService { |
| struct sdpService *prev; |
| struct sdpService *next; |
| uint32_t handle; /* will match what's in descr, but quicker to read it here */ |
| uint32_t len; |
| uint8_t descr[]; |
| }; |
| |
| struct sdpSearchResultNode { |
| struct sdpSearchResultNode *next; |
| struct sdpService *ss; |
| }; |
| |
| struct sdpInst { |
| l2c_handle_t conn; |
| uint16_t mtu; |
| |
| uniq_t contVal; |
| sg contResult; |
| union { |
| uint16_t numHandles; |
| }; |
| }; |
| |
| |
| struct sdpPdu { |
| uint8_t pduType; |
| uint16_t transactionID; |
| uint16_t paramLen; |
| } __packed; |
| |
| struct sdpPduSSRepl { |
| uint16_t totalHandles; |
| uint16_t numHandles; |
| } __packed; |
| |
| struct sdpCont { |
| uint8_t len; |
| uniq_t contVal; |
| } __packed; |
| |
| |
| /* our global state */ |
| |
| static pthread_rwlock_t mSvcsLock = PTHREAD_RWLOCK_INITIALIZER; |
| static struct sdpService *mSvcsHead = NULL; |
| static struct sdpService *mSvcsTail = NULL; |
| static uint32_t mNextHandle; |
| static struct sendQ *mSendQ; |
| |
| |
| |
| /* |
| * FUNCTION: sdpServiceFindByHandle |
| * USE: Find a service struct by handle |
| * PARAMS: handle - the handle |
| * RETURN: service struct or NULL |
| * NOTES: call with mSvcsLock held for read |
| */ |
| static struct sdpService* sdpServiceFindByHandle(uint32_t handle) |
| { |
| struct sdpService *ss; |
| |
| for (ss = mSvcsHead; ss; ss = ss->next) |
| if (ss->handle == handle) |
| return ss; |
| return NULL; |
| } |
| |
| /* |
| * FUNCTION: sdpServiceDescriptorDelInt |
| * USE: Delete a service struct |
| * PARAMS: ss - the service struct to delete |
| * RETURN: NONE |
| * NOTES: call with mSvcsLock held for write |
| */ |
| static void sdpServiceDescriptorDelInt(struct sdpService *ss) |
| { |
| if (ss->prev) |
| ss->prev->next = ss->next; |
| else |
| mSvcsHead = ss->next; |
| |
| if (ss->next) |
| ss->next->prev = ss->prev; |
| else |
| mSvcsTail = ss->prev; |
| |
| free(ss); |
| } |
| |
| /* |
| * FUNCTION: sdpServiceDescriptorAddInt |
| * USE: Called to register a service with SDP with a given handle |
| * PARAMS: svc - the service descriptor |
| * len - the descriptor length |
| * handle - the handle to use |
| * RETURN: success |
| * NOTES: call with mSvcsLock held for write |
| */ |
| static bool sdpServiceDescriptorAddInt(const void *svc, uint32_t len, uint32_t handle) |
| { |
| static const unsigned handle_attr_len = 8; |
| struct sdpService *ss; |
| |
| ss = (struct sdpService*)malloc(sizeof(struct sdpService) + len + handle_attr_len); |
| if (!ss) |
| return false; |
| |
| memcpy(ss->descr + handle_attr_len, svc, len); |
| ss->len = len + handle_attr_len; |
| utilSetBE8(ss->descr + 0, SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2)); |
| utilSetBE16(ss->descr + 1, SDP_ATTR_HANDLE); |
| utilSetBE8(ss->descr + 3, SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_4)); |
| utilSetBE32(ss->descr + 4, handle); |
| |
| ss->handle = handle; |
| ss->next = NULL; |
| ss->prev = mSvcsTail; |
| if (mSvcsTail) |
| mSvcsTail->next = ss; |
| else |
| mSvcsHead = ss; |
| |
| mSvcsTail = ss; |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: sdpFindHandle |
| * USE: Find a free SDP handle |
| * PARAMS: NONE |
| * RETURN: handle ot 0 on failure |
| * NOTES: call with mSvcsLock held for read |
| */ |
| static uint32_t sdpFindHandle(void) |
| { |
| uint32_t handle, origNextHandleVal = mNextHandle; |
| struct sdpService *t; |
| |
| /* repeatedly guess a good handle value and check it for uniqueness */ |
| do { |
| handle = mNextHandle; |
| mNextHandle = (mNextHandle == SDP_HANDLE_LAST) ? SDP_HANDLE_FIRST : mNextHandle + 1; |
| for (t = mSvcsHead; t && t->handle != handle; t = t->next); |
| /* until success or we come a full circle */ |
| } while (t && mNextHandle != origNextHandleVal); |
| |
| return t ? 0 : handle; |
| } |
| |
| /* |
| * FUNCTION: sdpServiceDescriptorAdd |
| * USE: Called to register a service with SDP |
| * PARAMS: svc - the service descriptor |
| * len - its length |
| * RETURN: success |
| * NOTES: |
| */ |
| uint32_t sdpServiceDescriptorAdd(const void *svc, uint32_t len) |
| { |
| uint32_t handle; |
| |
| pthread_rwlock_wrlock(&mSvcsLock); |
| handle = sdpFindHandle(); |
| if (handle && !sdpServiceDescriptorAddInt(svc, len, handle)) { |
| mNextHandle = handle; /* do not let the search go to waste */ |
| handle = 0; |
| } |
| pthread_rwlock_unlock(&mSvcsLock); |
| return handle; |
| } |
| |
| /* |
| * FUNCTION: sdpServiceDescriptorDel |
| * USE: Called to unregister a service with SDP |
| * PARAMS: handle - the service handle from sdpServiceDescriptorAdd() |
| * RETURN: success |
| * NOTES: |
| */ |
| bool sdpServiceDescriptorDel(uint32_t handle) |
| { |
| struct sdpService *ss; |
| bool ret; |
| |
| if (!handle) { |
| loge("Attempt to remove SDP's SDP descriptor externally\n"); |
| return false; |
| } |
| |
| pthread_rwlock_wrlock(&mSvcsLock); |
| ss = sdpServiceFindByHandle(handle); |
| if (ss){ |
| sdpServiceDescriptorDelInt(ss); |
| ret = true; |
| } |
| pthread_rwlock_unlock(&mSvcsLock); |
| return ret; |
| } |
| |
| /* |
| * FUNCTION: sdpSend |
| * USE: Enqueue a packet to be sent to the other side |
| * PARAMS: inst - the instance |
| * s - the packet |
| * RETURN: false on error |
| * NOTES: |
| */ |
| static bool sdpSend(struct sdpInst *inst, sg s) |
| { |
| #ifdef SDP_DBG |
| char line[128] = "SDP TX <ZLP>"; |
| uint32_t i; |
| uint8_t v; |
| |
| for(i = 0; i < sgLength(s); i++) { |
| sgSerialize(s, i, 1, &v); |
| if (i & 15) |
| sprintf(line + strlen(line), " %02X", v); |
| else { |
| if (i) |
| logd("%s\n", line); |
| sprintf(line, "SDP TX [%03X] %02X", i, v); |
| } |
| } |
| logd("%s\n", line); |
| #endif |
| return sendQueueTx(mSendQ, inst->conn, s); |
| } |
| |
| /* |
| * FUNCTION: sdpSendError |
| * USE: Send an error packet if at all possible |
| * PARAMS: inst - the instance |
| * transact - the transaction ID |
| * err - the error |
| * RETURN: false on error |
| * NOTES: |
| */ |
| static bool sdpSendError(struct sdpInst *inst, uint16_t transact, uint16_t err) |
| { |
| struct sdpPdu pduHdr; |
| sg rsg; |
| |
| utilSetBE8(&pduHdr.pduType, SDP_PDU_Error_Response); |
| utilSetBE16(&pduHdr.transactionID, transact); |
| utilSetBE16(&pduHdr.paramLen, sizeof(uint16_t)); |
| utilSetBE16(&err, err); |
| |
| rsg = sgNew(); |
| if (!rsg) |
| return false; |
| |
| if (sgConcatBackCopy(rsg, &pduHdr, sizeof(pduHdr)) && sgConcatBackCopy(rsg, &err, sizeof(err)) && sdpSend(inst, rsg)) |
| return true; |
| |
| sgFree(rsg); |
| return false; |
| } |
| |
| /* |
| * FUNCTION: sdpGetSearchReq |
| * USE: Read a list of UUIDs from an SG |
| * PARAMS: req - the SG to read from |
| * uuids - where to store UUIDs (array of at least SDP_MAX_UUIDS_IN_SEARCH) |
| * numUuidsP - where to stor enumber of UUIDs |
| * RETURN: error code to return or 0 for success |
| * NOTES: |
| */ |
| static uint16_t sdpGetSearchReq(sg req, struct uuid *uuids, uint8_t *numUuidsP) |
| { |
| uint8_t buf[16]; |
| uint8_t t, numUuids = 0; |
| uint32_t len; |
| |
| if (!sgSerializeCutFront(req, buf, 1)) /* must have at least a container header */ |
| return SDP_ERR_Invalid_Request_Syntax; |
| t = utilGetBE8(buf); |
| |
| if (SDP_ITEM_GET_TYPE(t) != SDP_TYPE_ARRAY) |
| return SDP_ERR_Invalid_Request_Syntax; |
| |
| switch (SDP_ITEM_GET_SIZE(t)) { |
| case SDP_SZ_u8: |
| if (!sgSerializeCutFront(req, buf, 1)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| len = utilGetBE8(buf); |
| break; |
| case SDP_SZ_u16: |
| if (!sgSerializeCutFront(req, buf, 2)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| len = utilGetBE16(buf); |
| break; |
| case SDP_SZ_u32: |
| if (!sgSerializeCutFront(req, buf, 4)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| len = utilGetBE32(buf); |
| break; |
| default: |
| return SDP_ERR_Invalid_Request_Syntax; |
| } |
| if (sgLength(req) < len) |
| return SDP_ERR_Invalid_Request_Syntax; |
| |
| len = sgLength(req) - len; /* how many bytes we expect to leave */ |
| |
| while (sgLength(req) > len && numUuids < SDP_MAX_UUIDS_IN_SEARCH) { |
| if (!sgSerializeCutFront(req, buf, 1)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| t = utilGetBE8(buf); |
| |
| if (SDP_ITEM_GET_TYPE(t) != SDP_TYPE_UUID) |
| return SDP_ERR_Invalid_Request_Syntax; |
| switch (SDP_ITEM_GET_SIZE(t)) { |
| case SDP_SZ_2: |
| if (!sgSerializeCutFront(req, buf, 2)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| uuidFromUuid16(uuids + numUuids++, utilGetBE16(buf)); |
| break; |
| case SDP_SZ_4: |
| if (!sgSerializeCutFront(req, buf, 4)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| uuidFromUuid32(uuids + numUuids++, utilGetBE32(buf)); |
| break; |
| case SDP_SZ_16: |
| if (!sgSerializeCutFront(req, buf, 16)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| uuidReadBE(uuids + numUuids++, buf); |
| break; |
| default: |
| return SDP_ERR_Invalid_Request_Syntax; |
| } |
| } |
| |
| if (sgLength(req) != len) |
| return SDP_ERR_Invalid_Request_Syntax; |
| |
| *numUuidsP = numUuids; |
| return 0; |
| } |
| |
| /* |
| * FUNCTION: sdpGetCont |
| * USE: Read a continuation state in our format from an SG |
| * PARAMS: req - the SG to read from |
| * ofst - offset in the SG to read from |
| * trunc - truncate the cont away? (only available if ofst == 0) |
| * contP - where to store the state (or 0 if none is present in a valid way) |
| * RETURN: error code to return or 0 for success |
| * NOTES: |
| */ |
| static uint16_t sdpGetCont(sg req, uint32_t ofst, bool trunc, uniq_t *contP) |
| { |
| uint8_t len; |
| |
| if (ofst && trunc) { |
| loge("Cannot truncate cont not at start of req\n"); |
| trunc = false; |
| } |
| |
| if (sizeof(uint8_t) != sgSerialize(req, ofst++, sizeof(uint8_t), &len)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| len = utilGetBE8(&len); |
| |
| if (trunc) |
| sgTruncFront(req, sizeof(uint8_t)); |
| |
| if (!len) { |
| *contP = 0; |
| return 0; |
| } |
| |
| if (len != sizeof(uniq_t)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| |
| /* we use our endianness here - other side assumes cont state is opaque */ |
| if (sizeof(uniq_t) != sgSerialize(req, ofst, sizeof(uniq_t), contP)) |
| return SDP_ERR_Invalid_Request_Syntax; |
| |
| if (trunc) |
| sgTruncFront(req, sizeof(uniq_t)); |
| |
| return 0; |
| } |
| |
| /* |
| * FUNCTION: sdpAppendCont |
| * USE: Append a continuation state in our format to an SG |
| * PARAMS: inst - the instance |
| * req - the SG to append it to |
| * RETURN: false on error |
| * NOTES: |
| */ |
| static bool sdpAppendCont(struct sdpInst *inst, sg req) |
| { |
| uint8_t buf[sizeof(uniq_t) + 1] = {0,}; |
| |
| if (!inst->contResult) |
| return sgConcatBackCopy(req, buf, 1); |
| |
| buf[0] = sizeof(uniq_t); |
| inst->contVal = uniqGetNext(); |
| /* we use our endianness here - other side assumes cont state is opaque */ |
| memcpy(buf + 1, &inst->contVal, sizeof(uniq_t)); |
| return sgConcatBackCopy(req, buf, sizeof(buf)); |
| } |
| |
| /* |
| * FUNCTION: sdpDoSearch |
| * USE: Perform a UUID-based search |
| * PARAMS: inst - the instance |
| * uuids - the uuids we want to find |
| * numUuids - how mnay there are |
| * maxRes - do not return more than this many results (negative for no maximum) |
| * resP - put head of result linked list here |
| * RETURN: error to return to other size or 0 if none |
| * NOTES: call with mSvcsLock held for read |
| */ |
| static uint16_t sdpDoSearch(struct sdpInst *inst, const struct uuid *uuids, uint8_t numUuids, int32_t maxRes, struct sdpSearchResultNode **resP) |
| { |
| const uint16_t wantMask = (1 << numUuids) - 1; |
| struct sdpSearchResultNode *res = NULL; |
| struct sdpService *ss; |
| uint8_t i; |
| |
| for (ss = mSvcsTail; ss; ss = ss->prev) { /* in reverse since we also create th elinked list in reverse - they balance out */ |
| const uint8_t *ptr = ss->descr, *end = ptr + ss->len; |
| uint16_t haveMask = 0; |
| struct uuid uuid = {0,}; |
| uint32_t sz; |
| uint8_t t; |
| |
| while (ptr < end && haveMask != wantMask) { |
| t = *ptr++; |
| |
| /* handle NIL gracefully */ |
| if (SDP_ITEM_GET_TYPE(t) == SDP_TYPE_NIL && SDP_ITEM_GET_SIZE(t) == SDP_SZ_NIL) |
| continue; |
| |
| switch (SDP_ITEM_GET_SIZE(t)) { |
| case SDP_SZ_1: |
| sz = 1; |
| break; |
| case SDP_SZ_2: |
| sz = 2; |
| break; |
| case SDP_SZ_4: |
| sz = 4; |
| break; |
| case SDP_SZ_8: |
| sz = 8; |
| break; |
| case SDP_SZ_16: |
| sz = 16; |
| break; |
| case SDP_SZ_u8: |
| sz = utilGetBE8(ptr); |
| ptr += 1; |
| break; |
| case SDP_SZ_u16: |
| sz = utilGetBE16(ptr); |
| ptr += 2; |
| break; |
| case SDP_SZ_u32: |
| sz = utilGetBE32(ptr); |
| ptr += 4; |
| break; |
| default: |
| sz = 0; |
| loge("unknown size mark in 0x%02X\n", t); |
| break; |
| } |
| |
| if (SDP_ITEM_GET_TYPE(t) == SDP_TYPE_UUID) { /* uuid? - check for match */ |
| if (sz == 2) |
| uuidFromUuid16(&uuid, utilGetBE16(ptr)); |
| else if (sz == 4) |
| uuidFromUuid32(&uuid, utilGetBE32(ptr)); |
| else if (sz == 16) |
| uuidReadBE(&uuid, ptr); |
| else |
| loge("weird uuid sz %ud\n", sz); |
| |
| for (i = 0; i < numUuids; i++) |
| if (uuidCmp(&uuid, uuids + i)) |
| haveMask |= 1 << i; |
| } else if (SDP_ITEM_GET_TYPE(t) == SDP_TYPE_ARRAY || SDP_ITEM_GET_TYPE(t) == SDP_TYPE_OR_LIST) /* list? - look inside */ |
| sz = 0; |
| |
| ptr += sz; |
| } |
| |
| if (haveMask == wantMask) { /* we have a match */ |
| if (!maxRes--) |
| break; |
| |
| struct sdpSearchResultNode *rn = (struct sdpSearchResultNode*)malloc(sizeof(struct sdpSearchResultNode)); |
| if (rn) { |
| rn->next = res; |
| res = rn; |
| rn->ss = ss; |
| } |
| } |
| } |
| *resP = res; |
| return 0; |
| } |
| |
| /* |
| * FUNCTION: sdpSSR |
| * USE: Handle an incoming service search request |
| * PARAMS: inst - the instance |
| * transact - the transaction ID |
| * req - the request |
| * RETURN: false on error |
| * NOTES: call with mSvcsLock held for read |
| */ |
| static bool sdpSSR(struct sdpInst *inst, uint16_t transact, sg req) |
| { |
| struct sdpSearchResultNode *searchRes = NULL, *t; |
| struct uuid searchReq[SDP_MAX_UUIDS_IN_SEARCH]; |
| uint16_t err, maxHandles; |
| uint32_t sendSz; |
| struct sdpPduSSRepl repl; |
| struct sdpPdu pduHdr; |
| uint8_t searchReqNum; |
| uniq_t cont; |
| sg rsg, end; |
| |
| err = sdpGetSearchReq(req, searchReq, &searchReqNum); |
| if (err) |
| return sdpSendError(inst, transact, err); |
| |
| logd("SSR query has %u UUIDS\n", searchReqNum); |
| |
| if (!searchReqNum) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| |
| if (!sgSerializeCutFront(req, &maxHandles, sizeof(maxHandles))) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| |
| maxHandles = utilGetBE16(&maxHandles); |
| |
| logd("SSR max handles: %u\n", maxHandles); |
| |
| if (!maxHandles) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| |
| err = sdpGetCont(req, 0, true, &cont); |
| if (err) |
| return sdpSendError(inst, transact, err); |
| |
| if (sgLength(req)) |
| logw("SDP SSR had %u extra bytes\n", sgLength(req)); |
| |
| if (cont && cont != inst->contVal) { |
| logw("SDP SSR had cont we didn't expect\n"); |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| } |
| if (!cont && inst->contVal) { |
| logi("dropping previous SDP cont\n"); |
| sgFree(inst->contResult); |
| inst->contVal = 0; |
| } |
| |
| if (!cont) { /* new request - perform the actual search */ |
| |
| err = sdpDoSearch(inst, searchReq, searchReqNum, (uint32_t)maxHandles, &searchRes); |
| if (err) |
| return sdpSendError(inst, transact, err); |
| |
| inst->contResult = sgNew(); |
| if (!inst->contResult) |
| return sdpSendError(inst, transact, SDP_ERR_Insufficient_Resources); |
| |
| err = 0; |
| while (searchRes) { |
| uint32_t handle; |
| |
| t = searchRes; |
| searchRes = searchRes->next; |
| handle = t->ss->handle; |
| utilSetBE32(&handle, handle); |
| if (!sgConcatBackCopy(inst->contResult, &handle, sizeof(handle))) |
| err = SDP_ERR_Insufficient_Resources; |
| free(t); |
| } |
| if (err) |
| return sdpSendError(inst, transact, err); |
| } |
| |
| /* see how much data we can send */ |
| sendSz = inst->mtu; |
| if (inst->mtu < sizeof(struct sdpPdu) + sizeof(struct sdpPduSSRepl) + sizeof(struct sdpCont) + sizeof(uint32_t)) /* too small an MTU */ |
| return sdpSendError(inst, transact, SDP_ERR_Insufficient_Resources); |
| sendSz = inst->mtu - (sizeof(struct sdpPdu) + sizeof(struct sdpPduSSRepl) + sizeof(struct sdpCont)); |
| |
| /* decide how much to actually send */ |
| sendSz = (sendSz / sizeof(uint32_t)); /* no half-handles can be sent */ |
| if (sgLength(inst->contResult) < sendSz) |
| sendSz = sgLength(inst->contResult); |
| |
| /* craft the headers */ |
| utilSetBE16(&repl.totalHandles, inst->numHandles); |
| utilSetBE16(&repl.numHandles, sendSz / sizeof(uint32_t)); |
| |
| utilSetBE8(&pduHdr.pduType, SDP_PDU_Service_Search_Response); |
| utilSetBE16(&pduHdr.transactionID, transact); |
| utilSetBE16(&pduHdr.paramLen, sizeof(struct sdpPduSSRepl) + sizeof(struct sdpCont) + sendSz); |
| |
| /* create the reply SG */ |
| rsg = sgNew(); |
| if (!rsg) |
| return sdpSendError(inst, transact, SDP_ERR_Insufficient_Resources); |
| |
| end = sgSplit(inst->contResult, sendSz); |
| if (!end || !sgConcatBackCopy(rsg, &pduHdr, sizeof(pduHdr)) || !sgConcatBackCopy(rsg, &repl, sizeof(repl))) |
| goto out_nomem; |
| |
| /* add the data to reply SG */ |
| sgConcat(rsg, inst->contResult); |
| if (!sgLength(end)) { |
| sgFree(end); |
| end = NULL; |
| } |
| inst->contResult = end; |
| |
| /* add continuation state to repy SG */ |
| if (!sdpAppendCont(inst, rsg)) |
| goto out_nomem; |
| |
| /* send it */ |
| if (sdpSend(inst, rsg)) |
| return true; |
| |
| sgFree(rsg); |
| return false; |
| |
| out_nomem: |
| sgFree(rsg); |
| return sdpSendError(inst, transact, SDP_ERR_Insufficient_Resources); |
| } |
| |
| /* |
| * FUNCTION: sdpSendResultsAR |
| * USE: Send a chunk of the AR response, and create & send & record a new cont value |
| * PARAMS: inst - the instance |
| * pduType - the PDU type to send |
| * transact - the transaction ID |
| * maxRetBytes - max bytes to return |
| * RETURN: success |
| * NOTES: inst->contVal is laready the new cont - we just package it up and send it |
| */ |
| static bool sdpSendResultsAR(struct sdpInst *inst, uint8_t pduType, uint16_t transact, uint16_t maxRetBytes) |
| { |
| uint16_t sendBytes = sgLength(inst->contResult); |
| uint8_t buf[sizeof(uint16_t)]; |
| struct sdpPdu pdu; |
| sg s, t; |
| |
| /* |
| * There are a few limitations on our send size in play here. We have: the MTU, the |
| * requested max reply size, the representation limits of various "size" fields, and |
| * the amount of data we have. The repreentatin limit does not need to be checked |
| * against since the L2C MTU itself is also 16-bits long anyways and thus protects |
| * us. We will first see if we can send all the data at once. If not, we'll then |
| * decide how to segment it. |
| */ |
| if (sgLength(inst->contResult) > maxRetBytes || inst->mtu > sgLength(inst->contResult) + sizeof(struct sdpPdu) + sizeof(uint16_t)/* size */ + sizeof(uint8_t)/*no-cont header */) { |
| const uint16_t maxAllowedByMtu = inst->mtu - sizeof(struct sdpPdu) - sizeof(struct sdpCont) - sizeof(uint16_t)/* size */; |
| |
| /* limit to requested size */ |
| if (sendBytes > maxRetBytes) |
| sendBytes = maxRetBytes; |
| |
| /* limit to mtu */ |
| if (sendBytes > maxAllowedByMtu) |
| sendBytes = maxAllowedByMtu; |
| } |
| |
| /* grab the data to send into s */ |
| t = sgSplit(inst->contResult, sendBytes); |
| if (!t) |
| return false; |
| s = inst->contResult; |
| if (!sgLength(t)) { |
| sgFree(t); |
| t= NULL; |
| } |
| inst->contResult = t; |
| |
| /* add the first part of the reply (length) */ |
| utilSetBE16(buf, sgLength(s)); |
| if (!sgConcatFrontCopy(s, buf, sizeof(uint16_t))) |
| goto fail; |
| |
| /* add cont */ |
| if (!sdpAppendCont(inst, s)) |
| goto fail; |
| |
| /* append the PDU header */ |
| utilSetBE8(&pdu.pduType, pduType); |
| utilSetBE16(&pdu.transactionID, transact); |
| utilSetBE16(&pdu.paramLen, sgLength(s)); |
| |
| if (!sgConcatFrontCopy(s, &pdu, sizeof(pdu))) |
| goto fail; |
| |
| /* will free sg on failure */ |
| if (sdpSend(inst, s)) |
| return true; |
| |
| fail: |
| sgFree(s); |
| if (inst->contResult) |
| sgFree(inst->contResult); |
| inst->contResult = NULL; |
| return false; |
| } |
| |
| /* |
| * FUNCTION: sdpCheckContAR |
| * USE: See if an incoming SAR or SSAR request is a continuation of a previous one |
| * PARAMS: inst - the instance |
| * pduType - the PDU type to send |
| * transact - the transaction ID |
| * req - the request |
| * maxRetBytes - max bytes to return |
| * retP - store return codo here |
| * RETURN: true if cont was found and we handled it. false to treat this as a new request |
| * NOTES: |
| */ |
| static bool sdpCheckContAR(struct sdpInst *inst, uint8_t pduType, uint16_t transact, sg req, uint16_t maxRetBytes, bool *retP) |
| { |
| uint8_t buf[4]; |
| uint32_t ofst = 0; |
| uniq_t cont; |
| |
| /* we need to first skip the data_sequence attr_id_list */ |
| if (sgLength(req) <= ofst) |
| return false; |
| |
| /* read data sequence header, sanity check it, skip it */ |
| sgSerialize(req, ofst++, 1, buf); |
| buf[0] = utilGetBE8(buf + 0); |
| if (SDP_ITEM_GET_TYPE(buf[0]) != SDP_TYPE_ARRAY) |
| return false; |
| switch (SDP_ITEM_GET_SIZE(buf[0])) { |
| case SDP_SZ_u8: |
| if (sgLength(req) < ofst + sizeof(uint8_t)) |
| return false; |
| sgSerialize(req, ofst, sizeof(uint8_t), buf); |
| ofst += sizeof(uint8_t) + utilGetBE8(buf); |
| break; |
| case SDP_SZ_u16: |
| if (sgLength(req) < ofst + sizeof(uint16_t)) |
| return false; |
| sgSerialize(req, ofst, sizeof(uint16_t), buf); |
| ofst += sizeof(uint16_t) + utilGetBE16(buf); |
| break; |
| case SDP_SZ_u32: |
| if (sgLength(req) < ofst + sizeof(uint32_t)) |
| return false; |
| sgSerialize(req, ofst, sizeof(uint32_t), buf); |
| ofst += sizeof(uint32_t) + utilGetBE32(buf); |
| break; |
| default: |
| return false; |
| } |
| |
| if (sgLength(req) <= ofst) |
| return false; |
| |
| /* now read cont state */ |
| if (sdpGetCont(req, ofst, false, &cont)) |
| return false; |
| |
| if (cont && cont == inst->contVal) { |
| *retP = sdpSendResultsAR(inst, pduType, transact, maxRetBytes); |
| return true; |
| } |
| if (inst->contVal) { |
| logw("dropping existing cont\n"); |
| sgFree(inst->contResult); |
| inst->contVal = 0; |
| } |
| |
| if (cont) |
| logw("Invalid cont seen - ignoring\n"); |
| |
| return false; |
| } |
| |
| /* |
| * FUNCTION: sdpGetNextAttrRange |
| * USE: Grab the next wanted attr range from the request |
| * PARAMS: req - the request |
| * ofstP - where to read from and store to the current offsent in the req |
| * len - lengs of req to consider |
| * firstP - where to store first attr id in range |
| * lastP - where to store last attr id in range |
| * RETURN: success |
| * NOTES: |
| */ |
| static bool sdpGetNextAttrRange(sg req, uint32_t *ofstP, uint32_t len, uint16_t *firstP, uint16_t *lastP) |
| { |
| uint8_t buf[4]; |
| |
| if (*ofstP >= len) |
| return false; |
| |
| sgSerialize(req, (*ofstP)++, sizeof(uint8_t), buf); |
| buf[0] = utilGetBE8(buf); |
| |
| if (SDP_ITEM_GET_TYPE(buf[0]) != SDP_TYPE_UINT) |
| return false; |
| |
| switch (SDP_ITEM_GET_SIZE(buf[0])) { |
| case SDP_SZ_2: |
| if (*ofstP + sizeof(uint16_t) > len) |
| return 0; |
| sgSerialize(req, *ofstP, sizeof(uint16_t), buf); |
| *ofstP += sizeof(uint16_t); |
| *firstP = *lastP = utilGetBE16(buf); |
| return true; |
| case SDP_SZ_4: |
| if (*ofstP + sizeof(uint32_t) > len) |
| return 0; |
| sgSerialize(req, *ofstP, sizeof(uint32_t), buf); |
| *ofstP += sizeof(uint32_t); |
| *firstP = utilGetBE32(buf) >> 16; |
| *lastP = utilGetBE32(buf); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /* |
| * FUNCTION: sdpWrap |
| * USE: Wrap the entire SG into an SDP "data sequence" wrapper |
| * PARAMS: s - the sg |
| * RETURN: success |
| * NOTES: |
| */ |
| static bool sdpWrap(sg s) |
| { |
| uint8_t hdr[5], hlen = 0; |
| uint32_t len = sgLength(s); |
| |
| if (len < 0xff) { |
| hdr[hlen++] = SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8); |
| utilSetBE8(hdr + hlen, len); |
| hlen += sizeof(uint8_t); |
| } else if (len < 0xffff) { |
| hdr[hlen++] = SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u16); |
| utilSetBE16(hdr + hlen, len); |
| hlen += sizeof(uint16_t); |
| } else { |
| hdr[hlen++] = SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u32); |
| utilSetBE32(hdr + hlen, len); |
| hlen += sizeof(uint32_t); |
| } |
| |
| return sgConcatFrontCopy(s, hdr, hlen); |
| } |
| |
| /* |
| * FUNCTION: sdpDoAR |
| * USE: Handle the "AR" part of the SAR or SSAR request |
| * PARAMS: inst - the instance |
| * pduType - the PDU type to send |
| * transact - the transaction ID |
| * req - the request |
| * maxRetBytes - how many attr bytes max to return |
| * res - the list of services (list to be freed by this func) |
| * doWrap - set to wrap each service's reply into an array |
| * RETURN: false on error |
| * NOTES: call with mSvcsLock held for read |
| */ |
| static bool sdpDoAR(struct sdpInst *inst, uint8_t pduType, uint16_t transact, sg req, uint16_t maxRetBytes, struct sdpSearchResultNode *res, bool doWrap) |
| { |
| sg ret; |
| uint32_t attrListLen = 0; |
| uint8_t buf[4]; |
| |
| /* get & check collection header, find size */ |
| if (!sgSerializeCutFront(req, buf, sizeof(uint8_t))) |
| goto syntax_err; |
| buf[0] = utilGetBE8(buf + 0); |
| |
| if (SDP_ITEM_GET_TYPE(buf[0]) != SDP_TYPE_ARRAY) |
| goto syntax_err; |
| |
| switch (SDP_ITEM_GET_SIZE(buf[0])) { |
| case SDP_SZ_u8: |
| if (!sgSerializeCutFront(req, buf, sizeof(uint8_t))) |
| goto syntax_err; |
| attrListLen = utilGetBE8(buf); |
| break; |
| case SDP_SZ_u16: |
| if (!sgSerializeCutFront(req, buf, sizeof(uint16_t))) |
| goto syntax_err; |
| attrListLen = utilGetBE16(buf); |
| break; |
| case SDP_SZ_u32: |
| if (!sgSerializeCutFront(req, buf, sizeof(uint32_t))) |
| goto syntax_err; |
| attrListLen = utilGetBE32(buf); |
| break; |
| default: |
| goto syntax_err; |
| } |
| |
| if (sgLength(req) < attrListLen) |
| goto syntax_err; |
| |
| logd("AR attr list len %ub\n", attrListLen); |
| |
| ret = sgNew(); |
| if (!ret) |
| goto oom_ret; |
| |
| while (res) { |
| struct sdpSearchResultNode *t = res; |
| struct sdpService *ss = t->ss; |
| const uint8_t *ptr = ss->descr; |
| const uint8_t *end = ptr + ss->len; |
| bool needNewAttr = true; |
| bool needNewRange = true; |
| uint16_t first = 0, last = 0; |
| uint32_t ofst = 0; |
| uint16_t attrid = 0; |
| const void* attrPtr = NULL; |
| uint32_t attrLen = 0, hdrLen = 0; |
| |
| const uint8_t *_start = ptr; |
| |
| sg part = sgNew(); |
| |
| if (!part) |
| goto oom; |
| |
| res = res->next; |
| free(t); |
| |
| /* both attrs in the service description and in request are sorted - this helps us to do this in O(n) */ |
| |
| while (1) { |
| |
| if (needNewRange && !sdpGetNextAttrRange(req, &ofst, attrListLen, &first, &last)) |
| break; |
| needNewRange = false; |
| |
| if (needNewAttr) { |
| |
| if (ptr >= end) |
| break; |
| |
| needNewAttr = false; |
| hdrLen = 4 /* at least this much always */; |
| |
| attrPtr = ptr; |
| if (SDP_ITEM_GET_TYPE(*ptr) != SDP_TYPE_UINT) { |
| logw("attrID not uint in SDP record\n"); |
| break; |
| } |
| if (SDP_ITEM_GET_SIZE(*ptr) != SDP_SZ_2) { |
| logw("attrID not uint16 in SDP record\n"); |
| break; |
| } |
| attrid = utilGetBE16(ptr + 1); |
| ptr += 3; |
| |
| if (SDP_ITEM_GET_TYPE(*ptr) == SDP_TYPE_NIL) { /* we do not care about nils */ |
| needNewAttr = true; |
| continue; |
| } |
| switch(SDP_ITEM_GET_SIZE(*ptr)){ |
| case SDP_SZ_1: |
| attrLen = 1; |
| ptr++; |
| break; |
| case SDP_SZ_2: |
| attrLen = 2; |
| ptr++; |
| break; |
| case SDP_SZ_4: |
| attrLen = 4; |
| ptr++; |
| break; |
| case SDP_SZ_8: |
| attrLen = 8; |
| ptr++; |
| break; |
| case SDP_SZ_16: |
| attrLen = 16; |
| ptr++; |
| break; |
| case SDP_SZ_u8: |
| attrLen = utilGetBE8(ptr + 1); |
| hdrLen += sizeof(uint8_t); |
| ptr += sizeof(uint8_t) + 1; |
| break; |
| case SDP_SZ_u16: |
| attrLen = utilGetBE16(ptr + 1); |
| hdrLen += sizeof(uint16_t); |
| ptr += sizeof(uint16_t) + 1; |
| break; |
| case SDP_SZ_u32: |
| attrLen = utilGetBE32(ptr + 1); |
| hdrLen += sizeof(uint32_t); |
| ptr += sizeof(uint32_t) + 1; |
| break; |
| } |
| ptr += attrLen; |
| } |
| |
| needNewAttr = attrid <= last; |
| needNewRange = attrid >= last; |
| |
| if (attrid >= first && attrid <= last && !sgConcatBackCopy(part, attrPtr, attrLen + hdrLen)) |
| logw("failed to concat %ub to AR part\n", attrLen + hdrLen); |
| } |
| |
| if (sgLength(part) && (!doWrap || sdpWrap(part))) |
| sgConcat(ret, part); |
| else |
| sgFree(part); |
| } |
| |
| if (!sdpWrap(ret)) |
| goto oom; |
| |
| inst->contResult = ret; |
| return sdpSendResultsAR(inst, pduType, transact, maxRetBytes); |
| |
| oom: |
| while (res) { |
| struct sdpSearchResultNode *t = res; |
| res = res->next; |
| free(t); |
| } |
| sgFree(ret); |
| |
| oom_ret: |
| return sdpSendError(inst, transact, SDP_ERR_Insufficient_Resources); |
| |
| syntax_err: |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| } |
| |
| /* |
| * FUNCTION: sdpSAR |
| * USE: Handle an incoming service attribute request |
| * PARAMS: inst - the instance |
| * transact - the transaction ID |
| * req - the request |
| * RETURN: false on error |
| * NOTES: call with mSvcsLock held for read |
| */ |
| static bool sdpSAR(struct sdpInst *inst, uint16_t transact, sg req) |
| { |
| struct sdpSearchResultNode *res; |
| struct sdpService *ss; |
| uint16_t maxRetBytes; |
| uint32_t handle; |
| bool ret; |
| |
| /* get handle */ |
| if (!sgSerializeCutFront(req, &handle, sizeof(handle))) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| handle = utilGetBE32(&handle); |
| |
| /* get the service */ |
| ss = sdpServiceFindByHandle(handle); |
| if (!ss) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Service_Record_Handle); |
| |
| /* get wanted max reply len */ |
| if (!sgSerializeCutFront(req, &maxRetBytes, sizeof(maxRetBytes))) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| maxRetBytes = utilGetBE16(&maxRetBytes); |
| |
| if (maxRetBytes < 7) /* as per spec */ |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| |
| logd("SAR max reply len: %u\n", maxRetBytes); |
| |
| /* check if continued request */ |
| if (sdpCheckContAR(inst, SDP_PDU_Service_Attribute_Response, transact, req, maxRetBytes, &ret)) |
| return ret; |
| |
| logd("SAR not cont. Proceeding with search\n"); |
| |
| /* create a node containing it */ |
| res = (struct sdpSearchResultNode*)malloc(sizeof(struct sdpSearchResultNode)); |
| if (res) |
| return sdpSendError(inst, transact, SDP_ERR_Insufficient_Resources); |
| res->ss = ss; |
| res->next = NULL; |
| |
| /* grab the wanted attributes */ |
| return sdpDoAR(inst, SDP_PDU_Service_Attribute_Response, transact, req, maxRetBytes, res, false); |
| } |
| |
| /* |
| * FUNCTION: sdpSSAR |
| * USE: Handle an incoming service search attribute request |
| * PARAMS: inst - the instance |
| * req - the request |
| * RETURN: false on error |
| * NOTES: call with mSvcsLock held for read |
| */ |
| static bool sdpSSAR(struct sdpInst *inst, uint16_t transact, sg req) |
| { |
| struct uuid searchReq[SDP_MAX_UUIDS_IN_SEARCH]; |
| struct sdpSearchResultNode *searchRes; |
| uint16_t maxRetBytes; |
| uint8_t i, searchReqNum; |
| uint16_t err; |
| bool ret; |
| |
| err = sdpGetSearchReq(req, searchReq, &searchReqNum); |
| if (err) |
| return sdpSendError(inst, transact, err); |
| |
| logd("SSAR query has %u UUIDS\n", searchReqNum); |
| for (i = 0; i < searchReqNum; i++) |
| logd("SSAR uuid[%u] = "FMT64"x"FMT64"x\n", i, CNV64(searchReq[i].hi), CNV64(searchReq[i].lo)); |
| |
| if (!searchReqNum) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| |
| /* get wanted max reply len */ |
| if (!sgSerializeCutFront(req, &maxRetBytes, sizeof(maxRetBytes))) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| maxRetBytes = utilGetBE16(&maxRetBytes); |
| |
| logd("SSAR max reply len: %u\n", maxRetBytes); |
| |
| if (maxRetBytes < 7) /* as per spec */ |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| |
| /* check if continued request */ |
| if (sdpCheckContAR(inst, SDP_PDU_Service_Search_Attribute_Response, transact, req, maxRetBytes, &ret)) |
| return ret; |
| |
| logd("SSAR not cont. Proceeding with search\n"); |
| |
| err = sdpDoSearch(inst, searchReq, searchReqNum, -1, &searchRes); |
| if (err) |
| return sdpSendError(inst, transact, err); |
| |
| /* grab the wanted attributes */ |
| return sdpDoAR(inst, SDP_PDU_Service_Search_Attribute_Response, transact, req, maxRetBytes, searchRes, true); |
| } |
| |
| /* |
| * FUNCTION: sdpRx |
| * USE: Handle an incoming request |
| * PARAMS: inst - the instance |
| * transact - the transaction ID |
| * req - the request |
| * RETURN: false on error |
| * NOTES: call with mSvcsLock held for read |
| */ |
| static bool sdpRx(struct sdpInst *inst, sg req) |
| { |
| struct sdpPdu reqPdu; |
| uint8_t pduType; |
| uint16_t transact, paramLen; |
| |
| #ifdef SDP_DBG |
| char line[128] = "SDP RX <ZLP>"; |
| uint32_t i; |
| uint8_t v; |
| |
| for(i = 0; i < sgLength(req); i++) { |
| sgSerialize(req, i, 1, &v); |
| if (i & 15) |
| sprintf(line + strlen(line), " %02X", v); |
| else { |
| if (i) |
| logd("%s\n", line); |
| sprintf(line, "SDP RX [%03X] %02X", i, v); |
| } |
| } |
| logd("%s\n", line); |
| #endif |
| |
| if (!sgSerializeCutFront(req, &reqPdu, sizeof(reqPdu))) |
| return sdpSendError(inst, 0, SDP_ERR_Invalid_PDU_Size); |
| |
| pduType = utilGetBE8(&reqPdu.pduType); |
| transact = utilGetBE16(&reqPdu.transactionID); |
| paramLen = utilGetBE16(&reqPdu.paramLen); |
| |
| if (paramLen != sgLength(req)) |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_PDU_Size); |
| |
| logd("SDP req %u trans %u with %ub of params\n", pduType, transact, paramLen); |
| |
| switch (pduType) { |
| case SDP_PDU_Service_Search_Request: |
| return sdpSSR(inst, transact, req); |
| case SDP_PDU_Service_Attribute_Request: |
| return sdpSAR(inst, transact, req); |
| case SDP_PDU_Service_Search_Attribute_Request: |
| return sdpSSAR(inst, transact, req); |
| default: |
| return sdpSendError(inst, transact, SDP_ERR_Invalid_Request_Syntax); |
| } |
| } |
| |
| /* |
| * FUNCTION: sdpInstState |
| * USE: Handle an event happening to the SDP instance |
| * PARAMS: userData - unused |
| * instance - the instance |
| * state - what happened |
| * data - data pertinent to the state |
| * len - length of said data |
| * RETURN: NONE |
| * NOTES: |
| */ |
| static void sdpInstState(void *userData, void *instance, uint8_t state, const void *data, uint32_t len) |
| { |
| struct sdpInst *inst = (struct sdpInst*)instance; |
| sg payload; |
| |
| switch(state){ |
| case L2C_STATE_OPEN: |
| logd("SDP open\n"); |
| break; |
| case L2C_STATE_MTU: |
| if (len != sizeof(uint16_t)) |
| loge("unexpected length for MTU\n"); |
| else { |
| inst->mtu = *(uint16_t*)data; |
| logd("SDP MTU: %d\n", inst->mtu); |
| } |
| break; |
| case L2C_STATE_ENCR: |
| logd("SDP ENCR\n"); |
| break; |
| case L2C_STATE_RX: |
| if (len != sizeof(sg)) |
| loge("unexpected length for RX\n"); |
| else { |
| payload = *(sg*)data; |
| pthread_rwlock_rdlock(&mSvcsLock); |
| if (!sdpRx(inst, payload)) |
| loge("SDP failed to process a request\n"); |
| pthread_rwlock_unlock(&mSvcsLock); |
| sgFree(payload); |
| } |
| break; |
| case L2C_STATE_CLOSED: |
| logi("SDP closed\n"); |
| if (inst->contResult) |
| sgFree(inst->contResult); |
| sendQueueDelPackets(mSendQ, inst->conn); |
| free(inst); |
| break; |
| default: |
| logw("SDP state call %u unsupported\n", state); |
| break; |
| } |
| } |
| |
| /* |
| * FUNCTION: sdpInstAlloc |
| * USE: Create a state struct for a connection to SDP |
| * PARAMS: userData - unused |
| * handle - the l2c connection handle |
| * instanceP - save the pointer to the instance here |
| * RETURN: result (SVC_ALLOC_*) |
| * NOTES: |
| */ |
| static uint8_t sdpInstAlloc(void *userData, l2c_handle_t handle, void **instanceP) |
| { |
| struct sdpInst *inst = (struct sdpInst*)calloc(1, sizeof(struct sdpInst)); |
| |
| if (!inst) |
| return SVC_ALLOC_FAIL_OTHER; |
| |
| inst->conn = handle; |
| *instanceP = inst; |
| return SVC_ALLOC_SUCCESS; |
| } |
| |
| /* |
| * FUNCTION: sdpInit |
| * USE: Called to init SDP and register it with L2CAP |
| * PARAMS: NONE |
| * RETURN: success |
| * NOTES: |
| */ |
| bool sdpInit(void) |
| { |
| static const uint8_t sdpDescrSdp[] = { |
| //service class ID list |
| SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(SDP_ATTR_SVC_CLS_ID_LIST), |
| SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 3, |
| SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), SDP_U16(SDP_SDP_SERVER_UUID), |
| |
| //ProtocolDescriptorList |
| SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(SDP_ATTR_PROTOCOL_DESCR_LIST), |
| SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 8, |
| SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 6, |
| SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), SDP_U16(SDP_PROTO_L2CAP), |
| SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(SDP_PSM), |
| |
| //browse group list |
| SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(SDP_ATTR_BROWSE_GRP_LIST), |
| SDP_ITEM_DESC(SDP_TYPE_ARRAY, SDP_SZ_u8), 3, |
| SDP_ITEM_DESC(SDP_TYPE_UUID, SDP_SZ_2), SDP_U16(SDP_BROWSE_GROUP_ID_PUBLIC), |
| |
| //magic data |
| SDP_ITEM_DESC(SDP_TYPE_UINT, SDP_SZ_2), SDP_U16(0xDDDD), |
| SDP_ITEM_DESC(SDP_TYPE_TEXT, SDP_SZ_u8), 18, |
| 0x42, 0x79, 0x20, 0x44, 0x6D, 0x69, 0x74, 0x72, 0x79, |
| 0x20, 0x47, 0x72, 0x69, 0x6e, 0x62, 0x65, 0x72, 0x67, |
| }; |
| static const struct l2cServicePsmDescriptor sdpSvc = { |
| .userData = NULL, |
| .serviceInstanceAlloc = sdpInstAlloc, |
| .serviceInstanceStateCbk = sdpInstState, |
| .serviceConnectionlessRx = NULL, |
| .mtu = 32768, |
| }; |
| |
| mNextHandle = SDP_HANDLE_FIRST; |
| |
| mSendQ = sendQueueAlloc(1); |
| if (!mSendQ) { |
| loge("Failed to alloc send queue\n"); |
| return false; |
| } |
| |
| if (!sdpServiceDescriptorAddInt(sdpDescrSdp, sizeof(sdpDescrSdp), SDP_HANDLE_SELF)) { |
| loge("SDP failed to register with itself!\n"); |
| sendQueueFree(mSendQ); |
| return false; |
| } |
| |
| if (!l2cApiServicePsmRegister(SDP_PSM, &sdpSvc)) { |
| loge("Failed to register SDP service\n"); |
| sdpServiceDescriptorDelInt(mSvcsHead); /* we are our only record - this is safe */ |
| sendQueueFree(mSendQ); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * FUNCTION: sdpDeinit |
| * USE: Clean up SDP |
| * PARAMS: NONE |
| * RETURN: success |
| * NOTES: |
| */ |
| void sdpDeinit(void) |
| { |
| sendQueueFree(mSendQ); |
| l2cApiServicePsmUnregister(SDP_PSM, NULL, NULL); |
| pthread_rwlock_wrlock(&mSvcsLock); |
| while (mSvcsHead) { |
| mSvcsTail = mSvcsHead; |
| mSvcsHead = mSvcsHead->next; |
| free(mSvcsTail); |
| } |
| mSvcsTail = NULL; |
| pthread_rwlock_unlock(&mSvcsLock); |
| } |
| |