/*	$NetBSD: sdp.c,v 1.12 2021/12/12 22:20:52 andvar Exp $	*/

/*-
 * Copyright (c) 2006 Itronix Inc.
 * All rights reserved.
 *
 * 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.
 * 3. The name of Itronix Inc. may not be used to endorse
 *    or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``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 ITRONIX INC. 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.
 */
/*
 * Copyright (c) 2009 The NetBSD Foundation, Inc.
 * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
 * All rights reserved.
 *
 * 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 AUTHOR 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 AUTHOR 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.c,v 1.12 2021/12/12 22:20:52 andvar Exp $");

#include <sys/types.h>

#include <dev/bluetooth/btdev.h>
#include <dev/bluetooth/bthidev.h>
#include <dev/bluetooth/btsco.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include <dev/hid/hid.h>

#include <prop/proplib.h>

#include <bluetooth.h>
#include <err.h>
#include <errno.h>
#include <sdp.h>
#include <stdlib.h>
#include <strings.h>
#include <usbhid.h>

#include "btdevctl.h"

static bool parse_hid_descriptor(sdp_data_t *);
static int32_t parse_boolean(sdp_data_t *);
static int32_t parse_pdl_param(sdp_data_t *, uint16_t);
static int32_t parse_pdl(sdp_data_t *, uint16_t);
static int32_t parse_apdl(sdp_data_t *, uint16_t);

static int config_pnp(prop_dictionary_t, sdp_data_t *);
static int config_hid(prop_dictionary_t, sdp_data_t *);
static int config_hset(prop_dictionary_t, sdp_data_t *);
static int config_hf(prop_dictionary_t, sdp_data_t *);

uint16_t pnp_services[] = {
	SDP_SERVICE_CLASS_PNP_INFORMATION,
};

uint16_t hid_services[] = {
	SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE,
};

uint16_t hset_services[] = {
	SDP_SERVICE_CLASS_HEADSET,
};

uint16_t hf_services[] = {
	SDP_SERVICE_CLASS_HANDSFREE_AUDIO_GATEWAY,
};

static struct {
	const char		*name;
	int			(*handler)(prop_dictionary_t, sdp_data_t *);
	const char		*description;
	uint16_t		*services;
	size_t			nservices;
} cfgtype[] = {
    {
	"HID",		config_hid,	"Human Interface Device",
	hid_services,	__arraycount(hid_services),
    },
    {
	"HSET",		config_hset,	"Headset",
	hset_services,	__arraycount(hset_services),
    },
    {
	"HF",		config_hf,	"Handsfree",
	hf_services,	__arraycount(hf_services),
    },
};

#define MAX_SSP		(2 + 1 * 3)	/* largest nservices is 1 */

static bool
cfg_ssa(sdp_session_t ss, uint16_t *services, size_t nservices, sdp_data_t *rsp)
{
	uint8_t buf[MAX_SSP];
	sdp_data_t ssp;
	size_t i;

	ssp.next = buf;
	ssp.end = buf + sizeof(buf);

	for (i = 0; i < nservices; i++)
		sdp_put_uuid16(&ssp, services[i]);

	ssp.end = ssp.next;
	ssp.next = buf;

	return sdp_service_search_attribute(ss, &ssp, NULL, rsp);
}

static bool
cfg_search(sdp_session_t ss, int i, prop_dictionary_t dict)
{
	sdp_data_t rsp, rec;

	/* check PnP Information first */
	if (!cfg_ssa(ss, pnp_services, __arraycount(pnp_services), &rsp))
		return false;

	while (sdp_get_seq(&rsp, &rec)) {
		if (config_pnp(dict, &rec) == 0)
			break;
	}

	/* then requested service */
	if (!cfg_ssa(ss, cfgtype[i].services, cfgtype[i].nservices, &rsp))
		return false;

	while (sdp_get_seq(&rsp, &rec)) {
		errno = (*cfgtype[i].handler)(dict, &rec);
		if (errno == 0)
			return true;
	}

	return false;
}

prop_dictionary_t
cfg_query(bdaddr_t *laddr, bdaddr_t *raddr, const char *service)
{
	prop_dictionary_t dict;
	sdp_session_t ss;
	size_t i;

	dict = prop_dictionary_create();
	if (dict == NULL)
		err(EXIT_FAILURE, "prop_dictionary_create()");

	for (i = 0; i < __arraycount(cfgtype); i++) {
		if (strcasecmp(service, cfgtype[i].name) == 0) {
			ss = sdp_open(laddr, raddr);
			if (ss == NULL)
				err(EXIT_FAILURE, "SDP connection failed");

			if (!cfg_search(ss, i, dict))
				errx(EXIT_FAILURE, "service %s not found", service);

			sdp_close(ss);
			return dict;
		}
	}

	printf("Known config types:\n");
	for (i = 0; i < __arraycount(cfgtype); i++)
		printf("\t%s\t%s\n", cfgtype[i].name, cfgtype[i].description);

	exit(EXIT_FAILURE);
}

