| /** @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 <errno.h> |
| #include <misc/byteorder.h> |
| |
| #include <bluetooth/log.h> |
| #include <bluetooth/sdp.h> |
| |
| #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; |
| } |