NetBSD/lib/libbluetooth/sdp_service.c

472 lines
9.5 KiB
C

/* $NetBSD: sdp_service.c,v 1.4 2010/11/20 12:12:21 plunky 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: sdp_service.c,v 1.4 2010/11/20 12:12:21 plunky Exp $");
#include <sys/atomic.h>
#include <errno.h>
#include <limits.h>
#include <sdp.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sdp-int.h"
/*
* If AttributeIDList is given as NULL, request all attributes.
* (this is actually const data but we can't declare it const)
*/
static uint8_t ail_default[] = { 0x0a, 0x00, 0x00, 0xff, 0xff };
/*
* This provides the maximum size that the response buffer will be
* allowed to grow to.
*
* Default is UINT16_MAX but it can be overridden at runtime.
*/
static size_t
sdp_response_max(void)
{
static size_t max = UINT16_MAX;
static unsigned int check = 1;
char *env, *ep;
unsigned long v;
while (atomic_swap_uint(&check, 0)) { /* only check env once */
env = getenv("SDP_RESPONSE_MAX");
if (env == NULL)
break;
errno = 0;
v = strtoul(env, &ep, 0);
if (env[0] == '\0' || *ep != '\0')
break;
if (errno == ERANGE && v == ULONG_MAX)
break;
/* lower limit is arbitrary */
if (v < UINT8_MAX || v > UINT32_MAX)
break;
max = v;
}
return max;
}
bool
sdp_service_search(struct sdp_session *ss, const sdp_data_t *ssp,
uint32_t *id, int *num)
{
struct iovec req[5];
sdp_data_t hdr;
uint8_t sdata[5], max[2];
uint8_t *ptr, *end;
ssize_t len;
uint16_t total, count, got;
/*
* setup ServiceSearchPattern
*/
len = ssp->end - ssp->next;
if (len < 0 || len > UINT16_MAX) {
errno = EINVAL;
return false;
}
hdr.next = sdata;
hdr.end = sdata + sizeof(sdata) + len;
sdp_put_seq(&hdr, len);
req[1].iov_base = sdata;
req[1].iov_len = hdr.next - sdata;
req[2].iov_base = ssp->next;
req[2].iov_len = len;
/*
* setup MaximumServiceRecordCount
*/
if (*num < 0 || *num > UINT16_MAX) {
errno = EINVAL;
return false;
}
be16enc(max, *num);
req[3].iov_base = max;
req[3].iov_len = sizeof(uint16_t);
/*
* clear ContinuationState
*/
ss->cs[0] = 0;
/*
* ServiceSearch Transaction
*/
got = 0;
for (;;) {
/*
* setup ContinuationState
*/
req[4].iov_base = ss->cs;
req[4].iov_len = ss->cs[0] + 1;
if (!_sdp_send_pdu(ss, SDP_PDU_SERVICE_SEARCH_REQUEST,
req, __arraycount(req)))
return false;
len = _sdp_recv_pdu(ss, SDP_PDU_SERVICE_SEARCH_RESPONSE);
if (len == -1)
return false;
ptr = ss->ibuf;
end = ss->ibuf + len;
/*
* extract TotalServiceRecordCount
*/
if (ptr + sizeof(uint16_t) > end)
break;
total = be16dec(ptr);
ptr += sizeof(uint16_t);
if (total > *num)
break;
/*
* extract CurrentServiceRecordCount
*/
if (ptr + sizeof(uint16_t) > end)
break;
count = be16dec(ptr);
ptr += sizeof(uint16_t);
if (got + count > total)
break;
/*
* extract ServiceRecordHandleList
*/
if (ptr + count * sizeof(uint32_t) > end)
break;
while (count-- > 0) {
id[got++] = be32dec(ptr);
ptr += sizeof(uint32_t);
}
/*
* extract ContinuationState
*/
if (ptr + 1 > end
|| ptr[0] > 16
|| ptr + ptr[0] + 1 != end)
break;
memcpy(ss->cs, ptr, (size_t)(ptr[0] + 1));
/*
* Complete?
*/
if (ss->cs[0] == 0) {
*num = got;
return true;
}
}
errno = EIO;
return false;
}
bool
sdp_service_attribute(struct sdp_session *ss, uint32_t id,
const sdp_data_t *ail, sdp_data_t *rsp)
{
struct iovec req[6];
sdp_data_t hdr;
uint8_t adata[5], handle[4], max[2];
uint8_t *ptr, *end, *rbuf;
ssize_t len;
size_t rlen, count;
/*
* setup ServiceRecordHandle
*/
be32enc(handle, id);
req[1].iov_base = handle;
req[1].iov_len = sizeof(uint32_t);
/*
* setup MaximumAttributeByteCount
*/
be16enc(max, ss->imtu - sizeof(uint16_t) - sizeof(ss->cs));
req[2].iov_base = max;
req[2].iov_len = sizeof(uint16_t);
/*
* setup AttributeIDList
*/
len = (ail == NULL ? (ssize_t)sizeof(ail_default) : (ail->end - ail->next));
if (len < 0 || len > UINT16_MAX) {
errno = EINVAL;
return false;
}
hdr.next = adata;
hdr.end = adata + sizeof(adata) + len;
sdp_put_seq(&hdr, len);
req[3].iov_base = adata;
req[3].iov_len = hdr.next - adata;
req[4].iov_base = (ail == NULL ? ail_default : ail->next);
req[4].iov_len = len;
/*
* clear ContinuationState
*/
ss->cs[0] = 0;
/*
* ServiceAttribute Transaction
*/
rlen = 0;
for (;;) {
/*
* setup ContinuationState
*/
req[5].iov_base = ss->cs;
req[5].iov_len = ss->cs[0] + 1;
if (!_sdp_send_pdu(ss, SDP_PDU_SERVICE_ATTRIBUTE_REQUEST,
req, __arraycount(req)))
return false;
len = _sdp_recv_pdu(ss, SDP_PDU_SERVICE_ATTRIBUTE_RESPONSE);
if (len == -1)
return false;
ptr = ss->ibuf;
end = ss->ibuf + len;
/*
* extract AttributeListByteCount
*/
if (ptr + sizeof(uint16_t) > end)
break;
count = be16dec(ptr);
ptr += sizeof(uint16_t);
if (count == 0 || ptr + count > end)
break;
/*
* extract AttributeList
*/
if (rlen + count > sdp_response_max())
break;
rbuf = realloc(ss->rbuf, rlen + count);
if (rbuf == NULL)
return false;
ss->rbuf = rbuf;
memcpy(rbuf + rlen, ptr, count);
rlen += count;
ptr += count;
/*
* extract ContinuationState
*/
if (ptr + 1 > end
|| ptr[0] > 16
|| ptr + ptr[0] + 1 != end)
break;
memcpy(ss->cs, ptr, (size_t)(ptr[0] + 1));
/*
* Complete?
*/
if (ss->cs[0] == 0) {
rsp->next = rbuf;
rsp->end = rbuf + rlen;
if (sdp_data_size(rsp) != (ssize_t)rlen
|| !sdp_data_valid(rsp)
|| !sdp_get_seq(rsp, rsp))
break;
return true;
}
}
errno = EIO;
return false;
}
bool
sdp_service_search_attribute(struct sdp_session *ss, const sdp_data_t *ssp,
const sdp_data_t *ail, sdp_data_t *rsp)
{
struct iovec req[7];
sdp_data_t hdr;
uint8_t sdata[5], adata[5], max[2];
uint8_t *ptr, *end, *rbuf;
ssize_t len;
size_t rlen, count;
/*
* setup ServiceSearchPattern
*/
len = ssp->end - ssp->next;
if (len < 0 || len > UINT16_MAX) {
errno = EINVAL;
return false;
}
hdr.next = sdata;
hdr.end = sdata + sizeof(sdata) + len;
sdp_put_seq(&hdr, len);
req[1].iov_base = sdata;
req[1].iov_len = hdr.next - sdata;
req[2].iov_base = ssp->next;
req[2].iov_len = len;
/*
* setup MaximumAttributeByteCount
*/
be16enc(max, ss->imtu - sizeof(uint16_t) - sizeof(ss->cs));
req[3].iov_base = max;
req[3].iov_len = sizeof(uint16_t);
/*
* setup AttributeIDList
*/
len = (ail == NULL ? (ssize_t)sizeof(ail_default) : (ail->end - ail->next));
if (len < 0 || len > UINT16_MAX) {
errno = EINVAL;
return false;
}
hdr.next = adata;
hdr.end = adata + sizeof(adata) + len;
sdp_put_seq(&hdr, len);
req[4].iov_base = adata;
req[4].iov_len = hdr.next - adata;
req[5].iov_base = (ail == NULL ? ail_default : ail->next);
req[5].iov_len = len;
/*
* clear ContinuationState
*/
ss->cs[0] = 0;
/*
* ServiceSearchAttribute Transaction
*/
rlen = 0;
for (;;) {
/*
* setup ContinuationState
*/
req[6].iov_base = ss->cs;
req[6].iov_len = ss->cs[0] + 1;
if (!_sdp_send_pdu(ss, SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_REQUEST,
req, __arraycount(req)))
return false;
len = _sdp_recv_pdu(ss, SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_RESPONSE);
if (len == -1)
return false;
ptr = ss->ibuf;
end = ss->ibuf + len;
/*
* extract AttributeListsByteCount
*/
if (ptr + sizeof(uint16_t) > end)
break;
count = be16dec(ptr);
ptr += sizeof(uint16_t);
if (count == 0 || ptr + count > end)
break;
/*
* extract AttributeLists
*/
if (rlen + count > sdp_response_max())
break;
rbuf = realloc(ss->rbuf, rlen + count);
if (rbuf == NULL)
return false;
ss->rbuf = rbuf;
memcpy(rbuf + rlen, ptr, count);
rlen += count;
ptr += count;
/*
* extract ContinuationState
*/
if (ptr + 1 > end
|| ptr[0] > 16
|| ptr + ptr[0] + 1 != end)
break;
memcpy(ss->cs, ptr, (size_t)(ptr[0] + 1));
/*
* Complete?
*/
if (ss->cs[0] == 0) {
rsp->next = rbuf;
rsp->end = rbuf + rlen;
if (sdp_data_size(rsp) != (ssize_t)rlen
|| !sdp_data_valid(rsp)
|| !sdp_get_seq(rsp, rsp))
break;
return true;
}
}
errno = EIO;
return false;
}