root/lib/libbluetooth/sdp_service.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;
}