#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)
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;
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;
}
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);
}
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:
if (sdp_get_uint(&value, &v)
&& v <= UINT16_MAX)
vendor = (int)v;
break;
case 0x0202:
if (sdp_get_uint(&value, &v)
&& v <= UINT16_MAX)
product = (int)v;
break;
case 0x0205:
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)
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;
}
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:
reconnect_initiate = parse_boolean(&value);
break;
case 0x0206:
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;
}
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;
}
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;
}
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;
}
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, ¶m))
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);
while (param == -1 && sdp_get_seq(value, &seq))
param = parse_pdl_param(&seq, proto);
return param;
}
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);
while (param == -1 && sdp_get_seq(value, &seq))
param = parse_pdl_param(&seq, proto);
return param;
}
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;
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;
}