/** @file * @brief Service Discovery Protocol handling. */ /* * Copyright (c) 2016 Intel Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include "l2cap_internal.h" #include "sdp_internal.h" #if !defined(CONFIG_BLUETOOTH_DEBUG_SDP) #undef BT_DBG #define BT_DBG(fmt, ...) #endif #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 */ static struct k_fifo sdp_buf; static NET_BUF_POOL(sdp_pool, CONFIG_BLUETOOTH_MAX_CONN, BT_L2CAP_BUF_SIZE(SDP_MTU), &sdp_buf, NULL, BT_BUF_USER_DATA_MIN); /** @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_buf, sizeof(struct bt_sdp_hdr)); if (!buf) { BT_ERR("Failed to create PDU"); return NULL; } 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; net_buf_pool_init(sdp_pool); 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", service); 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; }