Replace the existing Apache 2.0 boilerplate header with an SPDX tag throughout the zephyr code tree. This patch was generated via a script run over the master branch. Also updated doc/porting/application.rst that had a dependency on line numbers in a literal include. Manually updated subsys/logging/sys_log.c that had a malformed header in the original file. Also cleanup several cases that already had a SPDX tag and we either got a duplicate or missed updating. Jira: ZEP-1457 Change-Id: I6131a1d4ee0e58f5b938300c2d2fc77d2e69572c Signed-off-by: David B. Kinder <david.b.kinder@intel.com> Signed-off-by: Kumar Gala <kumar.gala@linaro.org>
812 lines
20 KiB
C
812 lines
20 KiB
C
/** @file
|
|
* @brief Service Discovery Protocol handling.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <misc/byteorder.h>
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLUETOOTH_DEBUG_SDP)
|
|
#include <bluetooth/log.h>
|
|
#include <bluetooth/sdp.h>
|
|
|
|
#include "l2cap_internal.h"
|
|
#include "sdp_internal.h"
|
|
|
|
#define SDP_PSM 0x0001
|
|
|
|
#define SDP_CHAN(_ch) CONTAINER_OF(_ch, struct bt_sdp, chan.chan)
|
|
|
|
#define SDP_DATA_MTU 200
|
|
|
|
#define SDP_MTU (SDP_DATA_MTU + sizeof(struct bt_sdp_hdr))
|
|
|
|
#define SDP_SERVICE_HANDLE_BASE 0x10000
|
|
|
|
struct bt_sdp {
|
|
struct bt_l2cap_br_chan chan;
|
|
struct k_fifo partial_resp_queue;
|
|
/* TODO: Allow more than one pending request */
|
|
};
|
|
|
|
static struct bt_sdp_record *db;
|
|
static uint8_t num_services;
|
|
|
|
static struct bt_sdp bt_sdp_pool[CONFIG_BLUETOOTH_MAX_CONN];
|
|
|
|
/* Pool for outgoing SDP packets */
|
|
NET_BUF_POOL_DEFINE(sdp_pool, CONFIG_BLUETOOTH_MAX_CONN,
|
|
BT_L2CAP_BUF_SIZE(SDP_MTU), BT_BUF_USER_DATA_MIN, NULL);
|
|
|
|
#define SDP_CLIENT_CHAN(_ch) CONTAINER_OF(_ch, struct bt_sdp_client, chan.chan)
|
|
|
|
#define SDP_CLIENT_MTU 64
|
|
|
|
struct bt_sdp_client {
|
|
struct bt_l2cap_br_chan chan;
|
|
/* list of waiting to be resolved UUID params */
|
|
sys_slist_t reqs;
|
|
/* required SDP transaction ID */
|
|
uint16_t tid;
|
|
/* UUID params holder being now resolved */
|
|
const struct bt_sdp_discover_params *param;
|
|
/* PDU continuation state object */
|
|
struct bt_sdp_pdu_cstate cstate;
|
|
/* buffer for collecting record data */
|
|
struct net_buf *rec_buf;
|
|
};
|
|
|
|
static struct bt_sdp_client bt_sdp_client_pool[CONFIG_BLUETOOTH_MAX_CONN];
|
|
|
|
/** @brief Callback for SDP connection
|
|
*
|
|
* Gets called when an SDP connection is established
|
|
*
|
|
* @param chan L2CAP channel
|
|
*
|
|
* @return None
|
|
*/
|
|
static void bt_sdp_connected(struct bt_l2cap_chan *chan)
|
|
{
|
|
struct bt_l2cap_br_chan *ch = CONTAINER_OF(chan,
|
|
struct bt_l2cap_br_chan,
|
|
chan);
|
|
|
|
struct bt_sdp *sdp = CONTAINER_OF(ch, struct bt_sdp, chan);
|
|
|
|
BT_DBG("chan %p cid 0x%04x", ch, ch->tx.cid);
|
|
|
|
k_fifo_init(&sdp->partial_resp_queue);
|
|
|
|
ch->tx.mtu = SDP_MTU;
|
|
ch->rx.mtu = SDP_MTU;
|
|
}
|
|
|
|
/** @brief Callback for SDP disconnection
|
|
*
|
|
* Gets called when an SDP connection is terminated
|
|
*
|
|
* @param chan L2CAP channel
|
|
*
|
|
* @return None
|
|
*/
|
|
static void bt_sdp_disconnected(struct bt_l2cap_chan *chan)
|
|
{
|
|
struct bt_l2cap_br_chan *ch = CONTAINER_OF(chan,
|
|
struct bt_l2cap_br_chan,
|
|
chan);
|
|
|
|
struct bt_sdp *sdp = CONTAINER_OF(ch, struct bt_sdp, chan);
|
|
|
|
BT_DBG("chan %p cid 0x%04x", ch, ch->tx.cid);
|
|
|
|
memset(sdp, 0, sizeof(*sdp));
|
|
}
|
|
|
|
/** @brief Creates an SDP PDU
|
|
*
|
|
* Creates an empty SDP PDU and returns the buffer
|
|
*
|
|
* @param None
|
|
*
|
|
* @return Pointer to the net_buf buffer
|
|
*/
|
|
struct net_buf *bt_sdp_create_pdu(void)
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
buf = bt_l2cap_create_pdu(&sdp_pool, 0);
|
|
/* NULL is not a possible return due to K_FOREVER */
|
|
net_buf_reserve(buf, sizeof(struct bt_sdp_hdr));
|
|
|
|
return buf;
|
|
}
|
|
|
|
/** @brief Sends out an SDP PDU
|
|
*
|
|
* Sends out an SDP PDU after adding the relevant header
|
|
*
|
|
* @param chan L2CAP channel
|
|
* @param buf Buffer to be sent out
|
|
* @param op Opcode to be used in the packet header
|
|
* @param tid Transaction ID to be used in the packet header
|
|
*
|
|
* @return None
|
|
*/
|
|
static void bt_sdp_send(struct bt_l2cap_chan *chan, struct net_buf *buf,
|
|
uint8_t op, uint16_t tid)
|
|
{
|
|
struct bt_sdp_hdr *hdr;
|
|
uint16_t param_len = buf->len;
|
|
|
|
hdr = net_buf_push(buf, sizeof(struct bt_sdp_hdr));
|
|
hdr->op_code = op;
|
|
hdr->tid = tid;
|
|
hdr->param_len = sys_cpu_to_be16(param_len);
|
|
|
|
bt_l2cap_chan_send(chan, buf);
|
|
}
|
|
|
|
/** @brief Sends an error response PDU
|
|
*
|
|
* Creates and sends an error response PDU
|
|
*
|
|
* @param chan L2CAP channel
|
|
* @param err Error code to be sent in the packet
|
|
* @param tid Transaction ID to be used in the packet header
|
|
*
|
|
* @return None
|
|
*/
|
|
static void send_err_rsp(struct bt_l2cap_chan *chan, uint16_t err,
|
|
uint16_t tid)
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
BT_DBG("tid %u, error %u", tid, err);
|
|
|
|
buf = bt_sdp_create_pdu();
|
|
|
|
net_buf_add_be16(buf, err);
|
|
|
|
bt_sdp_send(chan, buf, BT_SDP_ERROR_RSP, tid);
|
|
}
|
|
|
|
static const struct {
|
|
uint8_t op_code;
|
|
uint16_t (*func)(struct bt_sdp *sdp, struct net_buf *buf,
|
|
uint16_t tid);
|
|
} handlers[] = {
|
|
};
|
|
|
|
/** @brief Callback for SDP data receive
|
|
*
|
|
* Gets called when an SDP PDU is received. Calls the corresponding handler
|
|
* based on the op code of the PDU.
|
|
*
|
|
* @param chan L2CAP channel
|
|
* @param buf Received PDU
|
|
*
|
|
* @return None
|
|
*/
|
|
static void bt_sdp_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
{
|
|
struct bt_l2cap_br_chan *ch = CONTAINER_OF(chan,
|
|
struct bt_l2cap_br_chan, chan);
|
|
struct bt_sdp *sdp = CONTAINER_OF(ch, struct bt_sdp, chan);
|
|
struct bt_sdp_hdr *hdr = (struct bt_sdp_hdr *)buf->data;
|
|
uint16_t err = BT_SDP_INVALID_SYNTAX;
|
|
size_t i;
|
|
|
|
BT_DBG("chan %p, ch %p, cid 0x%04x", chan, ch, ch->tx.cid);
|
|
|
|
BT_ASSERT(sdp);
|
|
|
|
if (buf->len < sizeof(*hdr)) {
|
|
BT_ERR("Too small SDP PDU received");
|
|
return;
|
|
}
|
|
|
|
BT_DBG("Received SDP code 0x%02x len %u", hdr->op_code, buf->len);
|
|
|
|
net_buf_pull(buf, sizeof(*hdr));
|
|
|
|
if (sys_cpu_to_be16(hdr->param_len) != buf->len) {
|
|
err = BT_SDP_INVALID_PDU_SIZE;
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(handlers); i++) {
|
|
if (hdr->op_code != handlers[i].op_code) {
|
|
continue;
|
|
}
|
|
|
|
err = handlers[i].func(sdp, buf, hdr->tid);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
BT_WARN("SDP error 0x%02x", err);
|
|
send_err_rsp(chan, err, hdr->tid);
|
|
}
|
|
}
|
|
|
|
/** @brief Callback for SDP connection accept
|
|
*
|
|
* Gets called when an incoming SDP connection needs to be authorized.
|
|
* Registers the L2CAP callbacks and allocates an SDP context to the connection
|
|
*
|
|
* @param conn BT connection object
|
|
* @param chan L2CAP channel structure (to be returned)
|
|
*
|
|
* @return 0 for success, or relevant error code
|
|
*/
|
|
static int bt_sdp_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
|
|
{
|
|
static struct bt_l2cap_chan_ops ops = {
|
|
.connected = bt_sdp_connected,
|
|
.disconnected = bt_sdp_disconnected,
|
|
.recv = bt_sdp_recv,
|
|
};
|
|
int i;
|
|
|
|
BT_DBG("conn %p", conn);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_sdp_pool); i++) {
|
|
struct bt_sdp *sdp = &bt_sdp_pool[i];
|
|
|
|
if (sdp->chan.chan.conn) {
|
|
continue;
|
|
}
|
|
|
|
sdp->chan.chan.ops = &ops;
|
|
sdp->chan.rx.mtu = SDP_MTU;
|
|
|
|
*chan = &sdp->chan.chan;
|
|
|
|
return 0;
|
|
}
|
|
|
|
BT_ERR("No available SDP context for conn %p", conn);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
void bt_sdp_init(void)
|
|
{
|
|
static struct bt_l2cap_server server = {
|
|
.psm = SDP_PSM,
|
|
.accept = bt_sdp_accept,
|
|
};
|
|
int res;
|
|
|
|
res = bt_l2cap_br_server_register(&server);
|
|
if (res) {
|
|
BT_ERR("L2CAP server registration failed with error %d", res);
|
|
}
|
|
}
|
|
|
|
int bt_sdp_register_service(struct bt_sdp_record *service)
|
|
{
|
|
uint32_t handle = SDP_SERVICE_HANDLE_BASE;
|
|
|
|
if (!service) {
|
|
BT_ERR("No service record specified");
|
|
return 0;
|
|
}
|
|
|
|
if (db) {
|
|
handle = db->handle + 1;
|
|
}
|
|
|
|
service->next = db;
|
|
service->index = num_services++;
|
|
service->handle = handle;
|
|
*((uint32_t *)(service->attrs[0].val.data)) = handle;
|
|
db = service;
|
|
|
|
BT_DBG("Service registered at %u", handle);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define GET_PARAM(__node) \
|
|
CONTAINER_OF(__node, struct bt_sdp_discover_params, _node)
|
|
|
|
/* ServiceSearchAttribute PDU, ref to BT Core 4.2, Vol 3, part B, 4.7.1 */
|
|
static int sdp_client_ssa_search(struct bt_sdp_client *session)
|
|
{
|
|
const struct bt_sdp_discover_params *param;
|
|
struct bt_sdp_hdr *hdr;
|
|
struct net_buf *buf;
|
|
|
|
/*
|
|
* Select proper user params, if session->param is invalid it means
|
|
* getting new UUID from top of to be resolved params list. Otherwise
|
|
* the context is in a middle of partial SDP PDU responses and cached
|
|
* value from context can be used.
|
|
*/
|
|
if (!session->param) {
|
|
param = GET_PARAM(sys_slist_peek_head(&session->reqs));
|
|
} else {
|
|
param = session->param;
|
|
}
|
|
|
|
if (!param) {
|
|
BT_WARN("No UUIDs to be resolved on remote");
|
|
return -EINVAL;
|
|
}
|
|
|
|
buf = bt_l2cap_create_pdu(&sdp_pool, 0);
|
|
if (!buf) {
|
|
BT_ERR("No bufs for PDU");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
|
|
|
hdr->op_code = BT_SDP_SVC_SEARCH_ATTR_REQ;
|
|
/* BT_SDP_SEQ8 means length of sequence is on additional next byte */
|
|
net_buf_add_u8(buf, BT_SDP_SEQ8);
|
|
|
|
switch (param->uuid->type) {
|
|
case BT_UUID_TYPE_16:
|
|
/* Seq length */
|
|
net_buf_add_u8(buf, 0x03);
|
|
/* Seq type */
|
|
net_buf_add_u8(buf, BT_SDP_UUID16);
|
|
/* Seq value */
|
|
net_buf_add_be16(buf, BT_UUID_16(param->uuid)->val);
|
|
break;
|
|
case BT_UUID_TYPE_32:
|
|
net_buf_add_u8(buf, 0x05);
|
|
net_buf_add_u8(buf, BT_SDP_UUID32);
|
|
net_buf_add_be32(buf, BT_UUID_32(param->uuid)->val);
|
|
break;
|
|
case BT_UUID_TYPE_128:
|
|
net_buf_add_u8(buf, 0x11);
|
|
net_buf_add_u8(buf, BT_SDP_UUID128);
|
|
net_buf_add_mem(buf, BT_UUID_128(param->uuid)->val,
|
|
ARRAY_SIZE(BT_UUID_128(param->uuid)->val));
|
|
break;
|
|
default:
|
|
BT_ERR("Unknown UUID type %u", param->uuid->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Set attribute max bytes count to be returned from server */
|
|
net_buf_add_be16(buf, BT_SDP_MAX_ATTR_LEN);
|
|
/*
|
|
* Sequence definition where data is sequence of elements and where
|
|
* additional next byte points the size of elements within
|
|
*/
|
|
net_buf_add_u8(buf, BT_SDP_SEQ8);
|
|
net_buf_add_u8(buf, 0x05);
|
|
/* Data element definition for two following 16bits range elements */
|
|
net_buf_add_u8(buf, BT_SDP_UINT32);
|
|
/* Get all attributes. It enables filter out wanted only attributes */
|
|
net_buf_add_be16(buf, 0x0000);
|
|
net_buf_add_be16(buf, 0xffff);
|
|
|
|
/*
|
|
* Update and validate PDU ContinuationState. Initial SSA Request has
|
|
* zero length continuation state since no interaction has place with
|
|
* server so far, otherwise use the original state taken from remote's
|
|
* last response PDU that is cached by SDP client context.
|
|
*/
|
|
if (session->cstate.length == 0) {
|
|
net_buf_add_u8(buf, 0x00);
|
|
} else {
|
|
net_buf_add_u8(buf, session->cstate.length);
|
|
net_buf_add_mem(buf, session->cstate.data,
|
|
session->cstate.length);
|
|
}
|
|
|
|
/* set overall PDU length */
|
|
hdr->param_len = sys_cpu_to_be16(buf->len - sizeof(*hdr));
|
|
|
|
/* Update context param to the one being resolving now */
|
|
session->param = param;
|
|
session->tid++;
|
|
hdr->tid = sys_cpu_to_be16(session->tid);
|
|
|
|
return bt_l2cap_chan_send(&session->chan.chan, buf);
|
|
}
|
|
|
|
static void sdp_client_params_iterator(struct bt_sdp_client *session)
|
|
{
|
|
struct bt_l2cap_chan *chan = &session->chan.chan;
|
|
const struct bt_sdp_discover_params *param;
|
|
sys_snode_t *node, *node_s;
|
|
|
|
SYS_SLIST_FOR_EACH_NODE_SAFE(&session->reqs, node, node_s) {
|
|
param = GET_PARAM(node);
|
|
if (param != session->param) {
|
|
continue;
|
|
}
|
|
|
|
BT_DBG("");
|
|
|
|
/* Remove already checked UUID node */
|
|
sys_slist_remove(&session->reqs, NULL, node);
|
|
/* Invalidate cached param in context */
|
|
session->param = NULL;
|
|
/* Reset continuation state in current context */
|
|
memset(&session->cstate, 0, sizeof(session->cstate));
|
|
|
|
/* Check if there's valid next UUID */
|
|
if (!sys_slist_is_empty(&session->reqs)) {
|
|
sdp_client_ssa_search(session);
|
|
return;
|
|
}
|
|
|
|
/* No UUID items, disconnect channel */
|
|
bt_l2cap_chan_disconnect(chan);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint16_t sdp_client_get_total(struct bt_sdp_client *session,
|
|
struct net_buf *buf, uint16_t *total)
|
|
{
|
|
uint16_t pulled;
|
|
uint8_t seq;
|
|
|
|
/*
|
|
* Pull value of total octets of all attributes available to be
|
|
* collected when response gets completed for given UUID. Such info can
|
|
* be get from the very first response frame after initial SSA request
|
|
* was sent. For subsequent calls related to the same SSA request input
|
|
* buf and in/out function parameters stays neutral.
|
|
*/
|
|
if (session->cstate.length == 0) {
|
|
seq = net_buf_pull_u8(buf);
|
|
pulled = 1;
|
|
switch (seq) {
|
|
case BT_SDP_SEQ8:
|
|
*total = net_buf_pull_u8(buf);
|
|
pulled += 1;
|
|
break;
|
|
case BT_SDP_SEQ16:
|
|
*total = net_buf_pull_be16(buf);
|
|
pulled += 2;
|
|
break;
|
|
default:
|
|
BT_WARN("Sequence type 0x%02x not handled", seq);
|
|
*total = 0;
|
|
break;
|
|
}
|
|
|
|
BT_DBG("Total %u octets of all attributes", *total);
|
|
} else {
|
|
pulled = 0;
|
|
*total = 0;
|
|
}
|
|
|
|
return pulled;
|
|
}
|
|
|
|
static uint16_t get_record_len(struct net_buf *buf)
|
|
{
|
|
uint16_t len;
|
|
uint8_t seq;
|
|
|
|
seq = net_buf_pull_u8(buf);
|
|
|
|
switch (seq) {
|
|
case BT_SDP_SEQ8:
|
|
len = net_buf_pull_u8(buf);
|
|
break;
|
|
case BT_SDP_SEQ16:
|
|
len = net_buf_pull_be16(buf);
|
|
break;
|
|
default:
|
|
BT_WARN("Sequence type 0x%02x not handled", seq);
|
|
len = 0;
|
|
break;
|
|
}
|
|
|
|
BT_DBG("Record len %u", len);
|
|
|
|
return len;
|
|
}
|
|
|
|
enum uuid_state {
|
|
UUID_NOT_RESOLVED,
|
|
UUID_RESOLVED,
|
|
};
|
|
|
|
static void sdp_client_notify_result(struct bt_sdp_client *session,
|
|
enum uuid_state state)
|
|
{
|
|
struct bt_conn *conn = session->chan.chan.conn;
|
|
struct bt_sdp_client_result result;
|
|
uint16_t rec_len;
|
|
uint8_t user_ret;
|
|
|
|
result.uuid = session->param->uuid;
|
|
|
|
if (state == UUID_NOT_RESOLVED) {
|
|
result.resp_buf = NULL;
|
|
result.next_record_hint = false;
|
|
session->param->func(conn, &result);
|
|
return;
|
|
}
|
|
|
|
while (session->rec_buf->len) {
|
|
struct net_buf_simple_state buf_state;
|
|
|
|
rec_len = get_record_len(session->rec_buf);
|
|
/* tell the user about multi record resolution */
|
|
if (session->rec_buf->len > rec_len) {
|
|
result.next_record_hint = true;
|
|
} else {
|
|
result.next_record_hint = false;
|
|
}
|
|
|
|
/* save the original session buffer */
|
|
net_buf_simple_save(&session->rec_buf->b, &buf_state);
|
|
/* initialize internal result buffer instead of memcpy */
|
|
result.resp_buf = session->rec_buf;
|
|
/*
|
|
* Set user internal result buffer length as same as record
|
|
* length to fake user. User will see the individual record
|
|
* length as rec_len insted of whole session rec_buf length.
|
|
*/
|
|
result.resp_buf->len = rec_len;
|
|
|
|
user_ret = session->param->func(conn, &result);
|
|
|
|
/* restore original session buffer */
|
|
net_buf_simple_restore(&session->rec_buf->b, &buf_state);
|
|
/*
|
|
* sync session buffer data length with next record chunk not
|
|
* send to user so far
|
|
*/
|
|
net_buf_pull(session->rec_buf, rec_len);
|
|
if (user_ret == BT_SDP_DISCOVER_UUID_STOP) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sdp_client_receive(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
{
|
|
struct bt_sdp_client *session = SDP_CLIENT_CHAN(chan);
|
|
struct bt_sdp_hdr *hdr = (void *)buf->data;
|
|
struct bt_sdp_pdu_cstate *cstate;
|
|
uint16_t len, tid, frame_len;
|
|
uint16_t total;
|
|
|
|
BT_DBG("session %p buf %p", session, buf);
|
|
|
|
if (buf->len < sizeof(*hdr)) {
|
|
BT_ERR("Too small SDP PDU");
|
|
return;
|
|
}
|
|
|
|
if (hdr->op_code == BT_SDP_ERROR_RSP) {
|
|
BT_INFO("Error SDP PDU response");
|
|
return;
|
|
}
|
|
|
|
len = sys_be16_to_cpu(hdr->param_len);
|
|
tid = sys_be16_to_cpu(hdr->tid);
|
|
net_buf_pull(buf, sizeof(*hdr));
|
|
|
|
BT_DBG("SDP PDU tid %u len %u", tid, len);
|
|
|
|
if (buf->len != len) {
|
|
BT_ERR("SDP PDU length mismatch (%u != %u)", buf->len, len);
|
|
return;
|
|
}
|
|
|
|
if (tid != session->tid) {
|
|
BT_ERR("Mismatch transaction ID value in SDP PDU");
|
|
return;
|
|
}
|
|
|
|
switch (hdr->op_code) {
|
|
case BT_SDP_SVC_SEARCH_ATTR_RSP:
|
|
/* Get number of attributes in this frame. */
|
|
frame_len = net_buf_pull_be16(buf);
|
|
/* Check valid range of attributes length */
|
|
if (frame_len < 2) {
|
|
BT_ERR("Invalid attributes data length");
|
|
return;
|
|
}
|
|
|
|
/* Get PDU continuation state */
|
|
cstate = (struct bt_sdp_pdu_cstate *)(buf->data + frame_len);
|
|
|
|
if (cstate->length > BT_SDP_MAX_PDU_CSTATE_LEN) {
|
|
BT_ERR("Invalid SDP PDU Continuation State length %u",
|
|
cstate->length);
|
|
return;
|
|
}
|
|
|
|
if ((frame_len + cstate->length) > len) {
|
|
BT_ERR("Invalid frame payload length");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* No record found for given UUID. The check catches case when
|
|
* current response frame has Continuation State shortest and
|
|
* valid and this is the first response frame as well.
|
|
*/
|
|
if (frame_len == 2 && cstate->length == 0 &&
|
|
session->cstate.length == 0) {
|
|
BT_DBG("record for UUID 0x%s not found",
|
|
bt_uuid_str(session->param->uuid));
|
|
/* Call user UUID handler */
|
|
sdp_client_notify_result(session, UUID_NOT_RESOLVED);
|
|
net_buf_pull(buf, frame_len + sizeof(cstate->length));
|
|
goto iterate;
|
|
}
|
|
|
|
/* Get total value of all attributes to be collected */
|
|
frame_len -= sdp_client_get_total(session, buf, &total);
|
|
|
|
if (total > net_buf_tailroom(session->rec_buf)) {
|
|
BT_WARN("Not enough room for getting records data");
|
|
goto iterate;
|
|
}
|
|
|
|
net_buf_add_mem(session->rec_buf, buf->data, frame_len);
|
|
net_buf_pull(buf, frame_len);
|
|
|
|
/*
|
|
* check if current response says there's next portion to be
|
|
* fetched
|
|
*/
|
|
if (cstate->length) {
|
|
/* Cache original Continuation State in context */
|
|
memcpy(&session->cstate, cstate,
|
|
sizeof(struct bt_sdp_pdu_cstate));
|
|
|
|
net_buf_pull(buf, cstate->length +
|
|
sizeof(cstate->length));
|
|
|
|
/* Request for next portion of attributes data */
|
|
sdp_client_ssa_search(session);
|
|
break;
|
|
}
|
|
|
|
net_buf_pull(buf, sizeof(cstate->length));
|
|
|
|
BT_DBG("UUID 0x%s resolved", bt_uuid_str(session->param->uuid));
|
|
sdp_client_notify_result(session, UUID_RESOLVED);
|
|
iterate:
|
|
/* Get next UUID and start resolving it */
|
|
sdp_client_params_iterator(session);
|
|
break;
|
|
default:
|
|
BT_DBG("PDU 0x%0x response not handled", hdr->op_code);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int sdp_client_chan_connect(struct bt_sdp_client *session)
|
|
{
|
|
return bt_l2cap_br_chan_connect(session->chan.chan.conn,
|
|
&session->chan.chan, SDP_PSM);
|
|
}
|
|
|
|
static struct net_buf *sdp_client_alloc_buf(struct bt_l2cap_chan *chan)
|
|
{
|
|
struct bt_sdp_client *session = SDP_CLIENT_CHAN(chan);
|
|
|
|
BT_DBG("session %p chan %p", session, chan);
|
|
|
|
session->param = GET_PARAM(sys_slist_peek_head(&session->reqs));
|
|
|
|
return net_buf_alloc(session->param->pool, K_FOREVER);
|
|
}
|
|
|
|
static void sdp_client_connected(struct bt_l2cap_chan *chan)
|
|
{
|
|
struct bt_sdp_client *session = SDP_CLIENT_CHAN(chan);
|
|
|
|
BT_DBG("session %p chan %p connected", session, chan);
|
|
|
|
session->rec_buf = chan->ops->alloc_buf(chan);
|
|
|
|
sdp_client_ssa_search(session);
|
|
}
|
|
|
|
static void sdp_client_disconnected(struct bt_l2cap_chan *chan)
|
|
{
|
|
struct bt_sdp_client *session = SDP_CLIENT_CHAN(chan);
|
|
|
|
BT_DBG("session %p chan %p disconnected", session, chan);
|
|
|
|
net_buf_unref(session->rec_buf);
|
|
|
|
/*
|
|
* Reset session excluding L2CAP channel member. Let's the channel
|
|
* resets autonomous.
|
|
*/
|
|
memset(&session->reqs, 0, sizeof(*session) - sizeof(session->chan));
|
|
}
|
|
|
|
static struct bt_l2cap_chan_ops sdp_client_chan_ops = {
|
|
.connected = sdp_client_connected,
|
|
.disconnected = sdp_client_disconnected,
|
|
.recv = sdp_client_receive,
|
|
.alloc_buf = sdp_client_alloc_buf,
|
|
};
|
|
|
|
static struct bt_sdp_client *sdp_client_new_session(struct bt_conn *conn)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_sdp_client_pool); i++) {
|
|
struct bt_sdp_client *session = &bt_sdp_client_pool[i];
|
|
int err;
|
|
|
|
if (session->chan.chan.conn) {
|
|
continue;
|
|
}
|
|
|
|
sys_slist_init(&session->reqs);
|
|
|
|
session->chan.chan.ops = &sdp_client_chan_ops;
|
|
session->chan.chan.conn = conn;
|
|
session->chan.rx.mtu = SDP_CLIENT_MTU;
|
|
|
|
err = sdp_client_chan_connect(session);
|
|
if (err) {
|
|
memset(session, 0, sizeof(*session));
|
|
BT_ERR("Cannot connect %d", err);
|
|
return NULL;
|
|
}
|
|
|
|
return session;
|
|
}
|
|
|
|
BT_ERR("No available SDP client context");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct bt_sdp_client *sdp_client_get_session(struct bt_conn *conn)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_sdp_client_pool); i++) {
|
|
if (bt_sdp_client_pool[i].chan.chan.conn == conn) {
|
|
return &bt_sdp_client_pool[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Try to allocate session context since not found in pool and attempt
|
|
* connect to remote SDP endpoint.
|
|
*/
|
|
return sdp_client_new_session(conn);
|
|
}
|
|
|
|
int bt_sdp_discover(struct bt_conn *conn,
|
|
const struct bt_sdp_discover_params *params)
|
|
{
|
|
struct bt_sdp_client *session;
|
|
|
|
if (!params || !params->uuid || !params->func || !params->pool) {
|
|
BT_WARN("Invalid user params");
|
|
return -EINVAL;
|
|
}
|
|
|
|
session = sdp_client_get_session(conn);
|
|
if (!session) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
sys_slist_append(&session->reqs, (sys_snode_t *)¶ms->_node);
|
|
|
|
return 0;
|
|
}
|