/*
 * Configure PnP Information results
 */
static int
config_pnp(prop_dictionary_t dict, sdp_data_t *rec)
{
	sdp_data_t value;
	uintmax_t v;
	uint16_t attr;
	int vendor, product, source;

	vendor = -1;
	product = -1;
	source = -1;

	while (sdp_get_attr(rec, &attr, &value)) {
		switch (attr) {
		case 0x0201:	/* Vendor ID */
			if (sdp_get_uint(&value, &v)
			    && v <= UINT16_MAX)
				vendor = (int)v;

			break;

		case 0x0202:	/* Product ID */
			if (sdp_get_uint(&value, &v)
			    && v <= UINT16_MAX)
				product = (int)v;

			break;

		case 0x0205:	/* Vendor ID Source */
			if (sdp_get_uint(&value, &v)
			    && v <= UINT16_MAX)
				source = (int)v;

			break;

		default:
			break;
		}
	}

	if (vendor == -1 || product == -1)
		return ENOATTR;

	if (source != 0x0002)	/* "USB Implementers Forum" */
		return ENOATTR;

	if (!prop_dictionary_set_uint16(dict, BTDEVvendor, (uint16_t)vendor))
		return errno;

	if (!prop_dictionary_set_uint16(dict, BTDEVproduct, (uint16_t)product))
		return errno;

	return 0;
}

/*
 * Configure HID results
 */
static int
config_hid(prop_dictionary_t dict, sdp_data_t *rec)
{
	prop_object_t obj;
	int32_t control_psm, interrupt_psm,
		reconnect_initiate, hid_length;
	uint8_t *hid_descriptor;
	sdp_data_t value;
	const char *mode;
	uint16_t attr;

	control_psm = -1;
	interrupt_psm = -1;
	reconnect_initiate = -1;
	hid_descriptor = NULL;
	hid_length = -1;

	while (sdp_get_attr(rec, &attr, &value)) {
		switch (attr) {
		case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST:
			control_psm = parse_pdl(&value, SDP_UUID_PROTOCOL_L2CAP);
			break;

		case SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS:
			interrupt_psm = parse_apdl(&value, SDP_UUID_PROTOCOL_L2CAP);
			break;

		case 0x0205: /* HIDReconnectInitiate */
			reconnect_initiate = parse_boolean(&value);
			break;

		case 0x0206: /* HIDDescriptorList */
			if (parse_hid_descriptor(&value)) {
				hid_descriptor = value.next;
				hid_length = value.end - value.next;
			}
			break;

		default:
			break;
		}
	}

	if (control_psm == -1
	    || interrupt_psm == -1
	    || reconnect_initiate == -1
	    || hid_descriptor == NULL
	    || hid_length == -1)
		return ENOATTR;

	if (!prop_dictionary_set_string_nocopy(dict, BTDEVtype, "bthidev"))
		return errno;

	if (!prop_dictionary_set_int32(dict, BTHIDEVcontrolpsm, control_psm) ||
	    !prop_dictionary_set_int32(dict, BTHIDEVinterruptpsm,
	    			       interrupt_psm))
		return errno;

	obj = prop_data_create_copy(hid_descriptor, hid_length);
	if (obj == NULL || !prop_dictionary_set(dict, BTHIDEVdescriptor, obj))
		return errno;

	mode = hid_mode(obj);
	prop_object_release(obj);

	if (!prop_dictionary_set_string_nocopy(dict, BTDEVmode, mode))
		return errno;

	if (!reconnect_initiate) {
		if (!prop_dictionary_set_bool(dict, BTHIDEVreconnect, true))
			return errno;
	}

	return 0;
}

/*
 * Configure HSET results
 */
static int
config_hset(prop_dictionary_t dict, sdp_data_t *rec)
{
	sdp_data_t value;
	int32_t channel;
	uint16_t attr;

	channel = -1;

	while (sdp_get_attr(rec, &attr, &value)) {
		switch (attr) {
		case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST:
			channel = parse_pdl(&value, SDP_UUID_PROTOCOL_RFCOMM);
			break;

		default:
			break;
		}
	}

	if (channel == -1)
		return ENOATTR;

	if (!prop_dictionary_set_string_nocopy(dict, BTDEVtype, "btsco"))
		return errno;

	if (!prop_dictionary_set_int32(dict, BTSCOchannel, channel))
		return errno;

	return 0;
}

/*
 * Configure HF results
 */
