/* $NetBSD: service.c,v 1.5 2024/02/05 21:46:07 andvar Exp $ */ /*- * Copyright (c) 2009 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Iain Hibbert. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <sys/cdefs.h> __RCSID("$NetBSD: service.c,v 1.5 2024/02/05 21:46:07 andvar Exp $"); #include <bluetooth.h> #include <sdp.h> #include "sdpd.h" /* * This structure is a collection of pointers describing an output * buffer for sdpd_put_byte(), below. bytes are written at next when * it falls inside the range [start .. end - 1] */ typedef struct { uint8_t *start; /* start of buffer window */ uint8_t *next; /* current write position */ uint8_t *end; /* end of buffer window */ } sdpd_data_t; static bool sdpd_valid_ssp(sdp_data_t *); static bool sdpd_valid_ail(sdp_data_t *); static bool sdpd_match_ail(record_t *, sdp_data_t, sdpd_data_t *); static void sdpd_put_byte(sdpd_data_t *, uint8_t); static void sdpd_put_attr(sdpd_data_t *, uint16_t, sdp_data_t *); static void sdpd_open_seq(sdpd_data_t *); static void sdpd_close_seq(sdpd_data_t *, uint8_t *); uint16_t service_search_request(server_t *srv, int fd) { record_t *r; sdp_data_t d, s; int max, total, count; log_debug("ServiceSearchRequest by client on fd#%d", fd); d.next = srv->ibuf; d.end = srv->ibuf + srv->pdu.len; /* * extract ServiceSearchPattern */ if (!sdp_get_seq(&d, &s) || !sdpd_valid_ssp(&s)) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; /* * extract MaximumServiceRecordCount */ if (d.next + sizeof(uint16_t) > d.end) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; max = be16dec(d.next); d.next += sizeof(uint16_t); if (max < 0x0001) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; /* * validate ContinuationState * If none given, this is a new request */ if (d.next + 1 > d.end || d.next[0] > 16 || d.next + 1 + d.next[0] != d.end) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; if (d.next[0] == 0) { srv->fdidx[fd].offset = 0; db_unselect(srv, fd); db_select_ssp(srv, fd, &s); } else if (srv->fdidx[fd].offset == 0 || d.next[0] != sizeof(uint16_t) || be16dec(d.next + 1) != srv->fdidx[fd].offset) return SDP_ERROR_CODE_INVALID_CONTINUATION_STATE; /* * Ready our output buffer. We leave space at the start for * TotalServiceRecordCount and CurrentServiceRecordCount and * at the end for ContinuationState, and we must have space * for at least one ServiceRecordHandle. Then, step through * selected records and write as many handles that will fit * into the data space */ d.next = srv->obuf + sizeof(uint16_t) + sizeof(uint16_t); d.end = srv->obuf + srv->fdidx[fd].omtu - 1 - sizeof(uint16_t); count = total = 0; if (d.next + sizeof(uint32_t) > d.end) return SDP_ERROR_CODE_INSUFFICIENT_RESOURCES; r = NULL; while (db_next(srv, fd, &r) && total < max) { if (total >= srv->fdidx[fd].offset && d.next + sizeof(uint32_t) <= d.end) { be32enc(d.next, r->handle); d.next += sizeof(uint32_t); count++; } total++; } /* * encode TotalServiceRecordCount and CurrentServiceRecordCount */ be16enc(srv->obuf, total); be16enc(srv->obuf + sizeof(uint16_t), count); /* * encode ContinuationState which in this case will be the * number of ServiceRecordHandles already sent. */ if (r == NULL || total == max) { srv->fdidx[fd].offset = 0; db_unselect(srv, fd); d.next[0] = 0; d.next += 1; } else { srv->fdidx[fd].offset += count; d.next[0] = sizeof(uint16_t); be16enc(d.next + 1, srv->fdidx[fd].offset); d.next += 1 + sizeof(uint16_t); } /* * fill in PDU header and we are done */ srv->pdu.pid = SDP_PDU_SERVICE_SEARCH_RESPONSE; srv->pdu.len = d.next - srv->obuf; return 0; } uint16_t service_attribute_request(server_t *srv, int fd) { record_t *r; sdp_data_t a, d; sdpd_data_t b; uint8_t *tmp; uint32_t handle; int max; log_debug("ServiceAttributeRequest by client on fd#%d", fd); d.next = srv->ibuf; d.end = srv->ibuf + srv->pdu.len; /* * extract ServiceRecordHandle */ if (d.next + sizeof(uint32_t) > d.end) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; handle = be32dec(d.next); d.next += sizeof(uint32_t); /* * extract MaximumAttributeByteCount */ if (d.next + sizeof(uint16_t) > d.end) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; max = be16dec(d.next); d.next += sizeof(uint16_t); if (max < 0x0007) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; /* * extract AttributeIDList */ if (!sdp_get_seq(&d, &a) || !sdpd_valid_ail(&a)) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; /* * validate ContinuationState * If none given, this is a new request */ if (d.next + 1 > d.end || d.next[0] > 16 || d.next + 1 + d.next[0] != d.end) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; if (d.next[0] == 0) { srv->fdidx[fd].offset = 0; db_unselect(srv, fd); db_select_handle(srv, fd, handle); } else if (srv->fdidx[fd].offset == 0 || d.next[0] != sizeof(uint16_t) || be16dec(d.next + 1) != srv->fdidx[fd].offset) return SDP_ERROR_CODE_INVALID_CONTINUATION_STATE; /* * Set up the buffer window and write pointer, leaving space at * buffer start for AttributeListByteCount and for ContinuationState * at the end */ b.start = srv->obuf + sizeof(uint16_t); b.next = b.start - srv->fdidx[fd].offset; b.end = srv->obuf + srv->fdidx[fd].omtu - 1; if (b.start + max < b.end) b.end = b.start + max; /* * Match the selected record against AttributeIDList, writing * the data to the sparse buffer. */ r = NULL; db_next(srv, fd, &r); if (r == NULL) return SDP_ERROR_CODE_INVALID_SERVICE_RECORD_HANDLE; sdpd_match_ail(r, a, &b); if (b.next > b.end) { /* * b.end is the limit of AttributeList that we are allowed * to send so if we have exceeded that we need to adjust our * response downwards. Recalculate the new cut off to allow * writing the ContinuationState offset and ensure we don't * exceed MaximumAttributeByteCount. Also, make sure that * the continued length is not too short. */ tmp = b.next; b.next = srv->obuf + srv->fdidx[fd].omtu - 1 - sizeof(uint16_t); if (b.next > b.end) b.next = b.end; if (tmp - b.next < 0x0002) b.next = tmp - 0x0002; /* encode AttributeListByteCount */ be16enc(srv->obuf, (b.next - b.start)); /* calculate & append ContinuationState */ srv->fdidx[fd].offset += (b.next - b.start); b.next[0] = sizeof(uint16_t); be16enc(b.next + 1, srv->fdidx[fd].offset); b.next += 1 + sizeof(uint16_t); } else { /* encode AttributeListByteCount */ be16enc(srv->obuf, (b.next - b.start)); /* reset & append ContinuationState */ srv->fdidx[fd].offset = 0; db_unselect(srv, fd); b.next[0] = 0; b.next += 1; } /* * fill in PDU header and we are done */ srv->pdu.pid = SDP_PDU_SERVICE_ATTRIBUTE_RESPONSE; srv->pdu.len = b.next - srv->obuf; return 0; } uint16_t service_search_attribute_request(server_t *srv, int fd) { record_t *r; sdpd_data_t b; sdp_data_t a, d, s; uint8_t *tmp; int max; log_debug("ServiceSearchAttributeRequest by client on fd#%d", fd); d.next = srv->ibuf; d.end = srv->ibuf + srv->pdu.len; /* * extract ServiceSearchPattern */ if (!sdp_get_seq(&d, &s) || !sdpd_valid_ssp(&s)) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; /* * extract MaximumAttributeByteCount */ if (d.next + sizeof(uint16_t) > d.end) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; max = be16dec(d.next); d.next += sizeof(uint16_t); if (max < 0x0007) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; /* * extract AttributeIDList */ if (!sdp_get_seq(&d, &a) || !sdpd_valid_ail(&a)) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; /* * validate ContinuationState * If none given, this is a new request */ if (d.next + 1 > d.end || d.next[0] > 16 || d.next + 1 + d.next[0] != d.end) return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; if (d.next[0] == 0) { srv->fdidx[fd].offset = 0; db_unselect(srv, fd); db_select_ssp(srv, fd, &s); } else if (srv->fdidx[fd].offset == 0 || d.next[0] != sizeof(uint16_t) || be16dec(d.next + 1) != srv->fdidx[fd].offset) return SDP_ERROR_CODE_INVALID_CONTINUATION_STATE; /* * Set up the buffer window and write pointer, leaving space at * buffer start for AttributeListByteCount and for ContinuationState * at the end. */ b.start = srv->obuf + sizeof(uint16_t); b.end = srv->obuf + srv->fdidx[fd].omtu - 1; b.next = b.start - srv->fdidx[fd].offset; if (b.start + max < b.end) b.end = b.start + max; /* * match all selected records against the AttributeIDList, * wrapping the whole in a sequence. Where a record does * not match any attributes, delete the empty sequence. */ sdpd_open_seq(&b); r = NULL; while (db_next(srv, fd, &r)) { tmp = b.next; if (!sdpd_match_ail(r, a, &b)) b.next = tmp; } sdpd_close_seq(&b, b.start - srv->fdidx[fd].offset); if (b.next > b.end) { /* * b.end is the limit of AttributeLists that we are allowed * to send so if we have exceeded that we need to adjust our * response downwards. Recalculate the new cut off to allow * writing the ContinuationState offset and ensure we don't * exceed MaximumAttributeByteCount. Also, make sure that * the continued length is not too short. */ tmp = b.next; b.next = srv->obuf + srv->fdidx[fd].omtu - 1 - sizeof(uint16_t); if (b.next > b.end) b.next = b.end; if (tmp - b.next < 0x0002) b.next = tmp - 0x0002; /* encode AttributeListsByteCount */ be16enc(srv->obuf, (b.next - b.start)); /* calculate & append ContinuationState */ srv->fdidx[fd].offset += (b.next - b.start); b.next[0] = sizeof(uint16_t); be16enc(b.next + 1, srv->fdidx[fd].offset); b.next += 1 + sizeof(uint16_t); } else { /* encode AttributeListsByteCount */ be16enc(srv->obuf, (b.next - b.start)); /* reset & append ContinuationState */ srv->fdidx[fd].offset = 0; db_unselect(srv, fd); b.next[0] = 0; b.next += 1; } /* * fill in PDU header and we are done */ srv->pdu.pid = SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_RESPONSE; srv->pdu.len = b.next - srv->obuf; return 0; } /* * validate ServiceSearchPattern * * The ServiceSearchPattern is a list of data elements, where each element * is a UUID. The list must contain at least one UUID and the maximum number * of UUIDs is 12 */ static bool sdpd_valid_ssp(sdp_data_t *ssp) { sdp_data_t s = *ssp; uuid_t u; int n; if (!sdp_data_valid(&s)) return false; n = 0; while (sdp_get_uuid(&s, &u)) n++; if (n < 1 || n > 12 || s.next != s.end) return false; return true; } /* * validate AttributeIDList * * The AttributeIDList is a list of data elements, where each element is * either an attribute ID encoded as an unsigned 16-bit integer or a range * of attribute IDs encoded as an unsigned 32-bit integer where the high * order 16-bits are the beginning of the range and the low order 16-bits * are the ending * * The attribute IDs should be listed in ascending order without duplication * of any attribute ID values but we don't worry about that, since if the * remote party messes up, their results will be messed up */ static bool sdpd_valid_ail(sdp_data_t *ail) { sdp_data_t a = *ail; sdp_data_t d; if (!sdp_data_valid(&a)) return false; while (sdp_get_data(&a, &d)) { if (sdp_data_type(&d) != SDP_DATA_UINT16 && sdp_data_type(&d) != SDP_DATA_UINT32) return false; } return true; } /* * compare attributes in the ServiceRecord with the AttributeIDList * and copy any matches to a sequence in the output buffer. */ static bool sdpd_match_ail(record_t *rec, sdp_data_t ail, sdpd_data_t *buf) { sdp_data_t r, v; uint16_t a; uintmax_t ui; uint8_t *f; int lo, hi; bool rv; r = rec->data; f = buf->next; lo = hi = -1; rv = false; sdpd_open_seq(buf); while (sdp_get_attr(&r, &a, &v)) { while (a > hi) { if (ail.next == ail.end) goto done; if (sdp_data_type(&ail) == SDP_DATA_UINT16) { sdp_get_uint(&ail, &ui); lo = hi = ui; } else { sdp_get_uint(&ail, &ui); lo = (uint16_t)(ui >> 16); hi = (uint16_t)(ui); } } if (a < lo) continue; sdpd_put_attr(buf, a, &v); rv = true; } done: sdpd_close_seq(buf, f); return rv; } /* * output data. We only actually store the bytes when the * pointer is within the valid window. */ static void sdpd_put_byte(sdpd_data_t *buf, uint8_t byte) { if (buf->next >= buf->start && buf->next < buf->end) buf->next[0] = byte; buf->next++; } static void sdpd_put_attr(sdpd_data_t *buf, uint16_t attr, sdp_data_t *data) { uint8_t *p; sdpd_put_byte(buf, SDP_DATA_UINT16); sdpd_put_byte(buf, (uint8_t)(attr >> 8)); sdpd_put_byte(buf, (uint8_t)(attr)); for (p = data->next; p < data->end; p++) sdpd_put_byte(buf, *p); } /* * Since we always use a seq16 and never check the length, we will send * an invalid header if it grows too large. We could always use a seq32 * but the chance of overflow is small so ignore it for now. */ static void sdpd_open_seq(sdpd_data_t *buf) { buf->next += 3; } static void sdpd_close_seq(sdpd_data_t *buf, uint8_t *first) { uint8_t *next; size_t len; next = buf->next; buf->next = first; len = next - first - 3; sdpd_put_byte(buf, SDP_DATA_SEQ16); sdpd_put_byte(buf, 0xff & (len >> 8)); sdpd_put_byte(buf, 0xff & (len >> 0)); buf->next = next; }