| /****************************************************************************** |
| * |
| * 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 "bt_common.h" |
| #include "bt_types.h" |
| #include "bt_utils.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) |
| |
| extern fixed_queue_t *btu_general_alarm_queue; |
| |
| /* 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_t trans_num, |
| uint16_t param_len, uint8_t *p_req, |
| uint8_t *p_req_end); |
| |
| static void process_service_attr_req (tCONN_CB *p_ccb, uint16_t trans_num, |
| uint16_t param_len, uint8_t *p_req, |
| uint8_t *p_req_end); |
| |
| static void process_service_search_attr_req (tCONN_CB *p_ccb, uint16_t trans_num, |
| uint16_t param_len, uint8_t *p_req, |
| uint8_t *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 |
| |
| #ifndef SDP_TEXT_BAD_MAX_RECORDS_LIST |
| #define SDP_TEXT_BAD_MAX_RECORDS_LIST 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_t *p_req = (uint8_t *) (p_msg + 1) + p_msg->offset; |
| uint8_t *p_req_end = p_req + p_msg->len; |
| uint8_t pdu_id; |
| uint16_t trans_num, param_len; |
| |
| |
| /* Start inactivity timer */ |
| alarm_set_on_queue(p_ccb->sdp_conn_timer, SDP_INACT_TIMEOUT_MS, |
| sdp_conn_timer_timeout, p_ccb, btu_general_alarm_queue); |
| |
| /* 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_WARNING ("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_t trans_num, |
| uint16_t param_len, uint8_t *p_req, |
| uint8_t *p_req_end) |
| { |
| uint16_t max_replies, cur_handles, rem_handles, cont_offset; |
| tSDP_UUID_SEQ uid_seq; |
| uint8_t *p_rsp, *p_rsp_start, *p_rsp_param_len; |
| uint16_t rsp_param_len, num_rsp_handles, xx; |
| uint32_t rsp_handles[SDP_MAX_RECORDS] = {0}; |
| tSDP_RECORD *p_rec = NULL; |
| bool is_cont = false; |
| UNUSED(p_req_end); |
| |
| 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; |
| |
| |
| if ((!p_req) || (p_req > p_req_end)) |
| { |
| sdpu_build_n_send_error (p_ccb, trans_num, SDP_INVALID_REQ_SYNTAX, SDP_TEXT_BAD_MAX_RECORDS_LIST); |
| return; |
| } |
| |
| |
| /* 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 || (p_req >= p_req_end)) |
| { |
| 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; |
| p_ccb->cont_offset = 0; |
| } |
| |
| /* Calculate how many handles will fit in one PDU */ |
| cur_handles = (uint16_t)((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 */ |
| BT_HDR *p_buf = (BT_HDR *)osi_malloc(SDP_DATA_BUF_SIZE); |
| p_buf->offset = L2CAP_MIN_OFFSET; |
| p_rsp = p_rsp_start = (uint8_t *)(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_DEBUG("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_t trans_num, |
| uint16_t param_len, uint8_t *p_req, |
| uint8_t *p_req_end) |
| { |
| uint16_t max_list_len, len_to_send, cont_offset; |
| int16_t rem_len; |
| tSDP_ATTR_SEQ attr_seq, attr_seq_sav; |
| uint8_t *p_rsp, *p_rsp_start, *p_rsp_param_len; |
| uint16_t rsp_param_len, xx; |
| uint32_t rec_handle; |
| tSDP_RECORD *p_rec; |
| tSDP_ATTRIBUTE *p_attr; |
| bool is_cont = false; |
| uint16_t 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; |
| } |
| |
| /* Free and reallocate buffer */ |
| osi_free(p_ccb->rsp_list); |
| p_ccb->rsp_list = (uint8_t *)osi_malloc(max_list_len); |
| |
| /* 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; |
| } |
| 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 { |
| 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_t) (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 >= SDP_MAX_ATTR_LEN) |
| { |
| SDP_TRACE_ERROR("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_t)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_t) (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_t) ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_WORD); |
| p_ccb->rsp_list[1] = (uint8_t) ((p_ccb->list_len - 3) >> 8); |
| p_ccb->rsp_list[2] = (uint8_t) (p_ccb->list_len - 3); |
| } |
| else |
| { |
| cont_offset = 1; |
| |
| p_ccb->rsp_list[1] = (uint8_t) ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_BYTE); |
| p_ccb->rsp_list[2] = (uint8_t) (p_ccb->list_len - 3); |
| |
| p_ccb->list_len--; |
| len_to_send--; |
| } |
| } |
| |
| /* Get a buffer to use to build the response */ |
| BT_HDR *p_buf = (BT_HDR *)osi_malloc(SDP_DATA_BUF_SIZE); |
| p_buf->offset = L2CAP_MIN_OFFSET; |
| p_rsp = p_rsp_start = (uint8_t *)(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_t trans_num, |
| uint16_t param_len, uint8_t *p_req, |
| uint8_t *p_req_end) |
| { |
| uint16_t max_list_len; |
| int16_t rem_len; |
| uint16_t len_to_send, cont_offset; |
| tSDP_UUID_SEQ uid_seq; |
| uint8_t *p_rsp, *p_rsp_start, *p_rsp_param_len; |
| uint16_t rsp_param_len, xx; |
| tSDP_RECORD *p_rec; |
| tSDP_ATTR_SEQ attr_seq, attr_seq_sav; |
| tSDP_ATTRIBUTE *p_attr; |
| bool maxxed_out = false, is_cont = false; |
| uint8_t *p_seq_start; |
| uint16_t seq_len, attr_len; |
| UNUSED(p_req_end); |
| |
| /* 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)) ; |
| |
| /* Free and reallocate buffer */ |
| osi_free(p_ccb->rsp_list); |
| p_ccb->rsp_list = (uint8_t *)osi_malloc(max_list_len); |
| |
| /* 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; |
| } |
| 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 { |
| 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_t) (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_t) (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 >= SDP_MAX_ATTR_LEN) |
| { |
| SDP_TRACE_ERROR("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_t)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_t) (p_rsp - &p_ccb->rsp_list[0]); |
| cont_offset = 0; |
| |
| // The current SDP server design has a critical flaw where it can run into an infinite |
| // request/response loop with the client. Here's the scenario: |
| // - client makes SDP request |
| // - server returns the first fragment of the response with a continuation token |
| // - an SDP record is deleted from the server |
| // - client issues another request with previous continuation token |
| // - server has nothing to send back because the record is unavailable but in the |
| // first fragment, it had specified more response bytes than are now available |
| // - server sends back no additional response bytes and returns the same continuation token |
| // - client issues another request with the continuation token, and the process repeats |
| // |
| // We work around this design flaw here by checking if we will make forward progress |
| // (i.e. we will send > 0 response bytes) on a continued request. If not, we must have |
| // run into the above situation and we tell the peer an error occurred. |
| // |
| // TODO(sharvil): rewrite SDP server. |
| if (is_cont && len_to_send == 0) { |
| sdpu_build_n_send_error(p_ccb, trans_num, SDP_INVALID_CONT_STATE, NULL); |
| return; |
| } |
| |
| /* 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_t) ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_WORD); |
| p_ccb->rsp_list[1] = (uint8_t) ((p_ccb->list_len - 3) >> 8); |
| p_ccb->rsp_list[2] = (uint8_t) (p_ccb->list_len - 3); |
| } |
| else |
| { |
| cont_offset = 1; |
| |
| p_ccb->rsp_list[1] = (uint8_t) ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_BYTE); |
| p_ccb->rsp_list[2] = (uint8_t) (p_ccb->list_len - 3); |
| |
| p_ccb->list_len--; |
| len_to_send--; |
| } |
| } |
| |
| /* Get a buffer to use to build the response */ |
| BT_HDR *p_buf = (BT_HDR *)osi_malloc(SDP_DATA_BUF_SIZE); |
| p_buf->offset = L2CAP_MIN_OFFSET; |
| p_rsp = p_rsp_start = (uint8_t *)(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 */ |