static int
config_hf(prop_dictionary_t dict, sdp_data_t *rec)
{
	sdp_data_t value;
	int32_t channel;
	uint16_t attr;

	channel = -1;

	while (sdp_get_attr(rec, &attr, &value)) {
		switch (attr) {
		case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST:
			channel = parse_pdl(&value, SDP_UUID_PROTOCOL_RFCOMM);
			break;

		default:
			break;
		}
	}

	if (channel == -1)
		return ENOATTR;

	if (!prop_dictionary_set_string_nocopy(dict, BTDEVtype, "btsco"))
		return errno;

	if (!prop_dictionary_set_bool(dict, BTSCOlisten, true))
		return errno;

	if (!prop_dictionary_set_int32(dict, BTSCOchannel, channel))
		return errno;

	return 0;
}

/*
 * Parse HIDDescriptorList . This is a sequence of HIDDescriptors, of which
 * each is a data element sequence containing, minimally, a ClassDescriptorType
 * and ClassDescriptorData containing a byte array of data. Any extra elements
 * should be ignored.
 *
 * If a ClassDescriptorType "Report" is found, set SDP data value to the
 * ClassDescriptorData content and return true. Note that we don't need to
 * extract the actual length as the SDP data is guaranteed valid.
 */

static bool
parse_hid_descriptor(sdp_data_t *value)
{
	sdp_data_t list, desc;
	uintmax_t type;
	char *str;
	size_t len;

	if (!sdp_get_seq(value, &list))
		return false;

	while (sdp_get_seq(&list, &desc)) {
		if (sdp_get_uint(&desc, &type)
		    && type == UDESC_REPORT
		    && sdp_get_str(&desc, &str, &len)) {
			value->next = (uint8_t *)str;
			value->end = (uint8_t *)(str + len);
			return true;
		}
	}

	return false;
}

static int32_t
parse_boolean(sdp_data_t *value)
{
	bool bv;

	if (!sdp_get_bool(value, &bv))
		return -1;

	return bv;
}

/*
 * The ProtocolDescriptorList attribute describes one or
 * more protocol stacks that may be used to gain access to
 * the service described by the service record.
 *
 * If the ProtocolDescriptorList describes a single stack,
 * the attribute value takes the form of a data element
 * sequence in which each element of the sequence is a
 * protocol descriptor.
 *
 *	seq
 *	  <list>
 *
 * If it is possible for more than one kind of protocol
 * stack to be used to gain access to the service, the
 * ProtocolDescriptorList takes the form of a data element
 * alternative where each member is a data element sequence
 * consisting of a list of sequences describing each protocol
 *
 *	alt
 *	  seq
 *	    <list>
 *	  seq
 *	    <list>
 *
 * Each ProtocolDescriptorList is a list containing a sequence for
 * each protocol, where each sequence contains the protocol UUUID
 * and any protocol specific parameters.
 *
 *	seq
 *	  uuid		L2CAP
 *	  uint16	psm
 *	seq
 *	  uuid		RFCOMM
 *	  uint8		channel
 *
 * We want to extract the ProtocolSpecificParameter#1 for the
 * given protocol, which will be an unsigned int.
 */
static int32_t
parse_pdl_param(sdp_data_t *pdl, uint16_t proto)
{
	sdp_data_t seq;
	uintmax_t param;

	while (sdp_get_seq(pdl, &seq)) {
		if (!sdp_match_uuid16(&seq, proto))
			continue;

		if (sdp_get_uint(&seq, &param))
			return param;

		break;
	}

	return -1;
}

static int32_t
parse_pdl(sdp_data_t *value, uint16_t proto)
{
	sdp_data_t seq;
	int32_t param = -1;

	sdp_get_alt(value, value);	/* strip any alt header */

	while (param == -1 && sdp_get_seq(value, &seq))
		param = parse_pdl_param(&seq, proto);

	return param;
}

/*
 * Parse AdditionalProtocolDescriptorList
 */
static int32_t
parse_apdl(sdp_data_t *value, uint16_t proto)
{
	sdp_data_t seq;
	int32_t param = -1;

	sdp_get_seq(value, value);	/* strip seq header */

	while (param == -1 && sdp_get_seq(value, &seq))
		param = parse_pdl_param(&seq, proto);

	return param;
}

/*
 * return appropriate mode for HID descriptor
 */
const char *
hid_mode(prop_data_t desc)
{
	report_desc_t r;
	hid_data_t d;
	struct hid_item h;
	const char *mode;

	hid_init(NULL);

	mode = BTDEVauth;	/* default */

	r = hid_use_report_desc(prop_data_value(desc),
				prop_data_size(desc));
	if (r == NULL)
		err(EXIT_FAILURE, "hid_use_report_desc");

	d = hid_start_parse(r, ~0, -1);
	while (hid_get_item(d, &h) > 0) {
		if (h.kind == hid_collection
		    && HID_PAGE(h.usage) == HUP_GENERIC_DESKTOP
		    && HID_USAGE(h.usage) == HUG_KEYBOARD)
			mode = BTDEVencrypt;
	}

	hid_end_parse(d);
	hid_dispose_report_desc(r);

	return mode;
}
