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