summaryrefslogtreecommitdiffstats
path: root/stack/sdp/sdp_server.c
diff options
context:
space:
mode:
Diffstat (limited to 'stack/sdp/sdp_server.c')
-rw-r--r--stack/sdp/sdp_server.c835
1 files changed, 835 insertions, 0 deletions
diff --git a/stack/sdp/sdp_server.c b/stack/sdp/sdp_server.c
new file mode 100644
index 0000000..5736b8d
--- /dev/null
+++ b/stack/sdp/sdp_server.c
@@ -0,0 +1,835 @@
+/******************************************************************************
+ *
+ * Copyright (C) 1999-2012 Broadcom 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.
+ *
+ ******************************************************************************/
+
+/******************************************************************************
+ *
+ * This file contains functions that handle the SDP server functions.
+ * This is mainly dealing with client requests
+ *
+ ******************************************************************************/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "gki.h"
+#include "bt_types.h"
+#include "btu.h"
+
+#include "l2cdefs.h"
+#include "hcidefs.h"
+#include "hcimsgs.h"
+
+#include "sdp_api.h"
+#include "sdpint.h"
+
+
+#if SDP_SERVER_ENABLED == TRUE
+
+/* Maximum number of bytes to reserve out of SDP MTU for response data */
+#define SDP_MAX_SERVICE_RSPHDR_LEN 12
+#define SDP_MAX_SERVATTR_RSPHDR_LEN 10
+#define SDP_MAX_ATTR_RSPHDR_LEN 10
+
+/********************************************************************************/
+/* L O C A L F U N C T I O N P R O T O T Y P E S */
+/********************************************************************************/
+static void process_service_search (tCONN_CB *p_ccb, UINT16 trans_num,
+ UINT16 param_len, UINT8 *p_req,
+ UINT8 *p_req_end);
+
+static void process_service_attr_req (tCONN_CB *p_ccb, UINT16 trans_num,
+ UINT16 param_len, UINT8 *p_req,
+ UINT8 *p_req_end);
+
+static void process_service_search_attr_req (tCONN_CB *p_ccb, UINT16 trans_num,
+ UINT16 param_len, UINT8 *p_req,
+ UINT8 *p_req_end);
+
+
+/********************************************************************************/
+/* E R R O R T E X T S T R I N G S */
+/* */
+/* The default is to have no text string, but we allow the strings to be */
+/* configured in target.h if people want them. */
+/********************************************************************************/
+#ifndef SDP_TEXT_BAD_HEADER
+#define SDP_TEXT_BAD_HEADER NULL
+#endif
+
+#ifndef SDP_TEXT_BAD_PDU
+#define SDP_TEXT_BAD_PDU NULL
+#endif
+
+#ifndef SDP_TEXT_BAD_UUID_LIST
+#define SDP_TEXT_BAD_UUID_LIST NULL
+#endif
+
+#ifndef SDP_TEXT_BAD_HANDLE
+#define SDP_TEXT_BAD_HANDLE NULL
+#endif
+
+#ifndef SDP_TEXT_BAD_ATTR_LIST
+#define SDP_TEXT_BAD_ATTR_LIST NULL
+#endif
+
+#ifndef SDP_TEXT_BAD_CONT_LEN
+#define SDP_TEXT_BAD_CONT_LEN NULL
+#endif
+
+#ifndef SDP_TEXT_BAD_CONT_INX
+#define SDP_TEXT_BAD_CONT_INX NULL
+#endif
+
+/*******************************************************************************
+**
+** Function sdp_server_handle_client_req
+**
+** Description This is the main dispatcher of the SDP server. It is called
+** when any data is received from L2CAP, and dispatches the
+** request to the appropriate handler.
+**
+** Returns void
+**
+*******************************************************************************/
+void sdp_server_handle_client_req (tCONN_CB *p_ccb, BT_HDR *p_msg)
+{
+ UINT8 *p_req = (UINT8 *) (p_msg + 1) + p_msg->offset;
+ UINT8 *p_req_end = p_req + p_msg->len;
+ UINT8 pdu_id;
+ UINT16 trans_num, param_len;
+
+
+ /* Start inactivity timer */
+ btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_SDP, SDP_INACT_TIMEOUT);
+
+ /* The first byte in the message is the pdu type */
+ pdu_id = *p_req++;
+
+ /* Extract the transaction number and parameter length */
+ BE_STREAM_TO_UINT16 (trans_num, p_req);
+ BE_STREAM_TO_UINT16 (param_len, p_req);
+
+ if ((p_req + param_len) > p_req_end)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_PDU_SIZE, SDP_TEXT_BAD_HEADER);
+ return;
+ }
+
+ switch (pdu_id)
+ {
+ case SDP_PDU_SERVICE_SEARCH_REQ:
+ process_service_search (p_ccb, trans_num, param_len, p_req, p_req_end);
+ break;
+
+ case SDP_PDU_SERVICE_ATTR_REQ:
+ process_service_attr_req (p_ccb, trans_num, param_len, p_req, p_req_end);
+ break;
+
+ case SDP_PDU_SERVICE_SEARCH_ATTR_REQ:
+ process_service_search_attr_req (p_ccb, trans_num, param_len, p_req, p_req_end);
+ break;
+
+ default:
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_REQ_SYNTAX, SDP_TEXT_BAD_PDU);
+ SDP_TRACE_WARNING1 ("SDP - server got unknown PDU: 0x%x", pdu_id);
+ break;
+ }
+}
+
+
+
+/*******************************************************************************
+**
+** Function process_service_search
+**
+** Description This function handles a service search request from the
+** client. It builds a reply message with info from the database,
+** and sends the reply back to the client.
+**
+** Returns void
+**
+*******************************************************************************/
+static void process_service_search (tCONN_CB *p_ccb, UINT16 trans_num,
+ UINT16 param_len, UINT8 *p_req,
+ UINT8 *p_req_end)
+{
+ UINT16 max_replies, cur_handles, rem_handles, cont_offset;
+ tSDP_UUID_SEQ uid_seq;
+ UINT8 *p_rsp, *p_rsp_start, *p_rsp_param_len;
+ UINT16 rsp_param_len, num_rsp_handles, xx;
+ UINT32 rsp_handles[SDP_MAX_RECORDS];
+ tSDP_RECORD *p_rec = NULL;
+ BT_HDR *p_buf;
+ BOOLEAN is_cont = FALSE;
+
+ p_req = sdpu_extract_uid_seq (p_req, param_len, &uid_seq);
+
+ if ((!p_req) || (!uid_seq.num_uids))
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_REQ_SYNTAX, SDP_TEXT_BAD_UUID_LIST);
+ return;
+ }
+
+ /* Get the max replies we can send. Cap it at our max anyways. */
+ BE_STREAM_TO_UINT16 (max_replies, p_req);
+
+ if (max_replies > SDP_MAX_RECORDS)
+ max_replies = SDP_MAX_RECORDS;
+
+ /* Get a list of handles that match the UUIDs given to us */
+ for (num_rsp_handles = 0; num_rsp_handles < max_replies; )
+ {
+ p_rec = sdp_db_service_search (p_rec, &uid_seq);
+
+ if (p_rec)
+ rsp_handles[num_rsp_handles++] = p_rec->record_handle;
+ else
+ break;
+ }
+
+ /* Check if this is a continuation request */
+ if (*p_req)
+ {
+ if (*p_req++ != SDP_CONTINUATION_LEN)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_CONT_STATE,
+ SDP_TEXT_BAD_CONT_LEN);
+ return;
+ }
+ BE_STREAM_TO_UINT16 (cont_offset, p_req);
+
+ if (cont_offset != p_ccb->cont_offset)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_CONT_STATE,
+ SDP_TEXT_BAD_CONT_INX);
+ return;
+ }
+
+ rem_handles = num_rsp_handles - cont_offset; /* extract the remaining handles */
+ }
+ else
+ {
+ rem_handles = num_rsp_handles;
+ cont_offset = 0;
+ }
+
+ /* Calculate how many handles will fit in one PDU */
+ cur_handles = (UINT16)((p_ccb->rem_mtu_size - SDP_MAX_SERVICE_RSPHDR_LEN) / 4);
+
+ if (rem_handles <= cur_handles)
+ cur_handles = rem_handles;
+ else /* Continuation is set */
+ {
+ p_ccb->cont_offset += cur_handles;
+ is_cont = TRUE;
+ }
+
+ /* Get a buffer to use to build the response */
+ if ((p_buf = (BT_HDR *)GKI_getpoolbuf (SDP_POOL_ID)) == NULL)
+ {
+ SDP_TRACE_ERROR0 ("SDP - no buf for search rsp");
+ return;
+ }
+ p_buf->offset = L2CAP_MIN_OFFSET;
+ p_rsp = p_rsp_start = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
+
+ /* Start building a rsponse */
+ UINT8_TO_BE_STREAM (p_rsp, SDP_PDU_SERVICE_SEARCH_RSP);
+ UINT16_TO_BE_STREAM (p_rsp, trans_num);
+
+ /* Skip the length, we need to add it at the end */
+ p_rsp_param_len = p_rsp;
+ p_rsp += 2;
+
+ /* Put in total and current number of handles, and handles themselves */
+ UINT16_TO_BE_STREAM (p_rsp, num_rsp_handles);
+ UINT16_TO_BE_STREAM (p_rsp, cur_handles);
+
+/* SDP_TRACE_DEBUG5("SDP Service Rsp: tothdl %d, curhdlr %d, start %d, end %d, cont %d",
+ num_rsp_handles, cur_handles, cont_offset,
+ cont_offset + cur_handles-1, is_cont); */
+ for (xx = cont_offset; xx < cont_offset + cur_handles; xx++)
+ UINT32_TO_BE_STREAM (p_rsp, rsp_handles[xx]);
+
+ if (is_cont)
+ {
+ UINT8_TO_BE_STREAM (p_rsp, SDP_CONTINUATION_LEN);
+ UINT16_TO_BE_STREAM (p_rsp, p_ccb->cont_offset);
+ }
+ else
+ UINT8_TO_BE_STREAM (p_rsp, 0);
+
+ /* Go back and put the parameter length into the buffer */
+ rsp_param_len = p_rsp - p_rsp_param_len - 2;
+ UINT16_TO_BE_STREAM (p_rsp_param_len, rsp_param_len);
+
+ /* Set the length of the SDP data in the buffer */
+ p_buf->len = p_rsp - p_rsp_start;
+
+
+ /* Send the buffer through L2CAP */
+ L2CA_DataWrite (p_ccb->connection_id, p_buf);
+}
+
+
+/*******************************************************************************
+**
+** Function process_service_attr_req
+**
+** Description This function handles an attribute request from the client.
+** It builds a reply message with info from the database,
+** and sends the reply back to the client.
+**
+** Returns void
+**
+*******************************************************************************/
+static void process_service_attr_req (tCONN_CB *p_ccb, UINT16 trans_num,
+ UINT16 param_len, UINT8 *p_req,
+ UINT8 *p_req_end)
+{
+ UINT16 max_list_len, len_to_send, cont_offset;
+ INT16 rem_len;
+ tSDP_ATTR_SEQ attr_seq, attr_seq_sav;
+ UINT8 *p_rsp, *p_rsp_start, *p_rsp_param_len;
+ UINT16 rsp_param_len, xx;
+ UINT32 rec_handle;
+ tSDP_RECORD *p_rec;
+ tSDP_ATTRIBUTE *p_attr;
+ BT_HDR *p_buf;
+ BOOLEAN is_cont = FALSE;
+ UINT16 attr_len;
+
+ /* Extract the record handle */
+ BE_STREAM_TO_UINT32 (rec_handle, p_req);
+
+ if (p_req > p_req_end)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_SERV_REC_HDL, SDP_TEXT_BAD_HANDLE);
+ return;
+ }
+
+ /* Get the max list length we can send. Cap it at MTU size minus overhead */
+ BE_STREAM_TO_UINT16 (max_list_len, p_req);
+
+ if (max_list_len > (p_ccb->rem_mtu_size - SDP_MAX_ATTR_RSPHDR_LEN))
+ max_list_len = p_ccb->rem_mtu_size - SDP_MAX_ATTR_RSPHDR_LEN;
+
+ p_req = sdpu_extract_attr_seq (p_req, param_len, &attr_seq);
+
+ if ((!p_req) || (!attr_seq.num_attr) || (p_req > p_req_end))
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_REQ_SYNTAX, SDP_TEXT_BAD_ATTR_LIST);
+ return;
+ }
+
+ memcpy(&attr_seq_sav, &attr_seq, sizeof(tSDP_ATTR_SEQ)) ;
+
+ /* Find a record with the record handle */
+ p_rec = sdp_db_find_record (rec_handle);
+ if (!p_rec)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_SERV_REC_HDL, SDP_TEXT_BAD_HANDLE);
+ return;
+ }
+
+ /* Check if this is a continuation request */
+ if (*p_req)
+ {
+ if (*p_req++ != SDP_CONTINUATION_LEN)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_CONT_STATE, SDP_TEXT_BAD_CONT_LEN);
+ return;
+ }
+ BE_STREAM_TO_UINT16 (cont_offset, p_req);
+
+ if (cont_offset != p_ccb->cont_offset)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_CONT_STATE, SDP_TEXT_BAD_CONT_INX);
+ return;
+ }
+
+ if (!p_ccb->rsp_list)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_NO_RESOURCES, NULL);
+ return;
+ }
+ is_cont = TRUE;
+
+ /* Initialise for continuation response */
+ p_rsp = &p_ccb->rsp_list[0];
+ attr_seq.attr_entry[p_ccb->cont_info.next_attr_index].start = p_ccb->cont_info.next_attr_start_id;
+ }
+ else
+ {
+ /* Get a scratch buffer to store response */
+ if (!p_ccb->rsp_list)
+ {
+ p_ccb->rsp_list = (UINT8 *)GKI_getbuf (max_list_len);
+ if (p_ccb->rsp_list == NULL)
+ {
+ SDP_TRACE_ERROR0 ("SDP - no scratch buf for search rsp");
+ return;
+ }
+ }
+
+ p_ccb->cont_offset = 0;
+ p_rsp = &p_ccb->rsp_list[3]; /* Leave space for data elem descr */
+
+ /* Reset continuation parameters in p_ccb */
+ p_ccb->cont_info.prev_sdp_rec = NULL;
+ p_ccb->cont_info.next_attr_index = 0;
+ p_ccb->cont_info.attr_offset = 0;
+ }
+
+ /* Search for attributes that match the list given to us */
+ for (xx = p_ccb->cont_info.next_attr_index; xx < attr_seq.num_attr; xx++)
+ {
+ p_attr = sdp_db_find_attr_in_rec (p_rec, attr_seq.attr_entry[xx].start, attr_seq.attr_entry[xx].end);
+
+ if (p_attr)
+ {
+ /* Check if attribute fits. Assume 3-byte value type/length */
+ rem_len = max_list_len - (INT16) (p_rsp - &p_ccb->rsp_list[0]);
+
+ /* just in case */
+ if (rem_len <= 0)
+ {
+ p_ccb->cont_info.next_attr_index = xx;
+ p_ccb->cont_info.next_attr_start_id = p_attr->id;
+ break;
+ }
+
+ attr_len = sdpu_get_attrib_entry_len(p_attr);
+ /* if there is a partial attribute pending to be sent */
+ if (p_ccb->cont_info.attr_offset)
+ {
+ p_rsp = sdpu_build_partial_attrib_entry (p_rsp, p_attr, rem_len,
+ &p_ccb->cont_info.attr_offset);
+
+ /* If the partial attrib could not been fully added yet */
+ if (p_ccb->cont_info.attr_offset != attr_len)
+ break;
+ else /* If the partial attrib has been added in full by now */
+ p_ccb->cont_info.attr_offset = 0; /* reset attr_offset */
+ }
+ else if (rem_len < attr_len) /* Not enough space for attr... so add partially */
+ {
+ if (attr_len >= MAX_ATTR_LEN)
+ {
+ SDP_TRACE_ERROR2("SDP attr too big: max_list_len=%d,attr_len=%d", max_list_len, attr_len);
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_NO_RESOURCES, NULL);
+ return;
+ }
+
+ /* add the partial attribute if possible */
+ p_rsp = sdpu_build_partial_attrib_entry (p_rsp, p_attr, (UINT16)rem_len,
+ &p_ccb->cont_info.attr_offset);
+
+ p_ccb->cont_info.next_attr_index = xx;
+ p_ccb->cont_info.next_attr_start_id = p_attr->id;
+ break;
+ }
+ else /* build the whole attribute */
+ p_rsp = sdpu_build_attrib_entry (p_rsp, p_attr);
+
+ /* If doing a range, stick with this one till no more attributes found */
+ if (attr_seq.attr_entry[xx].start != attr_seq.attr_entry[xx].end)
+ {
+ /* Update for next time through */
+ attr_seq.attr_entry[xx].start = p_attr->id + 1;
+
+ xx--;
+ }
+ }
+ }
+ /* If all the attributes have been accomodated in p_rsp,
+ reset next_attr_index */
+ if (xx == attr_seq.num_attr)
+ p_ccb->cont_info.next_attr_index = 0;
+
+ len_to_send = (UINT16) (p_rsp - &p_ccb->rsp_list[0]);
+ cont_offset = 0;
+
+ if (!is_cont)
+ {
+ p_ccb->list_len = sdpu_get_attrib_seq_len(p_rec, &attr_seq_sav) + 3;
+ /* Put in the sequence header (2 or 3 bytes) */
+ if (p_ccb->list_len > 255)
+ {
+ p_ccb->rsp_list[0] = (UINT8) ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_WORD);
+ p_ccb->rsp_list[1] = (UINT8) ((p_ccb->list_len - 3) >> 8);
+ p_ccb->rsp_list[2] = (UINT8) (p_ccb->list_len - 3);
+ }
+ else
+ {
+ cont_offset = 1;
+
+ p_ccb->rsp_list[1] = (UINT8) ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_BYTE);
+ p_ccb->rsp_list[2] = (UINT8) (p_ccb->list_len - 3);
+
+ p_ccb->list_len--;
+ len_to_send--;
+ }
+ }
+
+ /* Get a buffer to use to build the response */
+ if ((p_buf = (BT_HDR *)GKI_getpoolbuf (SDP_POOL_ID)) == NULL)
+ {
+ SDP_TRACE_ERROR0 ("SDP - no buf for search rsp");
+ return;
+ }
+ p_buf->offset = L2CAP_MIN_OFFSET;
+ p_rsp = p_rsp_start = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
+
+ /* Start building a rsponse */
+ UINT8_TO_BE_STREAM (p_rsp, SDP_PDU_SERVICE_ATTR_RSP);
+ UINT16_TO_BE_STREAM (p_rsp, trans_num);
+
+ /* Skip the parameter length, add it when we know the length */
+ p_rsp_param_len = p_rsp;
+ p_rsp += 2;
+
+ UINT16_TO_BE_STREAM (p_rsp, len_to_send);
+
+ memcpy (p_rsp, &p_ccb->rsp_list[cont_offset], len_to_send);
+ p_rsp += len_to_send;
+
+ p_ccb->cont_offset += len_to_send;
+
+ /* If anything left to send, continuation needed */
+ if (p_ccb->cont_offset < p_ccb->list_len)
+ {
+ is_cont = TRUE;
+
+ UINT8_TO_BE_STREAM (p_rsp, SDP_CONTINUATION_LEN);
+ UINT16_TO_BE_STREAM (p_rsp, p_ccb->cont_offset);
+ }
+ else
+ UINT8_TO_BE_STREAM (p_rsp, 0);
+
+ /* Go back and put the parameter length into the buffer */
+ rsp_param_len = p_rsp - p_rsp_param_len - 2;
+ UINT16_TO_BE_STREAM (p_rsp_param_len, rsp_param_len);
+
+ /* Set the length of the SDP data in the buffer */
+ p_buf->len = p_rsp - p_rsp_start;
+
+
+ /* Send the buffer through L2CAP */
+ L2CA_DataWrite (p_ccb->connection_id, p_buf);
+}
+
+
+
+/*******************************************************************************
+**
+** Function process_service_search_attr_req
+**
+** Description This function handles a combined service search and attribute
+** read request from the client. It builds a reply message with
+** info from the database, and sends the reply back to the client.
+**
+** Returns void
+**
+*******************************************************************************/
+static void process_service_search_attr_req (tCONN_CB *p_ccb, UINT16 trans_num,
+ UINT16 param_len, UINT8 *p_req,
+ UINT8 *p_req_end)
+{
+ UINT16 max_list_len;
+ INT16 rem_len;
+ UINT16 len_to_send, cont_offset;
+ tSDP_UUID_SEQ uid_seq;
+ UINT8 *p_rsp, *p_rsp_start, *p_rsp_param_len;
+ UINT16 rsp_param_len, xx;
+ tSDP_RECORD *p_rec;
+ tSDP_ATTR_SEQ attr_seq, attr_seq_sav;
+ tSDP_ATTRIBUTE *p_attr;
+ BT_HDR *p_buf;
+ BOOLEAN maxxed_out = FALSE, is_cont = FALSE;
+ UINT8 *p_seq_start;
+ UINT16 seq_len, attr_len;
+
+ /* Extract the UUID sequence to search for */
+ p_req = sdpu_extract_uid_seq (p_req, param_len, &uid_seq);
+
+ if ((!p_req) || (!uid_seq.num_uids))
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_REQ_SYNTAX, SDP_TEXT_BAD_UUID_LIST);
+ return;
+ }
+
+ /* Get the max list length we can send. Cap it at our max list length. */
+ BE_STREAM_TO_UINT16 (max_list_len, p_req);
+
+ if (max_list_len > (p_ccb->rem_mtu_size - SDP_MAX_SERVATTR_RSPHDR_LEN))
+ max_list_len = p_ccb->rem_mtu_size - SDP_MAX_SERVATTR_RSPHDR_LEN;
+
+ p_req = sdpu_extract_attr_seq (p_req, param_len, &attr_seq);
+
+ if ((!p_req) || (!attr_seq.num_attr))
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_REQ_SYNTAX, SDP_TEXT_BAD_ATTR_LIST);
+ return;
+ }
+
+ memcpy(&attr_seq_sav, &attr_seq, sizeof(tSDP_ATTR_SEQ)) ;
+
+ /* Check if this is a continuation request */
+ if (*p_req)
+ {
+ if (*p_req++ != SDP_CONTINUATION_LEN)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_CONT_STATE, SDP_TEXT_BAD_CONT_LEN);
+ return;
+ }
+ BE_STREAM_TO_UINT16 (cont_offset, p_req);
+
+ if (cont_offset != p_ccb->cont_offset)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_CONT_STATE, SDP_TEXT_BAD_CONT_INX);
+ return;
+ }
+
+ if (!p_ccb->rsp_list)
+ {
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_NO_RESOURCES, NULL);
+ return;
+ }
+ is_cont = TRUE;
+
+ /* Initialise for continuation response */
+ p_rsp = &p_ccb->rsp_list[0];
+ attr_seq.attr_entry[p_ccb->cont_info.next_attr_index].start = p_ccb->cont_info.next_attr_start_id;
+ }
+ else
+ {
+ /* Get a scratch buffer to store response */
+ if (!p_ccb->rsp_list)
+ {
+ p_ccb->rsp_list = (UINT8 *)GKI_getbuf (max_list_len);
+ if (p_ccb->rsp_list == NULL)
+ {
+ SDP_TRACE_ERROR0 ("SDP - no scratch buf for search rsp");
+ return;
+ }
+ }
+
+ p_ccb->cont_offset = 0;
+ p_rsp = &p_ccb->rsp_list[3]; /* Leave space for data elem descr */
+
+ /* Reset continuation parameters in p_ccb */
+ p_ccb->cont_info.prev_sdp_rec = NULL;
+ p_ccb->cont_info.next_attr_index = 0;
+ p_ccb->cont_info.last_attr_seq_desc_sent = FALSE;
+ p_ccb->cont_info.attr_offset = 0;
+ }
+
+ /* Get a list of handles that match the UUIDs given to us */
+ for (p_rec = sdp_db_service_search (p_ccb->cont_info.prev_sdp_rec, &uid_seq); p_rec; p_rec = sdp_db_service_search (p_rec, &uid_seq))
+ {
+ /* Allow space for attribute sequence type and length */
+ p_seq_start = p_rsp;
+ if (p_ccb->cont_info.last_attr_seq_desc_sent == FALSE)
+ {
+ /* See if there is enough room to include a new service in the current response */
+ rem_len = max_list_len - (INT16) (p_rsp - &p_ccb->rsp_list[0]);
+ if (rem_len < 3)
+ {
+ /* Not enough room. Update continuation info for next response */
+ p_ccb->cont_info.next_attr_index = 0;
+ p_ccb->cont_info.next_attr_start_id = attr_seq.attr_entry[0].start;
+ break;
+ }
+ p_rsp += 3;
+ }
+
+ /* Get a list of handles that match the UUIDs given to us */
+ for (xx = p_ccb->cont_info.next_attr_index; xx < attr_seq.num_attr; xx++)
+ {
+ p_attr = sdp_db_find_attr_in_rec (p_rec, attr_seq.attr_entry[xx].start, attr_seq.attr_entry[xx].end);
+
+ if (p_attr)
+ {
+ /* Check if attribute fits. Assume 3-byte value type/length */
+ rem_len = max_list_len - (INT16) (p_rsp - &p_ccb->rsp_list[0]);
+
+ /* just in case */
+ if (rem_len <= 0)
+ {
+ p_ccb->cont_info.next_attr_index = xx;
+ p_ccb->cont_info.next_attr_start_id = p_attr->id;
+ maxxed_out = TRUE;
+ break;
+ }
+
+ attr_len = sdpu_get_attrib_entry_len(p_attr);
+ /* if there is a partial attribute pending to be sent */
+ if (p_ccb->cont_info.attr_offset)
+ {
+ p_rsp = sdpu_build_partial_attrib_entry (p_rsp, p_attr, rem_len,
+ &p_ccb->cont_info.attr_offset);
+
+ /* If the partial attrib could not been fully added yet */
+ if (p_ccb->cont_info.attr_offset != attr_len)
+ {
+ maxxed_out = TRUE;
+ break;
+ }
+ else /* If the partial attrib has been added in full by now */
+ p_ccb->cont_info.attr_offset = 0; /* reset attr_offset */
+ }
+ else if (rem_len < attr_len) /* Not enough space for attr... so add partially */
+ {
+ if (attr_len >= MAX_ATTR_LEN)
+ {
+ SDP_TRACE_ERROR2("SDP attr too big: max_list_len=%d,attr_len=%d", max_list_len, attr_len);
+ sdpu_build_n_send_error (p_ccb, trans_num, SDP_NO_RESOURCES, NULL);
+ return;
+ }
+
+ /* add the partial attribute if possible */
+ p_rsp = sdpu_build_partial_attrib_entry (p_rsp, p_attr, (UINT16)rem_len,
+ &p_ccb->cont_info.attr_offset);
+
+ p_ccb->cont_info.next_attr_index = xx;
+ p_ccb->cont_info.next_attr_start_id = p_attr->id;
+ maxxed_out = TRUE;
+ break;
+ }
+ else /* build the whole attribute */
+ p_rsp = sdpu_build_attrib_entry (p_rsp, p_attr);
+
+ /* If doing a range, stick with this one till no more attributes found */
+ if (attr_seq.attr_entry[xx].start != attr_seq.attr_entry[xx].end)
+ {
+ /* Update for next time through */
+ attr_seq.attr_entry[xx].start = p_attr->id + 1;
+
+ xx--;
+ }
+ }
+ }
+
+ /* Go back and put the type and length into the buffer */
+ if (p_ccb->cont_info.last_attr_seq_desc_sent == FALSE)
+ {
+ seq_len = sdpu_get_attrib_seq_len(p_rec, &attr_seq_sav);
+ if (seq_len != 0)
+ {
+ UINT8_TO_BE_STREAM (p_seq_start, (DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_WORD);
+ UINT16_TO_BE_STREAM (p_seq_start, seq_len);
+
+ if (maxxed_out)
+ p_ccb->cont_info.last_attr_seq_desc_sent = TRUE;
+ }
+ else
+ p_rsp = p_seq_start;
+ }
+
+ if (maxxed_out)
+ break;
+
+ /* Restore the attr_seq to look for in the next sdp record */
+ memcpy(&attr_seq, &attr_seq_sav, sizeof(tSDP_ATTR_SEQ)) ;
+
+ /* Reset the next attr index */
+ p_ccb->cont_info.next_attr_index = 0;
+ p_ccb->cont_info.prev_sdp_rec = p_rec;
+ p_ccb->cont_info.last_attr_seq_desc_sent = FALSE;
+ }
+
+ /* response length */
+ len_to_send = (UINT16) (p_rsp - &p_ccb->rsp_list[0]);
+ cont_offset = 0;
+
+ /* If first response, insert sequence header */
+ if (!is_cont)
+ {
+ /* Get the total list length for requested uid and attribute sequence */
+ p_ccb->list_len = sdpu_get_list_len(&uid_seq, &attr_seq_sav) + 3;
+ /* Put in the sequence header (2 or 3 bytes) */
+ if (p_ccb->list_len > 255)
+ {
+ p_ccb->rsp_list[0] = (UINT8) ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_WORD);
+ p_ccb->rsp_list[1] = (UINT8) ((p_ccb->list_len - 3) >> 8);
+ p_ccb->rsp_list[2] = (UINT8) (p_ccb->list_len - 3);
+ }
+ else
+ {
+ cont_offset = 1;
+
+ p_ccb->rsp_list[1] = (UINT8) ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_BYTE);
+ p_ccb->rsp_list[2] = (UINT8) (p_ccb->list_len - 3);
+
+ p_ccb->list_len--;
+ len_to_send--;
+ }
+ }
+
+ /* Get a buffer to use to build the response */
+ if ((p_buf = (BT_HDR *)GKI_getpoolbuf (SDP_POOL_ID)) == NULL)
+ {
+ SDP_TRACE_ERROR0 ("SDP - no buf for search rsp");
+ return;
+ }
+ p_buf->offset = L2CAP_MIN_OFFSET;
+ p_rsp = p_rsp_start = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
+
+ /* Start building a rsponse */
+ UINT8_TO_BE_STREAM (p_rsp, SDP_PDU_SERVICE_SEARCH_ATTR_RSP);
+ UINT16_TO_BE_STREAM (p_rsp, trans_num);
+
+ /* Skip the parameter length, add it when we know the length */
+ p_rsp_param_len = p_rsp;
+ p_rsp += 2;
+
+ /* Stream the list length to send */
+ UINT16_TO_BE_STREAM (p_rsp, len_to_send);
+
+ /* copy from rsp_list to the actual buffer to be sent */
+ memcpy (p_rsp, &p_ccb->rsp_list[cont_offset], len_to_send);
+ p_rsp += len_to_send;
+
+ p_ccb->cont_offset += len_to_send;
+
+ /* If anything left to send, continuation needed */
+ if (p_ccb->cont_offset < p_ccb->list_len)
+ {
+ is_cont = TRUE;
+
+ UINT8_TO_BE_STREAM (p_rsp, SDP_CONTINUATION_LEN);
+ UINT16_TO_BE_STREAM (p_rsp, p_ccb->cont_offset);
+ }
+ else
+ UINT8_TO_BE_STREAM (p_rsp, 0);
+
+ /* Go back and put the parameter length into the buffer */
+ rsp_param_len = p_rsp - p_rsp_param_len - 2;
+ UINT16_TO_BE_STREAM (p_rsp_param_len, rsp_param_len);
+
+ /* Set the length of the SDP data in the buffer */
+ p_buf->len = p_rsp - p_rsp_start;
+
+
+ /* Send the buffer through L2CAP */
+ L2CA_DataWrite (p_ccb->connection_id, p_buf);
+}
+
+#endif /* SDP_SERVER_ENABLED == TRUE */