/*      $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;
}