diff options
Diffstat (limited to 'usr.sbin/bluetooth')
159 files changed, 35859 insertions, 0 deletions
diff --git a/usr.sbin/bluetooth/Makefile b/usr.sbin/bluetooth/Makefile new file mode 100644 index 000000000000..5afc12450194 --- /dev/null +++ b/usr.sbin/bluetooth/Makefile @@ -0,0 +1,26 @@ +# $Id: Makefile,v 1.5 2003/09/08 02:28:35 max Exp $ + +.include <src.opts.mk> + +SUBDIR= \ + bluetooth-config \ + btpand \ + hccontrol \ + hcsecd \ + l2control \ + l2ping \ + rfcomm_pppd \ + sdpcontrol \ + sdpd + +.if ${MK_USB} != "no" +SUBDIR+= ath3kfw +SUBDIR+= bcmfw +SUBDIR+= bthidcontrol +SUBDIR+= bthidd +SUBDIR+= iwmbtfw +SUBDIR+= rtlbtfw +.endif + +.include <bsd.subdir.mk> + diff --git a/usr.sbin/bluetooth/Makefile.inc b/usr.sbin/bluetooth/Makefile.inc new file mode 100644 index 000000000000..e4fa9787eb5f --- /dev/null +++ b/usr.sbin/bluetooth/Makefile.inc @@ -0,0 +1 @@ +.include "${.CURDIR:H:H}/Makefile.inc" diff --git a/usr.sbin/bluetooth/ath3kfw/Makefile b/usr.sbin/bluetooth/ath3kfw/Makefile new file mode 100644 index 000000000000..39dcde33df80 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/Makefile @@ -0,0 +1,7 @@ +PACKAGE= bluetooth +PROG= ath3kfw +MAN= ath3kfw.8 +LIBADD+= usb +SRCS= main.c ath3k_fw.c ath3k_hw.c + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/ath3kfw/Makefile.depend b/usr.sbin/bluetooth/ath3kfw/Makefile.depend new file mode 100644 index 000000000000..34fbfadfcfb6 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libusb \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_dbg.h b/usr.sbin/bluetooth/ath3kfw/ath3k_dbg.h new file mode 100644 index 000000000000..06b0b3c58515 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_dbg.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * 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, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + */ +#ifndef __ATH3K_DEBUG_H__ +#define __ATH3K_DEBUG_H__ + +extern int ath3k_do_debug; +extern int ath3k_do_info; + +#define ath3k_debug(...) if (ath3k_do_debug) fprintf(stderr, __VA_ARGS__) +#define ath3k_err(...) fprintf(stderr, __VA_ARGS__) +#define ath3k_info(...) if (ath3k_do_info) fprintf(stdout, __VA_ARGS__) + +#endif diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_fw.c b/usr.sbin/bluetooth/ath3kfw/ath3k_fw.c new file mode 100644 index 000000000000..d67006760098 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_fw.c @@ -0,0 +1,110 @@ +/*- + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * 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, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <err.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "ath3k_fw.h" +#include "ath3k_dbg.h" + +int +ath3k_fw_read(struct ath3k_firmware *fw, const char *fwname) +{ + int fd; + struct stat sb; + unsigned char *buf; + ssize_t r; + + fd = open(fwname, O_RDONLY); + if (fd < 0) { + warn("%s: open: %s", __func__, fwname); + return (0); + } + + if (fstat(fd, &sb) != 0) { + warn("%s: stat: %s", __func__, fwname); + close(fd); + return (0); + } + + buf = calloc(1, sb.st_size); + if (buf == NULL) { + warn("%s: calloc", __func__); + close(fd); + return (0); + } + + /* XXX handle partial reads */ + r = read(fd, buf, sb.st_size); + if (r < 0) { + warn("%s: read", __func__); + free(buf); + close(fd); + return (0); + } + + if (r != sb.st_size) { + fprintf(stderr, "%s: read len %d != file size %d\n", + __func__, + (int) r, + (int) sb.st_size); + free(buf); + close(fd); + return (0); + } + + /* We have everything, so! */ + + bzero(fw, sizeof(*fw)); + + fw->fwname = strdup(fwname); + fw->len = sb.st_size; + fw->size = sb.st_size; + fw->buf = buf; + + close(fd); + return (1); +} + +void +ath3k_fw_free(struct ath3k_firmware *fw) +{ + if (fw->fwname) + free(fw->fwname); + if (fw->buf) + free(fw->buf); + bzero(fw, sizeof(*fw)); +} diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_fw.h b/usr.sbin/bluetooth/ath3kfw/ath3k_fw.h new file mode 100644 index 000000000000..f9d2c53e883d --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_fw.h @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * 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, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + */ +#ifndef __ATH3K_FW_H__ +#define __ATH3K_FW_H__ + +/* + * XXX TODO: ensure that the endian-ness of this stuff is + * correct! + */ +struct ath3k_version { + unsigned int rom_version; + unsigned int build_version; + unsigned int ram_version; + unsigned char ref_clock; + unsigned char reserved[0x07]; +}; + +struct ath3k_firmware { + char *fwname; + int len; /* firmware length */ + int size; /* buffer size */ + unsigned char *buf; +}; + +extern int ath3k_fw_read(struct ath3k_firmware *fw, const char *fwname); +extern void ath3k_fw_free(struct ath3k_firmware *fw); + +#endif diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c new file mode 100644 index 000000000000..cb32059e61d5 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c @@ -0,0 +1,356 @@ +/*- + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * 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, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <err.h> +#include <fcntl.h> +#include <sys/endian.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <libusb.h> + +#include "ath3k_fw.h" +#include "ath3k_hw.h" +#include "ath3k_dbg.h" + +#define XMIN(x, y) ((x) < (y) ? (x) : (y)) + +int +ath3k_load_fwfile(struct libusb_device_handle *hdl, + const struct ath3k_firmware *fw) +{ + int size, count, sent = 0; + int ret, r; + + count = fw->len; + + size = XMIN(count, FW_HDR_SIZE); + + ath3k_debug("%s: file=%s, size=%d\n", + __func__, fw->fwname, count); + + /* + * Flip the device over to configuration mode. + */ + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, + ATH3K_DNLOAD, + 0, + 0, + fw->buf + sent, + size, + 1000); /* XXX timeout */ + + if (ret != size) { + fprintf(stderr, "Can't switch to config mode; ret=%d\n", + ret); + return (-1); + } + + sent += size; + count -= size; + + /* Load in the rest of the data */ + while (count) { + size = XMIN(count, BULK_SIZE); + ath3k_debug("%s: transferring %d bytes, offset %d\n", + __func__, + sent, + size); + + ret = libusb_bulk_transfer(hdl, + 0x2, + fw->buf + sent, + size, + &r, + 1000); + + if (ret < 0 || r != size) { + fprintf(stderr, "Can't load firmware: err=%s, size=%d\n", + libusb_strerror(ret), + size); + return (-1); + } + sent += size; + count -= size; + } + return (0); +} + +int +ath3k_get_state(struct libusb_device_handle *hdl, unsigned char *state) +{ + int ret; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + ATH3K_GETSTATE, + 0, + 0, + state, + 1, + 1000); /* XXX timeout */ + + if (ret < 0) { + fprintf(stderr, + "%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 1); +} + +int +ath3k_get_version(struct libusb_device_handle *hdl, + struct ath3k_version *version) +{ + int ret; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + ATH3K_GETVERSION, + 0, + 0, + (unsigned char *) version, + sizeof(struct ath3k_version), + 1000); /* XXX timeout */ + + if (ret < 0) { + fprintf(stderr, + "%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + /* XXX endian fix! */ + + return (ret == sizeof(struct ath3k_version)); +} + +int +ath3k_load_patch(libusb_device_handle *hdl, const char *fw_path) +{ + int ret; + unsigned char fw_state; + struct ath3k_version fw_ver, pt_ver; + char fwname[FILENAME_MAX]; + struct ath3k_firmware fw; + uint32_t tmp; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("%s: Can't get state\n", __func__); + return (ret); + } + + if (fw_state & ATH3K_PATCH_UPDATE) { + ath3k_info("%s: Patch already downloaded\n", + __func__); + return (0); + } + + ret = ath3k_get_version(hdl, &fw_ver); + if (ret < 0) { + ath3k_debug("%s: Can't get version\n", __func__); + return (ret); + } + + /* XXX path info? */ + snprintf(fwname, FILENAME_MAX, "%s/ar3k/AthrBT_0x%08x.dfu", + fw_path, + fw_ver.rom_version); + + /* Read in the firmware */ + if (ath3k_fw_read(&fw, fwname) <= 0) { + ath3k_debug("%s: ath3k_fw_read() failed\n", + __func__); + return (-1); + } + + /* + * Extract the ROM/build version from the patch file. + */ + memcpy(&tmp, fw.buf + fw.len - 8, sizeof(tmp)); + pt_ver.rom_version = le32toh(tmp); + memcpy(&tmp, fw.buf + fw.len - 4, sizeof(tmp)); + pt_ver.build_version = le32toh(tmp); + + ath3k_info("%s: file %s: rom_ver=%d, build_ver=%d\n", + __func__, + fwname, + (int) pt_ver.rom_version, + (int) pt_ver.build_version); + + /* Check the ROM/build version against the firmware */ + if ((pt_ver.rom_version != fw_ver.rom_version) || + (pt_ver.build_version <= fw_ver.build_version)) { + ath3k_debug("Patch file version mismatch!\n"); + ath3k_fw_free(&fw); + return (-1); + } + + /* Load in the firmware */ + ret = ath3k_load_fwfile(hdl, &fw); + + /* free it */ + ath3k_fw_free(&fw); + + return (ret); +} + +int +ath3k_load_syscfg(libusb_device_handle *hdl, const char *fw_path) +{ + unsigned char fw_state; + char filename[FILENAME_MAX]; + struct ath3k_firmware fw; + struct ath3k_version fw_ver; + int clk_value, ret; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("Can't get state to change to load configuration err"); + return (-EBUSY); + } + + ret = ath3k_get_version(hdl, &fw_ver); + if (ret < 0) { + ath3k_err("Can't get version to change to load ram patch err"); + return (ret); + } + + switch (fw_ver.ref_clock) { + case ATH3K_XTAL_FREQ_26M: + clk_value = 26; + break; + case ATH3K_XTAL_FREQ_40M: + clk_value = 40; + break; + case ATH3K_XTAL_FREQ_19P2: + clk_value = 19; + break; + default: + clk_value = 0; + break; +} + + snprintf(filename, FILENAME_MAX, "%s/ar3k/ramps_0x%08x_%d%s", + fw_path, + fw_ver.rom_version, + clk_value, + ".dfu"); + + ath3k_info("%s: syscfg file = %s\n", + __func__, + filename); + + /* Read in the firmware */ + if (ath3k_fw_read(&fw, filename) <= 0) { + ath3k_err("%s: ath3k_fw_read() failed\n", + __func__); + return (-1); + } + + ret = ath3k_load_fwfile(hdl, &fw); + + ath3k_fw_free(&fw); + return (ret); +} + +int +ath3k_set_normal_mode(libusb_device_handle *hdl) +{ + int ret; + unsigned char fw_state; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("%s: can't get state\n", __func__); + return (ret); + } + + /* + * This isn't a fatal error - the device may have detached + * already. + */ + if ((fw_state & ATH3K_MODE_MASK) == ATH3K_NORMAL_MODE) { + ath3k_debug("%s: firmware is already in normal mode\n", + __func__); + return (0); + } + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR, /* XXX out direction? */ + ATH3K_SET_NORMAL_MODE, + 0, + 0, + NULL, + 0, + 1000); /* XXX timeout */ + + if (ret < 0) { + ath3k_err("%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 0); +} + +int +ath3k_switch_pid(libusb_device_handle *hdl) +{ + int ret; + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR, /* XXX set an out flag? */ + USB_REG_SWITCH_VID_PID, + 0, + 0, + NULL, + 0, + 1000); /* XXX timeout */ + + if (ret < 0) { + ath3k_debug("%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 0); +} diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_hw.h b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.h new file mode 100644 index 000000000000..b4d8fc3e462b --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.h @@ -0,0 +1,64 @@ +/*- + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * 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, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + */ +#ifndef __ATH3K_HW_H__ +#define __ATH3K_HW_H__ + +#define ATH3K_DNLOAD 0x01 +#define ATH3K_GETSTATE 0x05 +#define ATH3K_SET_NORMAL_MODE 0x07 +#define ATH3K_GETVERSION 0x09 +#define USB_REG_SWITCH_VID_PID 0x0a + +#define ATH3K_MODE_MASK 0x3F +#define ATH3K_NORMAL_MODE 0x0E + +#define ATH3K_PATCH_UPDATE 0x80 +#define ATH3K_SYSCFG_UPDATE 0x40 + +#define ATH3K_XTAL_FREQ_26M 0x00 +#define ATH3K_XTAL_FREQ_40M 0x01 +#define ATH3K_XTAL_FREQ_19P2 0x02 +#define ATH3K_NAME_LEN 0xFF + +#define USB_REQ_DFU_DNLOAD 1 +#define BULK_SIZE 4096 +#define FW_HDR_SIZE 20 + +extern int ath3k_load_fwfile(struct libusb_device_handle *hdl, + const struct ath3k_firmware *fw); +extern int ath3k_get_state(struct libusb_device_handle *hdl, + unsigned char *state); +extern int ath3k_get_version(struct libusb_device_handle *hdl, + struct ath3k_version *version); +extern int ath3k_load_patch(libusb_device_handle *hdl, const char *fw_path); +extern int ath3k_load_syscfg(libusb_device_handle *hdl, const char *fw_path); +extern int ath3k_set_normal_mode(libusb_device_handle *hdl); +extern int ath3k_switch_pid(libusb_device_handle *hdl); + +#endif diff --git a/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 b/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 new file mode 100644 index 000000000000..9e54445a012b --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 @@ -0,0 +1,92 @@ +.\"- +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2010 Maksim Yevmenkin <m_evmenkin@yahoo.com> +.\" Copyright (c) 2013, 2016 Adrian Chadd <adrian@freebsd.org> +.\" 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. +.\" +.Dd July 15, 2025 +.Dt ATH3KFW 8 +.Os +.Sh NAME +.Nm ath3kfw +.Nd load firmware for Atheros AR3011/AR3012 Bluetooth USB devices +.Sh SYNOPSIS +.Nm +.Op Fl DI +.Fl d Ar device_name +.Fl f Ar firmware_path +.Nm +.Fl h +.Sh DESCRIPTION +The +.Nm +utility downloads the specified firmware file to the specified +.Xr ugen 4 +device. +.Pp +This utility will +.Em only +work with Atheros AR3011 and AR3012 chip based Bluetooth USB devices. +The identification is currently based on USB vendor ID/product ID pair. +The vendor ID should be 0x0cf3 +.Pq Dv USB_VENDOR_ATHEROS2 +and the product ID should be one of the supported devices. +.Pp +Firmware files are available in the linux-firmware RPM. +.Pp +The +.Nm +utility will query the device to determine which firmware image and board +configuration to load in at runtime. +.Pp +The options are as follows: +.Bl -tag -width "-f firmware_path" +.It Fl D +Enable verbose debugging. +.It Fl d Ar device_name +Specify +.Xr ugen 4 +device name. +.It Fl f Ar firmware_path +Specify the directory containing the firmware files to search and upload. +.It Fl h +Display usage message and exit. +.It Fl I +Enable informational debugging. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr libusb 3 , +.Xr ugen 4 , +.Xr devd 8 +.Sh AUTHORS +The original utility was written by +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com . +This was written based on Linux ath3k by +.An Adrian Chadd Aq Mt adrian@freebsd.org . +.Sh BUGS +Most likely. +Please report if found. diff --git a/usr.sbin/bluetooth/ath3kfw/main.c b/usr.sbin/bluetooth/ath3kfw/main.c new file mode 100644 index 000000000000..c04ce2baafd1 --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/main.c @@ -0,0 +1,392 @@ +/*- + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * 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, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <err.h> +#include <fcntl.h> +#include <libgen.h> +#include <sys/stat.h> +#include <sys/param.h> + +#include <libusb.h> + +#include "ath3k_fw.h" +#include "ath3k_hw.h" +#include "ath3k_dbg.h" + +#define _DEFAULT_ATH3K_FIRMWARE_PATH "/usr/share/firmware/ath3k/" + +int ath3k_do_debug = 0; +int ath3k_do_info = 0; + +struct ath3k_devid { + uint16_t product_id; + uint16_t vendor_id; + int is_3012; +}; + +static struct ath3k_devid ath3k_list[] = { + + /* Atheros AR3012 with sflash firmware*/ + { .vendor_id = 0x0489, .product_id = 0xe04e, .is_3012 = 1 }, + { .vendor_id = 0x0489, .product_id = 0xe04d, .is_3012 = 1 }, + { .vendor_id = 0x0489, .product_id = 0xe056, .is_3012 = 1 }, + { .vendor_id = 0x0489, .product_id = 0xe057, .is_3012 = 1 }, + { .vendor_id = 0x0489, .product_id = 0xe05f, .is_3012 = 1 }, + { .vendor_id = 0x04c5, .product_id = 0x1330, .is_3012 = 1 }, + { .vendor_id = 0x04ca, .product_id = 0x3004, .is_3012 = 1 }, + { .vendor_id = 0x04ca, .product_id = 0x3005, .is_3012 = 1 }, + { .vendor_id = 0x04ca, .product_id = 0x3006, .is_3012 = 1 }, + { .vendor_id = 0x04ca, .product_id = 0x3008, .is_3012 = 1 }, + { .vendor_id = 0x04ca, .product_id = 0x300b, .is_3012 = 1 }, + { .vendor_id = 0x0930, .product_id = 0x0219, .is_3012 = 1 }, + { .vendor_id = 0x0930, .product_id = 0x0220, .is_3012 = 1 }, + { .vendor_id = 0x0b05, .product_id = 0x17d0, .is_3012 = 1 }, + { .vendor_id = 0x0CF3, .product_id = 0x0036, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0x3004, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0x3005, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0x3008, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0x311D, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0x311E, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0x311F, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0x3121, .is_3012 = 1 }, + { .vendor_id = 0x0CF3, .product_id = 0x817a, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0xe004, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0xe005, .is_3012 = 1 }, + { .vendor_id = 0x0cf3, .product_id = 0xe003, .is_3012 = 1 }, + { .vendor_id = 0x13d3, .product_id = 0x3362, .is_3012 = 1 }, + { .vendor_id = 0x13d3, .product_id = 0x3375, .is_3012 = 1 }, + { .vendor_id = 0x13d3, .product_id = 0x3393, .is_3012 = 1 }, + { .vendor_id = 0x13d3, .product_id = 0x3402, .is_3012 = 1 }, + + /* Atheros AR5BBU22 with sflash firmware */ + { .vendor_id = 0x0489, .product_id = 0xE036, .is_3012 = 1 }, + { .vendor_id = 0x0489, .product_id = 0xE03C, .is_3012 = 1 }, +}; + +static int +ath3k_is_3012(struct libusb_device_descriptor *d) +{ + int i; + + /* Search looking for whether it's an AR3012 */ + for (i = 0; i < (int) nitems(ath3k_list); i++) { + if ((ath3k_list[i].product_id == d->idProduct) && + (ath3k_list[i].vendor_id == d->idVendor)) { + fprintf(stderr, "%s: found AR3012\n", __func__); + return (ath3k_list[i].is_3012); + } + } + + /* Not found */ + return (0); +} + +static libusb_device * +ath3k_find_device(libusb_context *ctx, int bus_id, int dev_id) +{ + libusb_device **list, *dev = NULL, *found = NULL; + ssize_t cnt, i; + + cnt = libusb_get_device_list(ctx, &list); + if (cnt < 0) { + ath3k_err("%s: libusb_get_device_list() failed: code %lld\n", + __func__, + (long long int) cnt); + return (NULL); + } + + /* + * XXX TODO: match on the vendor/product id too! + */ + for (i = 0; i < cnt; i++) { + dev = list[i]; + if (bus_id == libusb_get_bus_number(dev) && + dev_id == libusb_get_device_address(dev)) { + /* + * Take a reference so it's not freed later on. + */ + found = libusb_ref_device(dev); + break; + } + } + + libusb_free_device_list(list, 1); + return (found); +} + +static int +ath3k_init_ar3012(libusb_device_handle *hdl, const char *fw_path) +{ + int ret; + + ret = ath3k_load_patch(hdl, fw_path); + if (ret < 0) { + ath3k_err("Loading patch file failed\n"); + return (ret); + } + + ret = ath3k_load_syscfg(hdl, fw_path); + if (ret < 0) { + ath3k_err("Loading sysconfig file failed\n"); + return (ret); + } + + ret = ath3k_set_normal_mode(hdl); + if (ret < 0) { + ath3k_err("Set normal mode failed\n"); + return (ret); + } + + ath3k_switch_pid(hdl); + return (0); +} + +static int +ath3k_init_firmware(libusb_device_handle *hdl, const char *file_prefix) +{ + struct ath3k_firmware fw; + char fwname[FILENAME_MAX]; + int ret; + + /* XXX path info? */ + snprintf(fwname, FILENAME_MAX, "%s/ath3k-1.fw", file_prefix); + + ath3k_debug("%s: loading ath3k-1.fw\n", __func__); + + /* Read in the firmware */ + if (ath3k_fw_read(&fw, fwname) <= 0) { + fprintf(stderr, "%s: ath3k_fw_read() failed\n", + __func__); + return (-1); + } + + /* Load in the firmware */ + ret = ath3k_load_fwfile(hdl, &fw); + + /* free it */ + ath3k_fw_free(&fw); + + return (ret); +} + +/* + * Parse ugen name and extract device's bus and address + */ + +static int +parse_ugen_name(char const *ugen, uint8_t *bus, uint8_t *addr) +{ + char *ep; + + if (strncmp(ugen, "ugen", 4) != 0) + return (-1); + + *bus = (uint8_t) strtoul(ugen + 4, &ep, 10); + if (*ep != '.') + return (-1); + + *addr = (uint8_t) strtoul(ep + 1, &ep, 10); + if (*ep != '\0') + return (-1); + + return (0); +} + +static void +usage(void) +{ + fprintf(stderr, + "Usage: ath3kfw (-D) -d ugenX.Y (-f firmware path) (-I)\n"); + fprintf(stderr, " -D: enable debugging\n"); + fprintf(stderr, " -d: device to operate upon\n"); + fprintf(stderr, " -f: firmware path, if not default\n"); + fprintf(stderr, " -I: enable informational output\n"); + exit(127); +} + +int +main(int argc, char *argv[]) +{ + struct libusb_device_descriptor d; + libusb_context *ctx; + libusb_device *dev; + libusb_device_handle *hdl; + unsigned char state; + struct ath3k_version ver; + int r; + uint8_t bus_id = 0, dev_id = 0; + int devid_set = 0; + int n; + char *firmware_path = NULL; + int is_3012 = 0; + + /* libusb setup */ + r = libusb_init(&ctx); + if (r != 0) { + ath3k_err("%s: libusb_init failed: code %d\n", + argv[0], + r); + exit(127); + } + + /* Enable debugging, just because */ + libusb_set_debug(ctx, 3); + + /* Parse command line arguments */ + while ((n = getopt(argc, argv, "Dd:f:hIm:p:v:")) != -1) { + switch (n) { + case 'd': /* ugen device name */ + devid_set = 1; + if (parse_ugen_name(optarg, &bus_id, &dev_id) < 0) + usage(); + break; + case 'D': + ath3k_do_debug = 1; + break; + case 'f': /* firmware path */ + if (firmware_path) + free(firmware_path); + firmware_path = strdup(optarg); + break; + case 'I': + ath3k_do_info = 1; + break; + case 'h': + default: + usage(); + break; + /* NOT REACHED */ + } + } + + /* Ensure the devid was given! */ + if (devid_set == 0) { + usage(); + /* NOTREACHED */ + } + + ath3k_debug("%s: opening dev %d.%d\n", + basename(argv[0]), + (int) bus_id, + (int) dev_id); + + /* Find a device based on the bus/dev id */ + dev = ath3k_find_device(ctx, bus_id, dev_id); + if (dev == NULL) { + ath3k_err("%s: device not found\n", __func__); + /* XXX cleanup? */ + exit(1); + } + + /* Get the device descriptor for this device entry */ + r = libusb_get_device_descriptor(dev, &d); + if (r != 0) { + warn("%s: libusb_get_device_descriptor: %s\n", + __func__, + libusb_strerror(r)); + exit(1); + } + + /* See if its an AR3012 */ + if (ath3k_is_3012(&d)) { + is_3012 = 1; + + /* If it's bcdDevice > 1, don't attach */ + if (d.bcdDevice > 0x0001) { + ath3k_debug("%s: AR3012; bcdDevice=%d, exiting\n", + __func__, + d.bcdDevice); + exit(0); + } + } + + /* XXX enforce that bInterfaceNumber is 0 */ + + /* XXX enforce the device/product id if they're non-zero */ + + /* Grab device handle */ + r = libusb_open(dev, &hdl); + if (r != 0) { + ath3k_err("%s: libusb_open() failed: code %d\n", __func__, r); + /* XXX cleanup? */ + exit(1); + } + + /* + * Get the initial NIC state. + */ + r = ath3k_get_state(hdl, &state); + if (r == 0) { + ath3k_err("%s: ath3k_get_state() failed!\n", __func__); + /* XXX cleanup? */ + exit(1); + } + ath3k_debug("%s: state=0x%02x\n", + __func__, + (int) state); + + /* And the version */ + r = ath3k_get_version(hdl, &ver); + if (r == 0) { + ath3k_err("%s: ath3k_get_version() failed!\n", __func__); + /* XXX cleanup? */ + exit(1); + } + ath3k_info("ROM version: %d, build version: %d, ram version: %d, " + "ref clock=%d\n", + ver.rom_version, + ver.build_version, + ver.ram_version, + ver.ref_clock); + + /* Default the firmware path */ + if (firmware_path == NULL) + firmware_path = strdup(_DEFAULT_ATH3K_FIRMWARE_PATH); + + if (is_3012) { + (void) ath3k_init_ar3012(hdl, firmware_path); + } else { + (void) ath3k_init_firmware(hdl, firmware_path); + } + + /* Shutdown */ + libusb_close(hdl); + hdl = NULL; + + libusb_unref_device(dev); + dev = NULL; + + libusb_exit(ctx); + ctx = NULL; +} diff --git a/usr.sbin/bluetooth/bcmfw/BCM-LEGAL.txt b/usr.sbin/bluetooth/bcmfw/BCM-LEGAL.txt new file mode 100644 index 000000000000..02ff4badbbdf --- /dev/null +++ b/usr.sbin/bluetooth/bcmfw/BCM-LEGAL.txt @@ -0,0 +1,7 @@ + +BCM firmware version 2.15 +Copyright (c) 2000-2002 Broadcom Corporation + +Permission to distribute from.. +Contact info: + bluetooth_help@broadcom.com diff --git a/usr.sbin/bluetooth/bcmfw/Makefile b/usr.sbin/bluetooth/bcmfw/Makefile new file mode 100644 index 000000000000..9d186d9f8869 --- /dev/null +++ b/usr.sbin/bluetooth/bcmfw/Makefile @@ -0,0 +1,8 @@ +# $Id: Makefile,v 1.6 2003/08/14 20:05:58 max Exp $ + +PACKAGE= bluetooth +PROG= bcmfw +MAN= bcmfw.8 +WARNS?= 2 + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/bcmfw/Makefile.depend b/usr.sbin/bluetooth/bcmfw/Makefile.depend new file mode 100644 index 000000000000..208c47654297 --- /dev/null +++ b/usr.sbin/bluetooth/bcmfw/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libnetgraph \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/bcmfw/README b/usr.sbin/bluetooth/bcmfw/README new file mode 100644 index 000000000000..cfaf4781d419 --- /dev/null +++ b/usr.sbin/bluetooth/bcmfw/README @@ -0,0 +1,21 @@ + +This directory will eventually also contain copies of the broadcom firmware. + +It WILL look like: +total 190 +drwxr-xr-x 3 julian wheel 512 May 10 00:40 . +drwxr-xr-x 11 julian wheel 512 May 10 14:48 .. +-rw-r--r-- 1 julian wheel 154 May 10 00:41 BCM-LEGAL.txt +-rw-r--r-- 1 julian wheel 56 May 10 00:14 BCM2033-FW.bin.md5 +-rw-r--r-- 1 julian wheel 158049 May 10 00:14 BCM2033-FW.bin.uue +-rw-r--r-- 1 julian wheel 56 May 10 00:14 BCM2033-MD.hex.md5 +-rw-r--r-- 1 julian wheel 4505 May 10 00:14 BCM2033-MD.hex.uue +drwxr-xr-x 2 julian wheel 512 May 10 00:52 CVS +-rw-r--r-- 1 julian wheel 516 May 10 00:14 Makefile +-rw-r--r-- 1 julian wheel 3013 May 10 00:14 bcmfw.8 +-rw-r--r-- 1 julian wheel 6806 May 10 00:14 bcmfw.c + +Until then, the firmware files can be fetched as part of + http://bluez.sourceforge.net/download/bluez-bluefw-0.9.tar.gz + + diff --git a/usr.sbin/bluetooth/bcmfw/bcmfw.8 b/usr.sbin/bluetooth/bcmfw/bcmfw.8 new file mode 100644 index 000000000000..28f0cbbafe00 --- /dev/null +++ b/usr.sbin/bluetooth/bcmfw/bcmfw.8 @@ -0,0 +1,105 @@ +.\"- +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2003 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. +.\" +.Dd July 15, 2025 +.Dt BCMFW 8 +.Os +.Sh NAME +.Nm bcmfw +.Nd load firmware for Broadcom BCM2033 Bluetooth USB devices +.Sh SYNOPSIS +.Nm +.Op Fl h +.Fl f Ar firmware_file_name +.Fl m Ar mini-driver_file_name +.Fl n Ar device_name +.Sh DESCRIPTION +The +.Nm +utility downloads the specified mini-driver and firmware files to the specified +device. +.Pp +This utility will +.Em only +work with Broadcom BCM2033 chip based Bluetooth USB devices. +The identification is currently based on USB vendor ID/product ID pair. +The vendor ID should be 0x0a5c +.Pq Dv USB_VENDOR_BROADCOM +and the product ID should be 0x2033. +.Pp +Due to copyright issues I will no longer provide mini-driver and firmware +files for the device. +These files can be obtained from the Linux BlueZ bluez-firmware package. +.Pp +Visit +.Pa http://www.bluez.org/download.html +for details. +.Pp +I am using the following files from the bluez-firmware-1.0 package: +.Pp +.Dl "MD5 (BCM2033-MD.hex) = 5580317158d07fc4ace90af04f8e1c73" +.Dl "MD5 (BCM2033-FW.bin) = b4e142b3272cfe5a84b32fda6b4b032f" +.Pp +The options are as follows: +.Bl -tag -width "-m mini-driver_file_name" +.It Fl f Ar firmware_file_name +Specify firmware file name for download. +.It Fl h +Display usage message and exit. +.It Fl m Ar mini-driver_file_name +Specify mini-driver file name for download. +.It Fl n Ar device_name +Specify device name. +.El +.Sh FILES +.Bl -tag -width "-m mini-driver_file_name" -compact +.It Pa BCM2033-MD.hex +Mini-driver image. +.It Pa BCM2033-FW.bin +Firmware image. +.It Pa /dev/ubtbcmfw Ns Ar N Ns Pa \&. Ns Ar EE +Endpoint +.Ar EE +of device +.Ar N . +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +To download the firmware into the +.Pa /dev/ubtbcmfw0 +device: +.Pp +.Dl "bcmfw -n ubtbcmfw0 -m BCM2033-MD.hex -f BCM2033-FW.bin" +.Sh SEE ALSO +.Xr ubtbcmfw 4 , +.Xr ugen 4 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +.Sh BUGS +Most likely. +Please report if found. diff --git a/usr.sbin/bluetooth/bcmfw/bcmfw.c b/usr.sbin/bluetooth/bcmfw/bcmfw.c new file mode 100644 index 000000000000..1361c7fe6d51 --- /dev/null +++ b/usr.sbin/bluetooth/bcmfw/bcmfw.c @@ -0,0 +1,309 @@ +/*- + * bcmfw.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2003 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. + * + * $Id: bcmfw.c,v 1.4 2003/04/27 19:28:09 max Exp $ + * + * Based on Linux BlueZ BlueFW-0.9 package + * + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <dev/usb/usb.h> +#include <dev/usb/usb_ioctl.h> +#include <errno.h> +#include <fcntl.h> +#include <netgraph.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#define BCMFW "bcmfw" +#define BCMFW_INTR_EP 1 +#define BCMFW_BULK_EP 2 +#define BCMFW_BSIZE 4096 + +#define USB_VENDOR_BROADCOM 0x0a5c +#define USB_PRODUCT_BROADCOM_BCM2033 0x2033 + +static int bcmfw_check_device + (char const *name); +static int bcmfw_load_firmware + (char const *name, char const *md, char const *fw); +static void bcmfw_usage + (void); + +/* + * Main + */ + +int +main(int argc, char *argv[]) +{ + char *name = NULL, *md = NULL, *fw = NULL; + int x; + + while ((x = getopt(argc, argv, "f:hn:m:")) != -1) { + switch (x) { + case 'f': /* firmware file */ + fw = optarg; + break; + + case 'n': /* name */ + name = optarg; + break; + + case 'm': /* Mini-driver */ + md = optarg; + break; + + case 'h': + default: + bcmfw_usage(); + /* NOT REACHED */ + } + } + + if (name == NULL || md == NULL || fw == NULL) + bcmfw_usage(); + /* NOT REACHED */ + + openlog(BCMFW, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_USER); + + if (bcmfw_check_device(name) < 0) + exit(1); + + if (bcmfw_load_firmware(name, md, fw) < 0) + exit(1); + + closelog(); + + return (0); +} /* main */ + +/* + * Check device VendorID/ProductID + */ + +static int +bcmfw_check_device(char const *name) +{ + usb_device_descriptor_t desc; + char path[BCMFW_BSIZE]; + int fd = -1, error = -1; + + snprintf(path, sizeof(path), "/dev/%s", name); + + if ((fd = open(path, O_WRONLY)) < 0) { + syslog(LOG_ERR, "Could not open(%s). %s (%d)", + path, strerror(errno), errno); + goto out; + } + + if (ioctl(fd, USB_GET_DEVICE_DESC, &desc) < 0) { + syslog(LOG_ERR, "Could not ioctl(%d, %ld, %p). %s (%d)", + fd, USB_GET_DEVICE_DESC, &desc, + strerror(errno), errno); + goto out; + } + + if (UGETW(desc.idVendor) != USB_VENDOR_BROADCOM || + UGETW(desc.idProduct) != USB_PRODUCT_BROADCOM_BCM2033) { + syslog(LOG_ERR, "Unsupported device, VendorID=%#x, " \ + "ProductID=%#x", UGETW(desc.idVendor), + UGETW(desc.idProduct)); + error = -1; + } else + error = 0; +out: + if (fd != -1) + close(fd); + + return (error); +} /* bcmfw_check_device */ + +/* + * Download minidriver and firmware + */ + +static int +bcmfw_load_firmware(char const *name, char const *md, char const *fw) +{ + char buf[BCMFW_BSIZE]; + int intr = -1, bulk = -1, fd = -1, error = -1, len; + + /* Open interrupt endpoint device */ + snprintf(buf, sizeof(buf), "/dev/%s.%d", name, BCMFW_INTR_EP); + if ((intr = open(buf, O_RDONLY)) < 0) { + syslog(LOG_ERR, "Could not open(%s). %s (%d)", + buf, strerror(errno), errno); + goto out; + } + + /* Open bulk endpoint device */ + snprintf(buf, sizeof(buf), "/dev/%s.%d", name, BCMFW_BULK_EP); + if ((bulk = open(buf, O_WRONLY)) < 0) { + syslog(LOG_ERR, "Could not open(%s). %s (%d)", + buf, strerror(errno), errno); + goto out; + } + + /* + * Load mini-driver + */ + + if ((fd = open(md, O_RDONLY)) < 0) { + syslog(LOG_ERR, "Could not open(%s). %s (%d)", + md, strerror(errno), errno); + goto out; + } + + for (;;) { + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + syslog(LOG_ERR, "Could not read(%s). %s (%d)", + md, strerror(errno), errno); + goto out; + } + if (len == 0) + break; + + len = write(bulk, buf, len); + if (len < 0) { + syslog(LOG_ERR, "Could not write(/dev/%s.%d). %s (%d)", + name, BCMFW_BULK_EP, strerror(errno), + errno); + goto out; + } + } + + close(fd); + fd = -1; + + usleep(10); + + /* + * Memory select + */ + + if (write(bulk, "#", 1) < 0) { + syslog(LOG_ERR, "Could not write(/dev/%s.%d). %s (%d)", + name, BCMFW_BULK_EP, strerror(errno), errno); + goto out; + } + + if (read(intr, buf, sizeof(buf)) < 0) { + syslog(LOG_ERR, "Could not read(/dev/%s.%d). %s (%d)", + name, BCMFW_INTR_EP, strerror(errno), errno); + goto out; + } + + if (buf[0] != '#') { + syslog(LOG_ERR, "%s: Memory select failed (%c)", name, buf[0]); + goto out; + } + + /* + * Load firmware + */ + + if ((fd = open(fw, O_RDONLY)) < 0) { + syslog(LOG_ERR, "Could not open(%s). %s (%d)", + fw, strerror(errno), errno); + goto out; + } + + for (;;) { + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + syslog(LOG_ERR, "Could not read(%s). %s (%d)", + fw, strerror(errno), errno); + goto out; + } + if (len == 0) + break; + + len = write(bulk, buf, len); + if (len < 0) { + syslog(LOG_ERR, "Could not write(/dev/%s.%d). %s (%d)", + name, BCMFW_BULK_EP, strerror(errno), + errno); + goto out; + } + } + + close(fd); + fd = -1; + + if (read(intr, buf, sizeof(buf)) < 0) { + syslog(LOG_ERR, "Could not read(/dev/%s.%d). %s (%d)", + name, BCMFW_INTR_EP, strerror(errno), errno); + goto out; + } + + if (buf[0] != '.') { + syslog(LOG_ERR, "%s: Could not load firmware (%c)", + name, buf[0]); + goto out; + } + + usleep(500000); + error = 0; +out: + if (fd != -1) + close(fd); + if (bulk != -1) + close(bulk); + if (intr != -1) + close(intr); + + return (error); +} /* bcmfw_load_firmware */ + +/* + * Display usage message and quit + */ + +static void +bcmfw_usage(void) +{ + fprintf(stdout, +"Usage: %s -n name -m md_file -f fw_file\n" +"Where:\n" \ +"\t-n name device name\n" \ +"\t-m mini-driver image mini-driver image file name for download\n" \ +"\t-f firmware image firmware image file name for download\n" \ +"\t-h display this message\n", BCMFW); + + exit(255); +} /* bcmfw_usage */ + diff --git a/usr.sbin/bluetooth/bluetooth-config/Makefile b/usr.sbin/bluetooth/bluetooth-config/Makefile new file mode 100644 index 000000000000..435ac6546b19 --- /dev/null +++ b/usr.sbin/bluetooth/bluetooth-config/Makefile @@ -0,0 +1,5 @@ +PACKAGE= bluetooth +SCRIPTS=bluetooth-config.sh +MAN= bluetooth-config.8 + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/bluetooth-config/Makefile.depend b/usr.sbin/bluetooth/bluetooth-config/Makefile.depend new file mode 100644 index 000000000000..11aba52f82cf --- /dev/null +++ b/usr.sbin/bluetooth/bluetooth-config/Makefile.depend @@ -0,0 +1,10 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.8 b/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.8 new file mode 100644 index 000000000000..fcf40b392d06 --- /dev/null +++ b/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.8 @@ -0,0 +1,111 @@ +.\" Copyright (c) 2019 Dirk Engling +.\" 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. +.\" +.Dd January 7, 2019 +.Dt BLUETOOTH-CONFIG 8 +.Os +.Sh NAME +.Nm bluetooth-config +.Nd a script to manage config files for the bluetooth sub system +.Sh SYNOPSIS +.Nm +.Ar scan +.Op Fl d Ar device +.Op Fl n Ar node +.Sh DESCRIPTION +The +.Nm +utility is an interactive script to provide a frontend to the complex bluetooth sub system daemons. +.Pp +The following options are available: +.Bl -tag -width indent+ +.It Fl d +Scan for a specific bluetooth device address. +.It Fl n +Limit scan to a specific host controller. +Hint: List all netgraph nodes with +.Ql /usr/sbin/ngctl list . +.El +.Pp +.Nm +will help finding and setting up bluetooth controllers, scan for nearby bluetooth devices in +pairing mode, lookup their names, allow mapping to friendly names in +.Pa /etc/bluetooth/hosts , +ask for the paring PIN, instrument +.Xr hcsecd 8 +to securely pair with new devices and, if the device offers HID endpoints such as mice or +keyboards, configure and restart +.Xr bthidd 8 . +.Pp +.Nm +can bring up any interface and daemon necessary for operation and, if a node is provided on +command line, will do so automatically for that interface. +.Sh FILES +.Bl -tag -width ".Pa /etc/bluetooth/hosts" -compact +.It Pa /etc/bluetooth/hosts +.It Pa sysrc -n bthidd_config +.It Pa sysrc -n hcsecd_config +.El +.Sh EXAMPLES +.Nm +scan -n ubt0 -a 00:26:bb:7a:58:95 +.Bd -ragged -offset indent +This will scan the bluetooth controller ubt0hci for a bluetooth device with the address +00:26:bb:7a:58:95, set up ubt0 if necessary and enter an interactive dialog to pair the +new device. +Since in this example a mouse is paired, +.Nm +will interact with +.Xr bthidd 8 , +enabling it if necessary and then write an HID descriptor to its config. +.Ed +.Pp +.Nm +scan +.Bd -ragged -offset indent +This will scan all bluetooth controllers on the systems for bluetooth devices, prompting +to bring up controllers or daemons along the way. +.Ed +.Sh SEE ALSO +.Xr bthost 1 , +.Xr bthidcontrol 8 , +.Xr bthidd 8 , +.Xr hccontrol 8 , +.Xr hcsecd 8 , +.Xr sdpcontrol 8 , +.Xr sysrc 8 +.Sh HISTORY +A +.Nm +utility first appeared in +.Fx 12.1 . +.Sh AUTHORS +.An Dirk Engling Aq Mt erdgeist@erdgeist.org +.Sh CAVEATS +.Nm +can not parse entries in +.Xr hcsecd 8 +config file and thus will ask the user to manually modify existing pairing PIN entries. +.Sh THANKS TO +Lars Engels and Warren Block for suggestions, help, and testing. diff --git a/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh b/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh new file mode 100755 index 000000000000..148325fcecbc --- /dev/null +++ b/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh @@ -0,0 +1,316 @@ +#!/bin/sh +#- +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# <erdgeist@erdgeist.org> wrote this file. As long as you retain this notice you +# can do whatever you want with this stuff. If we meet some day, and you think +# this stuff is worth it, you can buy me a beer in return Poul-Henning Kamp +# ---------------------------------------------------------------------------- +# +# + +# define our bail out shortcut +exerr () { echo -e "Error: $*" >&2 ; exit 1; } +print_syntax () { echo -e "Syntax: $0 scan [-d device] [-n node]"; exit 1; } + +main() { +unset node device started bdaddresses retry + +# Only one command at the moment is scan (+ add) +[ "$1" = "scan" ] || print_syntax +shift + +# Get command line options +while getopts :d:n: arg; do + case ${arg} in + d) device="$OPTARG";; + n) node="$OPTARG";; + ?) print_syntax;; + esac +done +shift "$((OPTIND-1))" + +# If there's leftover parameters, print usage +[ "$#" -eq 0 ] || print_syntax +shift + + +# No use running without super user rights +if [ $( id -u ) -ne 0 ]; then + exerr "$0 must modify files that belong to root. Re-run as root." +fi + +known_nodes=$( /usr/sbin/hccontrol read_node_list 2>/dev/null |\ + /usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 ) + +# Check if netgraph knows about any HCI nodes +if ! [ "${known_nodes}" ]; then + ng_nodes=$( /usr/sbin/ngctl list 2>/dev/null | \ + /usr/bin/grep -o "Name: .* Type: ubt" |/usr/bin/cut -d' ' -f2 ) + + [ "${ng_nodes}" ] || exerr "No Bluetooth host controllers found." + + unset found + for n in ${ng_nodes}; do + if [ "${n}" = "${node%hci}" ]; then + # Found the node but its stack is not set up? Do it now. + /usr/sbin/service bluetooth start ${node%hci} || exit 1 + found="YES" + fi + done + + # If we have Bluetooth controller nodes without a set up stack, + # ask the user if we shall start it up + if ! [ "${found}" ]; then + printf "No usable Bluetooth host controllers were found.\n" + printf "These host controllers exist in the system:\n" + printf " %s\n" "${ng_nodes}" + prompt="Choose a host controller to set up: [${ng_nodes%% *}]" + read -p "${prompt}" node + : ${node:="${ng_nodes%% *}"} + /usr/sbin/service bluetooth start ${node} || exit 1 + fi + + # Re-read known nodes + known_nodes=$(/usr/sbin/hccontrol read_node_list 2>/dev/null | + /usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 ) + + # check if we succeeded in bringing it up + [ "${known_nodes}" ] || exerr "Failed to set up Bluetooth stack" +fi + +# if a node was requested on command line, check if it is there +if [ "${node}" ]; then + unset found + for n in ${known_nodes}; do + [ "${n}" = "${node}" ] && found="YES" + [ "${n}" = "${node}hci" ] && node="${node}hci" && found="YES" + done + [ "${found}" ] || exerr "Node ${node} not found" +fi + +[ "${node}" ] && node="-n ${node}" + +while ! [ "${bdaddresses}" ]; do + retry=X${retry} + printf "Scanning for new Bluetooth devices (Attempt %d of 5) ... " \ + ${#retry} + bdaddresses=$( /usr/sbin/hccontrol -N ${node} inquiry 2>/dev/null | + /usr/bin/grep -o "BD_ADDR: .*" | /usr/bin/cut -d ' ' -f 2 ) + + # Count entries and, if a device was requested on command line, + # try to find it + unset found count + for bdaddress in ${bdaddresses}; do + count=X${count} + if [ "${bdaddress}" = "${device}" ]; then + found=YES + bdaddresses="${device}" + count=X + break + fi + done + + # If device was requested on command line but is not found, + # or no devices found at all, rescan until retry is exhausted + if ! [ "${found}" -o "${count}" -a -z "${device}" ]; then + printf "failed.\n" + if [ "${#retry}" -eq 5 ]; then + [ "${device}" ] && exerr "Device ${device} not found" + exerr "No new Bluetooth devices found" + fi + unset bdaddresses + sleep 2 + continue + fi + + [ ${#count} -gt 1 ] && plural=s || plural='' + printf "done.\nFound %d new bluetooth device%s " ${#count} ${plural} + printf "(now scanning for names):\n" + + # Looping again for the faster feedback + unset count + for bdaddress in ${bdaddresses}; do + count=X${count} + bdname=$( /usr/bin/bthost -b "${bdaddress}" 2>/dev/null ) + friendlyname=$( /usr/sbin/hccontrol Remote_Name_Request \ + ${bdaddress} 2> /dev/null | + /usr/bin/grep -o "Name: .*" |/usr/bin/cut -d ' ' -f 2- ) + + # sdpcontrol should be able to pull vendor + product id via sdp + printf "[%2d] %s\t\"%s\" (%s)\n" ${#count} "${bdaddress}" \ + "${friendlyname}" "${bdname}" + + eval bdaddress_${#count}=\${bdaddress} + eval bdname_${#count}=\${bdname} + eval friendlyname_${#count}=\${friendlyname} + done + + # If a device was pre-selected, do not query the user + [ "${device}" ] && topair=1 || unset topair + + # Even if only one device was found, user may chose 0 to rescan + while ! [ "${topair}" ]; do + prompt="Select device to pair with [1" + [ ${#count} -gt 1 ] && prompt="${prompt}-${#count}" + read -p "${prompt}, or 0 to rescan]: " topair + if ! [ "${topair}" -ge 0 -a "${topair}" -le "${#count}" ] \ + 2>/dev/null ; then + printf "Value out of range: %s.\n" {topair} + unset topair + fi + done + + [ "${topair}" -eq "0" ] && unset bdaddresses retry +done + +eval bdaddress=\${bdaddress_${topair}} +eval bdname=\${bdname_${topair}} +eval friendlyname=\${friendlyname_${topair}} + +# Do we need to add an entry to /etc/bluetooth/hosts? +if ! [ "${bdname}" ]; then + printf "\nAdding device ${bdaddress} to /etc/bluetooth/hosts.\n" + + while ! [ "${bdname}" ]; do + read -p "Enter friendly name. [${friendlyname}]: " _r + : ${_r:="${friendlyname}"} + + if [ "${_r}" ]; then + # Remove white space and non-friendly characters + bdname=$( printf "%s" "${_r}" | tr -c '[:alnum:]-,.' _ ) + if [ "${_r}" != "${bdname}" ]; then + printf "Notice: Using sanitized name" + printf "\"%s\" in /etc/bluetooth/hosts.\n" \ + "${bdname}" + fi + fi + done + + printf "%s\t%s\n" "${bdaddress}" "${bdname}" >> /etc/bluetooth/hosts +fi + +# If scanning for the name did not succeed, resort to bdname +: ${friendlyname:="${bdname}"} + +# now over to hcsecd + +# Since hcsecd does not allow querying for known devices, we need to +# check for bdaddr entries manually. +# +# Also we cannot really modify the PIN in an existing entry. So we +# need to prompt the user to manually do it and restart this script +if ! /usr/sbin/service hcsecd enabled; then + printf "\nWarning: hcsecd is not enabled.\n" + printf "This daemon manages pairing requests.\n" + read -p "Enable hcsecd? [yes]: " _r + case "${_r}" in + no|n|NO|N|No|nO) ;; + *) /usr/sbin/service hcsecd enable;; + esac +fi + +secd_config=$( /usr/sbin/sysrc -n hcsecd_config ) +secd_entries=$( /usr/bin/grep -Eo "bdaddr[[:space:]]+(${bdaddress}|${bdname})" \ + ${secd_config} | awk '{ print $2; }' ) + +if [ "${secd_entries}" ]; then + printf "\nWarning: An entry for device %s is already present in %s.\n" \ + ${secd_entries} ${secd_config} + printf "To modify pairing information, edit this file and run\n" + printf " service hcsecd restart\n" + read -p "Continue? [yes]: " _r + case "${_r}" in no|n|NO|N|No|nO) exit;; esac +else + printf "\nWriting pairing information description block to %s.\n" \ + ${secd_config} + printf "(To get PIN, put device in pairing mode first.)\n" + read -p "Enter PIN [nopin]: " pin + [ "${pin}" ] && pin=\""${pin}"\" || pin="nopin" + + # Write out new hcsecd config block + printf "\ndevice {\n\tbdaddr\t%s;\n\tname\t\"%s\";\n\tkey\tnokey\;\n\tpin\t%s\;\n}\n" \ + "${bdaddress}" "${friendlyname}" "${pin}" >> ${secd_config} + + # ... and make daemon reload config + # TODO: hcsecd should provide a reload hook + /usr/sbin/service hcsecd onerestart + + # TODO: we should check if hcsecd succeeded pairing and revert to an + # old version of hcsecd.conf so we can undo adding the block above and + # retry with a new PIN + # also, if there's a way to force devices to re-pair, try this +fi + +# now check for specific services to be provided by the device +# first up: HID + +/usr/sbin/sdpcontrol -a "${bdaddress}" search HID | \ + /usr/bin/grep -q "^Record Handle: " || exit 0 + +printf "\nThis device provides human interface device services.\n" +read -p "Set it up? [yes]: " _r +case "${_r}" in + no|n|NO|N|No|nO) exit 0;; + *);; +esac + +# Here we have found an HID and were asked to set it up +# NOTE: look out for the two exit 0 above if you extend this script + +if ! /usr/sbin/service bthidd enabled; then + printf "\nWarning: bthidd is not enabled." + printf "\nThis daemon manages Bluetooth HID devices.\n" + read -p "Enable bthidd? [yes]: " _r + case "${_r}" in + no|n|NO|N|No|nO) ;; + *) /usr/sbin/service bthidd enable;; + esac +fi + +# Check if bthidd already knows about this device +bthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \ + /usr/bin/grep "${bdaddress}" ) + +if [ "${bthidd_known}" ]; then + printf "Notice: Device %s already known to bthidd.\n" "${bdaddress}" + return 0 +fi + +bthidd_config=$( /usr/sbin/sysrc -n bthidd_config ) +printf "Writing HID descriptor block to %s ... " "${bthidd_config}" +/usr/sbin/bthidcontrol -a "${bdaddress}" query >> "${bthidd_config}" + +# Re-read config to see if we succeeded adding the device +bthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \ + grep "${bdaddress}" ) +if ! [ "${bthidd_known}" ]; then + printf "failed.\n" +else + printf "success.\nTo re-read its config, bthidd must be restarted.\n" + printf "Warning: If a Bluetooth keyboard is being used, the connection" + printf "might be lost.\n" + printf "It can be manually restarted later with\n" + printf " service bthidd restart\n" + read -p "Restart bthidd now? [yes]: " _r + case "${_r}" in + no|n|NO|N|No|nO) ;; + *) /usr/sbin/service bthidd onerestart;; + esac +fi + +} + +# After function definitions, main() can use them +main "$@" +exit 0 + +# TODO +# * If device is a keyboard, offer a text entry test field and if it does +# not succeed, leave some clues for debugging (i.e. if the node responds +# to pings, maybe switch keyboard on/off, etc) +# * Same if device is a mouse, i.e. hexdump /dev/sysmouse. +# * If device offers DUN profiles, ask the user if an entry in +# /etc/ppp/ppp.conf should be created +# * If OPUSH or SPP is offered, refer to the respective man pages to give +# some clues how to continue diff --git a/usr.sbin/bluetooth/bthidcontrol/Makefile b/usr.sbin/bluetooth/bthidcontrol/Makefile new file mode 100644 index 000000000000..bc6cc15ab9e7 --- /dev/null +++ b/usr.sbin/bluetooth/bthidcontrol/Makefile @@ -0,0 +1,15 @@ +# $Id: Makefile,v 1.2 2004/02/13 21:44:41 max Exp $ + +.PATH: ${.CURDIR:H}/bthidd + +PACKAGE= bluetooth +PROG= bthidcontrol +MAN= bthidcontrol.8 +SRCS= bthidcontrol.c hid.c lexer.l parser.y sdp.c +WARNS?= 1 +CFLAGS+= -DBTHIDCONTROL=1 -I${.CURDIR:H}/bthidd -I${SRCTOP}/lib/libsdp \ + -I${SRCTOP}/lib/libbluetooth + +LIBADD+= bluetooth sdp usbhid + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/bthidcontrol/Makefile.depend b/usr.sbin/bluetooth/bthidcontrol/Makefile.depend new file mode 100644 index 000000000000..0269d05a05a0 --- /dev/null +++ b/usr.sbin/bluetooth/bthidcontrol/Makefile.depend @@ -0,0 +1,19 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + lib/libsdp \ + lib/libusbhid \ + usr.bin/yacc.host \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.8 b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.8 new file mode 100644 index 000000000000..c74a93f99082 --- /dev/null +++ b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.8 @@ -0,0 +1,101 @@ +.\" 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. +.\" +.\" $Id: bthidcontrol.8,v 1.1 2004/02/13 21:44:41 max Exp $ +.\" +.Dd October 30, 2006 +.Dt BTHIDCONTROL 8 +.Os +.Sh NAME +.Nm bthidcontrol +.Nd Bluetooth HID control utility +.Sh SYNOPSIS +.Nm +.Fl h +.Nm +.Op Fl a Ar BD_ADDR +.Op Fl c Ar file +.Op Fl H Ar file +.Op Fl v +.Ar command +.Sh DESCRIPTION +The +.Nm +utility can be used to query remote Bluetooth HID devices, dump HID descriptors +in human readable form and perform simple manipulations on the Bluetooth HID +daemon configuration files. +.Pp +The +.Nm +utility will print results to the standard output and error messages to the +standard error. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar BD_ADDR +Specify BD_ADDR for the HID device. +Example: +.Fl a Li 00:01:02:03:04:05 . +.It Fl c Ar file +Specify path to the Bluetooth HID daemon configuration file. +The default path is +.Pa /etc/bluetooth/bthidd.conf . +.It Fl H Ar file +Specify path to the Bluetooth HID daemon known HIDs file. +The default path is +.Pa /var/db/bthidd.hids . +.It Fl h +Display usage message and exit. +.It Fl v +Be verbose and show items that are being used for padding. +.It Ar command +One of the supported commands (see below). +Special command +.Cm help +can be used to obtain the list of all supported commands. +To get more information about specific command use +.Cm help Ar command . +.El +.Sh COMMANDS +The currently supported node commands in +.Nm +are: +.Pp +.Bl -tag -width "Forget" -offset indent -compact +.It Cm Query +.It Cm Dump +.It Cm Known +.It Cm Forget +.El +.Sh FILES +.Bl -tag -width ".Pa /etc/bluetooth/bthidd.conf" -compact +.It Pa /etc/bluetooth/bthidd.conf +.It Pa /var/db/bthidd.hids +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr bthidd 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com diff --git a/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.c b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.c new file mode 100644 index 000000000000..40ece6ce0635 --- /dev/null +++ b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.c @@ -0,0 +1,217 @@ +/*- + * bthidcontrol.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: bthidcontrol.c,v 1.2 2004/02/13 21:44:41 max Exp $ + */ + +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidcontrol.h" + +static int do_bthid_command(bdaddr_p bdaddr, int argc, char **argv); +static struct bthid_command * find_bthid_command(char const *command, struct bthid_command *category); +static void print_bthid_command(struct bthid_command *category); +static void usage(void) __dead2; + +int32_t hid_sdp_query(bdaddr_t const *local, bdaddr_t const *remote, int32_t *error); + +uint32_t verbose = 0; + +/* + * bthidcontrol + */ + +int +main(int argc, char *argv[]) +{ + bdaddr_t bdaddr; + int opt; + + hid_init(NULL); + memcpy(&bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr)); + + while ((opt = getopt(argc, argv, "a:c:H:hv")) != -1) { + switch (opt) { + case 'a': /* bdaddr */ + if (!bt_aton(optarg, &bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(1, "%s: %s", optarg, hstrerror(h_errno)); + + memcpy(&bdaddr, he->h_addr, sizeof(bdaddr)); + } + break; + + case 'c': /* config file */ + config_file = optarg; + break; + + case 'H': /* HIDs file */ + hids_file = optarg; + break; + + case 'v': /* verbose */ + verbose++; + break; + + case 'h': + default: + usage(); + /* NOT REACHED */ + } + } + + argc -= optind; + argv += optind; + + if (*argv == NULL) + usage(); + + return (do_bthid_command(&bdaddr, argc, argv)); +} /* main */ + +/* Execute commands */ +static int +do_bthid_command(bdaddr_p bdaddr, int argc, char **argv) +{ + char *cmd = argv[0]; + struct bthid_command *c = NULL; + int e, help; + + help = 0; + if (strcasecmp(cmd, "help") == 0) { + argc --; + argv ++; + + if (argc <= 0) { + fprintf(stdout, "Supported commands:\n"); + print_bthid_command(sdp_commands); + print_bthid_command(hid_commands); + fprintf(stdout, "\nFor more information use " \ + "'help command'\n"); + + return (OK); + } + + help = 1; + cmd = argv[0]; + } + + c = find_bthid_command(cmd, sdp_commands); + if (c == NULL) + c = find_bthid_command(cmd, hid_commands); + + if (c == NULL) { + fprintf(stdout, "Unknown command: \"%s\"\n", cmd); + return (ERROR); + } + + if (!help) + e = (c->handler)(bdaddr, -- argc, ++ argv); + else + e = USAGE; + + switch (e) { + case OK: + case FAILED: + break; + + case ERROR: + fprintf(stdout, "Could not execute command \"%s\". %s\n", + cmd, strerror(errno)); + break; + + case USAGE: + fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description); + break; + + default: assert(0); break; + } + + return (e); +} /* do_bthid_command */ + +/* Try to find command in specified category */ +static struct bthid_command * +find_bthid_command(char const *command, struct bthid_command *category) +{ + struct bthid_command *c = NULL; + + for (c = category; c->command != NULL; c++) { + char *c_end = strchr(c->command, ' '); + + if (c_end != NULL) { + int len = c_end - c->command; + + if (strncasecmp(command, c->command, len) == 0) + return (c); + } else if (strcasecmp(command, c->command) == 0) + return (c); + } + + return (NULL); +} /* find_bthid_command */ + +/* Print commands in specified category */ +static void +print_bthid_command(struct bthid_command *category) +{ + struct bthid_command *c = NULL; + + for (c = category; c->command != NULL; c++) + fprintf(stdout, "\t%s\n", c->command); +} /* print_bthid_command */ + +/* Usage */ +static void +usage(void) +{ + fprintf(stderr, +"Usage: bthidcontrol options command\n" \ +"Where options are:\n" +" -a bdaddr specify bdaddr\n" \ +" -c file specify path to the bthidd config file\n" \ +" -H file specify path to the bthidd HIDs file\n" \ +" -h display usage and quit\n" \ +" -v be verbose\n" \ +" command one of the supported commands\n"); + exit(255); +} /* usage */ + diff --git a/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.h b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.h new file mode 100644 index 000000000000..e44b670f8f7e --- /dev/null +++ b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.h @@ -0,0 +1,51 @@ +/*- + * bthidcontrol.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: bthidcontrol.h,v 1.1 2004/02/12 23:25:51 max Exp $ + */ + +#ifndef __BTHIDCONTROL_H__ +#define __BTHIDCONTROL_H__ + +#define OK 0 /* everything was OK */ +#define ERROR 1 /* could not execute command */ +#define FAILED 2 /* error was reported */ +#define USAGE 3 /* invalid parameters */ + +struct bthid_command { + char const *command; + char const *description; + int (*handler)(bdaddr_t *, int, char **); +}; + +extern struct bthid_command hid_commands[]; +extern struct bthid_command sdp_commands[]; + +#endif /* __BTHIDCONTROL_H__ */ + diff --git a/usr.sbin/bluetooth/bthidcontrol/hid.c b/usr.sbin/bluetooth/bthidcontrol/hid.c new file mode 100644 index 000000000000..727eb8a716e7 --- /dev/null +++ b/usr.sbin/bluetooth/bthidcontrol/hid.c @@ -0,0 +1,216 @@ +/*- + * hid.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: hid.c,v 1.3 2004/02/17 22:14:57 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <stdio.h> +#include <string.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidcontrol.h" + +extern uint32_t verbose; + +static void hid_dump_descriptor (report_desc_t r); +static void hid_dump_item (char const *label, struct hid_item *h); + +static int +hid_dump(bdaddr_t *bdaddr, int argc, char **argv) +{ + struct hid_device *hd = NULL; + int e = FAILED; + + if (read_config_file() == 0) { + if ((hd = get_hid_device(bdaddr)) != NULL) { + hid_dump_descriptor(hd->desc); + e = OK; + } + + clean_config(); + } + + return (e); +} + +static int +hid_forget(bdaddr_t *bdaddr, int argc, char **argv) +{ + struct hid_device *hd = NULL; + int e = FAILED; + + if (read_config_file() == 0) { + if (read_hids_file() == 0) { + if ((hd = get_hid_device(bdaddr)) != NULL) { + hd->new_device = 1; + if (write_hids_file() == 0) + e = OK; + } + } + + clean_config(); + } + + return (e); +} + +static int +hid_known(bdaddr_t *bdaddr, int argc, char **argv) +{ + struct hid_device *hd = NULL; + struct hostent *he = NULL; + int e = FAILED; + + if (read_config_file() == 0) { + if (read_hids_file() == 0) { + e = OK; + + for (hd = get_next_hid_device(hd); + hd != NULL; + hd = get_next_hid_device(hd)) { + if (hd->new_device) + continue; + + he = bt_gethostbyaddr((char *) &hd->bdaddr, + sizeof(hd->bdaddr), + AF_BLUETOOTH); + + fprintf(stdout, +"%s %s\n", bt_ntoa(&hd->bdaddr, NULL), + (he != NULL && he->h_name != NULL)? + he->h_name : ""); + } + } + + clean_config(); + } + + return (e); +} + +static void +hid_dump_descriptor(report_desc_t r) +{ + struct hid_data *d = NULL; + struct hid_item h; + + for (d = hid_start_parse(r, ~0, -1); hid_get_item(d, &h); ) { + switch (h.kind) { + case hid_collection: + fprintf(stdout, +"Collection page=%s usage=%s\n", hid_usage_page(HID_PAGE(h.usage)), + hid_usage_in_page(h.usage)); + break; + + case hid_endcollection: + fprintf(stdout, "End collection\n"); + break; + + case hid_input: + hid_dump_item("Input ", &h); + break; + + case hid_output: + hid_dump_item("Output ", &h); + break; + + case hid_feature: + hid_dump_item("Feature", &h); + break; + } + } + + hid_end_parse(d); +} + +static void +hid_dump_item(char const *label, struct hid_item *h) +{ + if ((h->flags & HIO_CONST) && !verbose) + return; + + fprintf(stdout, +"%s id=%u size=%u count=%u page=%s usage=%s%s%s%s%s%s%s%s%s%s", + label, (uint8_t) h->report_ID, h->report_size, h->report_count, + hid_usage_page(HID_PAGE(h->usage)), + hid_usage_in_page(h->usage), + h->flags & HIO_CONST ? " Const" : "", + h->flags & HIO_VARIABLE ? " Variable" : "", + h->flags & HIO_RELATIVE ? " Relative" : "", + h->flags & HIO_WRAP ? " Wrap" : "", + h->flags & HIO_NONLINEAR ? " NonLinear" : "", + h->flags & HIO_NOPREF ? " NoPref" : "", + h->flags & HIO_NULLSTATE ? " NullState" : "", + h->flags & HIO_VOLATILE ? " Volatile" : "", + h->flags & HIO_BUFBYTES ? " BufBytes" : ""); + + fprintf(stdout, +", logical range %d..%d", + h->logical_minimum, h->logical_maximum); + + if (h->physical_minimum != h->physical_maximum) + fprintf(stdout, +", physical range %d..%d", + h->physical_minimum, h->physical_maximum); + + if (h->unit) + fprintf(stdout, +", unit=0x%02x exp=%d", h->unit, h->unit_exponent); + + fprintf(stdout, "\n"); +} + +struct bthid_command hid_commands[] = { +{ +"Dump", +"Dump HID descriptor for the specified device in human readable form. The\n" \ +"device must have an entry in the Bluetooth HID daemon configuration file.\n", +hid_dump +}, +{ +"Known", +"List all known to the Bluetooth HID daemon devices.\n", +hid_known +}, +{ +"Forget", +"Forget (mark as new) specified HID device. This command is useful when it\n" \ +"is required to remove device from the known HIDs file. This should be done\n" \ +"when reset button was pressed on the device or the battery was changed. The\n"\ +"Bluetooth HID daemon should be restarted.\n", +hid_forget +}, +{ NULL, NULL, NULL } +}; + diff --git a/usr.sbin/bluetooth/bthidcontrol/sdp.c b/usr.sbin/bluetooth/bthidcontrol/sdp.c new file mode 100644 index 000000000000..4754744a866a --- /dev/null +++ b/usr.sbin/bluetooth/bthidcontrol/sdp.c @@ -0,0 +1,503 @@ +/*- + * sdp.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: sdp.c,v 1.3 2004/02/17 22:14:57 max Exp $ + */ + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/sysctl.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <errno.h> +#include <sdp.h> +#include <stdio.h> +#include <string.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidcontrol.h" + +static int32_t hid_sdp_query (bdaddr_t const *local, struct hid_device *hd, int32_t *error); +static int32_t hid_sdp_parse_protocol_descriptor_list (sdp_attr_p a); +static int32_t hid_sdp_parse_hid_descriptor (sdp_attr_p a); +static int32_t hid_sdp_parse_boolean (sdp_attr_p a); + +/* + * Hard coded attribute IDs taken from the + * DEVICE IDENTIFICATION PROFILE SPECIFICATION V13 p.12 + */ + +#define SDP_ATTR_DEVICE_ID_SERVICE_VENDORID 0x0201 +#define SDP_ATTR_DEVICE_ID_SERVICE_PRODUCTID 0x0202 +#define SDP_ATTR_DEVICE_ID_SERVICE_VERSION 0x0203 +#define SDP_ATTR_DEVICE_ID_RANGE SDP_ATTR_RANGE( \ + SDP_ATTR_DEVICE_ID_SERVICE_VENDORID, SDP_ATTR_DEVICE_ID_SERVICE_VERSION ) + +static uint16_t service = SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE; +static uint16_t service_devid = SDP_SERVICE_CLASS_PNP_INFORMATION; +static uint32_t attrs_devid = SDP_ATTR_DEVICE_ID_RANGE; + +static uint32_t attrs[] = { +SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST), +SDP_ATTR_RANGE (SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS, + SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS), +SDP_ATTR_RANGE( 0x0205, /* HIDReconnectInitiate */ + 0x0205), +SDP_ATTR_RANGE( 0x0206, /* HIDDescriptorList */ + 0x0206), +SDP_ATTR_RANGE( 0x0209, /* HIDBatteryPower */ + 0x0209), +SDP_ATTR_RANGE( 0x020d, /* HIDNormallyConnectable */ + 0x020d) + }; +#define nattrs nitems(attrs) + +static sdp_attr_t values[8]; +#define nvalues nitems(values) + +static uint8_t buffer[nvalues][512]; + +/* + * Query remote device + */ + +#undef hid_sdp_query_exit +#define hid_sdp_query_exit(e) { \ + if (error != NULL) \ + *error = (e); \ + if (ss != NULL) { \ + sdp_close(ss); \ + ss = NULL; \ + } \ + return (((e) == 0)? 0 : -1); \ +} + +static void +hid_init_return_values() { + int i; + for (i = 0; i < nvalues; i ++) { + values[i].flags = SDP_ATTR_INVALID; + values[i].attr = 0; + values[i].vlen = sizeof(buffer[i]); + values[i].value = buffer[i]; + } +} + +static int32_t +hid_sdp_query(bdaddr_t const *local, struct hid_device *hd, int32_t *error) +{ + void *ss = NULL; + uint8_t *hid_descriptor = NULL, *v; + int32_t i, control_psm = -1, interrupt_psm = -1, + reconnect_initiate = -1, + normally_connectable = 0, battery_power = 0, + hid_descriptor_length = -1, type; + int16_t vendor_id = 0, product_id = 0, version = 0; + bdaddr_t sdp_local; + char devname[HCI_DEVNAME_SIZE]; + + if (local == NULL) + local = NG_HCI_BDADDR_ANY; + if (hd == NULL) + hid_sdp_query_exit(EINVAL); + + hid_init_return_values(); + + if ((ss = sdp_open(local, &hd->bdaddr)) == NULL) + hid_sdp_query_exit(ENOMEM); + if (sdp_error(ss) != 0) + hid_sdp_query_exit(sdp_error(ss)); + if (sdp_search(ss, 1, &service, nattrs, attrs, nvalues, values) != 0) + hid_sdp_query_exit(sdp_error(ss)); + + for (i = 0; i < nvalues; i ++) { + if (values[i].flags != SDP_ATTR_OK) + continue; + + switch (values[i].attr) { + case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST: + control_psm = hid_sdp_parse_protocol_descriptor_list(&values[i]); + break; + + case SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS: + interrupt_psm = hid_sdp_parse_protocol_descriptor_list(&values[i]); + break; + + case 0x0205: /* HIDReconnectInitiate */ + reconnect_initiate = hid_sdp_parse_boolean(&values[i]); + break; + + case 0x0206: /* HIDDescriptorList */ + if (hid_sdp_parse_hid_descriptor(&values[i]) == 0) { + hid_descriptor = values[i].value; + hid_descriptor_length = values[i].vlen; + } + break; + + case 0x0209: /* HIDBatteryPower */ + battery_power = hid_sdp_parse_boolean(&values[i]); + break; + + case 0x020d: /* HIDNormallyConnectable */ + normally_connectable = hid_sdp_parse_boolean(&values[i]); + break; + } + } + + hid_init_return_values(); + + if (sdp_search(ss, 1, &service_devid, 1, &attrs_devid, nvalues, values) != 0) + hid_sdp_query_exit(sdp_error(ss)); + + /* Try extract HCI bdaddr from opened SDP session */ + if (sdp_get_lcaddr(ss, &sdp_local) != 0 || + bt_devname(devname, &sdp_local) == 0) + hid_sdp_query_exit(ENOATTR); + + sdp_close(ss); + ss = NULL; + + /* If search is successful, scan through return vals */ + for (i = 0; i < 3; i ++ ) { + if (values[i].flags == SDP_ATTR_INVALID ) + continue; + + /* Expecting tag + uint16_t on all 3 attributes */ + if (values[i].vlen != 3) + continue; + + /* Make sure, we're reading a uint16_t */ + v = values[i].value; + SDP_GET8(type, v); + if (type != SDP_DATA_UINT16 ) + continue; + + switch (values[i].attr) { + case SDP_ATTR_DEVICE_ID_SERVICE_VENDORID: + SDP_GET16(vendor_id, v); + break; + case SDP_ATTR_DEVICE_ID_SERVICE_PRODUCTID: + SDP_GET16(product_id, v); + break; + case SDP_ATTR_DEVICE_ID_SERVICE_VERSION: + SDP_GET16(version, v); + break; + default: + break; + } + } + + if (control_psm == -1 || interrupt_psm == -1 || + reconnect_initiate == -1 || + hid_descriptor == NULL || hid_descriptor_length == -1) + hid_sdp_query_exit(ENOATTR); + hd->name = bt_devremote_name_gen(devname, &hd->bdaddr); + hd->vendor_id = vendor_id; + hd->product_id = product_id; + hd->version = version; + hd->control_psm = control_psm; + hd->interrupt_psm = interrupt_psm; + hd->reconnect_initiate = reconnect_initiate? 1 : 0; + hd->battery_power = battery_power? 1 : 0; + hd->normally_connectable = normally_connectable? 1 : 0; + hd->desc = hid_use_report_desc(hid_descriptor, hid_descriptor_length); + if (hd->desc == NULL) + hid_sdp_query_exit(ENOMEM); + + return (0); +} + +/* + * seq len 2 + * seq len 2 + * uuid value 3 + * uint16 value 3 + * seq len 2 + * uuid value 3 + */ + +static int32_t +hid_sdp_parse_protocol_descriptor_list(sdp_attr_p a) +{ + uint8_t *ptr = a->value; + uint8_t *end = a->value + a->vlen; + int32_t type, len, uuid, psm; + + if (end - ptr < 15) + return (-1); + + if (a->attr == SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS) { + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + } + + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + /* Protocol */ + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + /* UUID */ + if (ptr + 3 > end) + return (-1); + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_UUID16: + SDP_GET16(uuid, ptr); + if (uuid != SDP_UUID_PROTOCOL_L2CAP) + return (-1); + break; + + case SDP_DATA_UUID32: /* XXX FIXME can we have 32-bit UUID */ + case SDP_DATA_UUID128: /* XXX FIXME can we have 128-bit UUID */ + default: + return (-1); + } + + /* PSM */ + if (ptr + 3 > end) + return (-1); + SDP_GET8(type, ptr); + if (type != SDP_DATA_UINT16) + return (-1); + SDP_GET16(psm, ptr); + + return (psm); +} + +/* + * seq len 2 + * seq len 2 + * uint8 value8 2 + * str value 3 + */ + +static int32_t +hid_sdp_parse_hid_descriptor(sdp_attr_p a) +{ + uint8_t *ptr = a->value; + uint8_t *end = a->value + a->vlen; + int32_t type, len, descriptor_type; + + if (end - ptr < 9) + return (-1); + + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + while (ptr < end) { + /* Descriptor */ + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_SEQ8: + if (ptr + 1 > end) + return (-1); + SDP_GET8(len, ptr); + break; + + case SDP_DATA_SEQ16: + if (ptr + 2 > end) + return (-1); + SDP_GET16(len, ptr); + break; + + case SDP_DATA_SEQ32: + if (ptr + 4 > end) + return (-1); + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + + /* Descripor type */ + if (ptr + 1 > end) + return (-1); + SDP_GET8(type, ptr); + if (type != SDP_DATA_UINT8 || ptr + 1 > end) + return (-1); + SDP_GET8(descriptor_type, ptr); + + /* Descriptor value */ + if (ptr + 1 > end) + return (-1); + SDP_GET8(type, ptr); + switch (type) { + case SDP_DATA_STR8: + if (ptr + 1 > end) + return (-1); + SDP_GET8(len, ptr); + break; + + case SDP_DATA_STR16: + if (ptr + 2 > end) + return (-1); + SDP_GET16(len, ptr); + break; + + case SDP_DATA_STR32: + if (ptr + 4 > end) + return (-1); + SDP_GET32(len, ptr); + break; + + default: + return (-1); + } + if (ptr + len > end) + return (-1); + + if (descriptor_type == UDESC_REPORT && len > 0) { + a->value = ptr; + a->vlen = len; + + return (0); + } + + ptr += len; + } + + return (-1); +} + +/* bool8 int8 */ +static int32_t +hid_sdp_parse_boolean(sdp_attr_p a) +{ + if (a->vlen != 2 || a->value[0] != SDP_DATA_BOOL) + return (-1); + + return (a->value[1]); +} + +/* Perform SDP query */ +static int32_t +hid_query(bdaddr_t *bdaddr, int argc, char **argv) +{ + struct hid_device hd; + int e; + + memcpy(&hd.bdaddr, bdaddr, sizeof(hd.bdaddr)); + if (hid_sdp_query(NULL, &hd, &e) < 0) { + fprintf(stderr, "Could not perform SDP query on the " \ + "device %s. %s (%d)\n", bt_ntoa(bdaddr, NULL), + strerror(e), e); + return (FAILED); + } + + print_hid_device(&hd, stdout); + + return (OK); +} + +struct bthid_command sdp_commands[] = +{ +{ +"Query", +"Perform SDP query to the specified device and print HID configuration entry\n"\ +"for the device. The configuration entry should be appended to the Bluetooth\n"\ +"HID daemon configuration file and the daemon should be restarted.\n", +hid_query +}, +{ NULL, NULL, NULL } +}; + diff --git a/usr.sbin/bluetooth/bthidd/Makefile b/usr.sbin/bluetooth/bthidd/Makefile new file mode 100644 index 000000000000..b924d1985e9d --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/Makefile @@ -0,0 +1,16 @@ +# $Id: Makefile,v 1.6 2006/09/07 21:36:55 max Exp $ + +PACKAGE= bluetooth +PROG= bthidd +MAN= bthidd.8 +# bthidd.conf.5 +SRCS= bthidd.c btuinput.c client.c hid.c kbd.c lexer.l parser.y \ + server.c session.c + +CFLAGS+= -I${.CURDIR} + +LIBADD+= bluetooth usbhid + +NO_WMISSING_VARIABLE_DECLARATIONS= + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/bthidd/Makefile.depend b/usr.sbin/bluetooth/bthidd/Makefile.depend new file mode 100644 index 000000000000..595af15e5072 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/Makefile.depend @@ -0,0 +1,18 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + lib/libusbhid \ + usr.bin/yacc.host \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/bthidd/bthid_config.h b/usr.sbin/bluetooth/bthidd/bthid_config.h new file mode 100644 index 000000000000..829af57aff45 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthid_config.h @@ -0,0 +1,79 @@ +/* + * bthid_config.h + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: bthid_config.h,v 1.4 2006/09/07 21:06:53 max Exp $ + */ + +#ifndef _BTHID_CONFIG_H_ +#define _BTHID_CONFIG_H_ 1 + +#define BTHIDD_CONFFILE "/etc/bluetooth/bthidd.conf" +#define BTHIDD_HIDSFILE "/var/db/bthidd.hids" + +struct hid_device +{ + bdaddr_t bdaddr; /* HID device BDADDR */ + char * name; /* HID device name */ + uint16_t control_psm; /* control PSM */ + uint16_t interrupt_psm; /* interrupt PSM */ + uint16_t vendor_id; /* primary vendor id */ + uint16_t product_id; + uint16_t version; + unsigned new_device : 1; + unsigned reconnect_initiate : 1; + unsigned battery_power : 1; + unsigned normally_connectable : 1; + unsigned keyboard : 1; + unsigned mouse : 1; + unsigned has_wheel : 1; + unsigned has_hwheel : 1; + unsigned has_cons : 1; + unsigned reserved : 7; + report_desc_t desc; /* HID report descriptor */ + LIST_ENTRY(hid_device) next; /* link to the next */ +}; +typedef struct hid_device hid_device_t; +typedef struct hid_device * hid_device_p; + +extern char const *config_file; +extern char const *hids_file; + +int32_t read_config_file (void); +void clean_config (void); +hid_device_p get_hid_device (bdaddr_p bdaddr); +hid_device_p get_next_hid_device (hid_device_p d); +void print_hid_device (hid_device_p hid_device, FILE *f); + +int32_t read_hids_file (void); +int32_t write_hids_file (void); + +#endif /* ndef _BTHID_CONFIG_H_ */ + diff --git a/usr.sbin/bluetooth/bthidd/bthidd.8 b/usr.sbin/bluetooth/bthidd/bthidd.8 new file mode 100644 index 000000000000..1d609948d1b8 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthidd.8 @@ -0,0 +1,132 @@ +.\" Copyright (c) 2006 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. +.\" +.\" $Id: bthidd.8,v 1.1 2006/09/07 21:36:55 max Exp $ +.\" +.Dd April 30, 2018 +.Dt BTHIDD 8 +.Os +.Sh NAME +.Nm bthidd +.Nd Bluetooth HID daemon +.Sh SYNOPSIS +.Nm +.Fl h +.Nm +.Op Fl a Ar BD_ADDR +.Op Fl c Ar file +.Op Fl H Ar file +.Op Fl p Ar file +.Op Fl t Ar val +.Op Fl u +.Sh DESCRIPTION +The +.Nm +daemon handles remote Bluetooth HID devices. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar BD_ADDR +Specify the local address to listen on. +By default, the server will listen on +.Dv ANY +address. +The address can be specified as BD_ADDR or name. +If a name was specified, the +.Nm +daemon will attempt to resolve the name via +.Xr bt_gethostbyname 3 . +.It Fl c Ar file +Specify path to the configuration file. +The default path is +.Pa /etc/bluetooth/bthidd.conf . +.It Fl d +Do not detach from the controlling terminal, i.e., run in foreground. +.It Fl H Ar file +Specify path to the known HIDs file. +The default path is +.Pa /var/db/bthidd.hids . +.It Fl h +Display usage message and exit. +.It Fl p Ar file +Specify path to the PID file. +The default path is +.Pa /var/run/bthidd.pid . +.It Fl t Ar val +Specify client rescan interval in seconds. +The +.Nm +daemon will periodically scan for newly configured Bluetooth HID devices or +disconnected +.Dq passive +Bluetooth HID devices and will attempt to establish an outgoing connection. +The default rescan interval is 10 seconds. +.It Fl u +Enable support for input event device protocol. +Requires evdev and uinput drivers to be loaded with +.Xr kldload 8 +or compiled into the kernel. +.El +.Sh KNOWN LIMITATIONS +The +.Nm +daemon currently does not handle key auto repeat and double click mouse events. +Those events work under +.Xr X 7 Pq Pa ports/x11/xorg-docs +just fine, +but not in text console. +.Pp +This manual page needs more work. +A manual page documenting the format of the +.Pa /etc/bluetooth/bthidd.conf +configuration file is needed as well. +.Sh FILES +.Bl -tag -width ".Pa /etc/bluetooth/bthidd.conf" -compact +.It Pa /etc/bluetooth/bthidd.conf +.It Pa /var/db/bthidd.hids +.It Pa /var/run/bthidd.pid +.El +.Sh SEE ALSO +.Xr kbdmux 4 , +.Xr vkbd 4 , +.Xr bthidcontrol 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +.Sh CAVEATS +Any Bluetooth HID device that has +.Dv HUP_KEYBOARD +or +.Dv HUP_CONSUMER +entries in its descriptor is considered as +.Dq keyboard . +For each +.Dq keyboard +Bluetooth HID device, +the +.Nm +daemon will use a separate instance of the virtual keyboard interface +.Xr vkbd 4 . +Therefore the +.Xr kbdmux 4 +driver must be used to properly multiplex input from multiple keyboards. diff --git a/usr.sbin/bluetooth/bthidd/bthidd.c b/usr.sbin/bluetooth/bthidd/bthidd.c new file mode 100644 index 000000000000..fad467667bd1 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthidd.c @@ -0,0 +1,276 @@ +/* + * bthidd.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: bthidd.c,v 1.8 2006/09/07 21:06:53 max Exp $ + */ + +#include <sys/time.h> +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" + +static int32_t write_pid_file (char const *file); +static int32_t remove_pid_file (char const *file); +static int32_t elapsed (int32_t tval); +static void sighandler (int32_t s); +static void usage (void); + +/* + * bthidd + */ + +static int32_t done = 0; /* are we done? */ + +int32_t +main(int32_t argc, char *argv[]) +{ + struct bthid_server srv; + struct sigaction sa; + char const *pid_file = BTHIDD_PIDFILE; + char *ep; + int32_t opt, detach, tval, uinput; + + memset(&srv, 0, sizeof(srv)); + memset(&srv.bdaddr, 0, sizeof(srv.bdaddr)); + detach = 1; + tval = 10; /* sec */ + uinput = 0; + + while ((opt = getopt(argc, argv, "a:c:dH:hp:t:u")) != -1) { + switch (opt) { + case 'a': /* BDADDR */ + if (!bt_aton(optarg, &srv.bdaddr)) { + struct hostent *he; + + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(1, "%s: %s", optarg, hstrerror(h_errno)); + + memcpy(&srv.bdaddr, he->h_addr, sizeof(srv.bdaddr)); + } + break; + + case 'c': /* config file */ + config_file = optarg; + break; + + case 'd': /* do not detach */ + detach = 0; + break; + + case 'H': /* hids file */ + hids_file = optarg; + break; + + case 'p': /* pid file */ + pid_file = optarg; + break; + + case 't': /* rescan interval */ + tval = strtol(optarg, (char **) &ep, 10); + if (*ep != '\0' || tval <= 0) + usage(); + break; + + case 'u': /* enable evdev support */ + uinput = 1; + break; + + case 'h': + default: + usage(); + /* NOT REACHED */ + } + } + + openlog(BTHIDD_IDENT, LOG_PID|LOG_PERROR|LOG_NDELAY, LOG_USER); + + /* Become daemon if required */ + if (detach && daemon(0, 0) < 0) { + syslog(LOG_CRIT, "Could not become daemon. %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* Install signal handler */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sighandler; + + if (sigaction(SIGTERM, &sa, NULL) < 0 || + sigaction(SIGHUP, &sa, NULL) < 0 || + sigaction(SIGINT, &sa, NULL) < 0) { + syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)", + strerror(errno), errno); + exit(1); + } + + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)", + strerror(errno), errno); + exit(1); + } + + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT; + if (sigaction(SIGCHLD, &sa, NULL) < 0) { + syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (read_config_file() < 0 || read_hids_file() < 0 || + server_init(&srv) < 0 || write_pid_file(pid_file) < 0) + exit(1); + + srv.uinput = uinput; + + for (done = 0; !done; ) { + if (elapsed(tval)) + client_rescan(&srv); + + if (server_do(&srv) < 0) + break; + } + + server_shutdown(&srv); + remove_pid_file(pid_file); + clean_config(); + closelog(); + + return (0); +} + +/* + * Write pid file + */ + +static int32_t +write_pid_file(char const *file) +{ + FILE *pid; + + assert(file != NULL); + + if ((pid = fopen(file, "w")) == NULL) { + syslog(LOG_ERR, "Could not open file %s. %s (%d)", + file, strerror(errno), errno); + return (-1); + } + + fprintf(pid, "%d", getpid()); + fclose(pid); + + return (0); +} + +/* + * Remote pid file + */ + +static int32_t +remove_pid_file(char const *file) +{ + assert(file != NULL); + + if (unlink(file) < 0) { + syslog(LOG_ERR, "Could not unlink file %s. %s (%d)", + file, strerror(errno), errno); + return (-1); + } + + return (0); +} + +/* + * Returns true if desired time interval has elapsed + */ + +static int32_t +elapsed(int32_t tval) +{ + static struct timeval last = { 0, 0 }; + struct timeval now; + + gettimeofday(&now, NULL); + + if (now.tv_sec - last.tv_sec >= tval) { + last = now; + return (1); + } + + return (0); +} + +/* + * Signal handler + */ + +static void +sighandler(int32_t s) +{ + syslog(LOG_NOTICE, "Got signal %d, total number of signals %d", + s, ++ done); +} + +/* + * Display usage and exit + */ + +static void +usage(void) +{ + fprintf(stderr, +"Usage: %s [options]\n" \ +"Where options are:\n" \ +" -a address specify address to listen on (default ANY)\n" \ +" -c file specify config file name\n" \ +" -d run in foreground\n" \ +" -H file specify known HIDs file name\n" \ +" -h display this message\n" \ +" -p file specify PID file name\n" \ +" -t tval specify client rescan interval (sec)\n" \ +" -u enable evdev protocol support\n" \ +"", BTHIDD_IDENT); + exit(255); +} + diff --git a/usr.sbin/bluetooth/bthidd/bthidd.conf.sample b/usr.sbin/bluetooth/bthidd/bthidd.conf.sample new file mode 100644 index 000000000000..63b3ced5edce --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthidd.conf.sample @@ -0,0 +1,79 @@ + +device { + bdaddr 00:50:f2:e5:68:84; + name "Bluetooth Mouse"; + vendor_id 0x0000; + product_id 0x0000; + version 0x0000; + control_psm 0x11; + interrupt_psm 0x13; + reconnect_initiate true; + normally_connectable false; + hid_descriptor { + 0x05 0x01 0x09 0x02 0xa1 0x01 0x85 0x02 + 0x09 0x01 0xa1 0x00 0x05 0x09 0x19 0x01 + 0x29 0x05 0x15 0x00 0x25 0x01 0x75 0x01 + 0x95 0x05 0x81 0x02 0x75 0x03 0x95 0x01 + 0x81 0x01 0x05 0x01 0x09 0x30 0x09 0x31 + 0x09 0x38 0x15 0x81 0x25 0x7f 0x75 0x08 + 0x95 0x03 0x81 0x06 0xc0 0xc0 0x05 0x0c + 0x09 0x01 0xa1 0x01 0x85 0x03 0x05 0x01 + 0x09 0x02 0xa1 0x02 0x06 0x00 0xff 0x15 + 0x00 0x25 0x03 0x95 0x01 0x75 0x02 0x0a + 0x01 0xfe 0x81 0x02 0x75 0x06 0x81 0x01 + 0xc0 0xc0 + }; +} + +device { + bdaddr 00:50:f2:e3:fb:e1; + name "Bluetooth Keyboard"; + vendor_id 0x0000; + product_id 0x0000; + version 0x0000; + control_psm 0x11; + interrupt_psm 0x13; + reconnect_initiate true; + normally_connectable false; + hid_descriptor { + 0x05 0x01 0x09 0x06 0xa1 0x01 0x85 0x01 + 0x05 0x08 0x19 0x01 0x29 0x03 0x15 0x00 + 0x25 0x01 0x75 0x01 0x95 0x03 0x91 0x02 + 0x09 0x4b 0x95 0x01 0x91 0x02 0x95 0x04 + 0x91 0x01 0x05 0x07 0x19 0xe0 0x29 0xe7 + 0x95 0x08 0x81 0x02 0x75 0x08 0x95 0x01 + 0x81 0x01 0x19 0x00 0x29 0x91 0x26 0xff + 0x00 0x95 0x06 0x81 0x00 0xc0 0x05 0x0c + 0x09 0x01 0xa1 0x01 0x85 0x02 0x05 0x0c + 0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x1c + 0x09 0xe2 0x09 0xb7 0x09 0xcd 0x09 0xea + 0x09 0xe9 0x09 0xb6 0x09 0xb5 0x0a 0x83 + 0x01 0x0a 0x1a 0x02 0x0a 0x79 0x02 0x0a + 0xab 0x01 0x0a 0x08 0x02 0x0a 0x02 0x02 + 0x0a 0x03 0x02 0x0a 0x07 0x02 0x0a 0x01 + 0x02 0x0a 0x92 0x01 0x0a 0x9c 0x01 0x09 + 0x95 0x0a 0x23 0x02 0x0a 0x89 0x02 0x0a + 0x8b 0x02 0x0a 0x8c 0x02 0x0a 0x8a 0x01 + 0x0a 0x99 0x01 0x0a 0xa7 0x01 0x0a 0xb6 + 0x01 0x0a 0xb7 0x01 0x81 0x02 0x75 0x01 + 0x95 0x04 0x81 0x01 0x06 0x00 0xff 0x0a + 0x02 0xff 0x26 0xff 0x00 0x95 0x01 0x75 + 0x08 0x81 0x02 0xc0 0x05 0x01 0x09 0x80 + 0xa1 0x01 0x85 0x03 0x19 0x81 0x29 0x83 + 0x25 0x01 0x95 0x03 0x75 0x01 0x81 0x02 + 0x95 0x05 0x81 0x01 0xc0 0x05 0x0c 0x09 + 0x01 0xa1 0x01 0x85 0x04 0x05 0x01 0x09 + 0x06 0xa1 0x02 0x06 0x00 0xff 0x15 0x00 + 0x25 0x03 0x95 0x01 0x75 0x02 0x0a 0x01 + 0xfe 0x81 0x02 0x75 0x06 0x81 0x01 0xc0 + 0xc0 0x05 0x0c 0x09 0x01 0xa1 0x01 0x85 + 0x05 0x05 0x01 0x09 0x06 0xa1 0x02 0x06 + 0x00 0xff 0x25 0x01 0x75 0x01 0x95 0x02 + 0x0a 0x03 0xfe 0x0a 0x04 0xfe 0x81 0x02 + 0x95 0x06 0x81 0x01 0xc0 0xc0 0x05 0x0c + 0x09 0x01 0xa1 0x01 0x85 0xff 0x05 0x06 + 0x95 0x01 0x75 0x02 0x19 0x24 0x29 0x26 + 0x81 0x02 0x75 0x06 0x81 0x01 0xc0 + }; +} + diff --git a/usr.sbin/bluetooth/bthidd/bthidd.h b/usr.sbin/bluetooth/bthidd/bthidd.h new file mode 100644 index 000000000000..55fd03e57334 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthidd.h @@ -0,0 +1,102 @@ +/* + * bthidd.h + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: bthidd.h,v 1.7 2006/09/07 21:06:53 max Exp $ + */ + +#ifndef _BTHIDD_H_ +#define _BTHIDD_H_ 1 + +#define BTHIDD_IDENT "bthidd" +#define BTHIDD_PIDFILE "/var/run/" BTHIDD_IDENT ".pid" + +struct bthid_session; + +struct bthid_server +{ + bdaddr_t bdaddr; /* local bdaddr */ + int32_t cons; /* /dev/consolectl */ + int32_t ctrl; /* control channel (listen) */ + int32_t intr; /* intr. channel (listen) */ + int32_t maxfd; /* max fd in sets */ + int32_t uinput; /* enable evdev support */ + fd_set rfdset; /* read descriptor set */ + fd_set wfdset; /* write descriptor set */ + LIST_HEAD(, bthid_session) sessions; +}; + +typedef struct bthid_server bthid_server_t; +typedef struct bthid_server * bthid_server_p; + +struct bthid_session +{ + bthid_server_p srv; /* pointer back to server */ + int32_t ctrl; /* control channel */ + int32_t intr; /* interrupt channel */ + int32_t vkbd; /* virual keyboard */ + void *ctx; /* product specific dev state */ + int32_t ukbd; /* evdev user input */ + int32_t umouse;/* evdev user input */ + int32_t obutt; /* previous mouse buttons */ + int32_t consk; /* last consumer page key */ + bdaddr_t bdaddr;/* remote bdaddr */ + uint16_t state; /* session state */ +#define CLOSED 0 +#define W4CTRL 1 +#define W4INTR 2 +#define OPEN 3 + bitstr_t *keys1; /* keys map (new) */ + bitstr_t *keys2; /* keys map (old) */ + LIST_ENTRY(bthid_session) next; /* link to next */ +}; + +typedef struct bthid_session bthid_session_t; +typedef struct bthid_session * bthid_session_p; + +int32_t server_init (bthid_server_p srv); +void server_shutdown (bthid_server_p srv); +int32_t server_do (bthid_server_p srv); + +int32_t client_rescan (bthid_server_p srv); +int32_t client_connect (bthid_server_p srv, int fd); + +bthid_session_p session_open (bthid_server_p srv, hid_device_p const d); +bthid_session_p session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr); +bthid_session_p session_by_fd (bthid_server_p srv, int32_t fd); +int32_t session_run (bthid_session_p s); +void session_close (bthid_session_p s); + +void hid_initialise (bthid_session_p s); +int32_t hid_control (bthid_session_p s, uint8_t *data, int32_t len); +int32_t hid_interrupt (bthid_session_p s, uint8_t *data, int32_t len); + +#endif /* ndef _BTHIDD_H_ */ + diff --git a/usr.sbin/bluetooth/bthidd/btuinput.c b/usr.sbin/bluetooth/bthidd/btuinput.c new file mode 100644 index 000000000000..497a5527e3aa --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/btuinput.c @@ -0,0 +1,616 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2015-2017 Vladimir Kondratyev <wulf@FreeBSD.org> + * 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/param.h> +#include <sys/ioctl.h> +#include <sys/kbio.h> +#include <sys/sysctl.h> + +#include <dev/evdev/input.h> +#include <dev/evdev/uinput.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include <usbhid.h> + +#include "bthid_config.h" +#include "bthidd.h" +#include "btuinput.h" + +static int16_t const mbuttons[8] = { + BTN_LEFT, + BTN_MIDDLE, + BTN_RIGHT, + BTN_SIDE, + BTN_EXTRA, + BTN_FORWARD, + BTN_BACK, + BTN_TASK +}; + +static uint16_t const led_codes[3] = { + LED_CAPSL, /* CLKED */ + LED_NUML, /* NLKED */ + LED_SCROLLL, /* SLKED */ +}; + +#define NONE KEY_RESERVED + +static uint16_t const keymap[0x100] = { + /* 0x00 - 0x27 */ + NONE, NONE, NONE, NONE, KEY_A, KEY_B, KEY_C, KEY_D, + KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, + KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, + KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2, + KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, + /* 0x28 - 0x3f */ + KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB, + KEY_SPACE, KEY_MINUS, KEY_EQUAL, KEY_LEFTBRACE, + KEY_RIGHTBRACE, KEY_BACKSLASH, KEY_BACKSLASH, KEY_SEMICOLON, + KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, KEY_DOT, + KEY_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, + KEY_F3, KEY_F4, KEY_F5, KEY_F6, + /* 0x40 - 0x5f */ + KEY_F7, KEY_F8, KEY_F9, KEY_F10, + KEY_F11, KEY_F12, KEY_SYSRQ, KEY_SCROLLLOCK, + KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_PAGEUP, + KEY_DELETE, KEY_END, KEY_PAGEDOWN, KEY_RIGHT, + KEY_LEFT, KEY_DOWN, KEY_UP, KEY_NUMLOCK, + KEY_KPSLASH, KEY_KPASTERISK, KEY_KPMINUS, KEY_KPPLUS, + KEY_KPENTER, KEY_KP1, KEY_KP2, KEY_KP3, + KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, + /* 0x60 - 0x7f */ + KEY_KP8, KEY_KP9, KEY_KP0, KEY_KPDOT, + KEY_102ND, KEY_COMPOSE, KEY_POWER, KEY_KPEQUAL, + KEY_F13, KEY_F14, KEY_F15, KEY_F16, + KEY_F17, KEY_F18, KEY_F19, KEY_F20, + KEY_F21, KEY_F22, KEY_F23, KEY_F24, + KEY_OPEN, KEY_HELP, KEY_PROPS, KEY_FRONT, + KEY_STOP, KEY_AGAIN, KEY_UNDO, KEY_CUT, + KEY_COPY, KEY_PASTE, KEY_FIND, KEY_MUTE, + /* 0x80 - 0x9f */ + KEY_VOLUMEUP, KEY_VOLUMEDOWN, NONE, NONE, + NONE, KEY_KPCOMMA, NONE, KEY_RO, + KEY_KATAKANAHIRAGANA, KEY_YEN,KEY_HENKAN, KEY_MUHENKAN, + KEY_KPJPCOMMA, NONE, NONE, NONE, + KEY_HANGEUL, KEY_HANJA, KEY_KATAKANA, KEY_HIRAGANA, + KEY_ZENKAKUHANKAKU, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + /* 0xa0 - 0xbf */ + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + /* 0xc0 - 0xdf */ + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + NONE, NONE, NONE, NONE, + /* 0xe0 - 0xff */ + KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, KEY_LEFTMETA, + KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, KEY_RIGHTMETA, + KEY_PLAYPAUSE, KEY_STOPCD, KEY_PREVIOUSSONG,KEY_NEXTSONG, + KEY_EJECTCD, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_MUTE, + KEY_WWW, KEY_BACK, KEY_FORWARD, KEY_STOP, + KEY_FIND, KEY_SCROLLUP, KEY_SCROLLDOWN, KEY_EDIT, + KEY_SLEEP, KEY_COFFEE, KEY_REFRESH, KEY_CALC, + NONE, NONE, NONE, NONE, +}; + +/* Consumer page usage mapping */ +static uint16_t const consmap[0x300] = { + [0x030] = KEY_POWER, + [0x031] = KEY_RESTART, + [0x032] = KEY_SLEEP, + [0x034] = KEY_SLEEP, + [0x035] = KEY_KBDILLUMTOGGLE, + [0x036] = BTN_MISC, + [0x040] = KEY_MENU, + [0x041] = KEY_SELECT, + [0x042] = KEY_UP, + [0x043] = KEY_DOWN, + [0x044] = KEY_LEFT, + [0x045] = KEY_RIGHT, + [0x046] = KEY_ESC, + [0x047] = KEY_KPPLUS, + [0x048] = KEY_KPMINUS, + [0x060] = KEY_INFO, + [0x061] = KEY_SUBTITLE, + [0x063] = KEY_VCR, + [0x065] = KEY_CAMERA, + [0x069] = KEY_RED, + [0x06a] = KEY_GREEN, + [0x06b] = KEY_BLUE, + [0x06c] = KEY_YELLOW, + [0x06d] = KEY_ZOOM, + [0x06f] = KEY_BRIGHTNESSUP, + [0x070] = KEY_BRIGHTNESSDOWN, + [0x072] = KEY_BRIGHTNESS_TOGGLE, + [0x073] = KEY_BRIGHTNESS_MIN, + [0x074] = KEY_BRIGHTNESS_MAX, + [0x075] = KEY_BRIGHTNESS_AUTO, + [0x082] = KEY_VIDEO_NEXT, + [0x083] = KEY_LAST, + [0x084] = KEY_ENTER, + [0x088] = KEY_PC, + [0x089] = KEY_TV, + [0x08a] = KEY_WWW, + [0x08b] = KEY_DVD, + [0x08c] = KEY_PHONE, + [0x08d] = KEY_PROGRAM, + [0x08e] = KEY_VIDEOPHONE, + [0x08f] = KEY_GAMES, + [0x090] = KEY_MEMO, + [0x091] = KEY_CD, + [0x092] = KEY_VCR, + [0x093] = KEY_TUNER, + [0x094] = KEY_EXIT, + [0x095] = KEY_HELP, + [0x096] = KEY_TAPE, + [0x097] = KEY_TV2, + [0x098] = KEY_SAT, + [0x09a] = KEY_PVR, + [0x09c] = KEY_CHANNELUP, + [0x09d] = KEY_CHANNELDOWN, + [0x0a0] = KEY_VCR2, + [0x0b0] = KEY_PLAY, + [0x0b1] = KEY_PAUSE, + [0x0b2] = KEY_RECORD, + [0x0b3] = KEY_FASTFORWARD, + [0x0b4] = KEY_REWIND, + [0x0b5] = KEY_NEXTSONG, + [0x0b6] = KEY_PREVIOUSSONG, + [0x0b7] = KEY_STOPCD, + [0x0b8] = KEY_EJECTCD, + [0x0bc] = KEY_MEDIA_REPEAT, + [0x0b9] = KEY_SHUFFLE, + [0x0bf] = KEY_SLOW, + [0x0cd] = KEY_PLAYPAUSE, + [0x0cf] = KEY_VOICECOMMAND, + [0x0e2] = KEY_MUTE, + [0x0e5] = KEY_BASSBOOST, + [0x0e9] = KEY_VOLUMEUP, + [0x0ea] = KEY_VOLUMEDOWN, + [0x0f5] = KEY_SLOW, + [0x181] = KEY_BUTTONCONFIG, + [0x182] = KEY_BOOKMARKS, + [0x183] = KEY_CONFIG, + [0x184] = KEY_WORDPROCESSOR, + [0x185] = KEY_EDITOR, + [0x186] = KEY_SPREADSHEET, + [0x187] = KEY_GRAPHICSEDITOR, + [0x188] = KEY_PRESENTATION, + [0x189] = KEY_DATABASE, + [0x18a] = KEY_MAIL, + [0x18b] = KEY_NEWS, + [0x18c] = KEY_VOICEMAIL, + [0x18d] = KEY_ADDRESSBOOK, + [0x18e] = KEY_CALENDAR, + [0x18f] = KEY_TASKMANAGER, + [0x190] = KEY_JOURNAL, + [0x191] = KEY_FINANCE, + [0x192] = KEY_CALC, + [0x193] = KEY_PLAYER, + [0x194] = KEY_FILE, + [0x196] = KEY_WWW, + [0x199] = KEY_CHAT, + [0x19c] = KEY_LOGOFF, + [0x19e] = KEY_COFFEE, + [0x19f] = KEY_CONTROLPANEL, + [0x1a2] = KEY_APPSELECT, + [0x1a3] = KEY_NEXT, + [0x1a4] = KEY_PREVIOUS, + [0x1a6] = KEY_HELP, + [0x1a7] = KEY_DOCUMENTS, + [0x1ab] = KEY_SPELLCHECK, + [0x1ae] = KEY_KEYBOARD, + [0x1b1] = KEY_SCREENSAVER, + [0x1b4] = KEY_FILE, + [0x1b6] = KEY_IMAGES, + [0x1b7] = KEY_AUDIO, + [0x1b8] = KEY_VIDEO, + [0x1bc] = KEY_MESSENGER, + [0x1bd] = KEY_INFO, + [0x201] = KEY_NEW, + [0x202] = KEY_OPEN, + [0x203] = KEY_CLOSE, + [0x204] = KEY_EXIT, + [0x207] = KEY_SAVE, + [0x208] = KEY_PRINT, + [0x209] = KEY_PROPS, + [0x21a] = KEY_UNDO, + [0x21b] = KEY_COPY, + [0x21c] = KEY_CUT, + [0x21d] = KEY_PASTE, + [0x21f] = KEY_FIND, + [0x221] = KEY_SEARCH, + [0x222] = KEY_GOTO, + [0x223] = KEY_HOMEPAGE, + [0x224] = KEY_BACK, + [0x225] = KEY_FORWARD, + [0x226] = KEY_STOP, + [0x227] = KEY_REFRESH, + [0x22a] = KEY_BOOKMARKS, + [0x22d] = KEY_ZOOMIN, + [0x22e] = KEY_ZOOMOUT, + [0x22f] = KEY_ZOOMRESET, + [0x233] = KEY_SCROLLUP, + [0x234] = KEY_SCROLLDOWN, + [0x23d] = KEY_EDIT, + [0x25f] = KEY_CANCEL, + [0x269] = KEY_INSERT, + [0x26a] = KEY_DELETE, + [0x279] = KEY_REDO, + [0x289] = KEY_REPLY, + [0x28b] = KEY_FORWARDMAIL, + [0x28c] = KEY_SEND, + [0x2c7] = KEY_KBDINPUTASSIST_PREV, + [0x2c8] = KEY_KBDINPUTASSIST_NEXT, + [0x2c9] = KEY_KBDINPUTASSIST_PREVGROUP, + [0x2ca] = KEY_KBDINPUTASSIST_NEXTGROUP, + [0x2cb] = KEY_KBDINPUTASSIST_ACCEPT, + [0x2cc] = KEY_KBDINPUTASSIST_CANCEL, +}; + +static int32_t +uinput_open_common(hid_device_p const p, bdaddr_p local, const uint8_t *name) +{ + struct uinput_setup uisetup; + uint8_t phys[UINPUT_MAX_NAME_SIZE]; + uint8_t uniq[UINPUT_MAX_NAME_SIZE]; + int32_t fd; + + /* Take local and remote bdaddr */ + bt_ntoa(local, phys); + bt_ntoa(&p->bdaddr, uniq); + + /* Take device name from bthidd.conf. Fallback to generic name. */ + if (p->name != NULL) + name = p->name; + + /* Set device name and bus/vendor information */ + memset(&uisetup, 0, sizeof(uisetup)); + snprintf(uisetup.name, UINPUT_MAX_NAME_SIZE, + "%s, bdaddr %s", name, uniq); + uisetup.id.bustype = BUS_BLUETOOTH; + uisetup.id.vendor = p->vendor_id; + uisetup.id.product = p->product_id; + uisetup.id.version = p->version; + + fd = open("/dev/uinput", O_RDWR | O_NONBLOCK); + + if (ioctl(fd, UI_SET_PHYS, phys) < 0 || + ioctl(fd, UI_SET_BSDUNIQ, uniq) < 0 || + ioctl(fd, UI_DEV_SETUP, &uisetup) < 0) + return (-1); + + return (fd); +} + +/* + * Setup uinput device as 8button mouse with wheel(s) + * TODO: bring in more feature detection code from ums + */ +int32_t +uinput_open_mouse(hid_device_p const p, bdaddr_p local) +{ + size_t i; + int32_t fd; + + assert(p != NULL); + + if ((fd = uinput_open_common(p, local, "Bluetooth Mouse")) < 0) + goto bail_out; + + /* Advertise events and axes */ + if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0 || + ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 || + ioctl(fd, UI_SET_EVBIT, EV_REL) < 0 || + ioctl(fd, UI_SET_RELBIT, REL_X) < 0 || + ioctl(fd, UI_SET_RELBIT, REL_Y) < 0 || + (p->has_wheel && ioctl(fd, UI_SET_RELBIT, REL_WHEEL) < 0) || + (p->has_hwheel && ioctl(fd, UI_SET_RELBIT, REL_HWHEEL) < 0) || + ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_POINTER) < 0) + goto bail_out; + + /* Advertise mouse buttons */ + for (i = 0; i < nitems(mbuttons); i++) + if (ioctl(fd, UI_SET_KEYBIT, mbuttons[i]) < 0) + goto bail_out; + + if (ioctl(fd, UI_DEV_CREATE) >= 0) + return (fd); /* SUCCESS */ + +bail_out: + if (fd >= 0) + close(fd); + return (-1); +} + +/* + * Setup uinput keyboard + */ +int32_t +uinput_open_keyboard(hid_device_p const p, bdaddr_p local) +{ + size_t i; + int32_t fd; + + assert(p != NULL); + + if ((fd = uinput_open_common(p, local, "Bluetooth Keyboard")) < 0) + goto bail_out; + + /* Advertise key events and LEDs */ + if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 || + ioctl(fd, UI_SET_EVBIT, EV_LED) < 0 || + ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0 || + ioctl(fd, UI_SET_EVBIT, EV_REP) < 0 || + ioctl(fd, UI_SET_LEDBIT, LED_CAPSL) < 0 || + ioctl(fd, UI_SET_LEDBIT, LED_NUML) < 0 || + ioctl(fd, UI_SET_LEDBIT, LED_SCROLLL)) + goto bail_out; + + /* Advertise keycodes */ + for (i = 0; i < nitems(keymap); i++) + if (keymap[i] != NONE && + ioctl(fd, UI_SET_KEYBIT, keymap[i]) < 0) + goto bail_out; + + /* Advertise consumer page keys if any */ + if (p->has_cons) { + for (i = 0; i < nitems(consmap); i++) { + if (consmap[i] != NONE && + ioctl(fd, UI_SET_KEYBIT, consmap[i]) < 0) + goto bail_out; + } + } + + if (ioctl(fd, UI_DEV_CREATE) >= 0) + return (fd); /* SUCCESS */ + +bail_out: + if (fd >= 0) + close(fd); + return (-1); +} + +/* from sys/dev/evdev/evdev.h */ +#define EVDEV_RCPT_HW_MOUSE (1<<2) +#define EVDEV_RCPT_HW_KBD (1<<3) + +#define MASK_POLL_INTERVAL 5 /* seconds */ +#define MASK_SYSCTL "kern.evdev.rcpt_mask" + +static int32_t +uinput_get_rcpt_mask(void) +{ + static struct timespec last = { 0, 0 }; + struct timespec now; + static int32_t mask = 0; + size_t len; + time_t elapsed; + + if (clock_gettime(CLOCK_MONOTONIC_FAST, &now) == -1) + return mask; + + elapsed = now.tv_sec - last.tv_sec; + if (now.tv_nsec < last.tv_nsec) + elapsed--; + + if (elapsed >= MASK_POLL_INTERVAL) { + len = sizeof(mask); + if (sysctlbyname(MASK_SYSCTL, &mask, &len, NULL, 0) < 0) { + if (errno == ENOENT) + /* kernel is compiled w/o EVDEV_SUPPORT */ + mask = EVDEV_RCPT_HW_MOUSE | EVDEV_RCPT_HW_KBD; + else + mask = 0; + } + last = now; + } + return mask; +} + +static int32_t +uinput_write_event(int32_t fd, uint16_t type, uint16_t code, int32_t value) +{ + struct input_event ie; + + assert(fd >= 0); + + memset(&ie, 0, sizeof(ie)); + ie.type = type; + ie.code = code; + ie.value = value; + return (write(fd, &ie, sizeof(ie))); +} + +int32_t +uinput_rep_mouse(int32_t fd, int32_t x, int32_t y, int32_t z, int32_t t, + int32_t buttons, int32_t obuttons) +{ + size_t i; + int32_t rcpt_mask, mask; + + assert(fd >= 0); + + rcpt_mask = uinput_get_rcpt_mask(); + if (!(rcpt_mask & EVDEV_RCPT_HW_MOUSE)) + return (0); + + if ((x != 0 && uinput_write_event(fd, EV_REL, REL_X, x) < 0) || + (y != 0 && uinput_write_event(fd, EV_REL, REL_Y, y) < 0) || + (z != 0 && uinput_write_event(fd, EV_REL, REL_WHEEL, -z) < 0) || + (t != 0 && uinput_write_event(fd, EV_REL, REL_HWHEEL, t) < 0)) + return (-1); + + for (i = 0; i < nitems(mbuttons); i++) { + mask = 1 << i; + if ((buttons & mask) == (obuttons & mask)) + continue; + if (uinput_write_event(fd, EV_KEY, mbuttons[i], + (buttons & mask) != 0) < 0) + return (-1); + } + + if (uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) < 0) + return (-1); + + return (0); +} + +/* + * Translate and report keyboard page key events + */ +int32_t +uinput_rep_key(int32_t fd, int32_t key, int32_t make) +{ + int32_t rcpt_mask; + + assert(fd >= 0); + + rcpt_mask = uinput_get_rcpt_mask(); + if (!(rcpt_mask & EVDEV_RCPT_HW_KBD)) + return (0); + + if (key >= 0 && key < (int32_t)nitems(keymap) && + keymap[key] != NONE) { + if (uinput_write_event(fd, EV_KEY, keymap[key], make) > 0 && + uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) > 0) + return (0); + } + return (-1); +} + +/* + * Translate and report consumer page key events + */ +int32_t +uinput_rep_cons(int32_t fd, int32_t key, int32_t make) +{ + int32_t rcpt_mask; + + assert(fd >= 0); + + rcpt_mask = uinput_get_rcpt_mask(); + if (!(rcpt_mask & EVDEV_RCPT_HW_KBD)) + return (0); + + if (key >= 0 && key < (int32_t)nitems(consmap) && + consmap[key] != NONE) { + if (uinput_write_event(fd, EV_KEY, consmap[key], make) > 0 && + uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) > 0) + return (0); + } + return (-1); +} + +/* + * Translate and report LED events + */ +int32_t +uinput_rep_leds(int32_t fd, int state, int mask) +{ + size_t i; + int32_t rcpt_mask; + + assert(fd >= 0); + + rcpt_mask = uinput_get_rcpt_mask(); + if (!(rcpt_mask & EVDEV_RCPT_HW_KBD)) + return (0); + + for (i = 0; i < nitems(led_codes); i++) { + if (mask & (1 << i) && + uinput_write_event(fd, EV_LED, led_codes[i], + state & (1 << i) ? 1 : 0) < 0) + return (-1); + } + + return (0); +} + +/* + * Process status change from evdev + */ +int32_t +uinput_kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len) +{ + struct input_event ie; + int32_t leds, oleds; + size_t i; + + assert(s != NULL); + assert(s->vkbd >= 0); + assert(len == sizeof(struct input_event)); + + memcpy(&ie, data, sizeof(ie)); + switch (ie.type) { + case EV_LED: + ioctl(s->vkbd, KDGETLED, &oleds); + leds = oleds; + for (i = 0; i < nitems(led_codes); i++) { + if (led_codes[i] == ie.code) { + if (ie.value) + leds |= 1 << i; + else + leds &= ~(1 << i); + if (leds != oleds) + ioctl(s->vkbd, KDSETLED, leds); + break; + } + } + break; + case EV_REP: + /* FALLTHROUGH. Repeats are handled by evdev subsystem */ + default: + break; + } + + return (0); +} diff --git a/usr.sbin/bluetooth/bthidd/btuinput.h b/usr.sbin/bluetooth/bthidd/btuinput.h new file mode 100644 index 000000000000..caa11fc65c46 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/btuinput.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2015-2017 Vladimir Kondratyev <wulf@FreeBSD.org> + * 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. + */ + +#ifndef _UINPUT_H_ +#define _UINPUT_H_ + +int32_t uinput_open_mouse(hid_device_p const d, bdaddr_p local); +int32_t uinput_open_keyboard(hid_device_p const d, bdaddr_p local); +int32_t uinput_rep_mouse(int32_t fd, int32_t x, int32_t y, int32_t z, + int32_t t, int32_t buttons, int32_t obuttons); +int32_t uinput_rep_key(int32_t fd, int32_t key, int32_t make); +int32_t uinput_rep_cons(int32_t fd, int32_t key, int32_t make); +int32_t uinput_rep_leds(int32_t fd, int state, int mask); +int32_t uinput_kbd_status_changed(bthid_session_p s, uint8_t *data, + int32_t len); + +#endif /* ndef _UINPUT_H_ */ diff --git a/usr.sbin/bluetooth/bthidd/client.c b/usr.sbin/bluetooth/bthidd/client.c new file mode 100644 index 000000000000..63fef5867b98 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/client.c @@ -0,0 +1,257 @@ +/* + * client.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: client.c,v 1.7 2006/09/07 21:06:53 max Exp $ + */ + +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" + +static int32_t client_socket(bdaddr_p bdaddr, uint16_t psm); + +/* + * Get next config entry and create outbound connection (if required) + * + * XXX Do only one device at a time. At least one of my devices (3COM + * Bluetooth PCCARD) rejects Create_Connection command if another + * Create_Connection command is still pending. Weird... + */ + +static int32_t connect_in_progress = 0; + +int32_t +client_rescan(bthid_server_p srv) +{ + static hid_device_p d; + bthid_session_p s; + + assert(srv != NULL); + + if (connect_in_progress) + return (0); /* another connect is still pending */ + + d = get_next_hid_device(d); + if (d == NULL) + return (0); /* XXX should not happen? empty config? */ + + if ((s = session_by_bdaddr(srv, &d->bdaddr)) != NULL) + return (0); /* session already active */ + + if (!d->new_device) { + if (d->reconnect_initiate) + return (0); /* device will initiate reconnect */ + } + + syslog(LOG_NOTICE, "Opening outbound session for %s " \ + "(new_device=%d, reconnect_initiate=%d)", + bt_ntoa(&d->bdaddr, NULL), d->new_device, d->reconnect_initiate); + + if ((s = session_open(srv, d)) == NULL) { + syslog(LOG_CRIT, "Could not create outbound session for %s", + bt_ntoa(&d->bdaddr, NULL)); + return (-1); + } + + /* Open control channel */ + s->ctrl = client_socket(&s->bdaddr, d->control_psm); + if (s->ctrl < 0) { + syslog(LOG_ERR, "Could not open control channel to %s. %s (%d)", + bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno); + session_close(s); + return (-1); + } + + s->state = W4CTRL; + + FD_SET(s->ctrl, &srv->wfdset); + if (s->ctrl > srv->maxfd) + srv->maxfd = s->ctrl; + + connect_in_progress = 1; + + return (0); +} + +/* + * Process connect on the socket + */ + +int32_t +client_connect(bthid_server_p srv, int32_t fd) +{ + bthid_session_p s; + hid_device_p d; + int32_t error; + socklen_t len; + + assert(srv != NULL); + assert(fd >= 0); + + s = session_by_fd(srv, fd); + assert(s != NULL); + + d = get_hid_device(&s->bdaddr); + assert(d != NULL); + + error = 0; + len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + syslog(LOG_ERR, "Could not get socket error for %s. %s (%d)", + bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno); + session_close(s); + connect_in_progress = 0; + + return (-1); + } + + if (error != 0) { + syslog(LOG_ERR, "Could not connect to %s. %s (%d)", + bt_ntoa(&s->bdaddr, NULL), strerror(error), error); + session_close(s); + connect_in_progress = 0; + + return (0); + } + + switch (s->state) { + case W4CTRL: /* Control channel is open */ + assert(s->ctrl == fd); + assert(s->intr == -1); + + /* Open interrupt channel */ + s->intr = client_socket(&s->bdaddr, d->interrupt_psm); + if (s->intr < 0) { + syslog(LOG_ERR, "Could not open interrupt channel " \ + "to %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + session_close(s); + connect_in_progress = 0; + + return (-1); + } + + s->state = W4INTR; + + FD_SET(s->intr, &srv->wfdset); + if (s->intr > srv->maxfd) + srv->maxfd = s->intr; + + d->new_device = 0; /* reset new device flag */ + write_hids_file(); + break; + + case W4INTR: /* Interrupt channel is open */ + assert(s->ctrl != -1); + assert(s->intr == fd); + + s->state = OPEN; + connect_in_progress = 0; + + /* Create kbd/mouse after both channels are established */ + if (session_run(s) < 0) { + session_close(s); + return (-1); + } + break; + + default: + assert(0); + break; + } + + /* Move fd to from the write fd set into read fd set */ + FD_CLR(fd, &srv->wfdset); + FD_SET(fd, &srv->rfdset); + + return (0); +} + +/* + * Create bound non-blocking socket and initiate connect + */ + +static int +client_socket(bdaddr_p bdaddr, uint16_t psm) +{ + struct sockaddr_l2cap l2addr; + int32_t s, m; + + s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (s < 0) + return (-1); + + m = fcntl(s, F_GETFL); + if (m < 0) { + close(s); + return (-1); + } + + if (fcntl(s, F_SETFL, (m|O_NONBLOCK)) < 0) { + close(s); + return (-1); + } + + l2addr.l2cap_len = sizeof(l2addr); + l2addr.l2cap_family = AF_BLUETOOTH; + memset(&l2addr.l2cap_bdaddr, 0, sizeof(l2addr.l2cap_bdaddr)); + l2addr.l2cap_psm = 0; + l2addr.l2cap_bdaddr_type = BDADDR_BREDR; + l2addr.l2cap_cid = 0; + + if (bind(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { + close(s); + return (-1); + } + + memcpy(&l2addr.l2cap_bdaddr, bdaddr, sizeof(l2addr.l2cap_bdaddr)); + l2addr.l2cap_psm = htole16(psm); + + if (connect(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0 && + errno != EINPROGRESS) { + close(s); + return (-1); + } + + return (s); +} + diff --git a/usr.sbin/bluetooth/bthidd/hid.c b/usr.sbin/bluetooth/bthidd/hid.c new file mode 100644 index 000000000000..4de3c07119e2 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/hid.c @@ -0,0 +1,578 @@ +/* + * hid.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: hid.c,v 1.5 2006/09/07 21:06:53 max Exp $ + */ + +#include <sys/consio.h> +#include <sys/mouse.h> +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" +#include "btuinput.h" +#include "kbd.h" + +/* + * Inoffical and unannounced report ids for Apple Mice and trackpad + */ +#define TRACKPAD_REPORT_ID 0x28 +#define AMM_REPORT_ID 0x29 +#define BATT_STAT_REPORT_ID 0x30 +#define BATT_STRENGTH_REPORT_ID 0x47 +#define SURFACE_REPORT_ID 0x61 + +/* + * Apple magic mouse (AMM) specific device state + */ +#define AMM_MAX_BUTTONS 16 +struct apple_state { + int y [AMM_MAX_BUTTONS]; + int button_state; +}; + +#define MAGIC_MOUSE(D) (((D)->vendor_id == 0x5ac) && ((D)->product_id == 0x30d)) +#define AMM_BASIC_BLOCK 5 +#define AMM_FINGER_BLOCK 8 +#define AMM_VALID_REPORT(L) (((L) >= AMM_BASIC_BLOCK) && \ + ((L) <= 16*AMM_FINGER_BLOCK + AMM_BASIC_BLOCK) && \ + ((L) % AMM_FINGER_BLOCK) == AMM_BASIC_BLOCK) +#define AMM_WHEEL_SPEED 100 + +/* + * Probe for per-device initialisation + */ +void +hid_initialise(bthid_session_p s) +{ + hid_device_p hid_device = get_hid_device(&s->bdaddr); + + if (hid_device && MAGIC_MOUSE(hid_device)) { + /* Magic report to enable trackpad on Apple's Magic Mouse */ + static uint8_t rep[] = {0x53, 0xd7, 0x01}; + + if ((s->ctx = calloc(1, sizeof(struct apple_state))) == NULL) + return; + write(s->ctrl, rep, 3); + } +} + +/* + * Process data from control channel + */ + +int32_t +hid_control(bthid_session_p s, uint8_t *data, int32_t len) +{ + assert(s != NULL); + assert(data != NULL); + assert(len > 0); + + switch (data[0] >> 4) { + case 0: /* Handshake (response to command) */ + if (data[0] & 0xf) + syslog(LOG_ERR, "Got handshake message with error " \ + "response 0x%x from %s", + data[0], bt_ntoa(&s->bdaddr, NULL)); + break; + + case 1: /* HID Control */ + switch (data[0] & 0xf) { + case 0: /* NOP */ + break; + + case 1: /* Hard reset */ + case 2: /* Soft reset */ + syslog(LOG_WARNING, "Device %s requested %s reset", + bt_ntoa(&s->bdaddr, NULL), + ((data[0] & 0xf) == 1)? "hard" : "soft"); + break; + + case 3: /* Suspend */ + syslog(LOG_NOTICE, "Device %s requested Suspend", + bt_ntoa(&s->bdaddr, NULL)); + break; + + case 4: /* Exit suspend */ + syslog(LOG_NOTICE, "Device %s requested Exit Suspend", + bt_ntoa(&s->bdaddr, NULL)); + break; + + case 5: /* Virtual cable unplug */ + syslog(LOG_NOTICE, "Device %s unplugged virtual cable", + bt_ntoa(&s->bdaddr, NULL)); + session_close(s); + break; + + default: + syslog(LOG_WARNING, "Device %s sent unknown " \ + "HID_Control message 0x%x", + bt_ntoa(&s->bdaddr, NULL), data[0]); + break; + } + break; + + default: + syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \ + "channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL)); + break; + } + + return (0); +} + +/* + * Process data from the interrupt channel + */ + +int32_t +hid_interrupt(bthid_session_p s, uint8_t *data, int32_t len) +{ + hid_device_p hid_device; + hid_data_t d; + hid_item_t h; + int32_t report_id, usage, page, val, + mouse_x, mouse_y, mouse_z, mouse_t, mouse_butt, + mevents, kevents, i; + + assert(s != NULL); + assert(s->srv != NULL); + assert(data != NULL); + + if (len < 3) { + syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \ + "channel from %s", len, bt_ntoa(&s->bdaddr, NULL)); + return (-1); + } + + if (data[0] != 0xa1) { + syslog(LOG_ERR, "Got unexpected message 0x%x on " \ + "Interrupt channel from %s", + data[0], bt_ntoa(&s->bdaddr, NULL)); + return (-1); + } + + report_id = data[1]; + data ++; + len --; + + hid_device = get_hid_device(&s->bdaddr); + assert(hid_device != NULL); + + mouse_x = mouse_y = mouse_z = mouse_t = mouse_butt = 0; + mevents = kevents = 0; + + for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1); + hid_get_item(d, &h) > 0; ) { + if ((h.flags & HIO_CONST) || (h.report_ID != report_id) || + (h.kind != hid_input)) + continue; + + page = HID_PAGE(h.usage); + val = hid_get_data(data, &h); + + /* + * When the input field is an array and the usage is specified + * with a range instead of an ID, we have to derive the actual + * usage by using the item value as an index in the usage range + * list. + */ + if ((h.flags & HIO_VARIABLE)) { + usage = HID_USAGE(h.usage); + } else { + const uint32_t usage_offset = val - h.logical_minimum; + usage = HID_USAGE(h.usage_minimum + usage_offset); + } + + switch (page) { + case HUP_GENERIC_DESKTOP: + switch (usage) { + case HUG_X: + mouse_x = val; + mevents ++; + break; + + case HUG_Y: + mouse_y = val; + mevents ++; + break; + + case HUG_WHEEL: + mouse_z = -val; + mevents ++; + break; + + case HUG_SYSTEM_SLEEP: + if (val) + syslog(LOG_NOTICE, "Sleep button pressed"); + break; + } + break; + + case HUP_KEYBOARD: + kevents ++; + + if (h.flags & HIO_VARIABLE) { + if (val && usage < kbd_maxkey()) + bit_set(s->keys1, usage); + } else { + if (val && val < kbd_maxkey()) + bit_set(s->keys1, val); + + for (i = 1; i < h.report_count; i++) { + h.pos += h.report_size; + val = hid_get_data(data, &h); + if (val && val < kbd_maxkey()) + bit_set(s->keys1, val); + } + } + break; + + case HUP_BUTTON: + if (usage != 0) { + if (usage == 2) + usage = 3; + else if (usage == 3) + usage = 2; + + mouse_butt |= (val << (usage - 1)); + mevents ++; + } + break; + + case HUP_CONSUMER: + if (hid_device->keyboard && s->srv->uinput) { + if (h.flags & HIO_VARIABLE) { + uinput_rep_cons(s->ukbd, usage, !!val); + } else { + if (s->consk > 0) + uinput_rep_cons(s->ukbd, + s->consk, 0); + if (uinput_rep_cons(s->ukbd, val, 1) + == 0) + s->consk = val; + } + } + + if (!val) + break; + + switch (usage) { + case HUC_AC_PAN: + /* Horizontal scroll */ + mouse_t = val; + mevents ++; + val = 0; + break; + + case 0xb5: /* Scan Next Track */ + val = 0x19; + break; + + case 0xb6: /* Scan Previous Track */ + val = 0x10; + break; + + case 0xb7: /* Stop */ + val = 0x24; + break; + + case 0xcd: /* Play/Pause */ + val = 0x22; + break; + + case 0xe2: /* Mute */ + val = 0x20; + break; + + case 0xe9: /* Volume Up */ + val = 0x30; + break; + + case 0xea: /* Volume Down */ + val = 0x2E; + break; + + case 0x183: /* Media Select */ + val = 0x6D; + break; + + case 0x018a: /* Mail */ + val = 0x6C; + break; + + case 0x192: /* Calculator */ + val = 0x21; + break; + + case 0x194: /* My Computer */ + val = 0x6B; + break; + + case 0x221: /* WWW Search */ + val = 0x65; + break; + + case 0x223: /* WWW Home */ + val = 0x32; + break; + + case 0x224: /* WWW Back */ + val = 0x6A; + break; + + case 0x225: /* WWW Forward */ + val = 0x69; + break; + + case 0x226: /* WWW Stop */ + val = 0x68; + break; + + case 0x227: /* WWW Refresh */ + val = 0x67; + break; + + case 0x22a: /* WWW Favorites */ + val = 0x66; + break; + + default: + val = 0; + break; + } + + /* XXX FIXME - UGLY HACK */ + if (val != 0) { + if (hid_device->keyboard) { + int32_t buf[4] = { 0xe0, val, + 0xe0, val|0x80 }; + + assert(s->vkbd != -1); + write(s->vkbd, buf, sizeof(buf)); + } else + syslog(LOG_ERR, "Keyboard events " \ + "received from non-keyboard " \ + "device %s. Please report", + bt_ntoa(&s->bdaddr, NULL)); + } + break; + + case HUP_MICROSOFT: + switch (usage) { + case 0xfe01: + if (!hid_device->battery_power) + break; + + switch (val) { + case 1: + syslog(LOG_INFO, "Battery is OK on %s", + bt_ntoa(&s->bdaddr, NULL)); + break; + + case 2: + syslog(LOG_NOTICE, "Low battery on %s", + bt_ntoa(&s->bdaddr, NULL)); + break; + + case 3: + syslog(LOG_WARNING, "Very low battery "\ + "on %s", + bt_ntoa(&s->bdaddr, NULL)); + break; + } + break; + } + break; + } + } + hid_end_parse(d); + + /* + * Apple adheres to no standards and sends reports it does + * not introduce in its hid descriptor for its magic mouse. + * Handle those reports here. + */ + if (MAGIC_MOUSE(hid_device) && s->ctx) { + struct apple_state *c = (struct apple_state *)s->ctx; + int firm = 0, middle = 0; + int16_t v; + + data++, len--; /* Chomp report_id */ + + if (report_id != AMM_REPORT_ID || !AMM_VALID_REPORT(len)) + goto check_middle_button; + + /* + * The basics. When touches are detected, no normal mouse + * reports are sent. Collect clicks and dx/dy + */ + if (data[2] & 1) + mouse_butt |= 0x1; + if (data[2] & 2) + mouse_butt |= 0x4; + + if ((v = data[0] + ((data[2] & 0x0C) << 6))) + mouse_x += ((int16_t)(v << 6)) >> 6, mevents++; + if ((v = data[1] + ((data[2] & 0x30) << 4))) + mouse_y += ((int16_t)(v << 6)) >> 6, mevents++; + + /* + * The hard part: accumulate touch events and emulate middle + */ + for (data += AMM_BASIC_BLOCK, len -= AMM_BASIC_BLOCK; + len >= AMM_FINGER_BLOCK; + data += AMM_FINGER_BLOCK, len -= AMM_FINGER_BLOCK) { + int x, y, z, force, id; + + v = data[0] | ((data[1] & 0xf) << 8); + x = ((int16_t)(v << 4)) >> 4; + + v = (data[1] >> 4) | (data[2] << 4); + y = -(((int16_t)(v << 4)) >> 4); + + force = data[5] & 0x3f; + id = 0xf & ((data[5] >> 6) | (data[6] << 2)); + z = (y - c->y[id]) / AMM_WHEEL_SPEED; + + switch ((data[7] >> 4) & 0x7) { /* Phase */ + case 3: /* First touch */ + c->y[id] = y; + break; + case 4: /* Touch dragged */ + if (z) { + mouse_z += z; + c->y[id] += z * AMM_WHEEL_SPEED; + mevents++; + } + break; + default: + break; + } + /* Count firm touches vs. firm+middle touches */ + if (force >= 8 && ++firm && x > -350 && x < 350) + ++middle; + } + + /* + * If a new click is registered by mouse and there are firm + * touches which are all in center, make it a middle click + */ + if (mouse_butt && !c->button_state && firm && middle == firm) + mouse_butt = 0x2; + + /* + * If we're still clicking and have converted the click + * to a middle click, keep it middle clicking + */ +check_middle_button: + if (mouse_butt && c->button_state == 0x2) + mouse_butt = 0x2; + + if (mouse_butt != c->button_state) + c->button_state = mouse_butt, mevents++; + } + + /* + * XXX FIXME Feed keyboard events into kernel. + * The code below works, bit host also needs to track + * and handle repeat. + * + * Key repeat currently works in X, but not in console. + */ + + if (kevents > 0) { + if (hid_device->keyboard) { + assert(s->vkbd != -1); + kbd_process_keys(s); + } else + syslog(LOG_ERR, "Keyboard events received from " \ + "non-keyboard device %s. Please report", + bt_ntoa(&s->bdaddr, NULL)); + } + + /* + * XXX FIXME Feed mouse events into kernel. + * The code block below works, but it is not good enough. + * Need to track double-clicks etc. + * + * Double click currently works in X, but not in console. + */ + + if (mevents > 0) { + struct mouse_info mi; + + memset(&mi, 0, sizeof(mi)); + mi.operation = MOUSE_ACTION; + mi.u.data.buttons = mouse_butt; + + /* translate T-axis into button presses */ + if (mouse_t != 0) { + mi.u.data.buttons |= 1 << (mouse_t > 0 ? 6 : 5); + if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) + syslog(LOG_ERR, "Could not process mouse " \ + "events from %s. %s (%d)", + bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + } + + mi.u.data.x = mouse_x; + mi.u.data.y = mouse_y; + mi.u.data.z = mouse_z; + mi.u.data.buttons = mouse_butt; + + if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) + syslog(LOG_ERR, "Could not process mouse events from " \ + "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + + if (hid_device->mouse && s->srv->uinput && + uinput_rep_mouse(s->umouse, mouse_x, mouse_y, mouse_z, + mouse_t, mouse_butt, s->obutt) < 0) + syslog(LOG_ERR, "Could not process mouse events from " \ + "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + s->obutt = mouse_butt; + } + + return (0); +} diff --git a/usr.sbin/bluetooth/bthidd/kbd.c b/usr.sbin/bluetooth/bthidd/kbd.c new file mode 100644 index 000000000000..51c12d111c37 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/kbd.c @@ -0,0 +1,614 @@ +/* + * kbd.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: kbd.c,v 1.4 2006/09/07 21:06:53 max Exp $ + */ + +#include <sys/consio.h> +#include <sys/ioctl.h> +#include <sys/kbio.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/wait.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <dev/vkbd/vkbd_var.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" +#include "btuinput.h" +#include "kbd.h" + +static void kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd); +static int32_t kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const *eob); +static void uinput_kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd); + +/* + * HID code to PS/2 set 1 code translation table. + * + * http://www.microsoft.com/whdc/device/input/Scancode.mspx + * + * The table only contains "make" (key pressed) codes. + * The "break" (key released) code is generated as "make" | 0x80 + */ + +#define E0PREFIX (1U << 31) +#define NOBREAK (1 << 30) +#define CODEMASK (~(E0PREFIX|NOBREAK)) + +static int32_t const x[] = +{ +/*==================================================*/ +/* Name HID code Make Break*/ +/*==================================================*/ +/* No Event 00 */ -1, /* None */ +/* Overrun Error 01 */ NOBREAK|0xFF, /* None */ +/* POST Fail 02 */ NOBREAK|0xFC, /* None */ +/* ErrorUndefined 03 */ -1, /* Unassigned */ +/* a A 04 */ 0x1E, /* 9E */ +/* b B 05 */ 0x30, /* B0 */ +/* c C 06 */ 0x2E, /* AE */ +/* d D 07 */ 0x20, /* A0 */ +/* e E 08 */ 0x12, /* 92 */ +/* f F 09 */ 0x21, /* A1 */ +/* g G 0A */ 0x22, /* A2 */ +/* h H 0B */ 0x23, /* A3 */ +/* i I 0C */ 0x17, /* 97 */ +/* j J 0D */ 0x24, /* A4 */ +/* k K 0E */ 0x25, /* A5 */ +/* l L 0F */ 0x26, /* A6 */ +/* m M 10 */ 0x32, /* B2 */ +/* n N 11 */ 0x31, /* B1 */ +/* o O 12 */ 0x18, /* 98 */ +/* p P 13 */ 0x19, /* 99 */ +/* q Q 14 */ 0x10, /* 90 */ +/* r R 15 */ 0x13, /* 93 */ +/* s S 16 */ 0x1F, /* 9F */ +/* t T 17 */ 0x14, /* 94 */ +/* u U 18 */ 0x16, /* 96 */ +/* v V 19 */ 0x2F, /* AF */ +/* w W 1A */ 0x11, /* 91 */ +/* x X 1B */ 0x2D, /* AD */ +/* y Y 1C */ 0x15, /* 95 */ +/* z Z 1D */ 0x2C, /* AC */ +/* 1 ! 1E */ 0x02, /* 82 */ +/* 2 @ 1F */ 0x03, /* 83 */ +/* 3 # 20 */ 0x04, /* 84 */ +/* 4 $ 21 */ 0x05, /* 85 */ +/* 5 % 22 */ 0x06, /* 86 */ +/* 6 ^ 23 */ 0x07, /* 87 */ +/* 7 & 24 */ 0x08, /* 88 */ +/* 8 * 25 */ 0x09, /* 89 */ +/* 9 ( 26 */ 0x0A, /* 8A */ +/* 0 ) 27 */ 0x0B, /* 8B */ +/* Return 28 */ 0x1C, /* 9C */ +/* Escape 29 */ 0x01, /* 81 */ +/* Backspace 2A */ 0x0E, /* 8E */ +/* Tab 2B */ 0x0F, /* 8F */ +/* Space 2C */ 0x39, /* B9 */ +/* - _ 2D */ 0x0C, /* 8C */ +/* = + 2E */ 0x0D, /* 8D */ +/* [ { 2F */ 0x1A, /* 9A */ +/* ] } 30 */ 0x1B, /* 9B */ +/* \ | 31 */ 0x2B, /* AB */ +/* Europe 1 32 */ 0x2B, /* AB */ +/* ; : 33 */ 0x27, /* A7 */ +/* " ' 34 */ 0x28, /* A8 */ +/* ` ~ 35 */ 0x29, /* A9 */ +/* comma < 36 */ 0x33, /* B3 */ +/* . > 37 */ 0x34, /* B4 */ +/* / ? 38 */ 0x35, /* B5 */ +/* Caps Lock 39 */ 0x3A, /* BA */ +/* F1 3A */ 0x3B, /* BB */ +/* F2 3B */ 0x3C, /* BC */ +/* F3 3C */ 0x3D, /* BD */ +/* F4 3D */ 0x3E, /* BE */ +/* F5 3E */ 0x3F, /* BF */ +/* F6 3F */ 0x40, /* C0 */ +/* F7 40 */ 0x41, /* C1 */ +/* F8 41 */ 0x42, /* C2 */ +/* F9 42 */ 0x43, /* C3 */ +/* F10 43 */ 0x44, /* C4 */ +/* F11 44 */ 0x57, /* D7 */ +/* F12 45 */ 0x58, /* D8 */ +/* Print Screen 46 */ E0PREFIX|0x37, /* E0 B7 */ +/* Scroll Lock 47 */ 0x46, /* C6 */ +#if 0 +/* Break (Ctrl-Pause) 48 */ E0 46 E0 C6, /* None */ +/* Pause 48 */ E1 1D 45 E1 9D C5, /* None */ +#else +/* Break (Ctrl-Pause)/Pause 48 */ NOBREAK /* Special case */, /* None */ +#endif +/* Insert 49 */ E0PREFIX|0x52, /* E0 D2 */ +/* Home 4A */ E0PREFIX|0x47, /* E0 C7 */ +/* Page Up 4B */ E0PREFIX|0x49, /* E0 C9 */ +/* Delete 4C */ E0PREFIX|0x53, /* E0 D3 */ +/* End 4D */ E0PREFIX|0x4F, /* E0 CF */ +/* Page Down 4E */ E0PREFIX|0x51, /* E0 D1 */ +/* Right Arrow 4F */ E0PREFIX|0x4D, /* E0 CD */ +/* Left Arrow 50 */ E0PREFIX|0x4B, /* E0 CB */ +/* Down Arrow 51 */ E0PREFIX|0x50, /* E0 D0 */ +/* Up Arrow 52 */ E0PREFIX|0x48, /* E0 C8 */ +/* Num Lock 53 */ 0x45, /* C5 */ +/* Keypad / 54 */ E0PREFIX|0x35, /* E0 B5 */ +/* Keypad * 55 */ 0x37, /* B7 */ +/* Keypad - 56 */ 0x4A, /* CA */ +/* Keypad + 57 */ 0x4E, /* CE */ +/* Keypad Enter 58 */ E0PREFIX|0x1C, /* E0 9C */ +/* Keypad 1 End 59 */ 0x4F, /* CF */ +/* Keypad 2 Down 5A */ 0x50, /* D0 */ +/* Keypad 3 PageDn 5B */ 0x51, /* D1 */ +/* Keypad 4 Left 5C */ 0x4B, /* CB */ +/* Keypad 5 5D */ 0x4C, /* CC */ +/* Keypad 6 Right 5E */ 0x4D, /* CD */ +/* Keypad 7 Home 5F */ 0x47, /* C7 */ +/* Keypad 8 Up 60 */ 0x48, /* C8 */ +/* Keypad 9 PageUp 61 */ 0x49, /* C9 */ +/* Keypad 0 Insert 62 */ 0x52, /* D2 */ +/* Keypad . Delete 63 */ 0x53, /* D3 */ +/* Europe 2 64 */ 0x56, /* D6 */ +/* App 65 */ E0PREFIX|0x5D, /* E0 DD */ +/* Keyboard Power 66 */ E0PREFIX|0x5E, /* E0 DE */ +/* Keypad = 67 */ 0x59, /* D9 */ +/* F13 68 */ 0x64, /* E4 */ +/* F14 69 */ 0x65, /* E5 */ +/* F15 6A */ 0x66, /* E6 */ +/* F16 6B */ 0x67, /* E7 */ +/* F17 6C */ 0x68, /* E8 */ +/* F18 6D */ 0x69, /* E9 */ +/* F19 6E */ 0x6A, /* EA */ +/* F20 6F */ 0x6B, /* EB */ +/* F21 70 */ 0x6C, /* EC */ +/* F22 71 */ 0x6D, /* ED */ +/* F23 72 */ 0x6E, /* EE */ +/* F24 73 */ 0x76, /* F6 */ +/* Keyboard Execute 74 */ -1, /* Unassigned */ +/* Keyboard Help 75 */ -1, /* Unassigned */ +/* Keyboard Menu 76 */ -1, /* Unassigned */ +/* Keyboard Select 77 */ -1, /* Unassigned */ +/* Keyboard Stop 78 */ -1, /* Unassigned */ +/* Keyboard Again 79 */ -1, /* Unassigned */ +/* Keyboard Undo 7A */ -1, /* Unassigned */ +/* Keyboard Cut 7B */ -1, /* Unassigned */ +/* Keyboard Copy 7C */ -1, /* Unassigned */ +/* Keyboard Paste 7D */ -1, /* Unassigned */ +/* Keyboard Find 7E */ -1, /* Unassigned */ +/* Keyboard Mute 7F */ -1, /* Unassigned */ +/* Keyboard Volume Up 80 */ -1, /* Unassigned */ +/* Keyboard Volume Dn 81 */ -1, /* Unassigned */ +/* Keyboard Locking Caps Lock 82 */ -1, /* Unassigned */ +/* Keyboard Locking Num Lock 83 */ -1, /* Unassigned */ +/* Keyboard Locking Scroll Lock 84 */ -1, /* Unassigned */ +/* Keypad comma 85 */ 0x7E, /* FE */ +/* Keyboard Equal Sign 86 */ -1, /* Unassigned */ +/* Keyboard Int'l 1 87 */ 0x73, /* F3 */ +/* Keyboard Int'l 2 88 */ 0x70, /* F0 */ +/* Keyboard Int'l 2 89 */ 0x7D, /* FD */ +/* Keyboard Int'l 4 8A */ 0x79, /* F9 */ +/* Keyboard Int'l 5 8B */ 0x7B, /* FB */ +/* Keyboard Int'l 6 8C */ 0x5C, /* DC */ +/* Keyboard Int'l 7 8D */ -1, /* Unassigned */ +/* Keyboard Int'l 8 8E */ -1, /* Unassigned */ +/* Keyboard Int'l 9 8F */ -1, /* Unassigned */ +/* Keyboard Lang 1 90 */ 0x71, /* Kana */ +/* Keyboard Lang 2 91 */ 0x72, /* Eisu */ +/* Keyboard Lang 3 92 */ 0x78, /* F8 */ +/* Keyboard Lang 4 93 */ 0x77, /* F7 */ +/* Keyboard Lang 5 94 */ 0x76, /* F6 */ +/* Keyboard Lang 6 95 */ -1, /* Unassigned */ +/* Keyboard Lang 7 96 */ -1, /* Unassigned */ +/* Keyboard Lang 8 97 */ -1, /* Unassigned */ +/* Keyboard Lang 9 98 */ -1, /* Unassigned */ +/* Keyboard Alternate Erase 99 */ -1, /* Unassigned */ +/* Keyboard SysReq/Attention 9A */ -1, /* Unassigned */ +/* Keyboard Cancel 9B */ -1, /* Unassigned */ +/* Keyboard Clear 9C */ -1, /* Unassigned */ +/* Keyboard Prior 9D */ -1, /* Unassigned */ +/* Keyboard Return 9E */ -1, /* Unassigned */ +/* Keyboard Separator 9F */ -1, /* Unassigned */ +/* Keyboard Out A0 */ -1, /* Unassigned */ +/* Keyboard Oper A1 */ -1, /* Unassigned */ +/* Keyboard Clear/Again A2 */ -1, /* Unassigned */ +/* Keyboard CrSel/Props A3 */ -1, /* Unassigned */ +/* Keyboard ExSel A4 */ -1, /* Unassigned */ +/* Reserved A5 */ -1, /* Reserved */ +/* Reserved A6 */ -1, /* Reserved */ +/* Reserved A7 */ -1, /* Reserved */ +/* Reserved A8 */ -1, /* Reserved */ +/* Reserved A9 */ -1, /* Reserved */ +/* Reserved AA */ -1, /* Reserved */ +/* Reserved AB */ -1, /* Reserved */ +/* Reserved AC */ -1, /* Reserved */ +/* Reserved AD */ -1, /* Reserved */ +/* Reserved AE */ -1, /* Reserved */ +/* Reserved AF */ -1, /* Reserved */ +/* Reserved B0 */ -1, /* Reserved */ +/* Reserved B1 */ -1, /* Reserved */ +/* Reserved B2 */ -1, /* Reserved */ +/* Reserved B3 */ -1, /* Reserved */ +/* Reserved B4 */ -1, /* Reserved */ +/* Reserved B5 */ -1, /* Reserved */ +/* Reserved B6 */ -1, /* Reserved */ +/* Reserved B7 */ -1, /* Reserved */ +/* Reserved B8 */ -1, /* Reserved */ +/* Reserved B9 */ -1, /* Reserved */ +/* Reserved BA */ -1, /* Reserved */ +/* Reserved BB */ -1, /* Reserved */ +/* Reserved BC */ -1, /* Reserved */ +/* Reserved BD */ -1, /* Reserved */ +/* Reserved BE */ -1, /* Reserved */ +/* Reserved BF */ -1, /* Reserved */ +/* Reserved C0 */ -1, /* Reserved */ +/* Reserved C1 */ -1, /* Reserved */ +/* Reserved C2 */ -1, /* Reserved */ +/* Reserved C3 */ -1, /* Reserved */ +/* Reserved C4 */ -1, /* Reserved */ +/* Reserved C5 */ -1, /* Reserved */ +/* Reserved C6 */ -1, /* Reserved */ +/* Reserved C7 */ -1, /* Reserved */ +/* Reserved C8 */ -1, /* Reserved */ +/* Reserved C9 */ -1, /* Reserved */ +/* Reserved CA */ -1, /* Reserved */ +/* Reserved CB */ -1, /* Reserved */ +/* Reserved CC */ -1, /* Reserved */ +/* Reserved CD */ -1, /* Reserved */ +/* Reserved CE */ -1, /* Reserved */ +/* Reserved CF */ -1, /* Reserved */ +/* Reserved D0 */ -1, /* Reserved */ +/* Reserved D1 */ -1, /* Reserved */ +/* Reserved D2 */ -1, /* Reserved */ +/* Reserved D3 */ -1, /* Reserved */ +/* Reserved D4 */ -1, /* Reserved */ +/* Reserved D5 */ -1, /* Reserved */ +/* Reserved D6 */ -1, /* Reserved */ +/* Reserved D7 */ -1, /* Reserved */ +/* Reserved D8 */ -1, /* Reserved */ +/* Reserved D9 */ -1, /* Reserved */ +/* Reserved DA */ -1, /* Reserved */ +/* Reserved DB */ -1, /* Reserved */ +/* Reserved DC */ -1, /* Reserved */ +/* Reserved DD */ -1, /* Reserved */ +/* Reserved DE */ -1, /* Reserved */ +/* Reserved DF */ -1, /* Reserved */ +/* Left Control E0 */ 0x1D, /* 9D */ +/* Left Shift E1 */ 0x2A, /* AA */ +/* Left Alt E2 */ 0x38, /* B8 */ +/* Left GUI E3 */ E0PREFIX|0x5B, /* E0 DB */ +/* Right Control E4 */ E0PREFIX|0x1D, /* E0 9D */ +/* Right Shift E5 */ 0x36, /* B6 */ +/* Right Alt E6 */ E0PREFIX|0x38, /* E0 B8 */ +/* Right GUI E7 */ E0PREFIX|0x5C /* E0 DC */ +}; + +#define xsize (int32_t)nitems(x) + +/* + * Get a max HID keycode (aligned) + */ + +int32_t +kbd_maxkey(void) +{ + return (xsize); +} + +/* + * Process keys + */ + +int32_t +kbd_process_keys(bthid_session_p s) +{ + bitstr_t bit_decl(diff, xsize); + int32_t f1, f2, i; + + assert(s != NULL); + assert(s->srv != NULL); + + /* Check if the new keys have been pressed */ + bit_ffs(s->keys1, xsize, &f1); + + /* Check if old keys still pressed */ + bit_ffs(s->keys2, xsize, &f2); + + if (f1 == -1) { + /* no new key pressed */ + if (f2 != -1) { + /* release old keys */ + kbd_write(s->keys2, f2, 0, s->vkbd); + uinput_kbd_write(s->keys2, f2, 0, s->ukbd); + memset(s->keys2, 0, bitstr_size(xsize)); + } + + return (0); + } + + if (f2 == -1) { + /* no old keys, but new keys pressed */ + assert(f1 != -1); + + memcpy(s->keys2, s->keys1, bitstr_size(xsize)); + kbd_write(s->keys1, f1, 1, s->vkbd); + uinput_kbd_write(s->keys1, f1, 1, s->ukbd); + memset(s->keys1, 0, bitstr_size(xsize)); + + return (0); + } + + /* new keys got pressed, old keys got released */ + memset(diff, 0, bitstr_size(xsize)); + + for (i = f2; i < xsize; i ++) { + if (bit_test(s->keys2, i)) { + if (!bit_test(s->keys1, i)) { + bit_clear(s->keys2, i); + bit_set(diff, i); + } + } + } + + for (i = f1; i < xsize; i++) { + if (bit_test(s->keys1, i)) { + if (!bit_test(s->keys2, i)) + bit_set(s->keys2, i); + else + bit_clear(s->keys1, i); + } + } + + bit_ffs(diff, xsize, &f2); + if (f2 > 0) { + kbd_write(diff, f2, 0, s->vkbd); + uinput_kbd_write(diff, f2, 0, s->ukbd); + } + + bit_ffs(s->keys1, xsize, &f1); + if (f1 > 0) { + kbd_write(s->keys1, f1, 1, s->vkbd); + uinput_kbd_write(s->keys1, f1, 1, s->ukbd); + memset(s->keys1, 0, bitstr_size(xsize)); + } + + return (0); +} + +/* + * Translate given keymap and write keyscodes + */ +void +uinput_kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd) +{ + int32_t i; + + if (fd >= 0) { + for (i = fb; i < xsize; i++) { + if (bit_test(m, i)) + uinput_rep_key(fd, i, make); + } + } +} + +/* + * Translate given keymap and write keyscodes + */ + +static void +kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd) +{ + int32_t i, *b, *eob, n, buf[64]; + + b = buf; + eob = b + nitems(buf); + i = fb; + + while (i < xsize) { + if (bit_test(m, i)) { + n = kbd_xlate(i, make, b, eob); + if (n == -1) { + write(fd, buf, (b - buf) * sizeof(buf[0])); + b = buf; + continue; + } + + b += n; + } + + i ++; + } + + if (b != buf) + write(fd, buf, (b - buf) * sizeof(buf[0])); +} + +/* + * Translate HID code into PS/2 code and put codes into buffer b. + * Returns the number of codes put in b. Return -1 if buffer has not + * enough space. + */ + +#undef PUT +#define PUT(c, n, b, eob) \ +do { \ + if ((b) >= (eob)) \ + return (-1); \ + *(b) = (c); \ + (b) ++; \ + (n) ++; \ +} while (0) + +static int32_t +kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const *eob) +{ + int32_t c, n; + + n = 0; + + if (code >= xsize) + return (0); /* HID code is not in the table */ + + /* Handle special case - Pause/Break */ + if (code == 0x48) { + if (!make) + return (0); /* No break code */ + +#if 0 +XXX FIXME + if (ctrl_is_pressed) { + /* Break (Ctrl-Pause) */ + PUT(0xe0, n, b, eob); + PUT(0x46, n, b, eob); + PUT(0xe0, n, b, eob); + PUT(0xc6, n, b, eob); + } else { + /* Pause */ + PUT(0xe1, n, b, eob); + PUT(0x1d, n, b, eob); + PUT(0x45, n, b, eob); + PUT(0xe1, n, b, eob); + PUT(0x9d, n, b, eob); + PUT(0xc5, n, b, eob); + } +#endif + + return (n); + } + + if ((c = x[code]) == -1) + return (0); /* HID code translation is not defined */ + + if (make) { + if (c & E0PREFIX) + PUT(0xe0, n, b, eob); + + PUT((c & CODEMASK), n, b, eob); + } else if (!(c & NOBREAK)) { + if (c & E0PREFIX) + PUT(0xe0, n, b, eob); + + PUT((0x80|(c & CODEMASK)), n, b, eob); + } + + return (n); +} + +/* + * Process status change from vkbd(4) + */ + +int32_t +kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len) +{ + vkbd_status_t st; + uint8_t found, report_id; + hid_device_p hid_device; + hid_data_t d; + hid_item_t h; + uint8_t leds_mask = 0; + + assert(s != NULL); + assert(len == sizeof(vkbd_status_t)); + + memcpy(&st, data, sizeof(st)); + found = 0; + report_id = NO_REPORT_ID; + + hid_device = get_hid_device(&s->bdaddr); + assert(hid_device != NULL); + + data[0] = 0xa2; /* DATA output (HID output report) */ + data[1] = 0x00; + data[2] = 0x00; + + for (d = hid_start_parse(hid_device->desc, 1 << hid_output, -1); + hid_get_item(d, &h) > 0; ) { + if (HID_PAGE(h.usage) == HUP_LEDS) { + found++; + + if (report_id == NO_REPORT_ID) + report_id = h.report_ID; + else if (h.report_ID != report_id) + syslog(LOG_WARNING, "Output HID report IDs " \ + "for %s do not match: %d vs. %d. " \ + "Please report", + bt_ntoa(&s->bdaddr, NULL), + h.report_ID, report_id); + + switch(HID_USAGE(h.usage)) { + case 0x01: /* Num Lock LED */ + if (st.leds & LED_NUM) + hid_set_data(&data[1], &h, 1); + leds_mask |= LED_NUM; + break; + + case 0x02: /* Caps Lock LED */ + if (st.leds & LED_CAP) + hid_set_data(&data[1], &h, 1); + leds_mask |= LED_CAP; + break; + + case 0x03: /* Scroll Lock LED */ + if (st.leds & LED_SCR) + hid_set_data(&data[1], &h, 1); + leds_mask |= LED_SCR; + break; + + /* XXX add other LEDs ? */ + } + } + } + hid_end_parse(d); + + if (report_id != NO_REPORT_ID) { + data[2] = data[1]; + data[1] = report_id; + } + + if (found) + write(s->intr, data, (report_id != NO_REPORT_ID) ? 3 : 2); + + if (found && s->srv->uinput && hid_device->keyboard) + uinput_rep_leds(s->ukbd, st.leds, leds_mask); + + return (0); +} + diff --git a/usr.sbin/bluetooth/bthidd/kbd.h b/usr.sbin/bluetooth/bthidd/kbd.h new file mode 100644 index 000000000000..d12d51d0e62f --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/kbd.h @@ -0,0 +1,42 @@ +/* + * kbd.h + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: kbd.h,v 1.3 2006/09/07 21:06:53 max Exp $ + */ + +#ifndef _KBD_H_ +#define _KBD_H_ + +int32_t kbd_maxkey (void); +int32_t kbd_process_keys (bthid_session_p s); +int32_t kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len); + +#endif /* ndef _KBD_H_ */ diff --git a/usr.sbin/bluetooth/bthidd/lexer.l b/usr.sbin/bluetooth/bthidd/lexer.l new file mode 100644 index 000000000000..d8933e4ea5d7 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/lexer.l @@ -0,0 +1,132 @@ +%{ +/* + * lexer.l + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: lexer.l,v 1.3 2006/09/07 21:06:53 max Exp $ + */ +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <stdlib.h> +#include "parser.h" + + int yylex (void); + +#define YY_DECL int yylex(void) +%} + +%option yylineno noyywrap nounput noinput + +delim [ \t\n] +ws {delim}+ +empty {delim}* +comment \#.* + +hexdigit [0-9a-fA-F] +hexbyte {hexdigit}{hexdigit}? +hexword {hexdigit}{hexdigit}?{hexdigit}?{hexdigit}? + +device_word device +bdaddr_word bdaddr +name_word name +vendor_id_word vendor_id +product_id_word product_id +version_word version +control_psm_word control_psm +interrupt_psm_word interrupt_psm +reconnect_initiate_word reconnect_initiate +battery_power_word battery_power +normally_connectable_word normally_connectable +hid_descriptor_word hid_descriptor +true_word true +false_word false + +bdaddrstring {hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte} +hexbytestring 0x{hexbyte} +hexwordstring 0x{hexword} +string \".+\" + +%% + +\; return (';'); +\: return (':'); +\{ return ('{'); +\} return ('}'); + +{ws} ; +{empty} ; +{comment} ; + +{device_word} return (T_DEVICE); +{bdaddr_word} return (T_BDADDR); +{name_word} return (T_NAME); +{vendor_id_word} return (T_VENDOR_ID); +{product_id_word} return (T_PRODUCT_ID); +{version_word} return (T_VERSION); +{control_psm_word} return (T_CONTROL_PSM); +{interrupt_psm_word} return (T_INTERRUPT_PSM); +{reconnect_initiate_word} return (T_RECONNECT_INITIATE); +{battery_power_word} return (T_BATTERY_POWER); +{normally_connectable_word} return (T_NORMALLY_CONNECTABLE); +{hid_descriptor_word} return (T_HID_DESCRIPTOR); +{true_word} return (T_TRUE); +{false_word} return (T_FALSE); + +{bdaddrstring} { + return (bt_aton(yytext, &yylval.bdaddr)? + T_BDADDRSTRING : T_ERROR); + } + +{hexbytestring} { + char *ep; + + yylval.num = strtoul(yytext, &ep, 16); + + return (*ep == '\0'? T_HEXBYTE : T_ERROR); + } + +{hexwordstring} { + char *ep; + + yylval.num = strtoul(yytext, &ep, 16); + + return (*ep == '\0'? T_HEXWORD : T_ERROR); + } + +{string} { + yytext[strlen(yytext) - 1] = 0; + yylval.string = &yytext[1]; + return (T_STRING); + } + +. return (T_ERROR); + +%% + diff --git a/usr.sbin/bluetooth/bthidd/parser.y b/usr.sbin/bluetooth/bthidd/parser.y new file mode 100644 index 000000000000..a18ef3515b8b --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/parser.y @@ -0,0 +1,565 @@ +%{ +/* + * parser.y + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: parser.y,v 1.7 2006/09/07 21:06:53 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <usbhid.h> + +#ifndef BTHIDCONTROL +#include <stdarg.h> +#include <syslog.h> +#define SYSLOG syslog +#define LOGCRIT LOG_CRIT +#define LOGERR LOG_ERR +#define LOGWARNING LOG_WARNING +#define EOL +#else +#define SYSLOG fprintf +#define LOGCRIT stderr +#define LOGERR stderr +#define LOGWARNING stderr +#define EOL "\n" +#endif /* ndef BTHIDCONTROL */ + +#define NAMELESS_DEVICE "No Name" + +#include "bthid_config.h" + + int yylex (void); + void yyerror (char const *); +static int32_t check_hid_device(hid_device_p hid_device); +static void free_hid_device (hid_device_p hid_device); + +extern FILE *yyin; +extern int yylineno; + char const *config_file = BTHIDD_CONFFILE; + char const *hids_file = BTHIDD_HIDSFILE; + +static char buffer[1024]; +static int32_t hid_descriptor_size; +static hid_device_t *hid_device = NULL; +static LIST_HEAD(, hid_device) hid_devices; + +%} + +%union { + bdaddr_t bdaddr; + int32_t num; + char *string; +} + +%token <bdaddr> T_BDADDRSTRING +%token <num> T_HEXBYTE +%token <num> T_HEXWORD +%token <string> T_STRING +%token T_NAME +%token T_DEVICE T_BDADDR T_VENDOR_ID T_PRODUCT_ID T_VERSION T_CONTROL_PSM +%token T_INTERRUPT_PSM T_RECONNECT_INITIATE T_BATTERY_POWER +%token T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR +%token T_TRUE T_FALSE T_ERROR + +%% + +config: line + | config line + ; + +line: T_DEVICE + { + hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device)); + if (hid_device == NULL) { + SYSLOG(LOGCRIT, "Could not allocate new " \ + "config entry" EOL); + YYABORT; + } + + hid_device->new_device = 1; + } + '{' options '}' + { + if (check_hid_device(hid_device)) + LIST_INSERT_HEAD(&hid_devices,hid_device,next); + else + free_hid_device(hid_device); + + hid_device = NULL; + } + ; + +options: option ';' + | options option ';' + ; + +option: bdaddr + | name + | vendor_id + | product_id + | version + | control_psm + | interrupt_psm + | reconnect_initiate + | battery_power + | normally_connectable + | hid_descriptor + | parser_error + ; + +bdaddr: T_BDADDR T_BDADDRSTRING + { + memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr)); + } + ; + +name: T_NAME T_STRING + { + if (hid_device->name != NULL) { + free(hid_device->name); + hid_device->name = NULL; + } + + if (strcmp($2, NAMELESS_DEVICE)) { + hid_device->name = strdup($2); + if (hid_device->name == NULL) { + SYSLOG(LOGCRIT, "Could not allocate new " \ + "device name" EOL); + YYABORT; + } + } + } + ; + +vendor_id: T_VENDOR_ID T_HEXWORD + { + hid_device->vendor_id = $2; + } + ; + +product_id: T_PRODUCT_ID T_HEXWORD + { + hid_device->product_id = $2; + } + ; + +version: T_VERSION T_HEXWORD + { + hid_device->version = $2; + } + ; + +control_psm: T_CONTROL_PSM T_HEXBYTE + { + hid_device->control_psm = $2; + } + ; + +interrupt_psm: T_INTERRUPT_PSM T_HEXBYTE + { + hid_device->interrupt_psm = $2; + } + ; + +reconnect_initiate: T_RECONNECT_INITIATE T_TRUE + { + hid_device->reconnect_initiate = 1; + } + | T_RECONNECT_INITIATE T_FALSE + { + hid_device->reconnect_initiate = 0; + } + ; + +battery_power: T_BATTERY_POWER T_TRUE + { + hid_device->battery_power = 1; + } + | T_BATTERY_POWER T_FALSE + { + hid_device->battery_power = 0; + } + ; + +normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE + { + hid_device->normally_connectable = 1; + } + | T_NORMALLY_CONNECTABLE T_FALSE + { + hid_device->normally_connectable = 0; + } + ; + +hid_descriptor: T_HID_DESCRIPTOR + { + hid_descriptor_size = 0; + } + '{' hid_descriptor_bytes '}' + { + if (hid_device->desc != NULL) + hid_dispose_report_desc(hid_device->desc); + + hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size); + if (hid_device->desc == NULL) { + SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL); + YYABORT; + } + } + ; + +hid_descriptor_bytes: hid_descriptor_byte + | hid_descriptor_bytes hid_descriptor_byte + ; + +hid_descriptor_byte: T_HEXBYTE + { + if (hid_descriptor_size >= (int32_t) sizeof(buffer)) { + SYSLOG(LOGCRIT, "HID descriptor is too big" EOL); + YYABORT; + } + + buffer[hid_descriptor_size ++] = $1; + } + ; + +parser_error: T_ERROR + { + YYABORT; + } + +%% + +/* Display parser error message */ +void +yyerror(char const *message) +{ + SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno); +} + +/* Re-read config file */ +int32_t +read_config_file(void) +{ + int32_t e; + + if (config_file == NULL) { + SYSLOG(LOGERR, "Unknown config file name!" EOL); + return (-1); + } + + if ((yyin = fopen(config_file, "r")) == NULL) { + SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL, + config_file, strerror(errno), errno); + return (-1); + } + + clean_config(); + if (yyparse() < 0) { + SYSLOG(LOGERR, "Could not parse config file '%s'" EOL, + config_file); + e = -1; + } else + e = 0; + + fclose(yyin); + yyin = NULL; + + return (e); +} + +/* Clean config */ +void +clean_config(void) +{ + while (!LIST_EMPTY(&hid_devices)) { + hid_device_p d = LIST_FIRST(&hid_devices); + + LIST_REMOVE(d, next); + free_hid_device(d); + } +} + +/* Lookup config entry */ +hid_device_p +get_hid_device(bdaddr_p bdaddr) +{ + hid_device_p d; + + LIST_FOREACH(d, &hid_devices, next) + if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0) + break; + + return (d); +} + +/* Get next config entry */ +hid_device_p +get_next_hid_device(hid_device_p d) +{ + return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next)); +} + +/* Print config entry */ +void +print_hid_device(hid_device_p d, FILE *f) +{ + /* XXX FIXME hack! */ + struct report_desc { + unsigned int size; + unsigned char data[1]; + }; + /* XXX FIXME hack! */ + + struct report_desc *desc = (struct report_desc *) d->desc; + uint32_t i; + + fprintf(f, +"device {\n" \ +" bdaddr %s;\n" \ +" name \"%s\";\n" \ +" vendor_id 0x%04x;\n" \ +" product_id 0x%04x;\n" \ +" version 0x%04x;\n" \ +" control_psm 0x%x;\n" \ +" interrupt_psm 0x%x;\n" \ +" reconnect_initiate %s;\n" \ +" battery_power %s;\n" \ +" normally_connectable %s;\n" \ +" hid_descriptor {", + bt_ntoa(&d->bdaddr, NULL), + (d->name != NULL)? d->name : NAMELESS_DEVICE, + d->vendor_id, d->product_id, d->version, + d->control_psm, d->interrupt_psm, + d->reconnect_initiate? "true" : "false", + d->battery_power? "true" : "false", + d->normally_connectable? "true" : "false"); + + for (i = 0; i < desc->size; i ++) { + if ((i % 8) == 0) + fprintf(f, "\n "); + + fprintf(f, "0x%2.2x ", desc->data[i]); + } + + fprintf(f, +"\n" \ +" };\n" \ +"}\n"); +} + +/* Check config entry */ +static int32_t +check_hid_device(hid_device_p d) +{ + hid_data_t hd; + hid_item_t hi; + int32_t page, mdepth; + + if (get_hid_device(&d->bdaddr) != NULL) { + SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL, + bt_ntoa(&d->bdaddr, NULL)); + return (0); + } + + if (d->control_psm == 0) { + SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL); + return (0); + } + + if (d->interrupt_psm == 0) { + SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL); + return (0); + } + + if (d->desc == NULL) { + SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL); + return (0); + } + + mdepth = 0; + + /* XXX somehow need to make sure descriptor is valid */ + for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) { + switch (hi.kind) { + case hid_collection: + if (mdepth != 0) + mdepth++; + else if (hi.collection == 1 && + hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)) + mdepth++; + break; + case hid_endcollection: + if (mdepth != 0) + mdepth--; + break; + case hid_output: + case hid_feature: + break; + + case hid_input: + /* Check if the device may send keystrokes */ + page = HID_PAGE(hi.usage); + if (page == HUP_KEYBOARD) + d->keyboard = 1; + if (page == HUP_CONSUMER && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == 0) + d->has_cons = 1; + /* Check if the device may send relative motion events */ + if (mdepth == 0) + break; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + d->mouse = 1; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + d->mouse = 1; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + d->has_wheel = 1; + if (hi.usage == + HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + d->has_hwheel = 1; + break; + } + } + hid_end_parse(hd); + + return (1); +} + +/* Free config entry */ +static void +free_hid_device(hid_device_p d) +{ + if (d->desc != NULL) + hid_dispose_report_desc(d->desc); + + free(d->name); + memset(d, 0, sizeof(*d)); + free(d); +} + +/* Re-read hids file */ +int32_t +read_hids_file(void) +{ + FILE *f; + hid_device_t *d; + char *line; + bdaddr_t bdaddr; + int32_t lineno; + + if (hids_file == NULL) { + SYSLOG(LOGERR, "Unknown HIDs file name!" EOL); + return (-1); + } + + if ((f = fopen(hids_file, "r")) == NULL) { + if (errno == ENOENT) + return (0); + + SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL, + hids_file, strerror(errno), errno); + return (-1); + } + + for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) { + if ((line = strtok(buffer, "\r\n\t ")) == NULL) + continue; /* ignore empty lines */ + + if (!bt_aton(line, &bdaddr)) { + SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \ + "%s:%d" EOL, hids_file, lineno); + continue; + } + + if ((d = get_hid_device(&bdaddr)) != NULL) + d->new_device = 0; + } + + fclose(f); + + return (0); +} + +/* Write hids file */ +int32_t +write_hids_file(void) +{ + char path[PATH_MAX]; + FILE *f; + hid_device_t *d; + + if (hids_file == NULL) { + SYSLOG(LOGERR, "Unknown HIDs file name!" EOL); + return (-1); + } + + snprintf(path, sizeof(path), "%s.new", hids_file); + + if ((f = fopen(path, "w")) == NULL) { + SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL, + path, strerror(errno), errno); + return (-1); + } + + LIST_FOREACH(d, &hid_devices, next) + if (!d->new_device) + fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL)); + + fclose(f); + + if (rename(path, hids_file) < 0) { + SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \ + "%s (%d)" EOL, path, hids_file, strerror(errno), errno); + unlink(path); + return (-1); + } + + return (0); +} + diff --git a/usr.sbin/bluetooth/bthidd/server.c b/usr.sbin/bluetooth/bthidd/server.c new file mode 100644 index 000000000000..c532561265cd --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/server.c @@ -0,0 +1,356 @@ +/* + * server.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: server.c,v 1.9 2006/09/07 21:06:53 max Exp $ + */ + +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/evdev/input.h> +#include <dev/vkbd/vkbd_var.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" +#include "btuinput.h" +#include "kbd.h" + +#undef max +#define max(x, y) (((x) > (y))? (x) : (y)) + +static int32_t server_accept (bthid_server_p srv, int32_t fd); +static int32_t server_process(bthid_server_p srv, int32_t fd); + +/* + * Initialize server + */ + +int32_t +server_init(bthid_server_p srv) +{ + struct sockaddr_l2cap l2addr; + + assert(srv != NULL); + + srv->ctrl = srv->intr = -1; + FD_ZERO(&srv->rfdset); + FD_ZERO(&srv->wfdset); + LIST_INIT(&srv->sessions); + + /* Open /dev/consolectl */ + srv->cons = open("/dev/consolectl", O_RDWR); + if (srv->cons < 0) { + syslog(LOG_ERR, "Could not open /dev/consolectl. %s (%d)", + strerror(errno), errno); + return (-1); + } + + /* Create control socket */ + srv->ctrl = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (srv->ctrl < 0) { + syslog(LOG_ERR, "Could not create control L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->cons); + return (-1); + } + + l2addr.l2cap_len = sizeof(l2addr); + l2addr.l2cap_family = AF_BLUETOOTH; + memcpy(&l2addr.l2cap_bdaddr, &srv->bdaddr, sizeof(l2addr.l2cap_bdaddr)); + l2addr.l2cap_psm = htole16(0x11); + l2addr.l2cap_bdaddr_type = BDADDR_BREDR; + l2addr.l2cap_cid = 0; + + if (bind(srv->ctrl, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { + syslog(LOG_ERR, "Could not bind control L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + if (listen(srv->ctrl, 10) < 0) { + syslog(LOG_ERR, "Could not listen on control L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + /* Create interrupt socket */ + srv->intr = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (srv->intr < 0) { + syslog(LOG_ERR, "Could not create interrupt L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + l2addr.l2cap_psm = htole16(0x13); + + if (bind(srv->intr, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { + syslog(LOG_ERR, "Could not bind interrupt L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->intr); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + if (listen(srv->intr, 10) < 0) { + syslog(LOG_ERR, "Could not listen on interrupt L2CAP socket. "\ + "%s (%d)", strerror(errno), errno); + close(srv->intr); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + FD_SET(srv->ctrl, &srv->rfdset); + FD_SET(srv->intr, &srv->rfdset); + srv->maxfd = max(srv->ctrl, srv->intr); + + return (0); +} + +/* + * Shutdown server + */ + +void +server_shutdown(bthid_server_p srv) +{ + assert(srv != NULL); + + close(srv->cons); + close(srv->ctrl); + close(srv->intr); + + while (!LIST_EMPTY(&srv->sessions)) + session_close(LIST_FIRST(&srv->sessions)); + + memset(srv, 0, sizeof(*srv)); +} + +/* + * Do one server iteration + */ + +int32_t +server_do(bthid_server_p srv) +{ + struct timeval tv; + fd_set rfdset, wfdset; + int32_t n, fd; + + assert(srv != NULL); + + tv.tv_sec = 1; + tv.tv_usec = 0; + + /* Copy cached version of the fd sets and call select */ + memcpy(&rfdset, &srv->rfdset, sizeof(rfdset)); + memcpy(&wfdset, &srv->wfdset, sizeof(wfdset)); + + n = select(srv->maxfd + 1, &rfdset, &wfdset, NULL, &tv); + if (n < 0) { + if (errno == EINTR) + return (0); + + syslog(LOG_ERR, "Could not select(%d, %p, %p). %s (%d)", + srv->maxfd + 1, &rfdset, &wfdset, strerror(errno), errno); + + return (-1); + } + + /* Process descriptors (if any) */ + for (fd = 0; fd < srv->maxfd + 1 && n > 0; fd ++) { + if (FD_ISSET(fd, &rfdset)) { + n --; + + if (fd == srv->ctrl || fd == srv->intr) + server_accept(srv, fd); + else + server_process(srv, fd); + } else if (FD_ISSET(fd, &wfdset)) { + n --; + + client_connect(srv, fd); + } + } + + return (0); +} + +/* + * Accept new connection + */ + +static int32_t +server_accept(bthid_server_p srv, int32_t fd) +{ + bthid_session_p s; + hid_device_p d; + struct sockaddr_l2cap l2addr; + int32_t new_fd; + socklen_t len; + + len = sizeof(l2addr); + if ((new_fd = accept(fd, (struct sockaddr *) &l2addr, &len)) < 0) { + syslog(LOG_ERR, "Could not accept %s connection. %s (%d)", + (fd == srv->ctrl)? "control" : "interrupt", + strerror(errno), errno); + return (-1); + } + + /* Is device configured? */ + if ((d = get_hid_device(&l2addr.l2cap_bdaddr)) == NULL) { + syslog(LOG_ERR, "Rejecting %s connection from %s. " \ + "Device not configured", + (fd == srv->ctrl)? "control" : "interrupt", + bt_ntoa(&l2addr.l2cap_bdaddr, NULL)); + close(new_fd); + return (-1); + } + + /* Check if we have session for the device */ + if ((s = session_by_bdaddr(srv, &l2addr.l2cap_bdaddr)) == NULL) { + d->new_device = 0; /* reset new device flag */ + write_hids_file(); + + /* Create new inbound session */ + if ((s = session_open(srv, d)) == NULL) { + syslog(LOG_CRIT, "Could not open inbound session " + "for %s", bt_ntoa(&l2addr.l2cap_bdaddr, NULL)); + close(new_fd); + return (-1); + } + } + + /* Update descriptors */ + if (fd == srv->ctrl) { + assert(s->ctrl == -1); + s->ctrl = new_fd; + s->state = (s->intr == -1)? W4INTR : OPEN; + } else { + assert(s->intr == -1); + s->intr = new_fd; + s->state = (s->ctrl == -1)? W4CTRL : OPEN; + } + + FD_SET(new_fd, &srv->rfdset); + if (new_fd > srv->maxfd) + srv->maxfd = new_fd; + + syslog(LOG_NOTICE, "Accepted %s connection from %s", + (fd == srv->ctrl)? "control" : "interrupt", + bt_ntoa(&l2addr.l2cap_bdaddr, NULL)); + + /* Create virtual kbd/mouse after both channels are established */ + if (s->state == OPEN && session_run(s) < 0) { + session_close(s); + return (-1); + } + + return (0); +} + +/* + * Process data on the connection + */ + +static int32_t +server_process(bthid_server_p srv, int32_t fd) +{ + bthid_session_p s = session_by_fd(srv, fd); + int32_t len, to_read; + int32_t (*cb)(bthid_session_p, uint8_t *, int32_t); + union { + uint8_t b[1024]; + vkbd_status_t s; + struct input_event ie; + } data; + + if (s == NULL) + return (0); /* can happen on device disconnect */ + + + if (fd == s->ctrl) { + cb = hid_control; + to_read = sizeof(data.b); + } else if (fd == s->intr) { + cb = hid_interrupt; + to_read = sizeof(data.b); + } else if (fd == s->ukbd) { + cb = uinput_kbd_status_changed; + to_read = sizeof(data.ie); + } else { + assert(fd == s->vkbd); + + cb = kbd_status_changed; + to_read = sizeof(data.s); + } + + do { + len = read(fd, &data, to_read); + } while (len < 0 && errno == EINTR); + + if (len < 0) { + syslog(LOG_ERR, "Could not read data from %s (%s). %s (%d)", + bt_ntoa(&s->bdaddr, NULL), + (fd == s->ctrl)? "control" : "interrupt", + strerror(errno), errno); + session_close(s); + return (0); + } + + if (len == 0) { + syslog(LOG_NOTICE, "Remote device %s has closed %s connection", + bt_ntoa(&s->bdaddr, NULL), + (fd == s->ctrl)? "control" : "interrupt"); + session_close(s); + return (0); + } + + (*cb)(s, (uint8_t *) &data, len); + + return (0); +} + diff --git a/usr.sbin/bluetooth/bthidd/session.c b/usr.sbin/bluetooth/bthidd/session.c new file mode 100644 index 000000000000..ab9fa59dab65 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/session.c @@ -0,0 +1,249 @@ +/* + * session.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006 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. + * + * $Id: session.c,v 1.3 2006/09/07 21:06:53 max Exp $ + */ + +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" +#include "btuinput.h" +#include "kbd.h" + +/* + * Create new session + */ + +bthid_session_p +session_open(bthid_server_p srv, hid_device_p const d) +{ + bthid_session_p s; + + assert(srv != NULL); + assert(d != NULL); + + if ((s = (bthid_session_p) malloc(sizeof(*s))) == NULL) + return (NULL); + + s->srv = srv; + memcpy(&s->bdaddr, &d->bdaddr, sizeof(s->bdaddr)); + s->ctrl = -1; + s->intr = -1; + s->vkbd = -1; + s->ctx = NULL; + s->state = CLOSED; + s->ukbd = -1; + s->umouse = -1; + s->obutt = 0; + + s->keys1 = bit_alloc(kbd_maxkey()); + if (s->keys1 == NULL) { + free(s); + return (NULL); + } + + s->keys2 = bit_alloc(kbd_maxkey()); + if (s->keys2 == NULL) { + free(s->keys1); + free(s); + return (NULL); + } + + LIST_INSERT_HEAD(&srv->sessions, s, next); + + return (s); +} + +/* + * Initialize virtual keyboard and mouse after both channels are established + */ + +int32_t +session_run(bthid_session_p s) +{ + hid_device_p d = get_hid_device(&s->bdaddr); + struct sockaddr_l2cap local; + socklen_t len; + + if (d->keyboard) { + /* Open /dev/vkbdctl */ + s->vkbd = open("/dev/vkbdctl", O_RDWR); + if (s->vkbd < 0) { + syslog(LOG_ERR, "Could not open /dev/vkbdctl " \ + "for %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + return (-1); + } + /* Register session's vkbd descriptor (if needed) for read */ + FD_SET(s->vkbd, &s->srv->rfdset); + if (s->vkbd > s->srv->maxfd) + s->srv->maxfd = s->vkbd; + } + + /* Pass device for probing */ + hid_initialise(s); + + /* Take local bdaddr */ + len = sizeof(local); + getsockname(s->ctrl, (struct sockaddr *) &local, &len); + + if (d->mouse && s->srv->uinput) { + s->umouse = uinput_open_mouse(d, &local.l2cap_bdaddr); + if (s->umouse < 0) { + syslog(LOG_ERR, "Could not open /dev/uinput " \ + "for %s. %s (%d)", bt_ntoa(&s->bdaddr, + NULL), strerror(errno), errno); + return (-1); + } + } + if (d->keyboard && s->srv->uinput) { + s->ukbd = uinput_open_keyboard(d, &local.l2cap_bdaddr); + if (s->ukbd < 0) { + syslog(LOG_ERR, "Could not open /dev/uinput " \ + "for %s. %s (%d)", bt_ntoa(&s->bdaddr, + NULL), strerror(errno), errno); + return (-1); + } + /* Register session's ukbd descriptor (if needed) for read */ + FD_SET(s->ukbd, &s->srv->rfdset); + if (s->ukbd > s->srv->maxfd) + s->srv->maxfd = s->ukbd; + } + return (0); +} + +/* + * Lookup session by bdaddr + */ + +bthid_session_p +session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr) +{ + bthid_session_p s; + + assert(srv != NULL); + assert(bdaddr != NULL); + + LIST_FOREACH(s, &srv->sessions, next) + if (memcmp(&s->bdaddr, bdaddr, sizeof(s->bdaddr)) == 0) + break; + + return (s); +} + +/* + * Lookup session by fd + */ + +bthid_session_p +session_by_fd(bthid_server_p srv, int32_t fd) +{ + bthid_session_p s; + + assert(srv != NULL); + assert(fd >= 0); + + LIST_FOREACH(s, &srv->sessions, next) + if (s->ctrl == fd || s->intr == fd || + s->vkbd == fd || s->ukbd == fd) + break; + + return (s); +} + +/* + * Close session + */ + +void +session_close(bthid_session_p s) +{ + assert(s != NULL); + assert(s->srv != NULL); + + LIST_REMOVE(s, next); + + if (s->intr != -1) { + FD_CLR(s->intr, &s->srv->rfdset); + FD_CLR(s->intr, &s->srv->wfdset); + close(s->intr); + + if (s->srv->maxfd == s->intr) + s->srv->maxfd --; + } + + if (s->ctrl != -1) { + FD_CLR(s->ctrl, &s->srv->rfdset); + FD_CLR(s->ctrl, &s->srv->wfdset); + close(s->ctrl); + + if (s->srv->maxfd == s->ctrl) + s->srv->maxfd --; + } + + if (s->vkbd != -1) { + FD_CLR(s->vkbd, &s->srv->rfdset); + close(s->vkbd); + + if (s->srv->maxfd == s->vkbd) + s->srv->maxfd --; + } + + if (s->umouse != -1) + close(s->umouse); + + if (s->ukbd != -1) { + FD_CLR(s->ukbd, &s->srv->rfdset); + close(s->ukbd); + + if (s->srv->maxfd == s->ukbd) + s->srv->maxfd --; + } + + free(s->ctx); + free(s->keys1); + free(s->keys2); + + memset(s, 0, sizeof(*s)); + free(s); +} + diff --git a/usr.sbin/bluetooth/btpand/Makefile b/usr.sbin/bluetooth/btpand/Makefile new file mode 100644 index 000000000000..14848e66e42a --- /dev/null +++ b/usr.sbin/bluetooth/btpand/Makefile @@ -0,0 +1,12 @@ +# $NetBSD: Makefile,v 1.2 2008/08/18 08:25:32 plunky Exp $ + +PACKAGE= bluetooth +PROG= btpand +MAN= btpand.8 +SRCS= btpand.c bnep.c channel.c client.c event.c packet.c server.c sdp.c tap.c + +WARNS?= 3 + +LIBADD= bluetooth sdp util + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/btpand/Makefile.depend b/usr.sbin/bluetooth/btpand/Makefile.depend new file mode 100644 index 000000000000..8c9bfd44959f --- /dev/null +++ b/usr.sbin/bluetooth/btpand/Makefile.depend @@ -0,0 +1,18 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + lib/libsdp \ + lib/libutil \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/btpand/bnep.c b/usr.sbin/bluetooth/btpand/bnep.c new file mode 100644 index 000000000000..5fdcd9407525 --- /dev/null +++ b/usr.sbin/bluetooth/btpand/bnep.c @@ -0,0 +1,757 @@ +/* $NetBSD: bnep.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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: bnep.c,v 1.1 2008/08/17 13:20:57 plunky Exp $"); + +#include <sys/uio.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +#include "btpand.h" +#include "bnep.h" + +static bool bnep_recv_extension(packet_t *); +static size_t bnep_recv_control(channel_t *, uint8_t *, size_t, bool); +static size_t bnep_recv_control_command_not_understood(channel_t *, uint8_t *, size_t); +static size_t bnep_recv_setup_connection_req(channel_t *, uint8_t *, size_t); +static size_t bnep_recv_setup_connection_rsp(channel_t *, uint8_t *, size_t); +static size_t bnep_recv_filter_net_type_set(channel_t *, uint8_t *, size_t); +static size_t bnep_recv_filter_net_type_rsp(channel_t *, uint8_t *, size_t); +static size_t bnep_recv_filter_multi_addr_set(channel_t *, uint8_t *, size_t); +static size_t bnep_recv_filter_multi_addr_rsp(channel_t *, uint8_t *, size_t); + +static bool bnep_pfilter(channel_t *, packet_t *); +static bool bnep_mfilter(channel_t *, packet_t *); + +static uint8_t NAP_UUID[] = { + 0x00, 0x00, 0x11, 0x16, + 0x00, 0x00, + 0x10, 0x00, + 0x80, 0x00, + 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb +}; + +static uint8_t GN_UUID[] = { + 0x00, 0x00, 0x11, 0x17, + 0x00, 0x00, + 0x10, 0x00, + 0x80, 0x00, + 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb, +}; + +static uint8_t PANU_UUID[] = { + 0x00, 0x00, 0x11, 0x15, + 0x00, 0x00, + 0x10, 0x00, + 0x80, 0x00, + 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb +}; + +/* + * receive BNEP packet + * return true if packet is to be forwarded + */ +bool +bnep_recv(packet_t *pkt) +{ + size_t len; + uint8_t type; + + if (pkt->len < 1) + return false; + + type = pkt->ptr[0]; + packet_adj(pkt, 1); + + switch (BNEP_TYPE(type)) { + case BNEP_GENERAL_ETHERNET: + if (pkt->len < (ETHER_ADDR_LEN * 2) + ETHER_TYPE_LEN) { + log_debug("dropped short packet (type 0x%2.2x)", type); + return false; + } + + pkt->dst = pkt->ptr; + packet_adj(pkt, ETHER_ADDR_LEN); + pkt->src = pkt->ptr; + packet_adj(pkt, ETHER_ADDR_LEN); + pkt->type = pkt->ptr; + packet_adj(pkt, ETHER_TYPE_LEN); + break; + + case BNEP_CONTROL: + len = bnep_recv_control(pkt->chan, pkt->ptr, pkt->len, false); + if (len == 0) + return false; + + packet_adj(pkt, len); + break; + + case BNEP_COMPRESSED_ETHERNET: + if (pkt->len < ETHER_TYPE_LEN) { + log_debug("dropped short packet (type 0x%2.2x)", type); + return false; + } + + pkt->dst = pkt->chan->laddr; + pkt->src = pkt->chan->raddr; + pkt->type = pkt->ptr; + packet_adj(pkt, ETHER_TYPE_LEN); + break; + + case BNEP_COMPRESSED_ETHERNET_SRC_ONLY: + if (pkt->len < ETHER_ADDR_LEN + ETHER_TYPE_LEN) { + log_debug("dropped short packet (type 0x%2.2x)", type); + return false; + } + + pkt->dst = pkt->chan->laddr; + pkt->src = pkt->ptr; + packet_adj(pkt, ETHER_ADDR_LEN); + pkt->type = pkt->ptr; + packet_adj(pkt, ETHER_TYPE_LEN); + break; + + case BNEP_COMPRESSED_ETHERNET_DST_ONLY: + if (pkt->len < ETHER_ADDR_LEN + ETHER_TYPE_LEN) { + log_debug("dropped short packet (type 0x%2.2x)", type); + return false; + } + + pkt->dst = pkt->ptr; + packet_adj(pkt, ETHER_ADDR_LEN); + pkt->src = pkt->chan->raddr; + pkt->type = pkt->ptr; + packet_adj(pkt, ETHER_TYPE_LEN); + break; + + default: + /* + * Any packet containing a reserved BNEP + * header packet type SHALL be dropped. + */ + + log_debug("dropped packet with reserved type 0x%2.2x", type); + return false; + } + + if (BNEP_TYPE_EXT(type) + && !bnep_recv_extension(pkt)) + return false; /* invalid extensions */ + + if (BNEP_TYPE(type) == BNEP_CONTROL + || pkt->chan->state != CHANNEL_OPEN) + return false; /* no forwarding */ + + return true; +} + +static bool +bnep_recv_extension(packet_t *pkt) +{ + exthdr_t *eh; + size_t len, size; + uint8_t type; + + do { + if (pkt->len < 2) + return false; + + type = pkt->ptr[0]; + size = pkt->ptr[1]; + + if (pkt->len < size + 2) + return false; + + switch (type) { + case BNEP_EXTENSION_CONTROL: + len = bnep_recv_control(pkt->chan, pkt->ptr + 2, size, true); + if (len != size) + log_err("ignored spurious data in exthdr"); + + break; + + default: + /* Unknown extension headers in data packets */ + /* SHALL be forwarded irrespective of any */ + /* network protocol or multicast filter settings */ + /* and any local filtering policy. */ + + eh = malloc(sizeof(exthdr_t)); + if (eh == NULL) { + log_err("exthdr malloc() failed: %m"); + break; + } + + eh->ptr = pkt->ptr; + eh->len = size; + STAILQ_INSERT_TAIL(&pkt->extlist, eh, next); + break; + } + + packet_adj(pkt, size + 2); + } while (BNEP_TYPE_EXT(type)); + + return true; +} + +static size_t +bnep_recv_control(channel_t *chan, uint8_t *ptr, size_t size, bool isext) +{ + uint8_t type; + size_t len; + + if (size-- < 1) + return 0; + + type = *ptr++; + + switch (type) { + case BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD: + len = bnep_recv_control_command_not_understood(chan, ptr, size); + break; + + case BNEP_SETUP_CONNECTION_REQUEST: + if (isext) + return 0; /* not allowed in extension headers */ + + len = bnep_recv_setup_connection_req(chan, ptr, size); + break; + + case BNEP_SETUP_CONNECTION_RESPONSE: + if (isext) + return 0; /* not allowed in extension headers */ + + len = bnep_recv_setup_connection_rsp(chan, ptr, size); + break; + + case BNEP_FILTER_NET_TYPE_SET: + len = bnep_recv_filter_net_type_set(chan, ptr, size); + break; + + case BNEP_FILTER_NET_TYPE_RESPONSE: + len = bnep_recv_filter_net_type_rsp(chan, ptr, size); + break; + + case BNEP_FILTER_MULTI_ADDR_SET: + len = bnep_recv_filter_multi_addr_set(chan, ptr, size); + break; + + case BNEP_FILTER_MULTI_ADDR_RESPONSE: + len = bnep_recv_filter_multi_addr_rsp(chan, ptr, size); + break; + + default: + len = 0; + break; + } + + if (len == 0) + bnep_send_control(chan, BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD, type); + + return len; +} + +static size_t +bnep_recv_control_command_not_understood(channel_t *chan, uint8_t *ptr, size_t size) +{ + uint8_t type; + + if (size < 1) + return 0; + + type = *ptr++; + log_err("received Control Command Not Understood (0x%2.2x)", type); + + /* we didn't send any reserved commands, just cut them off */ + channel_close(chan); + + return 1; +} + +static size_t +bnep_recv_setup_connection_req(channel_t *chan, uint8_t *ptr, size_t size) +{ + uint8_t off; + int src, dst, rsp; + size_t len; + + if (size < 1) + return 0; + + len = *ptr++; + if (size < (len * 2 + 1)) + return 0; + + if (chan->state != CHANNEL_WAIT_CONNECT_REQ + && chan->state != CHANNEL_OPEN) { + log_debug("ignored"); + return (len * 2 + 1); + } + + if (len == 2) + off = 2; + else if (len == 4) + off = 0; + else if (len == 16) + off = 0; + else { + rsp = BNEP_SETUP_INVALID_UUID_SIZE; + goto done; + } + + if (memcmp(ptr, NAP_UUID + off, len) == 0) + dst = SDP_SERVICE_CLASS_NAP; + else if (memcmp(ptr, GN_UUID + off, len) == 0) + dst = SDP_SERVICE_CLASS_GN; + else if (memcmp(ptr, PANU_UUID + off, len) == 0) + dst = SDP_SERVICE_CLASS_PANU; + else + dst = 0; + + if (dst != service_class) { + rsp = BNEP_SETUP_INVALID_DST_UUID; + goto done; + } + + ptr += len; + + if (memcmp(ptr, NAP_UUID + off, len) == 0) + src = SDP_SERVICE_CLASS_NAP; + else if (memcmp(ptr, GN_UUID + off, len) == 0) + src = SDP_SERVICE_CLASS_GN; + else if (memcmp(ptr, PANU_UUID + off, len) == 0) + src = SDP_SERVICE_CLASS_PANU; + else + src = 0; + + if ((dst != SDP_SERVICE_CLASS_PANU && src != SDP_SERVICE_CLASS_PANU) + || src == 0) { + rsp = BNEP_SETUP_INVALID_SRC_UUID; + goto done; + } + + rsp = BNEP_SETUP_SUCCESS; + chan->state = CHANNEL_OPEN; + channel_timeout(chan, 0); + +done: + log_debug("addr %s response 0x%2.2x", + ether_ntoa((struct ether_addr *)chan->raddr), rsp); + + bnep_send_control(chan, BNEP_SETUP_CONNECTION_RESPONSE, rsp); + return (len * 2 + 1); +} + +static size_t +bnep_recv_setup_connection_rsp(channel_t *chan, uint8_t *ptr, size_t size) +{ + int rsp; + + if (size < 2) + return 0; + + rsp = be16dec(ptr); + + if (chan->state != CHANNEL_WAIT_CONNECT_RSP) { + log_debug("ignored"); + return 2; + } + + log_debug("addr %s response 0x%2.2x", + ether_ntoa((struct ether_addr *)chan->raddr), rsp); + + if (rsp == BNEP_SETUP_SUCCESS) { + chan->state = CHANNEL_OPEN; + channel_timeout(chan, 0); + } else { + channel_close(chan); + } + + return 2; +} + +static size_t +bnep_recv_filter_net_type_set(channel_t *chan, uint8_t *ptr, size_t size) +{ + pfilter_t *pf; + int i, nf, rsp; + size_t len; + + if (size < 2) + return 0; + + len = be16dec(ptr); + ptr += 2; + + if (size < (len + 2)) + return 0; + + if (chan->state != CHANNEL_OPEN) { + log_debug("ignored"); + return (len + 2); + } + + nf = len / 4; + pf = malloc(nf * sizeof(pfilter_t)); + if (pf == NULL) { + rsp = BNEP_FILTER_TOO_MANY_FILTERS; + goto done; + } + + log_debug("nf = %d", nf); + + for (i = 0; i < nf; i++) { + pf[i].start = be16dec(ptr); + ptr += 2; + pf[i].end = be16dec(ptr); + ptr += 2; + + if (pf[i].start > pf[i].end) { + free(pf); + rsp = BNEP_FILTER_INVALID_RANGE; + goto done; + } + + log_debug("pf[%d] = %#4.4x, %#4.4x", i, pf[i].start, pf[i].end); + } + + if (chan->pfilter) + free(chan->pfilter); + + chan->pfilter = pf; + chan->npfilter = nf; + + rsp = BNEP_FILTER_SUCCESS; + +done: + log_debug("addr %s response 0x%2.2x", + ether_ntoa((struct ether_addr *)chan->raddr), rsp); + + bnep_send_control(chan, BNEP_FILTER_NET_TYPE_RESPONSE, rsp); + return (len + 2); +} + +static size_t +bnep_recv_filter_net_type_rsp(channel_t *chan, uint8_t *ptr, size_t size) +{ + int rsp; + + if (size < 2) + return 0; + + if (chan->state != CHANNEL_OPEN) { + log_debug("ignored"); + return 2; + } + + rsp = be16dec(ptr); + + log_debug("addr %s response 0x%2.2x", + ether_ntoa((struct ether_addr *)chan->raddr), rsp); + + /* we did not send any filter_net_type_set message */ + return 2; +} + +static size_t +bnep_recv_filter_multi_addr_set(channel_t *chan, uint8_t *ptr, size_t size) +{ + mfilter_t *mf; + int i, nf, rsp; + size_t len; + + if (size < 2) + return 0; + + len = be16dec(ptr); + ptr += 2; + + if (size < (len + 2)) + return 0; + + if (chan->state != CHANNEL_OPEN) { + log_debug("ignored"); + return (len + 2); + } + + nf = len / (ETHER_ADDR_LEN * 2); + mf = malloc(nf * sizeof(mfilter_t)); + if (mf == NULL) { + rsp = BNEP_FILTER_TOO_MANY_FILTERS; + goto done; + } + + log_debug("nf = %d", nf); + + for (i = 0; i < nf; i++) { + memcpy(mf[i].start, ptr, ETHER_ADDR_LEN); + ptr += ETHER_ADDR_LEN; + + memcpy(mf[i].end, ptr, ETHER_ADDR_LEN); + ptr += ETHER_ADDR_LEN; + + if (memcmp(mf[i].start, mf[i].end, ETHER_ADDR_LEN) > 0) { + free(mf); + rsp = BNEP_FILTER_INVALID_RANGE; + goto done; + } + + log_debug("pf[%d] = " + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, " + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", i, + mf[i].start[0], mf[i].start[1], mf[i].start[2], + mf[i].start[3], mf[i].start[4], mf[i].start[5], + mf[i].end[0], mf[i].end[1], mf[i].end[2], + mf[i].end[3], mf[i].end[4], mf[i].end[5]); + } + + if (chan->mfilter) + free(chan->mfilter); + + chan->mfilter = mf; + chan->nmfilter = nf; + + rsp = BNEP_FILTER_SUCCESS; + +done: + log_debug("addr %s response 0x%2.2x", + ether_ntoa((struct ether_addr *)chan->raddr), rsp); + + bnep_send_control(chan, BNEP_FILTER_MULTI_ADDR_RESPONSE, rsp); + return (len + 2); +} + +static size_t +bnep_recv_filter_multi_addr_rsp(channel_t *chan, uint8_t *ptr, size_t size) +{ + int rsp; + + if (size < 2) + return false; + + if (chan->state != CHANNEL_OPEN) { + log_debug("ignored"); + return 2; + } + + rsp = be16dec(ptr); + log_debug("addr %s response 0x%2.2x", + ether_ntoa((struct ether_addr *)chan->raddr), rsp); + + /* we did not send any filter_multi_addr_set message */ + return 2; +} + +void +bnep_send_control(channel_t *chan, unsigned type, ...) +{ + packet_t *pkt; + uint8_t *p; + va_list ap; + + assert(chan->state != CHANNEL_CLOSED); + + pkt = packet_alloc(chan); + if (pkt == NULL) + return; + + p = pkt->ptr; + va_start(ap, type); + + *p++ = BNEP_CONTROL; + *p++ = (uint8_t)type; + + switch(type) { + case BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD: + *p++ = va_arg(ap, int); + break; + + case BNEP_SETUP_CONNECTION_REQUEST: + *p++ = va_arg(ap, int); + be16enc(p, va_arg(ap, int)); + p += 2; + be16enc(p, va_arg(ap, int)); + p += 2; + break; + + case BNEP_SETUP_CONNECTION_RESPONSE: + case BNEP_FILTER_NET_TYPE_RESPONSE: + case BNEP_FILTER_MULTI_ADDR_RESPONSE: + be16enc(p, va_arg(ap, int)); + p += 2; + break; + + case BNEP_FILTER_NET_TYPE_SET: /* TODO */ + case BNEP_FILTER_MULTI_ADDR_SET: /* TODO */ + default: + log_err("Can't send control type 0x%2.2x", type); + break; + } + + va_end(ap); + pkt->len = p - pkt->ptr; + + channel_put(chan, pkt); + packet_free(pkt); +} + +/* + * BNEP send packet routine + * return true if packet can be removed from queue + */ +bool +bnep_send(channel_t *chan, packet_t *pkt) +{ + struct iovec iov[2]; + uint8_t *p, *type, *proto; + exthdr_t *eh; + bool src, dst; + size_t nw; + + if (pkt->type == NULL) { + iov[0].iov_base = pkt->ptr; + iov[0].iov_len = pkt->len; + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + } else { + p = chan->sendbuf; + + dst = (memcmp(pkt->dst, chan->raddr, ETHER_ADDR_LEN) != 0); + src = (memcmp(pkt->src, chan->laddr, ETHER_ADDR_LEN) != 0); + + type = p; + p += 1; + + if (dst && src) + *type = BNEP_GENERAL_ETHERNET; + else if (dst && !src) + *type = BNEP_COMPRESSED_ETHERNET_DST_ONLY; + else if (!dst && src) + *type = BNEP_COMPRESSED_ETHERNET_SRC_ONLY; + else /* (!dst && !src) */ + *type = BNEP_COMPRESSED_ETHERNET; + + if (dst) { + memcpy(p, pkt->dst, ETHER_ADDR_LEN); + p += ETHER_ADDR_LEN; + } + + if (src) { + memcpy(p, pkt->src, ETHER_ADDR_LEN); + p += ETHER_ADDR_LEN; + } + + proto = p; + memcpy(p, pkt->type, ETHER_TYPE_LEN); + p += ETHER_TYPE_LEN; + + STAILQ_FOREACH(eh, &pkt->extlist, next) { + if (p + eh->len > chan->sendbuf + chan->mtu) + break; + + *type |= BNEP_EXT; + type = p; + + memcpy(p, eh->ptr, eh->len); + p += eh->len; + } + + *type &= ~BNEP_EXT; + + iov[0].iov_base = chan->sendbuf; + iov[0].iov_len = (p - chan->sendbuf); + + if ((chan->npfilter == 0 || bnep_pfilter(chan, pkt)) + && (chan->nmfilter == 0 || bnep_mfilter(chan, pkt))) { + iov[1].iov_base = pkt->ptr; + iov[1].iov_len = pkt->len; + } else if (be16dec(proto) == ETHERTYPE_VLAN + && pkt->len >= ETHER_VLAN_ENCAP_LEN) { + iov[1].iov_base = pkt->ptr; + iov[1].iov_len = ETHER_VLAN_ENCAP_LEN; + } else { + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + memset(proto, 0, ETHER_TYPE_LEN); + } + } + + if (iov[0].iov_len + iov[1].iov_len > chan->mtu) { + log_err("packet exceeded MTU (dropped)"); + return false; + } + + nw = writev(chan->fd, iov, __arraycount(iov)); + return (nw > 0); +} + +static bool +bnep_pfilter(channel_t *chan, packet_t *pkt) +{ + int proto, i; + + proto = be16dec(pkt->type); + if (proto == ETHERTYPE_VLAN) { /* IEEE 802.1Q tag header */ + if (pkt->len < 4) + return false; + + proto = be16dec(pkt->ptr + 2); + } + + for (i = 0; i < chan->npfilter; i++) { + if (chan->pfilter[i].start <= proto + && chan->pfilter[i].end >=proto) + return true; + } + + return false; +} + +static bool +bnep_mfilter(channel_t *chan, packet_t *pkt) +{ + int i; + + if (!ETHER_IS_MULTICAST(pkt->dst)) + return true; + + for (i = 0; i < chan->nmfilter; i++) { + if (memcmp(pkt->dst, chan->mfilter[i].start, ETHER_ADDR_LEN) >= 0 + && memcmp(pkt->dst, chan->mfilter[i].end, ETHER_ADDR_LEN) <= 0) + return true; + } + + return false; +} diff --git a/usr.sbin/bluetooth/btpand/bnep.h b/usr.sbin/bluetooth/btpand/bnep.h new file mode 100644 index 000000000000..7a187e551eed --- /dev/null +++ b/usr.sbin/bluetooth/btpand/bnep.h @@ -0,0 +1,73 @@ +/* $NetBSD: bnep.h,v 1.1 2008/08/17 13:20:57 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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. + */ + + +/* + * Constants defined in the Bluetooth Network Encapsulation + * Protocol (BNEP) specification v1.0 + */ + +#define BNEP_MTU_MIN 1691 + +#define BNEP_EXT 0x80 +#define BNEP_TYPE(x) ((x) & 0x7f) +#define BNEP_TYPE_EXT(x) (((x) & BNEP_EXT) == BNEP_EXT) + +/* BNEP packet types */ +#define BNEP_GENERAL_ETHERNET 0x00 +#define BNEP_CONTROL 0x01 +#define BNEP_COMPRESSED_ETHERNET 0x02 +#define BNEP_COMPRESSED_ETHERNET_SRC_ONLY 0x03 +#define BNEP_COMPRESSED_ETHERNET_DST_ONLY 0x04 + +/* BNEP extension header types */ +#define BNEP_EXTENSION_CONTROL 0x00 + +/* BNEP control types */ +#define BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD 0x00 +#define BNEP_SETUP_CONNECTION_REQUEST 0x01 +#define BNEP_SETUP_CONNECTION_RESPONSE 0x02 +#define BNEP_FILTER_NET_TYPE_SET 0x03 +#define BNEP_FILTER_NET_TYPE_RESPONSE 0x04 +#define BNEP_FILTER_MULTI_ADDR_SET 0x05 +#define BNEP_FILTER_MULTI_ADDR_RESPONSE 0x06 + +/* BNEP setup response codes */ +#define BNEP_SETUP_SUCCESS 0x0000 +#define BNEP_SETUP_INVALID_SRC_UUID 0x0001 +#define BNEP_SETUP_INVALID_DST_UUID 0x0002 +#define BNEP_SETUP_INVALID_UUID_SIZE 0x0003 +#define BNEP_SETUP_NOT_ALLOWED 0x0004 + +/* BNEP filter return codes */ +#define BNEP_FILTER_SUCCESS 0x0000 +#define BNEP_FILTER_UNSUPPORTED_REQUEST 0x0001 +#define BNEP_FILTER_INVALID_RANGE 0x0002 +#define BNEP_FILTER_TOO_MANY_FILTERS 0x0003 +#define BNEP_FILTER_SECURITY_FAILURE 0x0004 diff --git a/usr.sbin/bluetooth/btpand/btpand.8 b/usr.sbin/bluetooth/btpand/btpand.8 new file mode 100644 index 000000000000..8993b221361f --- /dev/null +++ b/usr.sbin/bluetooth/btpand/btpand.8 @@ -0,0 +1,239 @@ +.\" $NetBSD: btpand.8,v 1.3 2008/08/17 14:43:07 plunky Exp $ +.\" +.\" Copyright (c) 2008 Iain Hibbert +.\" 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 ``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 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. +.\" +.Dd August 17, 2008 +.Dt BTPAND 8 +.Os +.Sh NAME +.Nm btpand +.Nd Bluetooth PAN daemon +.Sh SYNOPSIS +.Nm +.Op Fl i Ar ifname +.Op Fl m Ar mode +.Fl a Ar addr +.Fl d Ar device +.Brq Fl s Ar service | Fl S Ar service Op Fl p Ar psm +.Nm +.Op Fl c Ar path +.Op Fl i Ar ifname +.Op Fl l Ar limit +.Op Fl m Ar mode +.Op Fl p Ar psm +.Fl d Ar device +.Brq Fl s Ar service | Fl S Ar service +.Sh DESCRIPTION +The +.Nm +daemon handles Bluetooth Personal Area Networking services +in the system. +It can operate in client mode as a Personal Area Networking User +.Pq PANU +or in server mode as Network Access Point +.Pq NAP , +Group ad-hoc Network +.Pq GN +or PANU host. +.Nm +connects to the system via a +.Xr tap 4 +virtual Ethernet device and forwards Ethernet packets to +remote Bluetooth devices using the Bluetooth Network Encapsulation +Protocol +.Pq BNEP . +.Pp +The PANU client is the device that uses either the NAP or GN +service, or can talk directly to a PANU host in a crossover +cable fashion. +.Pp +A GN host forwards Ethernet packets to each of the connected PAN +users as needed but does not provide access to any additional networks. +.Pp +The NAP service provides some of the features of an Ethernet bridge, +with the NAP host forwarding Ethernet packets between each of the +connected PAN users, and a different network +media. +.Pp +Note, the only differences between NAP and GN services as implemented by +.Nm +are in the SDP service record. +The bridging of packets by the NAP must be configured separately. +.Pp +The options are as follows: +.Bl -tag -width ".Fl a Ar address" +.It Fl a Ar address +In client mode, address of remote server. +May be given as BDADDR or name, in which case +.Nm +will attempt to resolve the address via the +.Xr bt_gethostbyname 3 +call. +.It Fl c Ar path +In server mode, specify +.Ar path +to the +.Xr sdpd 8 +control socket. +The default path is +.Pa /var/run/sdp . +.It Fl d Ar device +Restrict connections to the local +.Ar device . +May be given as BDADDR or name, in which case +.Nm +will attempt to resolve the address via the +.Xr bt_devaddr 3 +call. +.Nm +will set the +.Xr tap 4 +interface physical address to the BDADDR +of the Bluetooth radio. +.It Fl i Ar ifname +.Nm +uses the +.Xr tap 4 +driver to create a new network interface for use. +Use this option to select a specific +.Xr tap 4 +device interface which must already be created. +.It Fl l Ar limit +In server mode, limit the number of simultaneous connections. +The default limit is 7 for NAP and GN servers, +and 1 for a PANU server. +.It Fl m Ar mode +Set L2CAP connection link mode. +Supported modes are: +.Pp +.Bl -tag -width 8n -compact +.It auth +require devices to be paired. +.It encrypt +auth, plus enable encryption. +.It secure +encryption, plus change of link key. +.El +.Pp +NOT YET SUPPORTED. +Use global device settings to set authentication and encryption. +.It Fl p Ar psm +Use an alternative L2CAP Protocol/Service Multiplexer +.Pq PSM +for server mode or client mode +.Pq when not using Service Discovery . +The default PSM for BNEP is 15 +.Pq 0x000f . +.It Fl s Ar service +Name of +.Ar service +to provide or connect to, the following services are recognised: +.Pp +.Bl -tag -width 8n -compact +.It GN +Group ad-hoc Network. +.It NAP +Network Access Point. +.It PANU +Personal Area Networking User. +.El +.It Fl S Ar service +As per +.Fl s +except that +.Nm +will not use SDP services for connection setup. +.El +.Pp +When providing networking services, the Bluetooth PAN profile says that the +.Sq Class of Device +property of the bluetooth controller SHALL include Networking capability +.Pq set bit 0x020000 . +See +.Xr hccontrol 8 +for details. +.Pp +After +.Nm +has set up the client or server connection and opened the +.Xr tap 4 +interface, it will create a pid file and detach. +.Sh FILES +.Bl -tag -width "Pa /etc/bluetooth/hosts" -compact +.It Pa /dev/tap +.It Pa /etc/bluetooth/hosts +.It Pa /var/run/sdp +.It Pa /var/run/tap Ns Em N Ns No .pid +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Dl ifconfig tap1 create +.Dl btpand -a host -d mydevice -s NAP -i tap1 +.Dl dhclient tap1 +.Pp +Will create a connection to the NAP on +.Ar host , +and link that to the +.Ar tap1 +interface. +.Pp +.Dl btpand -d mydevice -s GN +.Pp +Will create a Group Network and register the GN service with the local +SDP server. +.Sh SEE ALSO +.Xr bluetooth 3 , +.Xr bridge 4 , +.Xr tap 4 , +.Xr dhclient 8 , +.Xr hccontrol 8 , +.Xr ifconfig 8 , +.Xr sdpd 8 +.Pp +The +.Qq Personal Area Networking Profile +and +.Qq Bluetooth Network Encapsulation Protocol +specifications are available at +.Dl http://www.bluetooth.com/ +.Sh AUTHORS +.An Iain Hibbert +.Sh BUGS +There is no way to supply alternative values for the SDP record. +.Pp +There is no way to set net type or multicast address filters. +.Pp +.Nm +does not do any address routing except to directly connected +unicast addresses. +All other packets are multicast. +.Pp +As +.Nm +uses the BDADDR of the Bluetooth radio as the physical address +of the tap, only one instance can be run per radio. +.Pp +.Nm +can only provide a single service. diff --git a/usr.sbin/bluetooth/btpand/btpand.c b/usr.sbin/bluetooth/btpand/btpand.c new file mode 100644 index 000000000000..f0b29837188f --- /dev/null +++ b/usr.sbin/bluetooth/btpand/btpand.c @@ -0,0 +1,294 @@ +/* $NetBSD: btpand.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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/wait.h> + +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <err.h> +#include <fcntl.h> +#include <paths.h> +#include <sdp.h> +#include <stdio.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "btpand.h" + +/* global variables */ +const char * control_path; /* -c <path> */ +const char * interface_name; /* -i <ifname> */ +const char * service_name; /* -s <service> */ +uint16_t service_class; + +bdaddr_t local_bdaddr; /* -d <addr> */ +bdaddr_t remote_bdaddr; /* -a <addr> */ +uint16_t l2cap_psm; /* -p <psm> */ +int l2cap_mode; /* -m <mode> */ + +int server_limit; /* -n <limit> */ + +static const struct { + const char * name; + uint16_t class; + const char * desc; +} services[] = { + { "PANU", SDP_SERVICE_CLASS_PANU, "Personal Area Networking User" }, + { "NAP", SDP_SERVICE_CLASS_NAP, "Network Access Point" }, + { "GN", SDP_SERVICE_CLASS_GN, "Group Network" }, +}; + +static void main_exit(int) __dead2; +static void main_detach(void); +static void usage(void) __dead2; + +int +main(int argc, char *argv[]) +{ + unsigned long ul; + char * ep; + int ch, status; + + while ((ch = getopt(argc, argv, "a:c:d:i:l:m:p:S:s:")) != -1) { + switch (ch) { + case 'a': /* remote address */ + if (!bt_aton(optarg, &remote_bdaddr)) { + struct hostent *he; + + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(EXIT_FAILURE, "%s: %s", + optarg, hstrerror(h_errno)); + + bdaddr_copy(&remote_bdaddr, + (bdaddr_t *)he->h_addr); + } + + break; + + case 'c': /* control socket path */ + control_path = optarg; + break; + + case 'd': /* local address */ + if (!bt_devaddr(optarg, &local_bdaddr)) { + struct hostent *he; + + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(EXIT_FAILURE, "%s: %s", + optarg, hstrerror(h_errno)); + + bdaddr_copy(&local_bdaddr, + (bdaddr_t *)he->h_addr); + } + break; + + case 'i': /* tap interface name */ + if (strchr(optarg, '/') == NULL) { + asprintf(&ep, "/dev/%s", optarg); + interface_name = ep; + } else + interface_name = optarg; + break; + + case 'l': /* limit server sessions */ + ul = strtoul(optarg, &ep, 10); + if (*optarg == '\0' || *ep != '\0' || ul == 0) + errx(EXIT_FAILURE, "%s: invalid session limit", + optarg); + + server_limit = ul; + break; + + case 'm': /* link mode */ + warnx("Setting link mode is not yet supported"); + break; + + case 'p': /* protocol/service multiplexer */ + ul = strtoul(optarg, &ep, 0); + if (*optarg == '\0' || *ep != '\0' + || ul > 0xffff || L2CAP_PSM_INVALID(ul)) + errx(EXIT_FAILURE, "%s: invalid PSM", optarg); + + l2cap_psm = ul; + break; + + case 's': /* service */ + case 'S': /* service (no SDP) */ + for (ul = 0; ul < __arraycount(services); ul++) { + if (strcasecmp(optarg, services[ul].name) == 0) + break; + } + + if (ul == __arraycount(services)) + errx(EXIT_FAILURE, "%s: unknown service", optarg); + + if (ch == 's') + service_name = services[ul].name; + + service_class = services[ul].class; + break; + + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + /* validate options */ + if (bdaddr_any(&local_bdaddr) || service_class == 0) + usage(); + + if (!bdaddr_any(&remote_bdaddr) && (server_limit != 0 || + control_path != NULL || (service_name != NULL && l2cap_psm != 0))) + usage(); + + /* default options */ + if (interface_name == NULL) + interface_name = "/dev/tap"; + + if (l2cap_psm == 0) + l2cap_psm = L2CAP_PSM_BNEP; + + if (bdaddr_any(&remote_bdaddr) && server_limit == 0) { + if (service_class == SDP_SERVICE_CLASS_PANU) + server_limit = 1; + else + server_limit = 7; + } + +#ifdef L2CAP_LM_MASTER + if (server_limit > 1 && service_class != SDP_SERVICE_CLASS_PANU) + l2cap_mode |= L2CAP_LM_MASTER; +#endif + + /* + * fork() now so that the setup can be done in the child process + * (as kqueue is not inherited) but block in the parent until the + * setup is finished so we can return an error if necessary. + */ + switch(fork()) { + case -1: /* bad */ + err(EXIT_FAILURE, "fork() failed"); + + case 0: /* child */ + signal(SIGPIPE, SIG_IGN); + + openlog(getprogname(), LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_DAEMON); + + channel_init(); + server_init(); + event_init(); + client_init(); + tap_init(); + + main_detach(); + + event_dispatch(); + break; + + default: /* parent */ + signal(SIGUSR1, main_exit); + wait(&status); + + if (WIFEXITED(status)) + exit(WEXITSTATUS(status)); + + break; + } + + err(EXIT_FAILURE, "exiting"); +} + +static void +main_exit(int s) +{ + + /* child is all grown up */ + _exit(EXIT_SUCCESS); +} + +static void +main_detach(void) +{ + int fd; + + if (kill(getppid(), SIGUSR1) == -1) + log_err("Could not signal main process: %m"); + + if (setsid() == -1) + log_err("setsid() failed"); + + fd = open(_PATH_DEVNULL, O_RDWR, 0); + if (fd == -1) { + log_err("Could not open %s", _PATH_DEVNULL); + } else { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + close(fd); + } +} + +static void +usage(void) +{ + const char *p = getprogname(); + int n = strlen(p); + + fprintf(stderr, + "usage: %s [-i ifname] [-m mode] -a address -d device\n" + " %*s {-s service | -S service [-p psm]}\n" + " %s [-c path] [-i ifname] [-l limit] [-m mode] [-p psm] -d device\n" + " %*s {-s service | -S service}\n" + "\n" + "Where:\n" + "\t-a address remote bluetooth device\n" + "\t-c path SDP server socket\n" + "\t-d device local bluetooth device\n" + "\t-i ifname tap interface\n" + "\t-l limit limit server sessions\n" + "\t-m mode L2CAP link mode (NOT YET SUPPORTED)\n" + "\t-p psm L2CAP PSM\n" + "\t-S service service name (no SDP)\n" + "\t-s service service name\n" + "\n" + "Known services:\n" + "", p, n, "", p, n, ""); + + for (n = 0; n < __arraycount(services); n++) + fprintf(stderr, "\t%s\t%s\n", services[n].name, services[n].desc); + + exit(EXIT_FAILURE); +} diff --git a/usr.sbin/bluetooth/btpand/btpand.h b/usr.sbin/bluetooth/btpand/btpand.h new file mode 100644 index 000000000000..a3beb7f211da --- /dev/null +++ b/usr.sbin/bluetooth/btpand/btpand.h @@ -0,0 +1,213 @@ +/* $NetBSD: btpand.h,v 1.1 2008/08/17 13:20:57 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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/types.h> +#include <sys/queue.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/ethernet.h> + +#include <assert.h> +#include <bluetooth.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "event.h" + +#ifndef __arraycount +#define __arraycount(__x) (int)(sizeof((__x)) / sizeof((__x)[0])) +#endif + +#ifndef L2CAP_PSM_INVALID +#define L2CAP_PSM_INVALID(psm) (((psm) & 0x0101) != 0x0001) +#endif + +#ifndef L2CAP_PSM_BNEP +#define L2CAP_PSM_BNEP 15 +#endif + +typedef struct channel channel_t; +typedef struct pfilter pfilter_t; +typedef struct mfilter mfilter_t; +typedef struct packet packet_t; +typedef struct pkthdr pkthdr_t; +typedef struct pktlist pktlist_t; +typedef struct exthdr exthdr_t; +typedef struct extlist extlist_t; + +LIST_HEAD(chlist, channel); +STAILQ_HEAD(extlist, exthdr); +STAILQ_HEAD(pktlist, pkthdr); + +enum channel_state { + CHANNEL_CLOSED, + CHANNEL_WAIT_CONNECT_REQ, + CHANNEL_WAIT_CONNECT_RSP, + CHANNEL_OPEN, +}; + +#define CHANNEL_MAXQLEN 128 + +/* BNEP or tap channel */ +struct channel { + enum channel_state state; + bool oactive; + + uint8_t laddr[ETHER_ADDR_LEN]; + uint8_t raddr[ETHER_ADDR_LEN]; + size_t mru; + size_t mtu; + + int npfilter; + pfilter_t * pfilter; + + int nmfilter; + mfilter_t * mfilter; + + pktlist_t pktlist; + int qlen; + + int fd; + struct event rd_ev; + struct event wr_ev; + uint8_t * sendbuf; + + bool (*send)(channel_t *, packet_t *); + bool (*recv)(packet_t *); + + int tick; + + struct pidfh *pfh; + + int refcnt; + LIST_ENTRY(channel) next; +}; + +/* network protocol type filter */ +struct pfilter { + uint16_t start; + uint16_t end; +}; + +/* multicast address filter */ +struct mfilter { + uint8_t start[ETHER_ADDR_LEN]; + uint8_t end[ETHER_ADDR_LEN]; +}; + +/* packet data buffer */ +struct packet { + channel_t * chan; /* source channel */ + uint8_t * dst; /* dest address */ + uint8_t * src; /* source address */ + uint8_t * type; /* protocol type */ + uint8_t * ptr; /* data pointer */ + size_t len; /* data length */ + int refcnt; /* reference count */ + extlist_t extlist;/* extension headers */ + uint8_t buf[0]; /* data starts here */ +}; + +/* extension header */ +struct exthdr { + STAILQ_ENTRY(exthdr) next; + uint8_t * ptr; + uint8_t len; +}; + +/* packet header */ +struct pkthdr { + STAILQ_ENTRY(pkthdr) next; + packet_t * data; +}; + +/* global variables */ +extern const char * control_path; +extern const char * service_name; +extern const char * interface_name; +extern bdaddr_t local_bdaddr; +extern bdaddr_t remote_bdaddr; +extern uint16_t l2cap_psm; +extern int l2cap_mode; +extern uint16_t service_class; +extern int server_limit; + +/* + * Bluetooth addresses are stored the other way around than + * Ethernet addresses even though they are of the same family + */ +static inline void +b2eaddr(void *dst, bdaddr_t *src) +{ + uint8_t *d = dst; + int i; + + for (i = 0; i < ETHER_ADDR_LEN; i++) + d[i] = src->b[ETHER_ADDR_LEN - i - 1]; +} + +#define log_err(fmt, args...) syslog(LOG_ERR, fmt , ##args) +#define log_info(fmt, args...) syslog(LOG_INFO, fmt , ##args) +#define log_notice(fmt, args...) syslog(LOG_NOTICE, fmt , ##args) +#define log_debug(fmt, args...) syslog(LOG_DEBUG, "%s: " fmt, __func__ , ##args) + +/* bnep.c */ +bool bnep_send(channel_t *, packet_t *); +bool bnep_recv(packet_t *); +void bnep_send_control(channel_t *, unsigned, ...); + +/* channel.c */ +void channel_init(void); +channel_t * channel_alloc(void); +bool channel_open(channel_t *, int); +void channel_close(channel_t *); +void channel_free(channel_t *); +void channel_timeout(channel_t *, int); +void channel_put(channel_t *, packet_t *); + +/* client.c */ +void client_init(void); + +/* packet.c */ +packet_t * packet_alloc(channel_t *); +void packet_free(packet_t *); +void packet_adj(packet_t *, size_t); +pkthdr_t * pkthdr_alloc(packet_t *); +void pkthdr_free(pkthdr_t *); + +/* server.c */ +void server_init(void); +void server_update(int); + +/* tap.c */ +void tap_init(void); diff --git a/usr.sbin/bluetooth/btpand/channel.c b/usr.sbin/bluetooth/btpand/channel.c new file mode 100644 index 000000000000..960b1ae30152 --- /dev/null +++ b/usr.sbin/bluetooth/btpand/channel.c @@ -0,0 +1,336 @@ +/* $NetBSD: channel.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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: channel.c,v 1.1 2008/08/17 13:20:57 plunky Exp $"); + +#include <sys/param.h> +#include <sys/ioctl.h> + +#include <libutil.h> +#include <unistd.h> +#define L2CAP_SOCKET_CHECKED +#include "btpand.h" + +static struct chlist channel_list; +static int channel_count; +static int channel_tick; + +static void channel_start(int, short, void *); +static void channel_read(int, short, void *); +static void channel_dispatch(packet_t *); +static void channel_watchdog(int, short, void *); + +void +channel_init(void) +{ + + LIST_INIT(&channel_list); +} + +channel_t * +channel_alloc(void) +{ + channel_t *chan; + + chan = malloc(sizeof(channel_t)); + if (chan == NULL) { + log_err("%s() failed: %m", __func__); + return NULL; + } + + memset(chan, 0, sizeof(channel_t)); + STAILQ_INIT(&chan->pktlist); + chan->state = CHANNEL_CLOSED; + LIST_INSERT_HEAD(&channel_list, chan, next); + + server_update(++channel_count); + + return chan; +} + +bool +channel_open(channel_t *chan, int fd) +{ + int n; + + assert(chan->refcnt == 0); + assert(chan->state != CHANNEL_CLOSED); + + if (chan->mtu > 0) { + chan->sendbuf = malloc(chan->mtu); + if (chan->sendbuf == NULL) { + log_err("Could not malloc channel sendbuf: %m"); + return false; + } + } + + n = 1; + if (ioctl(fd, FIONBIO, &n) == -1) { + log_err("Could not set non-blocking IO: %m"); + return false; + } + + event_set(&chan->rd_ev, fd, EV_READ | EV_PERSIST, channel_read, chan); + if (event_add(&chan->rd_ev, NULL) == -1) { + log_err("Could not add channel read event: %m"); + return false; + } + + event_set(&chan->wr_ev, fd, EV_WRITE, channel_start, chan); + + chan->refcnt++; + chan->fd = fd; + + log_debug("(fd#%d)", chan->fd); + + return true; +} + +void +channel_close(channel_t *chan) +{ + pkthdr_t *ph; + + assert(chan->state != CHANNEL_CLOSED); + + log_debug("(fd#%d)", chan->fd); + + chan->state = CHANNEL_CLOSED; + event_del(&chan->rd_ev); + event_del(&chan->wr_ev); + close(chan->fd); + chan->refcnt--; + chan->tick = 0; + + while ((ph = STAILQ_FIRST(&chan->pktlist)) != NULL) { + STAILQ_REMOVE_HEAD(&chan->pktlist, next); + pkthdr_free(ph); + chan->qlen--; + } + + if (chan->pfh != NULL) { + pidfile_remove(chan->pfh); + chan->pfh = NULL; + } + + if (chan->refcnt == 0) + channel_free(chan); +} + +void +channel_free(channel_t *chan) +{ + + assert(chan->refcnt == 0); + assert(chan->state == CHANNEL_CLOSED); + assert(chan->qlen == 0); + assert(STAILQ_EMPTY(&chan->pktlist)); + + LIST_REMOVE(chan, next); + free(chan->pfilter); + free(chan->mfilter); + free(chan->sendbuf); + free(chan); + + server_update(--channel_count); + + if (server_limit == 0) { + log_info("connection closed, exiting"); + exit(EXIT_SUCCESS); + } +} + +static void +channel_start(int fd, short ev, void *arg) +{ + channel_t *chan = arg; + pkthdr_t *ph; + + chan->oactive = true; + + while (chan->qlen > 0) { + ph = STAILQ_FIRST(&chan->pktlist); + + channel_timeout(chan, 10); + if (chan->send(chan, ph->data) == false) { + if (event_add(&chan->wr_ev, NULL) == -1) { + log_err("Could not add channel write event: %m"); + channel_close(chan); + } + return; + } + + STAILQ_REMOVE_HEAD(&chan->pktlist, next); + pkthdr_free(ph); + chan->qlen--; + } + + channel_timeout(chan, 0); + chan->oactive = false; +} + +static void +channel_read(int fd, short ev, void *arg) +{ + channel_t *chan = arg; + packet_t *pkt; + ssize_t nr; + + pkt = packet_alloc(chan); + if (pkt == NULL) { + channel_close(chan); + return; + } + + nr = read(fd, pkt->buf, chan->mru); + if (nr == -1) { + log_err("channel read error: %m"); + packet_free(pkt); + channel_close(chan); + return; + } + if (nr == 0) { /* EOF */ + log_debug("(fd#%d) EOF", fd); + packet_free(pkt); + channel_close(chan); + return; + } + pkt->len = nr; + + if (chan->recv(pkt) == true) + channel_dispatch(pkt); + + packet_free(pkt); +} + +static void +channel_dispatch(packet_t *pkt) +{ + channel_t *chan; + + /* + * This is simple routing. I'm not sure if its allowed by + * the PAN or BNEP specifications, but it seems logical + * to send unicast packets to connected destinations where + * possible. + */ + if (!ETHER_IS_MULTICAST(pkt->dst)) { + LIST_FOREACH(chan, &channel_list, next) { + if (chan == pkt->chan + || chan->state != CHANNEL_OPEN) + continue; + + if (memcmp(pkt->dst, chan->raddr, ETHER_ADDR_LEN) == 0) { + if (chan->qlen > CHANNEL_MAXQLEN) + log_notice("Queue overflow"); + else + channel_put(chan, pkt); + + return; + } + } + } + + LIST_FOREACH(chan, &channel_list, next) { + if (chan == pkt->chan + || chan->state != CHANNEL_OPEN) + continue; + + if (chan->qlen > CHANNEL_MAXQLEN) { + log_notice("Queue overflow"); + continue; + } + + channel_put(chan, pkt); + } +} + +void +channel_put(channel_t *chan, packet_t *pkt) +{ + pkthdr_t *ph; + + ph = pkthdr_alloc(pkt); + if (ph == NULL) + return; + + chan->qlen++; + STAILQ_INSERT_TAIL(&chan->pktlist, ph, next); + + if (!chan->oactive) + channel_start(chan->fd, EV_WRITE, chan); +} + +/* + * Simple watchdog timer, only ticks when it is required and + * closes the channel down if it times out. + */ +void +channel_timeout(channel_t *chan, int to) +{ + static struct event ev; + + if (to == 0) + chan->tick = 0; + else + chan->tick = (channel_tick + to) % 60; + + if (channel_tick == 0) { + evtimer_set(&ev, channel_watchdog, &ev); + channel_watchdog(0, 0, &ev); + } +} + +static void +channel_watchdog(int fd, short ev, void *arg) +{ + static struct timeval tv = { .tv_sec = 1 }; + channel_t *chan, *next; + int tick; + + tick = (channel_tick % 60) + 1; + channel_tick = 0; + + next = LIST_FIRST(&channel_list); + while ((chan = next) != NULL) { + next = LIST_NEXT(chan, next); + + if (chan->tick == tick) + channel_close(chan); + else if (chan->tick != 0) + channel_tick = tick; + } + + if (channel_tick != 0 && evtimer_add(arg, &tv) < 0) { + log_err("Could not add watchdog event: %m"); + exit(EXIT_FAILURE); + } +} diff --git a/usr.sbin/bluetooth/btpand/client.c b/usr.sbin/bluetooth/btpand/client.c new file mode 100644 index 000000000000..ecef7e9c3c14 --- /dev/null +++ b/usr.sbin/bluetooth/btpand/client.c @@ -0,0 +1,229 @@ +/* $NetBSD: client.c,v 1.2 2008/12/06 20:01:14 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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: client.c,v 1.2 2008/12/06 20:01:14 plunky Exp $"); + +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <sdp.h> +#include <unistd.h> + +#include "btpand.h" +#include "bnep.h" +#include "sdp.h" + +static void client_query(void); + +void +client_init(void) +{ + struct sockaddr_l2cap sa; + channel_t *chan; + socklen_t len; + int fd, n; + uint16_t mru, mtu; + + if (bdaddr_any(&remote_bdaddr)) + return; + + if (service_name) + client_query(); + + fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (fd == -1) { + log_err("Could not open L2CAP socket: %m"); + exit(EXIT_FAILURE); + } + + memset(&sa, 0, sizeof(sa)); + sa.l2cap_family = AF_BLUETOOTH; + sa.l2cap_len = sizeof(sa); + sa.l2cap_bdaddr_type = BDADDR_BREDR; + sa.l2cap_cid = 0; + + bdaddr_copy(&sa.l2cap_bdaddr, &local_bdaddr); + if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + log_err("Could not bind client socket: %m"); + exit(EXIT_FAILURE); + } + + mru = BNEP_MTU_MIN; + if (setsockopt(fd, SOL_L2CAP, SO_L2CAP_IMTU, &mru, sizeof(mru)) == -1) { + log_err("Could not set L2CAP IMTU (%d): %m", mru); + exit(EXIT_FAILURE); + } + + log_info("Opening connection to service 0x%4.4x at %s", + service_class, bt_ntoa(&remote_bdaddr, NULL)); + + sa.l2cap_psm = htole16(l2cap_psm); + bdaddr_copy(&sa.l2cap_bdaddr, &remote_bdaddr); + if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + log_err("Could not connect: %m"); + exit(EXIT_FAILURE); + } + + len = sizeof(mru); + if (getsockopt(fd, SOL_L2CAP, SO_L2CAP_IMTU, &mru, &len) == -1) { + log_err("Could not get IMTU: %m"); + exit(EXIT_FAILURE); + } + if (mru < BNEP_MTU_MIN) { + log_err("L2CAP IMTU too small (%d)", mru); + exit(EXIT_FAILURE); + } + + len = sizeof(n); + if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, &len) == -1) { + log_err("Could not read SO_RCVBUF"); + exit(EXIT_FAILURE); + } + if (n < (mru * 10)) { + n = mru * 10; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) + log_info("Could not increase SO_RCVBUF (from %d)", n); + } + + len = sizeof(mtu); + if (getsockopt(fd, SOL_L2CAP, SO_L2CAP_OMTU, &mtu, &len) == -1) { + log_err("Could not get L2CAP OMTU: %m"); + exit(EXIT_FAILURE); + } + if (mtu < BNEP_MTU_MIN) { + log_err("L2CAP OMTU too small (%d)", mtu); + exit(EXIT_FAILURE); + } + + len = sizeof(n); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, &len) == -1) { + log_err("Could not get socket send buffer size: %m"); + close(fd); + return; + } + if (n < (mtu * 2)) { + n = mtu * 2; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)) == -1) { + log_err("Could not set socket send buffer size (%d): %m", n); + close(fd); + return; + } + } + n = mtu; + if (setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &n, sizeof(n)) == -1) { + log_err("Could not set socket low water mark (%d): %m", n); + close(fd); + return; + } + + chan = channel_alloc(); + if (chan == NULL) + exit(EXIT_FAILURE); + + chan->send = bnep_send; + chan->recv = bnep_recv; + chan->mru = mru; + chan->mtu = mtu; + b2eaddr(chan->raddr, &remote_bdaddr); + b2eaddr(chan->laddr, &local_bdaddr); + chan->state = CHANNEL_WAIT_CONNECT_RSP; + channel_timeout(chan, 10); + if (!channel_open(chan, fd)) + exit(EXIT_FAILURE); + + bnep_send_control(chan, BNEP_SETUP_CONNECTION_REQUEST, + 2, service_class, SDP_SERVICE_CLASS_PANU); +} + +static void +client_query(void) +{ + uint8_t buffer[512]; + sdp_attr_t attr; + uint32_t range; + void *ss; + int rv; + uint8_t *seq0, *seq1; + + attr.flags = SDP_ATTR_INVALID; + attr.attr = 0; + attr.vlen = sizeof(buffer); + attr.value = buffer; + + range = SDP_ATTR_RANGE(SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST); + + ss = sdp_open(&local_bdaddr, &remote_bdaddr); + if (ss == NULL || (errno = sdp_error(ss)) != 0) { + log_err("%s: %m", service_name); + exit(EXIT_FAILURE); + } + + log_info("Searching for %s service at %s", + service_name, bt_ntoa(&remote_bdaddr, NULL)); + + rv = sdp_search(ss, 1, &service_class, 1, &range, 1, &attr); + if (rv != 0) { + log_err("%s: %s", service_name, strerror(sdp_error(ss))); + exit(EXIT_FAILURE); + } + + sdp_close(ss); + + if (attr.flags != SDP_ATTR_OK + || attr.attr != SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST) { + log_err("%s service not found", service_name); + exit(EXIT_FAILURE); + } + + /* + * we expect the following protocol descriptor list + * + * seq len + * seq len + * uuid value == L2CAP + * uint16 value16 => PSM + * seq len + * uuid value == BNEP + */ + if (_sdp_get_seq(&attr.value, attr.value + attr.vlen, &seq0) + && _sdp_get_seq(&seq0, attr.value, &seq1) + && _sdp_match_uuid16(&seq1, seq0, SDP_UUID_PROTOCOL_L2CAP) + && _sdp_get_uint16(&seq1, seq0, &l2cap_psm) + && _sdp_get_seq(&seq0, attr.value, &seq1) + && _sdp_match_uuid16(&seq1, seq0, SDP_UUID_PROTOCOL_BNEP)) { + log_info("Found PSM %d for service %s", l2cap_psm, service_name); + return; + } + + log_err("%s query failed", service_name); + exit(EXIT_FAILURE); +} diff --git a/usr.sbin/bluetooth/btpand/event.c b/usr.sbin/bluetooth/btpand/event.c new file mode 100644 index 000000000000..61825b63a3fc --- /dev/null +++ b/usr.sbin/bluetooth/btpand/event.c @@ -0,0 +1,311 @@ +/* + * event.h + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2009 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. + */ + + +/* + * Hack to provide libevent (see devel/libevent port) like API. + * Should be removed if FreeBSD ever decides to import libevent into base. + */ + +#include <sys/select.h> +#include <sys/time.h> +#include <sys/queue.h> +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> + +#include "event.h" +#define L2CAP_SOCKET_CHECKED +#include "btpand.h" + +#define __event_link(ev) \ +do { \ + TAILQ_INSERT_TAIL(&pending, ev, next); \ + ev->flags |= EV_PENDING; \ +} while (0) + +static void tv_add(struct timeval *, struct timeval const *); +static void tv_sub(struct timeval *, struct timeval const *); +static int tv_cmp(struct timeval const *, struct timeval const *); +static int __event_dispatch(void); +static void __event_add_current(struct event *); +static void __event_del_current(struct event *); + + +static TAILQ_HEAD(, event) pending; +static TAILQ_HEAD(, event) current; + +void +event_init(void) +{ + TAILQ_INIT(&pending); +} + +int +event_dispatch(void) +{ + while (__event_dispatch() == 0) + ; + + return (-1); +} + +static int +__event_dispatch(void) +{ + fd_set r, w; + int nfd; + struct event *ev; + struct timeval now, timeout, t; + + FD_ZERO(&r); + FD_ZERO(&w); + + nfd = 0; + + gettimeofday(&now, NULL); + + timeout.tv_sec = 10; /* arbitrary */ + timeout.tv_usec = 0; + + TAILQ_INIT(¤t); + + /* + * Build fd_set's + */ + + event_log_debug("%s: building fd set...", __func__); + + while (!TAILQ_EMPTY(&pending)) { + ev = TAILQ_FIRST(&pending); + event_del(ev); + + if (ev->flags & EV_HAS_TIMEOUT) { + if (tv_cmp(&now, &ev->expire) >= 0) + t.tv_sec = t.tv_usec = 0; + else { + t = ev->expire; + tv_sub(&t, &now); + } + + if (tv_cmp(&t, &timeout) < 0) + timeout = t; + } + + if (ev->fd >= 0) { + if (ev->flags & EV_READ) { + FD_SET(ev->fd, &r); + nfd = (nfd > ev->fd) ? nfd : ev->fd; + } + + if (ev->flags & EV_WRITE) { + FD_SET(ev->fd, &w); + nfd = (nfd > ev->fd) ? nfd : ev->fd; + } + } + + __event_add_current(ev); + } + + event_log_debug("%s: waiting for events...", __func__); + + nfd = select(nfd + 1, &r, &w, NULL, &timeout); + if (nfd < 0) + return (-1); + + /* + * Process current pending + */ + + event_log_debug("%s: processing events...", __func__); + + gettimeofday(&now, NULL); + + while (!TAILQ_EMPTY(¤t)) { + ev = TAILQ_FIRST(¤t); + __event_del_current(ev); + + /* check if fd is ready for reading/writing */ + if (nfd > 0 && ev->fd >= 0) { + if (FD_ISSET(ev->fd, &r) || FD_ISSET(ev->fd, &w)) { + if (ev->flags & EV_PERSIST) { + if (ev->flags & EV_HAS_TIMEOUT) + event_add(ev, &ev->timeout); + else + event_add(ev, NULL); + } + + nfd --; + + event_log_debug("%s: calling %p(%d, %p), " \ + "ev=%p", __func__, ev->cb, ev->fd, + ev->cbarg, ev); + + (ev->cb)(ev->fd, + (ev->flags & (EV_READ|EV_WRITE)), + ev->cbarg); + + continue; + } + } + + /* if event has no timeout - just requeue */ + if ((ev->flags & EV_HAS_TIMEOUT) == 0) { + event_add(ev, NULL); + continue; + } + + /* check if event has expired */ + if (tv_cmp(&now, &ev->expire) >= 0) { + if (ev->flags & EV_PERSIST) + event_add(ev, &ev->timeout); + + event_log_debug("%s: calling %p(%d, %p), ev=%p", + __func__, ev->cb, ev->fd, ev->cbarg, ev); + + (ev->cb)(ev->fd, + (ev->flags & (EV_READ|EV_WRITE)), + ev->cbarg); + + continue; + } + + assert((ev->flags & (EV_PENDING|EV_CURRENT)) == 0); + __event_link(ev); + } + + return (0); +} + +void +__event_set(struct event *ev, int fd, short flags, + void (*cb)(int, short, void *), void *cbarg) +{ + ev->fd = fd; + ev->flags = flags; + ev->cb = cb; + ev->cbarg = cbarg; +} + +int +__event_add(struct event *ev, const struct timeval *timeout) +{ + assert((ev->flags & (EV_PENDING|EV_CURRENT)) == 0); + + if (timeout != NULL) { + gettimeofday(&ev->expire, NULL); + tv_add(&ev->expire, timeout); + ev->timeout = *timeout; + ev->flags |= EV_HAS_TIMEOUT; + } else + ev->flags &= ~EV_HAS_TIMEOUT; + + __event_link(ev); + + return (0); +} + +int +__event_del(struct event *ev) +{ + assert((ev->flags & EV_CURRENT) == 0); + + if ((ev->flags & EV_PENDING) != 0) { + TAILQ_REMOVE(&pending, ev, next); + ev->flags &= ~EV_PENDING; + } + + return (0); +} + +static void +__event_add_current(struct event *ev) +{ + assert((ev->flags & (EV_PENDING|EV_CURRENT)) == 0); + + TAILQ_INSERT_TAIL(¤t, ev, next); + ev->flags |= EV_CURRENT; +} + +static void +__event_del_current(struct event *ev) +{ + assert((ev->flags & (EV_CURRENT|EV_PENDING)) == EV_CURRENT); + + TAILQ_REMOVE(¤t, ev, next); + ev->flags &= ~EV_CURRENT; +} + +static void +tv_add(struct timeval *a, struct timeval const *b) +{ + a->tv_sec += b->tv_sec; + a->tv_usec += b->tv_usec; + + if(a->tv_usec >= 1000000) { + a->tv_usec -= 1000000; + a->tv_sec += 1; + } +} + +static void +tv_sub(struct timeval *a, struct timeval const *b) +{ + if (a->tv_usec < b->tv_usec) { + a->tv_usec += 1000000; + a->tv_sec -= 1; + } + + a->tv_usec -= b->tv_usec; + a->tv_sec -= b->tv_sec; +} + +static int +tv_cmp(struct timeval const *a, struct timeval const *b) +{ + if (a->tv_sec > b->tv_sec) + return (1); + + if (a->tv_sec < b->tv_sec) + return (-1); + + if (a->tv_usec > b->tv_usec) + return (1); + + if (a->tv_usec < b->tv_usec) + return (-1); + + return (0); +} + diff --git a/usr.sbin/bluetooth/btpand/event.h b/usr.sbin/bluetooth/btpand/event.h new file mode 100644 index 000000000000..dd588e86f6bf --- /dev/null +++ b/usr.sbin/bluetooth/btpand/event.h @@ -0,0 +1,148 @@ +/* + * event.h + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2009 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. + */ + + +/* + * Hack to provide libevent (see devel/libevent port) like API. + * Should be removed if FreeBSD ever decides to import libevent into base. + */ + +#ifndef _EVENT_H_ +#define _EVENT_H_ 1 + +#define EV_READ 0x02 +#define EV_WRITE 0x04 +#define EV_PERSIST 0x10 /* Persistent event */ +#define EV_PENDING (1 << 13) /* internal use only! */ +#define EV_HAS_TIMEOUT (1 << 14) /* internal use only! */ +#define EV_CURRENT (1 << 15) /* internal use only! */ + +struct event +{ + int fd; + short flags; + void (*cb)(int, short, void *); + void *cbarg; + struct timeval timeout; + struct timeval expire; + +#ifdef EVENT_DEBUG + char const *files[3]; + int lines[3]; +#endif + + TAILQ_ENTRY(event) next; +}; + +void event_init (void); +int event_dispatch (void); + +void __event_set (struct event *, int, short, + void (*)(int, short, void *), void *); +int __event_add (struct event *, struct timeval const *); +int __event_del (struct event *); + +#ifdef EVENT_DEBUG +#define event_log_err(fmt, args...) syslog(LOG_ERR, fmt, ##args) +#define event_log_info(fmt, args...) syslog(LOG_INFO, fmt, ##args) +#define event_log_notice(fmt, args...) syslog(LOG_NOTICE, fmt, ##args) +#define event_log_debug(fmt, args...) syslog(LOG_DEBUG, fmt, ##args) + +#define event_set(ev, fd, flags, cb, cbarg) \ + _event_set(__FILE__, __LINE__, ev, fd, flags, cb, cbarg) +#define event_add(ev, timeout) \ + _event_add(__FILE__, __LINE__, ev, timeout) +#define event_del(ev) \ + _event_del(__FILE__, __LINE__, ev) + +#define evtimer_set(ev, cb, cbarg) \ + _event_set(__FILE__, __LINE__, ev, -1, 0, cb, cbarg) +#define evtimer_add(ev, timeout) \ + _event_add(__FILE__, __LINE__, ev, timeout) + +static inline void +_event_set(char const *file, int line, struct event *ev, int fd, short flags, + void (*cb)(int, short, void *), void *cbarg) +{ + event_log_debug("set %s:%d ev=%p, fd=%d, flags=%#x, cb=%p, cbarg=%p", + file, line, ev, fd, flags, cb, cbarg); + + ev->files[0] = file; + ev->lines[0] = line; + + __event_set(ev, fd, flags, cb, cbarg); +} + +static inline int +_event_add(char const *file, int line, struct event *ev, + struct timeval const *timeout) { + event_log_debug("add %s:%d ev=%p, fd=%d, flags=%#x, cb=%p, cbarg=%p, " \ + "timeout=%p", file, line, ev, ev->fd, ev->flags, ev->cb, + ev->cbarg, timeout); + + ev->files[1] = file; + ev->lines[1] = line; + + return (__event_add(ev, timeout)); +} + +static inline int +_event_del(char const *file, int line, struct event *ev) +{ + event_log_debug("del %s:%d ev=%p, fd=%d, flags=%#x, cb=%p, cbarg=%p", + file, line, ev, ev->fd, ev->flags, ev->cb, ev->cbarg); + + ev->files[2] = file; + ev->lines[2] = line; + + return (__event_del(ev)); +} +#else +#define event_log_err(fmt, args...) +#define event_log_info(fmt, args...) +#define event_log_notice(fmt, args...) +#define event_log_debug(fmt, args...) + +#define event_set(ev, fd, flags, cb, cbarg) \ + __event_set(ev, fd, flags, cb, cbarg) +#define event_add(ev, timeout) \ + __event_add(ev, timeout) +#define event_del(ev) \ + __event_del(ev) + +#define evtimer_set(ev, cb, cbarg) \ + __event_set(ev, -1, 0, cb, cbarg) +#define evtimer_add(ev, timeout) \ + __event_add(ev, timeout) +#endif /* EVENT_DEBUG */ + +#endif /* ndef _EVENT_H_ */ diff --git a/usr.sbin/bluetooth/btpand/packet.c b/usr.sbin/bluetooth/btpand/packet.c new file mode 100644 index 000000000000..3051b0fbaecd --- /dev/null +++ b/usr.sbin/bluetooth/btpand/packet.c @@ -0,0 +1,112 @@ +/* $NetBSD: packet.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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: packet.c,v 1.1 2008/08/17 13:20:57 plunky Exp $"); + +#define L2CAP_SOCKET_CHECKED +#include "btpand.h" + +packet_t * +packet_alloc(channel_t *chan) +{ + packet_t *pkt; + + pkt = malloc(sizeof(packet_t) + chan->mru); + if (pkt == NULL) { + log_err("%s() failed: %m", __func__); + return NULL; + } + + memset(pkt, 0, sizeof(packet_t)); + STAILQ_INIT(&pkt->extlist); + pkt->ptr = pkt->buf; + + pkt->chan = chan; + chan->refcnt++; + + return pkt; +} + +void +packet_free(packet_t *pkt) +{ + exthdr_t *eh; + + if (pkt->refcnt-- > 0) + return; + + while ((eh = STAILQ_FIRST(&pkt->extlist)) != NULL) { + STAILQ_REMOVE_HEAD(&pkt->extlist, next); + free(eh); + } + + pkt->chan->refcnt--; + if (pkt->chan->refcnt == 0) + channel_free(pkt->chan); + + free(pkt); +} + +void +packet_adj(packet_t *pkt, size_t size) +{ + + assert(pkt->refcnt == 0); + assert(pkt->len >= size); + + pkt->ptr += size; + pkt->len -= size; +} + +pkthdr_t * +pkthdr_alloc(packet_t *pkt) +{ + pkthdr_t *ph; + + ph = malloc(sizeof(pkthdr_t)); + if (ph == NULL) { + log_err("%s() failed: %m", __func__); + return NULL; + } + + ph->data = pkt; + pkt->refcnt++; + + return ph; +} + +void +pkthdr_free(pkthdr_t *ph) +{ + + packet_free(ph->data); + free(ph); +} diff --git a/usr.sbin/bluetooth/btpand/sdp.c b/usr.sbin/bluetooth/btpand/sdp.c new file mode 100644 index 000000000000..a21710781cf2 --- /dev/null +++ b/usr.sbin/bluetooth/btpand/sdp.c @@ -0,0 +1,211 @@ +/* $NetBSD: sdp.c,v 1.2 2008/12/06 20:01:14 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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.2 2008/12/06 20:01:14 plunky Exp $"); + +#include <string.h> + +#define L2CAP_SOCKET_CHECKED +#include "sdp.h" + +/* + * SDP data stream manipulation routines + */ + +/* Bluetooth Base UUID */ +static const uuid_t BASE_UUID = { + 0x00000000, + 0x0000, + 0x1000, + 0x80, + 0x00, + { 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb } +}; + +/* + * _sdp_match_uuid16(ptr, limit, uuid) + * + * examine SDP data stream at ptr for a UUID, and return + * true if it matches the supplied short alias bluetooth UUID. + * limit is the first address past the end of valid data. + */ +bool +_sdp_match_uuid16(uint8_t **ptr, uint8_t *limit, uint16_t uuid) +{ + uint8_t *p = *ptr; + uuid_t u1, u2; + + memcpy(&u1, &BASE_UUID, sizeof(uuid_t)); + u1.time_low = uuid; + + if (!_sdp_get_uuid(&p, limit, &u2) + || !uuid_equal(&u1, &u2, NULL)) + return false; + + *ptr = p; + return true; +} + +/* + * _sdp_get_uuid(ptr, limit, uuid) + * + * examine SDP data stream at ptr for a UUID, and extract + * to given storage, advancing ptr. + * limit is the first address past the end of valid data. + */ +bool +_sdp_get_uuid(uint8_t **ptr, uint8_t *limit, uuid_t *uuid) +{ + uint8_t *p = *ptr; + + if (p + 1 > limit) + return false; + + switch (*p++) { + case SDP_DATA_UUID16: + if (p + 2 > limit) + return false; + + memcpy(uuid, &BASE_UUID, sizeof(uuid_t)); + uuid->time_low = be16dec(p); + p += 2; + break; + + case SDP_DATA_UUID32: + if (p + 4 > limit) + return false; + + memcpy(uuid, &BASE_UUID, sizeof(uuid_t)); + uuid->time_low = be32dec(p); + p += 4; + break; + + case SDP_DATA_UUID128: + if (p + 16 > limit) + return false; + + uuid_dec_be(p, uuid); + p += 16; + break; + + default: + return false; + } + + *ptr = p; + return true; +} + +/* + * _sdp_get_seq(ptr, limit, seq) + * + * examine SDP data stream at ptr for a sequence. return + * seq pointer if found and advance ptr to next object. + * limit is the first address past the end of valid data. + */ +bool +_sdp_get_seq(uint8_t **ptr, uint8_t *limit, uint8_t **seq) +{ + uint8_t *p = *ptr; + int32_t l; + + if (p + 1 > limit) + return false; + + switch (*p++) { + case SDP_DATA_SEQ8: + if (p + 1 > limit) + return false; + + l = *p; + p += 1; + break; + + case SDP_DATA_SEQ16: + if (p + 2 > limit) + return false; + + l = be16dec(p); + p += 2; + break; + + case SDP_DATA_SEQ32: + if (p + 4 > limit) + return false; + + l = be32dec(p); + p += 4; + break; + + default: + return false; + } + if (p + l > limit) + return false; + + *seq = p; + *ptr = p + l; + return true; +} + +/* + * _sdp_get_uint16(ptr, limit, value) + * + * examine SDP data stream at ptr for a uint16_t, and + * extract to given storage, advancing ptr. + * limit is the first address past the end of valid data. + */ +bool +_sdp_get_uint16(uint8_t **ptr, uint8_t *limit, uint16_t *value) +{ + uint8_t *p = *ptr; + uint16_t v; + + if (p + 1 > limit) + return false; + + switch (*p++) { + case SDP_DATA_UINT16: + if (p + 2 > limit) + return false; + + v = be16dec(p); + p += 2; + break; + + default: + return false; + } + + *value = v; + *ptr = p; + return true; +} diff --git a/usr.sbin/bluetooth/btpand/sdp.h b/usr.sbin/bluetooth/btpand/sdp.h new file mode 100644 index 000000000000..66c5c601bd1a --- /dev/null +++ b/usr.sbin/bluetooth/btpand/sdp.h @@ -0,0 +1,39 @@ +/* $NetBSD: sdp.h,v 1.2 2008/12/06 20:01:15 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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 <bluetooth.h> +#include <sdp.h> +#include <stdbool.h> +#include <uuid.h> + +bool _sdp_match_uuid16(uint8_t **, uint8_t *, uint16_t); +bool _sdp_get_uuid(uint8_t **, uint8_t *, uuid_t *); +bool _sdp_get_seq(uint8_t **, uint8_t *, uint8_t **); +bool _sdp_get_uint16(uint8_t **, uint8_t *, uint16_t *); diff --git a/usr.sbin/bluetooth/btpand/server.c b/usr.sbin/bluetooth/btpand/server.c new file mode 100644 index 000000000000..f0967d058ca4 --- /dev/null +++ b/usr.sbin/bluetooth/btpand/server.c @@ -0,0 +1,294 @@ +/* $NetBSD: server.c,v 1.2 2009/01/24 17:29:28 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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: server.c,v 1.2 2009/01/24 17:29:28 plunky Exp $"); + +#include <sys/ioctl.h> + +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <inttypes.h> +#include <errno.h> +#include <sdp.h> +#include <unistd.h> + +#include "btpand.h" +#include "bnep.h" + +static struct event server_ev; +static int server_fd; +static int server_avail; + +static void * server_ss; +static uint32_t server_handle; + +static void server_open(void); +static void server_close(void); +static void server_read(int, short, void *); +static void server_register(void); + +void +server_init(void) +{ + + server_fd = -1; +} + +/* + * The server_update() function is called whenever the channel count is + * changed. We maintain the SDP record and open or close the server socket + * as required. + */ +void +server_update(int count) +{ + + if (server_limit == 0) + return; + + log_debug("count %d", count); + + server_avail = UINT8_MAX - (count - 1) * UINT8_MAX / server_limit; + log_info("Service Availability: %d/%d", server_avail, UINT8_MAX); + + if (server_avail == 0 && server_fd != -1) + server_close(); + + if (server_avail > 0 && server_fd == -1) + server_open(); + + if (service_name) + server_register(); +} + +static void +server_open(void) +{ + struct sockaddr_l2cap sa; + uint16_t mru; + + server_fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (server_fd == -1) { + log_err("Could not open L2CAP socket: %m"); + exit(EXIT_FAILURE); + } + + memset(&sa, 0, sizeof(sa)); + sa.l2cap_family = AF_BLUETOOTH; + sa.l2cap_len = sizeof(sa); + sa.l2cap_psm = htole16(l2cap_psm); + sa.l2cap_bdaddr_type = BDADDR_BREDR; + sa.l2cap_cid = 0; + + bdaddr_copy(&sa.l2cap_bdaddr, &local_bdaddr); + if (bind(server_fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + log_err("Could not bind server socket: %m"); + exit(EXIT_FAILURE); + } + + mru = BNEP_MTU_MIN; + if (setsockopt(server_fd, SOL_L2CAP, + SO_L2CAP_IMTU, &mru, sizeof(mru)) == -1) { + log_err("Could not set L2CAP IMTU (%d): %m", mru); + exit(EXIT_FAILURE); + } + + if (listen(server_fd, 0) == -1) { + log_err("Could not listen on server socket: %m"); + exit(EXIT_FAILURE); + } + + event_set(&server_ev, server_fd, EV_READ | EV_PERSIST, server_read, NULL); + if (event_add(&server_ev, NULL) == -1) { + log_err("Could not add server event: %m"); + exit(EXIT_FAILURE); + } + + log_info("server socket open"); +} + +static void +server_close(void) +{ + + event_del(&server_ev); + close(server_fd); + server_fd = -1; + + log_info("server socket closed"); +} + +/* + * handle connection request + */ +static void +server_read(int s, short ev, void *arg) +{ + struct sockaddr_l2cap ra, la; + channel_t *chan; + socklen_t len; + int fd, n; + uint16_t mru, mtu; + + len = sizeof(ra); + fd = accept(s, (struct sockaddr *)&ra, &len); + if (fd == -1) + return; + + n = 1; + if (ioctl(fd, FIONBIO, &n) == -1) { + log_err("Could not set NonBlocking IO: %m"); + close(fd); + return; + } + + len = sizeof(mru); + if (getsockopt(fd, SOL_L2CAP, SO_L2CAP_IMTU, &mru, &len) == -1) { + log_err("Could not get L2CAP IMTU: %m"); + close(fd); + return; + } + if(mru < BNEP_MTU_MIN) { + log_err("L2CAP IMTU too small (%d)", mru); + close(fd); + return; + } + + len = sizeof(n); + if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, &len) == -1) { + log_err("Could not read SO_RCVBUF"); + close(fd); + return; + } + if (n < (mru * 10)) { + n = mru * 10; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) + log_info("Could not increase SO_RCVBUF (from %d)", n); + } + + len = sizeof(mtu); + if (getsockopt(fd, SOL_L2CAP, SO_L2CAP_OMTU, &mtu, &len) == -1) { + log_err("Could not get L2CAP OMTU: %m"); + close(fd); + return; + } + if (mtu < BNEP_MTU_MIN) { + log_err("L2CAP OMTU too small (%d)", mtu); + close(fd); + return; + } + + len = sizeof(n); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, &len) == -1) { + log_err("Could not get socket send buffer size: %m"); + close(fd); + return; + } + + if (n < (mtu * 2)) { + n = mtu * 2; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)) == -1) { + log_err("Could not set socket send buffer size (%d): %m", n); + close(fd); + return; + } + } + + n = mtu; + if (setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &n, sizeof(n)) == -1) { + log_err("Could not set socket low water mark (%d): %m", n); + close(fd); + return; + } + + len = sizeof(la); + if (getsockname(fd, (struct sockaddr *)&la, &len) == -1) { + log_err("Could not get socket address: %m"); + close(fd); + return; + } + + log_info("Accepted connection from %s", bt_ntoa(&ra.l2cap_bdaddr, NULL)); + + chan = channel_alloc(); + if (chan == NULL) { + close(fd); + return; + } + + chan->send = bnep_send; + chan->recv = bnep_recv; + chan->mru = mru; + chan->mtu = mtu; + b2eaddr(chan->raddr, &ra.l2cap_bdaddr); + b2eaddr(chan->laddr, &la.l2cap_bdaddr); + chan->state = CHANNEL_WAIT_CONNECT_REQ; + channel_timeout(chan, 10); + if (!channel_open(chan, fd)) { + chan->state = CHANNEL_CLOSED; + channel_free(chan); + close(fd); + return; + } +} + +static void +server_register(void) +{ + sdp_nap_profile_t p; + int rv; + + if (server_ss == NULL) { + server_ss = sdp_open_local(control_path); + if (server_ss == NULL || sdp_error(server_ss) != 0) { + log_err("failed to contact SDP server"); + return; + } + } + + memset(&p, 0, sizeof(p)); + p.psm = l2cap_psm; + p.load_factor = server_avail; + p.security_description = (l2cap_mode == 0 ? 0x0000 : 0x0001); + + if (server_handle) + rv = sdp_change_service(server_ss, server_handle, + (uint8_t *)&p, sizeof(p)); + else + rv = sdp_register_service(server_ss, service_class, + &local_bdaddr, (uint8_t *)&p, sizeof(p), &server_handle); + + if (rv != 0) { + errno = sdp_error(server_ss); + log_err("%s: %m", service_name); + exit(EXIT_FAILURE); + } +} diff --git a/usr.sbin/bluetooth/btpand/tap.c b/usr.sbin/bluetooth/btpand/tap.c new file mode 100644 index 000000000000..011ae3989154 --- /dev/null +++ b/usr.sbin/bluetooth/btpand/tap.c @@ -0,0 +1,169 @@ +/* $NetBSD: tap.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Iain Hibbert + * 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 ``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 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: tap.c,v 1.1 2008/08/17 13:20:57 plunky Exp $"); + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/uio.h> + +#include <net/if_tap.h> + +#include <fcntl.h> +#include <libutil.h> +#include <paths.h> +#include <stdio.h> +#include <unistd.h> + +#define L2CAP_SOCKET_CHECKED +#include "btpand.h" + +static bool tap_send(channel_t *, packet_t *); +static bool tap_recv(packet_t *); + +void +tap_init(void) +{ + channel_t *chan; + struct ifreq ifr; + int fd, s; + char pidfile[PATH_MAX]; + + fd = open(interface_name, O_RDWR); + if (fd == -1) { + log_err("Could not open \"%s\": %m", interface_name); + exit(EXIT_FAILURE); + } + + memset(&ifr, 0, sizeof(ifr)); + if (ioctl(fd, TAPGIFNAME, &ifr) == -1) { + log_err("Could not get interface name: %m"); + exit(EXIT_FAILURE); + } + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s == -1) { + log_err("Could not open PF_LINK socket: %m"); + exit(EXIT_FAILURE); + } + + ifr.ifr_addr.sa_family = AF_LINK; + ifr.ifr_addr.sa_len = ETHER_ADDR_LEN; + b2eaddr(ifr.ifr_addr.sa_data, &local_bdaddr); + + if (ioctl(s, SIOCSIFLLADDR, &ifr) == -1) { + log_err("Could not set %s physical address: %m", ifr.ifr_name); + exit(EXIT_FAILURE); + } + + if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) { + log_err("Could not get interface flags: %m"); + exit(EXIT_FAILURE); + } + + if ((ifr.ifr_flags & IFF_UP) == 0) { + ifr.ifr_flags |= IFF_UP; + + if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) { + log_err("Could not set IFF_UP: %m"); + exit(EXIT_FAILURE); + } + } + + close(s); + + log_info("Using interface %s with addr %s", ifr.ifr_name, + ether_ntoa((struct ether_addr *)&ifr.ifr_addr.sa_data)); + + chan = channel_alloc(); + if (chan == NULL) + exit(EXIT_FAILURE); + + chan->send = tap_send; + chan->recv = tap_recv; + chan->mru = ETHER_HDR_LEN + ETHER_MAX_LEN; + memcpy(chan->raddr, ifr.ifr_addr.sa_data, ETHER_ADDR_LEN); + memcpy(chan->laddr, ifr.ifr_addr.sa_data, ETHER_ADDR_LEN); + chan->state = CHANNEL_OPEN; + if (!channel_open(chan, fd)) + exit(EXIT_FAILURE); + + snprintf(pidfile, sizeof(pidfile), "%s/%s.pid", + _PATH_VARRUN, ifr.ifr_name); + chan->pfh = pidfile_open(pidfile, 0600, NULL); + if (chan->pfh == NULL) + log_err("can't create pidfile"); + else if (pidfile_write(chan->pfh) < 0) { + log_err("can't write pidfile"); + pidfile_remove(chan->pfh); + chan->pfh = NULL; + } +} + +static bool +tap_send(channel_t *chan, packet_t *pkt) +{ + struct iovec iov[4]; + ssize_t nw; + + iov[0].iov_base = pkt->dst; + iov[0].iov_len = ETHER_ADDR_LEN; + iov[1].iov_base = pkt->src; + iov[1].iov_len = ETHER_ADDR_LEN; + iov[2].iov_base = pkt->type; + iov[2].iov_len = ETHER_TYPE_LEN; + iov[3].iov_base = pkt->ptr; + iov[3].iov_len = pkt->len; + + /* tap device write never fails */ + nw = writev(chan->fd, iov, __arraycount(iov)); + assert(nw > 0); + + return true; +} + +static bool +tap_recv(packet_t *pkt) +{ + + if (pkt->len < ETHER_HDR_LEN) + return false; + + pkt->dst = pkt->ptr; + packet_adj(pkt, ETHER_ADDR_LEN); + pkt->src = pkt->ptr; + packet_adj(pkt, ETHER_ADDR_LEN); + pkt->type = pkt->ptr; + packet_adj(pkt, ETHER_TYPE_LEN); + + return true; +} diff --git a/usr.sbin/bluetooth/hccontrol/Makefile b/usr.sbin/bluetooth/hccontrol/Makefile new file mode 100644 index 000000000000..b662bc9fbbc2 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/Makefile @@ -0,0 +1,15 @@ +# $Id: Makefile,v 1.7 2003/08/14 20:06:17 max Exp $ + +PACKAGE= bluetooth +CONFS= bluetooth.device.conf +CONFSDIR= /etc/defaults +PROG= hccontrol +MAN= hccontrol.8 +SRCS= send_recv.c link_policy.c link_control.c le.c\ + host_controller_baseband.c info.c status.c node.c hccontrol.c \ + util.c adv_data.c +WARNS?= 2 + +LIBADD= bluetooth + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/hccontrol/Makefile.depend b/usr.sbin/bluetooth/hccontrol/Makefile.depend new file mode 100644 index 000000000000..5d0531350f25 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/hccontrol/adv_data.c b/usr.sbin/bluetooth/hccontrol/adv_data.c new file mode 100644 index 000000000000..e8d91ff59151 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/adv_data.c @@ -0,0 +1,248 @@ +/*- + * adv_data.c + * + * SPDX-License-Identifier: BSD-2-Clause + + * Copyright (c) 2020 Marc Veldman <marc@bumblingdork.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. + * + * $Id$ + */ + +#include <sys/types.h> +#include <stdio.h> +#include <string.h> +#include <uuid.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include "hccontrol.h" + +static char* const adv_data2str(int len, uint8_t* data, char* buffer, + int size); +static char* const adv_name2str(int len, uint8_t* advdata, char* buffer, + int size); +static char* const adv_uuid2str(int datalen, uint8_t* data, char* buffer, + int size); + +void dump_adv_data(int len, uint8_t* advdata) +{ + int n=0; + fprintf(stdout, "\tADV Data: "); + for (n = 0; n < len+1; n++) { + fprintf(stdout, "%02x ", advdata[n]); + } + fprintf(stdout, "\n"); +} + +void print_adv_data(int len, uint8_t* advdata) +{ + int n=0; + while(n < len) + { + char buffer[2048]; + uint8_t datalen = advdata[n]; + uint8_t datatype = advdata[++n]; + /* Skip type */ + ++n; + datalen--; + switch (datatype) { + case 0x01: + fprintf(stdout, + "\tFlags: %s\n", + adv_data2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0x02: + fprintf(stdout, + "\tIncomplete list of service" + " class UUIDs (16-bit): %s\n", + adv_data2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0x03: + fprintf(stdout, + "\tComplete list of service " + "class UUIDs (16-bit): %s\n", + adv_data2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0x07: + fprintf(stdout, + "\tComplete list of service " + "class UUIDs (128 bit): %s\n", + adv_uuid2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0x08: + fprintf(stdout, + "\tShortened local name: %s\n", + adv_name2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0x09: + fprintf(stdout, + "\tComplete local name: %s\n", + adv_name2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0x0a: + fprintf(stdout, + "\tTx Power level: %d dBm\n", + (int8_t)advdata[n]); + break; + case 0x0d: + fprintf(stdout, + "\tClass of device: %s\n", + adv_data2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0x16: + fprintf(stdout, + "\tService data: %s\n", + adv_data2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0x19: + fprintf(stdout, + "\tAppearance: %s\n", + adv_data2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + break; + case 0xff: + fprintf(stdout, + "\tManufacturer: %s\n", + hci_manufacturer2str( + advdata[n]|advdata[n+1]<<8)); + fprintf(stdout, + "\tManufacturer specific data: %s\n", + adv_data2str( + datalen-2, + &advdata[n+2], + buffer, + sizeof(buffer))); + break; + default: + fprintf(stdout, + "\tUNKNOWN datatype: %02x data %s\n", + datatype, + adv_data2str( + datalen, + &advdata[n], + buffer, + sizeof(buffer))); + } + n += datalen; + } +} + +static char* const adv_data2str(int datalen, uint8_t* data, char* buffer, + int size) +{ + int i = 0; + char tmpbuf[5]; + + if (buffer == NULL) + return NULL; + + memset(buffer, 0, size); + + while(i < datalen) { + (void)snprintf(tmpbuf, sizeof(tmpbuf), "%02x ", data[i]); + /* Check if buffer is full */ + if (strlcat(buffer, tmpbuf, size) > size) + break; + i++; + } + return buffer; +} + +static char* const adv_name2str(int datalen, uint8_t* data, char* buffer, + int size) +{ + if (buffer == NULL) + return NULL; + + memset(buffer, 0, size); + + (void)strlcpy(buffer, (char*)data, datalen+1); + return buffer; +} + +static char* const adv_uuid2str(int datalen, uint8_t* data, char* buffer, + int size) +{ + int i; + uuid_t uuid; + uint32_t ustatus; + char* tmpstr; + + if (buffer == NULL) + return NULL; + + memset(buffer, 0, size); + if (datalen < 16) + return buffer; + uuid.time_low = le32dec(data+12); + uuid.time_mid = le16dec(data+10); + uuid.time_hi_and_version = le16dec(data+8); + uuid.clock_seq_hi_and_reserved = data[7]; + uuid.clock_seq_low = data[6]; + for(i = 0; i < _UUID_NODE_LEN; i++){ + uuid.node[i] = data[5 - i]; + } + uuid_to_string(&uuid, &tmpstr, &ustatus); + if(ustatus == uuid_s_ok) { + strlcpy(buffer, tmpstr, size); + } + free(tmpstr); + + return buffer; +} diff --git a/usr.sbin/bluetooth/hccontrol/bluetooth.device.conf b/usr.sbin/bluetooth/hccontrol/bluetooth.device.conf new file mode 100644 index 000000000000..c400a3bf2e50 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/bluetooth.device.conf @@ -0,0 +1,110 @@ +# Copyright (c) 2005 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. +# + +# The authentication_enable parameter controls if the device requires to +# authenticate the remote device at connection setup. At connection setup, +# only the devices with the authentication_enable parameter enabled will +# try to authenticate the other device. +# +# Possible values: +# +# NO or 0 authentication disabled (default); +# YES or 1 authentication enabled. + +# authentication_enable="NO" + +# The class parameter is used to indicate the capabilities of the device to +# other devices. +# +# For more details see "Assigned Numbers - Bluetooth Baseband" document +# +# Possible value: +# +# xx:xx:xx where xx is a hex number + +# class="ff:01:0c" + +# The connectable parameter controls whether or not the device should +# periodically scan for page attempts from other devices. +# +# Possible values: +# +# NO or 0 do not scan for page attempts; +# YES or 1 scan for page attempts (default). + +# connectable="YES" + +# The discoverable parameter controls whether or not the device should +# periodically scan for inquiry requests from other devices. +# +# Possible values: +# +# NO or 0 do not scan for inquiry requests (default); +# YES or 1 scan for inquiry requests. + +# discoverable="NO" + +# The encryption_mode parameter controls if the device requires encryption +# to the remote device at connection setup. At connection setup, only the +# devices with the authentication_enable parameter enabled and encryption_mode +# parameter enabled will try to encrypt the connection to the other device. +# +# Possible values: +# +# NONE or 0 encryption disabled (default); +# P2P or 1 encryption only for point-to-point packets; +# ALL or 2 encryption for both point-to-point and broadcast packets. + +# encryption_mode="NONE" + +# HCI node debug level. Higher values mean more verbose output. +# +# Possible values: 0 - 4 + +# hci_debug_level="3" + +# L2CAP node debug level. Higher values mean more verbose output. +# +# Possible values: 0 - 4 + +# l2cap_debug_level="3" + +# The local_name parameter provides the ability to modify the user friendly +# name for the device. + +# local_name="My device" + +# The role_switch parameter controls whether the local device should perform +# role switch. By default, if role switch is supported, the local device will +# try to perform role switch and become Master on incoming connection. Some +# devices do not support role switch and thus incoming connections from such +# devices will fail. If role switch is disabled then accepting device will +# remain Slave. +# +# NO or 0 do not perform role switch; +# YES or 1 perform role switch (default). + +# role_switch="YES" + diff --git a/usr.sbin/bluetooth/hccontrol/hccontrol.8 b/usr.sbin/bluetooth/hccontrol/hccontrol.8 new file mode 100644 index 000000000000..28143ecf34a3 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/hccontrol.8 @@ -0,0 +1,216 @@ +.\" Copyright (c) 2001-2002 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. +.\" +.\" $Id: hccontrol.8,v 1.6 2003/08/06 21:26:38 max Exp $ +.\" +.Dd May 3, 2020 +.Dt HCCONTROL 8 +.Os +.Sh NAME +.Nm hccontrol +.Nd Bluetooth HCI configuration utility +.Sh SYNOPSIS +.Nm +.Op Fl hN +.Op Fl n Ar HCI_node_name +.Ar command +.Op Ar parameters ... +.Sh DESCRIPTION +The +.Nm +utility connects to the specified Netgraph node of type +.Dv HCI +or the first one found if none is specified and attempts to send the specified +command to the HCI Netgraph node or to the associated Bluetooth device. +The +.Nm +utility will print results to the standard output and error messages to +the standard error. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl h +Display usage message and exit. +.It Fl N +Show Bluetooth addresses as numbers. +Normally +.Nm +attempts to resolve Bluetooth addresses, and display them symbolically. +.It Fl n Ar HCI_node_name +Connect to the specified HCI Netgraph node. +.It Ar command +One of the supported commands (see below). +The special command +.Cm help +can be used to obtain the list of all supported commands. +To get more information about a specific command use +.Cm help Ar command . +.It Ar parameters +One or more optional space separated command parameters. +Many commands require a remote device address as one of the parameters. +The remote device address can be specified as BD_ADDR or a name. +If a name was specified then the +.Nm +utility will attempt to resolve the name via +.Xr bt_gethostbyname 3 . +.El +.Sh COMMANDS +The currently supported HCI commands in +.Nm +are: +.Pp +.Bl -tag -width 40n -offset indent -compact +.It Cm Inquiry +.It Cm Create_Connection +.It Cm Disconnect +.It Cm Add_SCO_Connection +.It Cm Change_Connection_Packet_Type +.It Cm Remote_Name_Request +.It Cm Read_Remote_Supported_Features +.It Cm Read_Remote_Version_Information +.It Cm Read_Clock_Offset +.It Cm Role_Discovery +.It Cm Switch_Role +.It Cm Read_Link_Policy_Settings +.It Cm Write_Link_Policy_Settings +.It Cm Reset +.It Cm Read_Pin_Type +.It Cm Write_Pin_Type +.It Cm Read_Stored_Link_Key +.It Cm Write_Stored_Link_Key +.It Cm Delete_Stored_Link_Key +.It Cm Change_Local_Name +.It Cm Read_Local_Name +.It Cm Read_Connection_Accept_Timeout +.It Cm Write_Connection_Accept_Timeout +.It Cm Read_Page_Timeout +.It Cm Write_Page_Timeout +.It Cm Read_Scan_Enable +.It Cm Write_Scan_Enable +.It Cm Read_Page_Scan_Activity +.It Cm Write_Page_Scan_Activity +.It Cm Read_Inquiry_Scan_Activity +.It Cm Write_Inquiry_Scan_Activity +.It Cm Read_Authentication_Enable +.It Cm Write_Authentication_Enable +.It Cm Read_Encryption_Mode +.It Cm Write_Encryption_Mode +.It Cm Read_Class_Of_Device +.It Cm Write_Class_Of_Device +.It Cm Read_Voice_Settings +.It Cm Write_Voice_Settings +.It Cm Read_Number_Broadcast_Retransmissions +.It Cm Write_Number_Broadcast_Retransmissions +.It Cm Read_Hold_Mode_Activity +.It Cm Write_Hold_Mode_Activity +.It Cm Read_SCO_Flow_Control_Enable +.It Cm Write_SCO_Flow_Control_Enable +.It Cm Read_Link_Supervision_Timeout +.It Cm Write_Link_Supervision_Timeout +.It Cm Read_Page_Scan_Period_Mode +.It Cm Write_Page_Scan_Period_Mode +.It Cm Read_Page_Scan_Mode +.It Cm Write_Page_Scan_Mode +.It Cm Read_LE_Host_Support +.It Cm Write_LE_Host_Support +.It Cm Read_Local_Version_Information +.It Cm Read_Local_Supported_Commands +.It Cm Read_Local_Supported_Features +.It Cm Read_Buffer_Size +.It Cm Read_Country_Code +.It Cm Read_BD_ADDR +.It Cm Read_Failed_Contact_Counter +.It Cm Reset_Failed_Contact_Counter +.It Cm Get_Link_Quality +.It Cm Read_RSSI +.It Cm LE_Enable +.It Cm LE_Read_Local_Supported_Features +.It Cm LE_Set_Advertising_Parameters +.It Cm LE_Read_Advertising_Physical_Channel_Tx_Power +.It Cm LE_Set_Advertising_Data +.It Cm LE_Set_Scan_Response_Data +.It Cm LE_Set_Advertising_Enable +.It Cm LE_Set_Scan_Parameters +.It Cm LE_Set_Scan_Enable +.It Cm LE_Read_Supported_States +.It Cm LE_Read_Buffer_Size +.It Cm LE_Scan +.It Cm LE_Read_White_List_Size +.It Cm LE_Clear_White_List +.It Cm LE_Add_Device_To_White_List +.It Cm LE_Remove_Device_From_White_List +.It Cm LE_Connect +.It Cm LE_Read_Channel_Map +.It Cm LE_Read_Remote_Features +.It Cm LE_Rand +.El +.Pp +The currently supported node commands in +.Nm +are: +.Pp +.Bl -tag -width 40n -offset indent -compact +.It Cm Read_Node_State +.It Cm Initialize +.It Cm Read_Debug_Level +.It Cm Write_Debug_Level +.It Cm Read_Node_Buffer_Size +.It Cm Read_Node_BD_ADDR +.It Cm Read_Node_Features +.It Cm Read_Node_Stat +.It Cm Reset_Node_Stat +.It Cm Flush_Neighbor_Cache +.It Cm Read_Neighbor_Cache +.It Cm Read_Connection_List +.It Cm Read_Node_Link_Policy_Settings_Mask +.It Cm Write_Node_Link_Policy_Settings_Mask +.It Cm Read_Node_Packet_Mask +.It Cm Write_Node_Packet_Mask +.It Cm Read_Node_Role_Switch +.It Cm Write_Node_Role_Switch +.It Cm Read_Node_List +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Make the blutooth LE host, ubt0hci, scannable through +.Xr hccontrol 8 commands: +.Bd -literal -offset indent +hccontrol -n ubt0hci le_set_advertising_enable disable +hccontrol -n ubt0hci le_set_advertising_param +hccontrol -n ubt0hci le_read_advertising_channel_tx_power +hccontrol -n ubt0hci le_set_advertising_data +hccontrol -n ubt0hci le_set_scan_response -n FBSD_Host +hccontrol -n ubt0hci le_set_advertising_enable enable +.Ed +.Sh SEE ALSO +.Xr bluetooth 3 , +.Xr netgraph 3 , +.Xr netgraph 4 , +.Xr ng_hci 4 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +.Sh BUGS +Most likely. +Please report if found. diff --git a/usr.sbin/bluetooth/hccontrol/hccontrol.c b/usr.sbin/bluetooth/hccontrol/hccontrol.c new file mode 100644 index 000000000000..bd63c9aff6ec --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/hccontrol.c @@ -0,0 +1,334 @@ +/*- + * hccontrol.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: hccontrol.c,v 1.5 2003/09/05 00:38:24 max Exp $ + */ + +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/sysctl.h> +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <netgraph/ng_message.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "hccontrol.h" + +/* Prototypes */ +static int do_hci_command (char const *, int, char **); +static struct hci_command * find_hci_command (char const *, struct hci_command *); +static int find_hci_nodes (struct nodeinfo **); +static void print_hci_command (struct hci_command *); +static void usage (void); + +/* Globals */ +int verbose = 0; +int timeout; +int numeric_bdaddr = 0; + +/* Main */ +int +main(int argc, char *argv[]) +{ + char *node = NULL; + int n; + + /* Process command line arguments */ + while ((n = getopt(argc, argv, "n:Nvh")) != -1) { + switch (n) { + case 'n': + node = optarg; + break; + + case 'N': + numeric_bdaddr = 1; + break; + + case 'v': + verbose = 1; + break; + + case 'h': + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (*argv == NULL) + usage(); + + n = do_hci_command(node, argc, argv); + + return (n); +} /* main */ + +/* Create socket and bind it */ +static int +socket_open(char const *node) +{ + struct sockaddr_hci addr; + struct ng_btsocket_hci_raw_filter filter; + int s, mib[4], num; + size_t size; + struct nodeinfo *nodes; + char *lnode = NULL; + + num = find_hci_nodes(&nodes); + if (num == 0) + errx(7, "Could not find HCI nodes"); + + if (node == NULL) { + node = lnode = strdup(nodes[0].name); + if (num > 1) + fprintf(stdout, "Using HCI node: %s\n", node); + } + + free(nodes); + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + err(1, "Could not create socket"); + + memset(&addr, 0, sizeof(addr)); + addr.hci_len = sizeof(addr); + addr.hci_family = AF_BLUETOOTH; + strncpy(addr.hci_node, node, sizeof(addr.hci_node)); + if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) + err(2, "Could not bind socket, node=%s", node); + + if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) + err(3, "Could not connect socket, node=%s", node); + + free(lnode); + memset(&filter, 0, sizeof(filter)); + bit_set(filter.event_mask, NG_HCI_EVENT_COMMAND_COMPL - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_COMMAND_STATUS - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_INQUIRY_COMPL - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_INQUIRY_RESULT - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_CON_COMPL - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_DISCON_COMPL - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_REMOTE_NAME_REQ_COMPL - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_READ_REMOTE_FEATURES_COMPL - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_READ_REMOTE_VER_INFO_COMPL - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_RETURN_LINK_KEYS - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_READ_CLOCK_OFFSET_COMPL - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_CON_PKT_TYPE_CHANGED - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_ROLE_CHANGE - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_LE -1); + + if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, + (void * const) &filter, sizeof(filter)) < 0) + err(4, "Could not setsockopt()"); + + size = nitems(mib); + if (sysctlnametomib("net.bluetooth.hci.command_timeout",mib,&size) < 0) + err(5, "Could not sysctlnametomib()"); + + if (sysctl(mib, nitems(mib), + (void *) &timeout, &size, NULL, 0) < 0) + err(6, "Could not sysctl()"); + + timeout ++; + + return (s); +} /* socket_open */ + +/* Execute commands */ +static int +do_hci_command(char const *node, int argc, char **argv) +{ + char *cmd = argv[0]; + struct hci_command *c = NULL; + int s, e, help; + + help = 0; + if (strcasecmp(cmd, "help") == 0) { + argc --; + argv ++; + + if (argc <= 0) { + fprintf(stdout, "Supported commands:\n"); + print_hci_command(link_control_commands); + print_hci_command(link_policy_commands); + print_hci_command(host_controller_baseband_commands); + print_hci_command(info_commands); + print_hci_command(status_commands); + print_hci_command(le_commands); + print_hci_command(node_commands); + fprintf(stdout, "\nFor more information use " \ + "'help command'\n"); + + return (OK); + } + + help = 1; + cmd = argv[0]; + } + + c = find_hci_command(cmd, link_control_commands); + if (c != NULL) + goto execute; + + c = find_hci_command(cmd, link_policy_commands); + if (c != NULL) + goto execute; + + c = find_hci_command(cmd, host_controller_baseband_commands); + if (c != NULL) + goto execute; + + c = find_hci_command(cmd, info_commands); + if (c != NULL) + goto execute; + + c = find_hci_command(cmd, status_commands); + if (c != NULL) + goto execute; + + c = find_hci_command(cmd, le_commands); + if (c != NULL) + goto execute; + + + c = find_hci_command(cmd, node_commands); + if (c == NULL) { + fprintf(stdout, "Unknown command: \"%s\"\n", cmd); + return (ERROR); + } +execute: + if (!help) { + s = socket_open(node); + e = (c->handler)(s, -- argc, ++ argv); + close(s); + } else + e = USAGE; + + switch (e) { + case OK: + case FAILED: + break; + + case ERROR: + fprintf(stdout, "Could not execute command \"%s\". %s\n", + cmd, strerror(errno)); + break; + + case USAGE: + fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description); + break; + + default: assert(0); break; + } + + + return (e); +} /* do_hci_command */ + +/* Try to find command in specified category */ +static struct hci_command * +find_hci_command(char const *command, struct hci_command *category) +{ + struct hci_command *c = NULL; + + for (c = category; c->command != NULL; c++) { + char *c_end = strchr(c->command, ' '); + + if (c_end != NULL) { + int len = c_end - c->command; + + if (strncasecmp(command, c->command, len) == 0) + return (c); + } else if (strcasecmp(command, c->command) == 0) + return (c); + } + + return (NULL); +} /* find_hci_command */ + +/* Find all HCI nodes */ +static int +find_hci_nodes(struct nodeinfo** nodes) +{ + struct ng_btsocket_hci_raw_node_list_names r; + struct sockaddr_hci addr; + int s; + const char * node = "ubt0hci"; + + r.num_names = MAX_NODE_NUM; + r.names = (struct nodeinfo*)calloc(MAX_NODE_NUM, sizeof(struct nodeinfo)); + if (r.names == NULL) + err(8, "Could not allocate memory"); + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + err(9, "Could not create socket"); + + memset(&addr, 0, sizeof(addr)); + addr.hci_len = sizeof(addr); + addr.hci_family = AF_BLUETOOTH; + strncpy(addr.hci_node, node, sizeof(addr.hci_node)); + if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) + err(10, "Could not bind socket"); + + if (ioctl(s, SIOC_HCI_RAW_NODE_LIST_NAMES, &r, sizeof(r)) < 0) + err(11, "Could not get list of HCI nodes"); + + close(s); + + *nodes = r.names; + + return (r.num_names); +} /* find_hci_nodes */ + +/* Print commands in specified category */ +static void +print_hci_command(struct hci_command *category) +{ + struct hci_command *c = NULL; + + for (c = category; c->command != NULL; c++) + fprintf(stdout, "\t%s\n", c->command); +} /* print_hci_command */ + +/* Usage */ +static void +usage(void) +{ + fprintf(stdout, "Usage: hccontrol [-hN] [-n HCI_node_name] cmd [p1] [..]\n"); + exit(255); +} /* usage */ + diff --git a/usr.sbin/bluetooth/hccontrol/hccontrol.h b/usr.sbin/bluetooth/hccontrol/hccontrol.h new file mode 100644 index 000000000000..9dd6345dd59f --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/hccontrol.h @@ -0,0 +1,90 @@ +/*- + * hccontrol.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: hccontrol.h,v 1.2 2003/05/19 17:29:29 max Exp $ + */ + +#ifndef _HCCONTROL_H_ +#define _HCCONTROL_H_ + +#define OK 0 /* everything was OK */ +#define ERROR 1 /* could not execute command */ +#define FAILED 2 /* error was reported */ +#define USAGE 3 /* invalid parameters */ + +#define MAX_NODE_NUM 16 /* max number of nodes */ + +struct hci_command { + char const *command; + char const *description; + int (*handler)(int, int, char **); +}; + +extern int timeout; +extern int verbose; +extern struct hci_command link_control_commands[]; +extern struct hci_command link_policy_commands[]; +extern struct hci_command host_controller_baseband_commands[]; +extern struct hci_command info_commands[]; +extern struct hci_command status_commands[]; +extern struct hci_command node_commands[]; +extern struct hci_command le_commands[]; + +int hci_request (int, int, char const *, int, char *, int *); +int hci_simple_request (int, int, char *, int *); +int hci_send (int, char const *, int); +int hci_recv (int, char *, int *); + +char const * hci_link2str (int); +char const * hci_pin2str (int); +char const * hci_scan2str (int); +char const * hci_encrypt2str (int, int); +char const * hci_coding2str (int); +char const * hci_vdata2str (int); +char const * hci_hmode2str (int, char *, int); +char const * hci_ver2str (int); +char const * hci_lmpver2str (int); +char const * hci_manufacturer2str(int); +char const * hci_commands2str (uint8_t *, char *, int); +char const * hci_features2str (uint8_t *, char *, int); +char const * hci_le_features2str (uint8_t *, char *, int); +char const * hci_cc2str (int); +char const * hci_con_state2str (int); +char const * hci_status2str (int); +char const * hci_bdaddr2str (bdaddr_t const *); +char const * hci_addrtype2str (int type); +char const * hci_role2str (int role); +char const * hci_mc_accuracy2str (int accuracy); +char const * hci_le_chanmap2str (uint8_t *, char *, int); + +void dump_adv_data(int len, uint8_t* advdata); +void print_adv_data(int len, uint8_t* advdata); + +#endif /* _HCCONTROL_H_ */ + diff --git a/usr.sbin/bluetooth/hccontrol/host_controller_baseband.c b/usr.sbin/bluetooth/hccontrol/host_controller_baseband.c new file mode 100644 index 000000000000..38bfcc321046 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/host_controller_baseband.c @@ -0,0 +1,1962 @@ +/*- + * host_controller_baseband.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: host_controller_baseband.c,v 1.4 2003/08/18 19:19:53 max Exp $ + */ + +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include "hccontrol.h" + +/* Convert hex ASCII to int4 */ +static int +hci_hexa2int4(const char *a) +{ + if ('0' <= *a && *a <= '9') + return (*a - '0'); + + if ('A' <= *a && *a <= 'F') + return (*a - 'A' + 0xa); + + if ('a' <= *a && *a <= 'f') + return (*a - 'a' + 0xa); + + return (-1); +} + +/* Convert hex ASCII to int8 */ +static int +hci_hexa2int8(const char *a) +{ + int hi = hci_hexa2int4(a); + int lo = hci_hexa2int4(a + 1); + + if (hi < 0 || lo < 0) + return (-1); + + return ((hi << 4) | lo); +} + +/* Convert ascii hex string to the uint8_t[] */ +static int +hci_hexstring2array(char const *s, uint8_t *a, int asize) +{ + int i, l, b; + + l = strlen(s) / 2; + if (l > asize) + l = asize; + + for (i = 0; i < l; i++) { + b = hci_hexa2int8(s + i * 2); + if (b < 0) + return (-1); + + a[i] = (b & 0xff); + } + + return (0); +} + +/* Send RESET to the unit */ +static int +hci_reset(int s, int argc, char **argv) +{ + ng_hci_status_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_RESET), (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_reset */ + +/* Send Read_PIN_Type command to the unit */ +static int +hci_read_pin_type(int s, int argc, char **argv) +{ + ng_hci_read_pin_type_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_PIN_TYPE), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "PIN type: %s [%#02x]\n", + hci_pin2str(rp.pin_type), rp.pin_type); + + return (OK); +} /* hci_read_pin_type */ + +/* Send Write_PIN_Type command to the unit */ +static int +hci_write_pin_type(int s, int argc, char **argv) +{ + ng_hci_write_pin_type_cp cp; + ng_hci_write_pin_type_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 1) + return (USAGE); + + cp.pin_type = (uint8_t) n; + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_PIN_TYPE), + (char const *) &cp, sizeof(cp), + (char *) &rp , &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_pin_type */ + +/* Send Read_Stored_Link_Key command to the unit */ +static int +hci_read_stored_link_key(int s, int argc, char **argv) +{ + struct { + ng_hci_cmd_pkt_t hdr; + ng_hci_read_stored_link_key_cp cp; + } __attribute__ ((packed)) cmd; + + struct { + ng_hci_event_pkt_t hdr; + union { + ng_hci_command_compl_ep cc; + ng_hci_return_link_keys_ep key; + uint8_t b[NG_HCI_EVENT_PKT_SIZE]; + } ep; + } __attribute__ ((packed)) event; + + int n, n1; + + /* Send command */ + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.type = NG_HCI_CMD_PKT; + cmd.hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_STORED_LINK_KEY)); + cmd.hdr.length = sizeof(cmd.cp); + + switch (argc) { + case 1: + /* parse BD_ADDR */ + if (!bt_aton(argv[0], &cmd.cp.bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(argv[0])) == NULL) + return (USAGE); + + memcpy(&cmd.cp.bdaddr, he->h_addr, sizeof(cmd.cp.bdaddr)); + } + break; + + default: + cmd.cp.read_all = 1; + break; + } + + if (hci_send(s, (char const *) &cmd, sizeof(cmd)) != OK) + return (ERROR); + + /* Receive events */ +again: + memset(&event, 0, sizeof(event)); + n = sizeof(event); + if (hci_recv(s, (char *) &event, &n) != OK) + return (ERROR); + + if (n <= sizeof(event.hdr)) { + errno = EMSGSIZE; + return (ERROR); + } + + if (event.hdr.type != NG_HCI_EVENT_PKT) { + errno = EIO; + return (ERROR); + } + + /* Parse event */ + switch (event.hdr.event) { + case NG_HCI_EVENT_COMMAND_COMPL: { + ng_hci_read_stored_link_key_rp *rp = NULL; + + if (event.ep.cc.opcode == 0x0000 || + event.ep.cc.opcode != cmd.hdr.opcode) + goto again; + + rp = (ng_hci_read_stored_link_key_rp *)(event.ep.b + + sizeof(event.ep.cc)); + + fprintf(stdout, "Complete: Status: %s [%#x]\n", + hci_status2str(rp->status), rp->status); + fprintf(stdout, "Maximum Number of keys: %d\n", + le16toh(rp->max_num_keys)); + fprintf(stdout, "Number of keys read: %d\n", + le16toh(rp->num_keys_read)); + } break; + + case NG_HCI_EVENT_RETURN_LINK_KEYS: { + struct _key { + bdaddr_t bdaddr; + uint8_t key[NG_HCI_KEY_SIZE]; + } __attribute__ ((packed)) *k = NULL; + + fprintf(stdout, "Event: Number of keys: %d\n", + event.ep.key.num_keys); + + k = (struct _key *)(event.ep.b + sizeof(event.ep.key)); + for (n = 0; n < event.ep.key.num_keys; n++) { + fprintf(stdout, "\t%d: %s ", + n + 1, hci_bdaddr2str(&k->bdaddr)); + + for (n1 = 0; n1 < sizeof(k->key); n1++) + fprintf(stdout, "%02x", k->key[n1]); + fprintf(stdout, "\n"); + + k ++; + } + + goto again; + + } break; + + default: + goto again; + } + + return (OK); +} /* hci_read_store_link_key */ + +/* Send Write_Stored_Link_Key command to the unit */ +static int +hci_write_stored_link_key(int s, int argc, char **argv) +{ + struct { + ng_hci_write_stored_link_key_cp p; + bdaddr_t bdaddr; + uint8_t key[NG_HCI_KEY_SIZE]; + } cp; + ng_hci_write_stored_link_key_rp rp; + int32_t n; + + memset(&cp, 0, sizeof(cp)); + + switch (argc) { + case 2: + cp.p.num_keys_write = 1; + + /* parse BD_ADDR */ + if (!bt_aton(argv[0], &cp.bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(argv[0])) == NULL) + return (USAGE); + + memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr)); + } + + /* parse key */ + if (hci_hexstring2array(argv[1], cp.key, sizeof(cp.key)) < 0) + return (USAGE); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_STORED_LINK_KEY), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Number of keys written: %d\n", rp.num_keys_written); + + return (OK); +} /* hci_write_stored_link_key */ + + +/* Send Delete_Stored_Link_Key command to the unit */ +static int +hci_delete_stored_link_key(int s, int argc, char **argv) +{ + ng_hci_delete_stored_link_key_cp cp; + ng_hci_delete_stored_link_key_rp rp; + int32_t n; + + memset(&cp, 0, sizeof(cp)); + + switch (argc) { + case 1: + /* parse BD_ADDR */ + if (!bt_aton(argv[0], &cp.bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(argv[0])) == NULL) + return (USAGE); + + memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr)); + } + break; + + default: + cp.delete_all = 1; + break; + } + + /* send command */ + n = sizeof(cp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_DELETE_STORED_LINK_KEY), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Number of keys deleted: %d\n", rp.num_keys_deleted); + + return (OK); +} /* hci_delete_stored_link_key */ + +/* Send Change_Local_Name command to the unit */ +static int +hci_change_local_name(int s, int argc, char **argv) +{ + ng_hci_change_local_name_cp cp; + ng_hci_change_local_name_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + snprintf(cp.name, sizeof(cp.name), "%s", argv[0]); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_CHANGE_LOCAL_NAME), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_change_local_name */ + +/* Send Read_Local_Name command to the unit */ +static int +hci_read_local_name(int s, int argc, char **argv) +{ + ng_hci_read_local_name_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_LOCAL_NAME), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Local name: %s\n", rp.name); + + return (OK); +} /* hci_read_local_name */ + +/* Send Read_Connection_Accept_Timeout to the unit */ +static int +hci_read_connection_accept_timeout(int s, int argc, char **argv) +{ + ng_hci_read_con_accept_timo_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_CON_ACCEPT_TIMO), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + rp.timeout = le16toh(rp.timeout); + fprintf(stdout, "Connection accept timeout: %.2f msec [%d slots]\n", + rp.timeout * 0.625, rp.timeout); + + return (OK); +} /* hci_read_connection_accept_timeout */ + +/* Send Write_Connection_Accept_Timeout to the unit */ +static int +hci_write_connection_accept_timeout(int s, int argc, char **argv) +{ + ng_hci_write_con_accept_timo_cp cp; + ng_hci_write_con_accept_timo_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 1 || n > 0xb540) + return (USAGE); + + cp.timeout = (uint16_t) n; + cp.timeout = htole16(cp.timeout); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_CON_ACCEPT_TIMO), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_connection_accept_timeout */ + +/* Send Read_Page_Timeout command to the unit */ +static int +hci_read_page_timeout(int s, int argc, char **argv) +{ + ng_hci_read_page_timo_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_PAGE_TIMO), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + rp.timeout = le16toh(rp.timeout); + fprintf(stdout, "Page timeout: %.2f msec [%d slots]\n", + rp.timeout * 0.625, rp.timeout); + + return (OK); +} /* hci_read_page_timeoout */ + +/* Send Write_Page_Timeout command to the unit */ +static int +hci_write_page_timeout(int s, int argc, char **argv) +{ + ng_hci_write_page_timo_cp cp; + ng_hci_write_page_timo_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 1 || n > 0xffff) + return (USAGE); + + cp.timeout = (uint16_t) n; + cp.timeout = htole16(cp.timeout); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_PAGE_TIMO), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_page_timeout */ + +/* Send Read_Scan_Enable command to the unit */ +static int +hci_read_scan_enable(int s, int argc, char **argv) +{ + ng_hci_read_scan_enable_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_SCAN_ENABLE), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Scan enable: %s [%#02x]\n", + hci_scan2str(rp.scan_enable), rp.scan_enable); + + return (OK); +} /* hci_read_scan_enable */ + +/* Send Write_Scan_Enable command to the unit */ +static int +hci_write_scan_enable(int s, int argc, char **argv) +{ + ng_hci_write_scan_enable_cp cp; + ng_hci_write_scan_enable_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 3) + return (USAGE); + + cp.scan_enable = (uint8_t) n; + break; + + default: + return (USAGE); + } + + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_SCAN_ENABLE), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_scan_enable */ + +/* Send Read_Page_Scan_Activity command to the unit */ +static int +hci_read_page_scan_activity(int s, int argc, char **argv) +{ + ng_hci_read_page_scan_activity_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_PAGE_SCAN_ACTIVITY), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + rp.page_scan_interval = le16toh(rp.page_scan_interval); + rp.page_scan_window = le16toh(rp.page_scan_window); + + fprintf(stdout, "Page Scan Interval: %.2f msec [%d slots]\n", + rp.page_scan_interval * 0.625, rp.page_scan_interval); + fprintf(stdout, "Page Scan Window: %.2f msec [%d slots]\n", + rp.page_scan_window * 0.625, rp.page_scan_window); + + return (OK); +} /* hci_read_page_scan_activity */ + +/* Send Write_Page_Scan_Activity command to the unit */ +static int +hci_write_page_scan_activity(int s, int argc, char **argv) +{ + ng_hci_write_page_scan_activity_cp cp; + ng_hci_write_page_scan_activity_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 2: + /* page scan interval */ + if (sscanf(argv[0], "%d", &n) != 1 || n < 0x12 || n > 0x1000) + return (USAGE); + + cp.page_scan_interval = (uint16_t) n; + + /* page scan window */ + if (sscanf(argv[1], "%d", &n) != 1 || n < 0x12 || n > 0x1000) + return (USAGE); + + cp.page_scan_window = (uint16_t) n; + + if (cp.page_scan_window > cp.page_scan_interval) + return (USAGE); + + cp.page_scan_interval = htole16(cp.page_scan_interval); + cp.page_scan_window = htole16(cp.page_scan_window); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_PAGE_SCAN_ACTIVITY), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_page_scan_activity */ + +/* Send Read_Inquiry_Scan_Activity command to the unit */ +static int +hci_read_inquiry_scan_activity(int s, int argc, char **argv) +{ + ng_hci_read_inquiry_scan_activity_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_INQUIRY_SCAN_ACTIVITY), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + rp.inquiry_scan_interval = le16toh(rp.inquiry_scan_interval); + rp.inquiry_scan_window = le16toh(rp.inquiry_scan_window); + + fprintf(stdout, "Inquiry Scan Interval: %.2f msec [%d slots]\n", + rp.inquiry_scan_interval * 0.625, rp.inquiry_scan_interval); + fprintf(stdout, "Inquiry Scan Window: %.2f msec [%d slots]\n", + rp.inquiry_scan_window * 0.625, rp.inquiry_scan_interval); + + return (OK); +} /* hci_read_inquiry_scan_activity */ + +/* Send Write_Inquiry_Scan_Activity command to the unit */ +static int +hci_write_inquiry_scan_activity(int s, int argc, char **argv) +{ + ng_hci_write_inquiry_scan_activity_cp cp; + ng_hci_write_inquiry_scan_activity_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 2: + /* inquiry scan interval */ + if (sscanf(argv[0], "%d", &n) != 1 || n < 0x12 || n > 0x1000) + return (USAGE); + + cp.inquiry_scan_interval = (uint16_t) n; + + /* inquiry scan window */ + if (sscanf(argv[1], "%d", &n) != 1 || n < 0x12 || n > 0x1000) + return (USAGE); + + cp.inquiry_scan_window = (uint16_t) n; + + if (cp.inquiry_scan_window > cp.inquiry_scan_interval) + return (USAGE); + + cp.inquiry_scan_interval = + htole16(cp.inquiry_scan_interval); + cp.inquiry_scan_window = htole16(cp.inquiry_scan_window); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_INQUIRY_SCAN_ACTIVITY), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_inquiry_scan_activity */ + +/* Send Read_Authentication_Enable command to the unit */ +static int +hci_read_authentication_enable(int s, int argc, char **argv) +{ + ng_hci_read_auth_enable_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_AUTH_ENABLE), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Authentication Enable: %s [%d]\n", + rp.auth_enable? "Enabled" : "Disabled", rp.auth_enable); + + return (OK); +} /* hci_read_authentication_enable */ + +/* Send Write_Authentication_Enable command to the unit */ +static int +hci_write_authentication_enable(int s, int argc, char **argv) +{ + ng_hci_write_auth_enable_cp cp; + ng_hci_write_auth_enable_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 1) + return (USAGE); + + cp.auth_enable = (uint8_t) n; + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_AUTH_ENABLE), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_authentication_enable */ + +/* Send Read_Encryption_Mode command to the unit */ +static int +hci_read_encryption_mode(int s, int argc, char **argv) +{ + ng_hci_read_encryption_mode_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_ENCRYPTION_MODE), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Encryption mode: %s [%#02x]\n", + hci_encrypt2str(rp.encryption_mode, 0), rp.encryption_mode); + + return (OK); +} /* hci_read_encryption_mode */ + +/* Send Write_Encryption_Mode command to the unit */ +static int +hci_write_encryption_mode(int s, int argc, char **argv) +{ + ng_hci_write_encryption_mode_cp cp; + ng_hci_write_encryption_mode_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 2) + return (USAGE); + + cp.encryption_mode = (uint8_t) n; + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_ENCRYPTION_MODE), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_encryption_mode */ + +/* Send Read_Class_Of_Device command to the unit */ +static int +hci_read_class_of_device(int s, int argc, char **argv) +{ + ng_hci_read_unit_class_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_UNIT_CLASS), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Class: %02x:%02x:%02x\n", + rp.uclass[2], rp.uclass[1], rp.uclass[0]); + + return (0); +} /* hci_read_class_of_device */ + +/* Send Write_Class_Of_Device command to the unit */ +static int +hci_write_class_of_device(int s, int argc, char **argv) +{ + ng_hci_write_unit_class_cp cp; + ng_hci_write_unit_class_rp rp; + int n0, n1, n2; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%x:%x:%x", &n2, &n1, &n0) != 3) + return (USAGE); + + cp.uclass[0] = (n0 & 0xff); + cp.uclass[1] = (n1 & 0xff); + cp.uclass[2] = (n2 & 0xff); + break; + + default: + return (USAGE); + } + + /* send command */ + n0 = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_UNIT_CLASS), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n0) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_class_of_device */ + +/* Send Read_Voice_Settings command to the unit */ +static int +hci_read_voice_settings(int s, int argc, char **argv) +{ + ng_hci_read_voice_settings_rp rp; + int n, + input_coding, + input_data_format, + input_sample_size; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_VOICE_SETTINGS), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + rp.settings = le16toh(rp.settings); + + input_coding = (rp.settings & 0x0300) >> 8; + input_data_format = (rp.settings & 0x00c0) >> 6; + input_sample_size = (rp.settings & 0x0020) >> 5; + + fprintf(stdout, "Voice settings: %#04x\n", rp.settings); + fprintf(stdout, "Input coding: %s [%d]\n", + hci_coding2str(input_coding), input_coding); + fprintf(stdout, "Input data format: %s [%d]\n", + hci_vdata2str(input_data_format), input_data_format); + + if (input_coding == 0x00) /* Only for Linear PCM */ + fprintf(stdout, "Input sample size: %d bit [%d]\n", + input_sample_size? 16 : 8, input_sample_size); + + return (OK); +} /* hci_read_voice_settings */ + +/* Send Write_Voice_Settings command to the unit */ +static int +hci_write_voice_settings(int s, int argc, char **argv) +{ + ng_hci_write_voice_settings_cp cp; + ng_hci_write_voice_settings_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%x", &n) != 1) + return (USAGE); + + cp.settings = (uint16_t) n; + cp.settings = htole16(cp.settings); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_VOICE_SETTINGS), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_voice_settings */ + +/* Send Read_Number_Broadcast_Restransmissions */ +static int +hci_read_number_broadcast_retransmissions(int s, int argc, char **argv) +{ + ng_hci_read_num_broadcast_retrans_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_NUM_BROADCAST_RETRANS), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Number of broadcast retransmissions: %d\n", + rp.counter); + + return (OK); +} /* hci_read_number_broadcast_retransmissions */ + +/* Send Write_Number_Broadcast_Restransmissions */ +static int +hci_write_number_broadcast_retransmissions(int s, int argc, char **argv) +{ + ng_hci_write_num_broadcast_retrans_cp cp; + ng_hci_write_num_broadcast_retrans_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 0xff) + return (USAGE); + + cp.counter = (uint8_t) n; + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_NUM_BROADCAST_RETRANS), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_number_broadcast_retransmissions */ + +/* Send Read_Hold_Mode_Activity command to the unit */ +static int +hci_read_hold_mode_activity(int s, int argc, char **argv) +{ + ng_hci_read_hold_mode_activity_rp rp; + int n; + char buffer[1024]; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_HOLD_MODE_ACTIVITY), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Hold Mode Activities: %#02x\n", rp.hold_mode_activity); + if (rp.hold_mode_activity == 0) + fprintf(stdout, "Maintain current Power State"); + else + fprintf(stdout, "%s", hci_hmode2str(rp.hold_mode_activity, + buffer, sizeof(buffer))); + + fprintf(stdout, "\n"); + + return (OK); +} /* hci_read_hold_mode_activity */ + +/* Send Write_Hold_Mode_Activity command to the unit */ +static int +hci_write_hold_mode_activity(int s, int argc, char **argv) +{ + ng_hci_write_hold_mode_activity_cp cp; + ng_hci_write_hold_mode_activity_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 4) + return (USAGE); + + cp.hold_mode_activity = (uint8_t) n; + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_HOLD_MODE_ACTIVITY), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_hold_mode_activity */ + +/* Send Read_SCO_Flow_Control_Enable command to the unit */ +static int +hci_read_sco_flow_control_enable(int s, int argc, char **argv) +{ + ng_hci_read_sco_flow_control_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_SCO_FLOW_CONTROL), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "SCO flow control %s [%d]\n", + rp.flow_control? "enabled" : "disabled", rp.flow_control); + + return (OK); +} /* hci_read_sco_flow_control_enable */ + +/* Send Write_SCO_Flow_Control_Enable command to the unit */ +static int +hci_write_sco_flow_control_enable(int s, int argc, char **argv) +{ + ng_hci_write_sco_flow_control_cp cp; + ng_hci_write_sco_flow_control_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 1) + return (USAGE); + + cp.flow_control = (uint8_t) n; + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_SCO_FLOW_CONTROL), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_sco_flow_control_enable */ + +/* Send Read_Link_Supervision_Timeout command to the unit */ +static int +hci_read_link_supervision_timeout(int s, int argc, char **argv) +{ + ng_hci_read_link_supervision_timo_cp cp; + ng_hci_read_link_supervision_timo_rp rp; + int n; + + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_LINK_SUPERVISION_TIMO), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + rp.timeout = le16toh(rp.timeout); + + fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle)); + fprintf(stdout, "Link supervision timeout: %.2f msec [%d slots]\n", + rp.timeout * 0.625, rp.timeout); + + return (OK); +} /* hci_read_link_supervision_timeout */ + +/* Send Write_Link_Supervision_Timeout command to the unit */ +static int +hci_write_link_supervision_timeout(int s, int argc, char **argv) +{ + ng_hci_write_link_supervision_timo_cp cp; + ng_hci_write_link_supervision_timo_rp rp; + int n; + + switch (argc) { + case 2: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + + /* link supervision timeout */ + if (sscanf(argv[1], "%d", &n) != 1 || n < 0 || n > 0xffff) + return (USAGE); + + cp.timeout = (uint16_t) (n & 0x0fff); + cp.timeout = htole16(cp.timeout); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_LINK_SUPERVISION_TIMO), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_link_supervision_timeout */ + +/* Send Read_Page_Scan_Period_Mode command to the unit */ +static int +hci_read_page_scan_period_mode(int s, int argc, char **argv) +{ + ng_hci_read_page_scan_period_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_PAGE_SCAN_PERIOD), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Page scan period mode: %#02x\n", + rp.page_scan_period_mode); + + return (OK); +} /* hci_read_page_scan_period_mode */ + +/* Send Write_Page_Scan_Period_Mode command to the unit */ +static int +hci_write_page_scan_period_mode(int s, int argc, char **argv) +{ + ng_hci_write_page_scan_period_cp cp; + ng_hci_write_page_scan_period_rp rp; + int n; + + /* parse command arguments */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 2) + return (USAGE); + + cp.page_scan_period_mode = (n & 0xff); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_PAGE_SCAN_PERIOD), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_page_scan_period_mode */ + +/* Send Read_Page_Scan_Mode command to the unit */ +static int +hci_read_page_scan_mode(int s, int argc, char **argv) +{ + ng_hci_read_page_scan_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_PAGE_SCAN), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Page scan mode: %#02x\n", rp.page_scan_mode); + + return (OK); +} /* hci_read_page_scan_mode */ + +/* Send Write_Page_Scan_Mode command to the unit */ +static int +hci_write_page_scan_mode(int s, int argc, char **argv) +{ + ng_hci_write_page_scan_cp cp; + ng_hci_write_page_scan_rp rp; + int n; + + /* parse command arguments */ + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 3) + return (USAGE); + + cp.page_scan_mode = (n & 0xff); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_PAGE_SCAN), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_page_scan_mode */ + +static int +hci_read_le_host_support(int s, int argc, char **argv) +{ + ng_hci_read_le_host_supported_rp rp; + int n; + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_READ_LE_HOST_SUPPORTED), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "LE Host support: %#02x\n", rp.le_supported_host); + fprintf(stdout, "Simultaneous LE Host : %#02x\n", rp.simultaneous_le_host); + + return (OK); + +} +static int +hci_write_le_host_support(int s, int argc, char **argv) +{ + ng_hci_write_le_host_supported_cp cp; + ng_hci_write_le_host_supported_rp rp; + + int n; + + cp.le_supported_host = 0; + cp.simultaneous_le_host = 0; + switch (argc) { + case 2: + if (sscanf(argv[1], "%d", &n) != 1 || (n != 0 && n != 1)){ + printf("-ARGC2: %d\n", n); + return (USAGE); + } + cp.simultaneous_le_host = (n &1); + + case 1: + if (sscanf(argv[0], "%d", &n) != 1 || (n != 0 && n != 1)){ + printf("+ARGC1: %d\n", n); + return (USAGE); + } + + cp.le_supported_host = (n &1); + break; + + default: + return (USAGE); + } + + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_WRITE_LE_HOST_SUPPORTED), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} + +struct hci_command host_controller_baseband_commands[] = { +{ +"reset", +"\nThe Reset command will reset the Host Controller and the Link Manager.\n" \ +"After the reset is completed, the current operational state will be lost,\n" \ +"the Bluetooth unit will enter standby mode and the Host Controller will\n" \ +"automatically revert to the default values for the parameters for which\n" \ +"default values are defined in the specification.", +&hci_reset +}, +{ +"read_pin_type", +"\nThe Read_PIN_Type command is used for the Host to read whether the Link\n" \ +"Manager assumes that the Host supports variable PIN codes only a fixed PIN\n" \ +"code.", +&hci_read_pin_type +}, +{ +"write_pin_type <pin_type>", +"\nThe Write_PIN_Type command is used for the Host to write to the Host\n" \ +"Controller whether the Host supports variable PIN codes or only a fixed PIN\n"\ +"code.\n\n" \ +"\t<pin_type> - dd; 0 - Variable; 1 - Fixed", +&hci_write_pin_type +}, +{ +"read_stored_link_key [<BD_ADDR>]", +"\nThe Read_Stored_Link_Key command provides the ability to read one or\n" \ +"more link keys stored in the Bluetooth Host Controller. The Bluetooth Host\n" \ +"Controller can store a limited number of link keys for other Bluetooth\n" \ +"devices.\n\n" \ +"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name", +&hci_read_stored_link_key +}, +{ +"write_stored_link_key <BD_ADDR> <key>", +"\nThe Write_Stored_Link_Key command provides the ability to write one\n" \ +"or more link keys to be stored in the Bluetooth Host Controller. The\n" \ +"Bluetooth Host Controller can store a limited number of link keys for other\n"\ +"Bluetooth devices. If no additional space is available in the Bluetooth\n"\ +"Host Controller then no additional link keys will be stored.\n\n" \ +"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name\n" \ +"\t<key> - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx up to 16 bytes link key", +&hci_write_stored_link_key +}, +{ +"delete_stored_link_key [<BD_ADDR>]", +"\nThe Delete_Stored_Link_Key command provides the ability to remove one\n" \ +"or more of the link keys stored in the Bluetooth Host Controller. The\n" \ +"Bluetooth Host Controller can store a limited number of link keys for other\n"\ +"Bluetooth devices.\n\n" \ +"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name", +&hci_delete_stored_link_key +}, +{ +"change_local_name <name>", +"\nThe Change_Local_Name command provides the ability to modify the user\n" \ +"friendly name for the Bluetooth unit.\n\n" \ +"\t<name> - string", +&hci_change_local_name +}, +{ +"read_local_name", +"\nThe Read_Local_Name command provides the ability to read the\n" \ +"stored user-friendly name for the Bluetooth unit.", +&hci_read_local_name +}, +{ +"read_connection_accept_timeout", +"\nThis command will read the value for the Connection_Accept_Timeout\n" \ +"configuration parameter. The Connection_Accept_Timeout configuration\n" \ +"parameter allows the Bluetooth hardware to automatically deny a\n" \ +"connection request after a specified time period has occurred and\n" \ +"the new connection is not accepted. Connection Accept Timeout\n" \ +"measured in Number of Baseband slots.", +&hci_read_connection_accept_timeout +}, +{ +"write_connection_accept_timeout <timeout>", +"\nThis command will write the value for the Connection_Accept_Timeout\n" \ +"configuration parameter.\n\n" \ +"\t<timeout> - dddd; measured in number of baseband slots.", +&hci_write_connection_accept_timeout +}, +{ +"read_page_timeout", +"\nThis command will read the value for the Page_Timeout configuration\n" \ +"parameter. The Page_Timeout configuration parameter defines the\n" \ +"maximum time the local Link Manager will wait for a baseband page\n" \ +"response from the remote unit at a locally initiated connection\n" \ +"attempt. Page Timeout measured in Number of Baseband slots.", +&hci_read_page_timeout +}, +{ +"write_page_timeout <timeout>", +"\nThis command will write the value for the Page_Timeout configuration\n" \ +"parameter.\n\n" \ +"\t<timeout> - dddd; measured in number of baseband slots.", +&hci_write_page_timeout +}, +{ +"read_scan_enable", +"\nThis command will read the value for the Scan_Enable parameter. The\n" \ +"Scan_Enable parameter controls whether or not the Bluetooth uint\n" \ +"will periodically scan for page attempts and/or inquiry requests\n" \ +"from other Bluetooth unit.\n\n" \ +"\t0x00 - No Scans enabled.\n" \ +"\t0x01 - Inquiry Scan enabled. Page Scan disabled.\n" \ +"\t0x02 - Inquiry Scan disabled. Page Scan enabled.\n" \ +"\t0x03 - Inquiry Scan enabled. Page Scan enabled.", +&hci_read_scan_enable +}, +{ +"write_scan_enable <scan_enable>", +"\nThis command will write the value for the Scan_Enable parameter.\n" \ +"The Scan_Enable parameter controls whether or not the Bluetooth\n" \ +"unit will periodically scan for page attempts and/or inquiry\n" \ +"requests from other Bluetooth unit.\n\n" \ +"\t<scan_enable> - dd;\n" \ +"\t0 - No Scans enabled.\n" \ +"\t1 - Inquiry Scan enabled. Page Scan disabled.\n" \ +"\t2 - Inquiry Scan disabled. Page Scan enabled.\n" \ +"\t3 - Inquiry Scan enabled. Page Scan enabled.", +&hci_write_scan_enable +}, +{ +"read_page_scan_activity", +"\nThis command will read the value for Page_Scan_Activity configuration\n" \ +"parameters. The Page_Scan_Interval configuration parameter defines the\n" \ +"amount of time between consecutive page scans. This time interval is \n" \ +"defined from when the Host Controller started its last page scan until\n" \ +"it begins the next page scan. The Page_Scan_Window configuration parameter\n" \ +"defines the amount of time for the duration of the page scan. The\n" \ +"Page_Scan_Window can only be less than or equal to the Page_Scan_Interval.", +&hci_read_page_scan_activity +}, +{ +"write_page_scan_activity interval(dddd) window(dddd)", +"\nThis command will write the value for Page_Scan_Activity configuration\n" \ +"parameter. The Page_Scan_Interval configuration parameter defines the\n" \ +"amount of time between consecutive page scans. This is defined as the time\n" \ +"interval from when the Host Controller started its last page scan until it\n" \ +"begins the next page scan. The Page_Scan_Window configuration parameter\n" \ +"defines the amount of time for the duration of the page scan. \n" \ +"The Page_Scan_Window can only be less than or equal to the Page_Scan_Interval.\n\n" \ +"\t<interval> - Range: 0x0012 -- 0x100, Time = N * 0.625 msec\n" \ +"\t<window> - Range: 0x0012 -- 0x100, Time = N * 0.625 msec", +&hci_write_page_scan_activity +}, +{ +"read_inquiry_scan_activity", +"\nThis command will read the value for Inquiry_Scan_Activity configuration\n" \ +"parameter. The Inquiry_Scan_Interval configuration parameter defines the\n" \ +"amount of time between consecutive inquiry scans. This is defined as the\n" \ +"time interval from when the Host Controller started its last inquiry scan\n" \ +"until it begins the next inquiry scan.", +&hci_read_inquiry_scan_activity +}, +{ +"write_inquiry_scan_activity interval(dddd) window(dddd)", +"\nThis command will write the value for Inquiry_Scan_Activity configuration\n"\ +"parameter. The Inquiry_Scan_Interval configuration parameter defines the\n" \ +"amount of time between consecutive inquiry scans. This is defined as the\n" \ +"time interval from when the Host Controller started its last inquiry scan\n" \ +"until it begins the next inquiry scan. The Inquiry_Scan_Window configuration\n" \ +"parameter defines the amount of time for the duration of the inquiry scan.\n" \ +"The Inquiry_Scan_Window can only be less than or equal to the Inquiry_Scan_Interval.\n\n" \ +"\t<interval> - Range: 0x0012 -- 0x100, Time = N * 0.625 msec\n" \ +"\t<window> - Range: 0x0012 -- 0x100, Time = N * 0.625 msec", +&hci_write_inquiry_scan_activity +}, +{ +"read_authentication_enable", +"\nThis command will read the value for the Authentication_Enable parameter.\n"\ +"The Authentication_Enable parameter controls if the local unit requires\n"\ +"to authenticate the remote unit at connection setup (between the\n" \ +"Create_Connection command or acceptance of an incoming ACL connection\n"\ +"and the corresponding Connection Complete event). At connection setup, only\n"\ +"the unit(s) with the Authentication_Enable parameter enabled will try to\n"\ +"authenticate the other unit.", +&hci_read_authentication_enable +}, +{ +"write_authentication_enable enable(0|1)", +"\nThis command will write the value for the Authentication_Enable parameter.\n"\ +"The Authentication_Enable parameter controls if the local unit requires to\n"\ +"authenticate the remote unit at connection setup (between the\n" \ +"Create_Connection command or acceptance of an incoming ACL connection\n" \ +"and the corresponding Connection Complete event). At connection setup, only\n"\ +"the unit(s) with the Authentication_Enable parameter enabled will try to\n"\ +"authenticate the other unit.", +&hci_write_authentication_enable +}, +{ +"read_encryption_mode", +"\nThis command will read the value for the Encryption_Mode parameter. The\n" \ +"Encryption_Mode parameter controls if the local unit requires encryption\n" \ +"to the remote unit at connection setup (between the Create_Connection\n" \ +"command or acceptance of an incoming ACL connection and the corresponding\n" \ +"Connection Complete event). At connection setup, only the unit(s) with\n" \ +"the Authentication_Enable parameter enabled and Encryption_Mode parameter\n" \ +"enabled will try to encrypt the connection to the other unit.\n\n" \ +"\t<encryption_mode>:\n" \ +"\t0x00 - Encryption disabled.\n" \ +"\t0x01 - Encryption only for point-to-point packets.\n" \ +"\t0x02 - Encryption for both point-to-point and broadcast packets.", +&hci_read_encryption_mode +}, +{ +"write_encryption_mode mode(0|1|2)", +"\tThis command will write the value for the Encryption_Mode parameter.\n" \ +"The Encryption_Mode parameter controls if the local unit requires\n" \ +"encryption to the remote unit at connection setup (between the\n" \ +"Create_Connection command or acceptance of an incoming ACL connection\n" \ +"and the corresponding Connection Complete event). At connection setup,\n" \ +"only the unit(s) with the Authentication_Enable parameter enabled and\n" \ +"Encryption_Mode parameter enabled will try to encrypt the connection to\n" \ +"the other unit.\n\n" \ +"\t<encryption_mode> (dd)\n" \ +"\t0 - Encryption disabled.\n" \ +"\t1 - Encryption only for point-to-point packets.\n" \ +"\t2 - Encryption for both point-to-point and broadcast packets.", +&hci_write_encryption_mode +}, +{ +"read_class_of_device", +"\nThis command will read the value for the Class_of_Device parameter.\n" \ +"The Class_of_Device parameter is used to indicate the capabilities of\n" \ +"the local unit to other units.", +&hci_read_class_of_device +}, +{ +"write_class_of_device class(xx:xx:xx)", +"\nThis command will write the value for the Class_of_Device parameter.\n" \ +"The Class_of_Device parameter is used to indicate the capabilities of \n" \ +"the local unit to other units.\n\n" \ +"\t<class> (xx:xx:xx) - class of device", +&hci_write_class_of_device +}, +{ +"read_voice_settings", +"\nThis command will read the values for the Voice_Setting parameter.\n" \ +"The Voice_Setting parameter controls all the various settings for voice\n" \ +"connections. These settings apply to all voice connections, and cannot be\n" \ +"set for individual voice connections. The Voice_Setting parameter controls\n" \ +"the configuration for voice connections: Input Coding, Air coding format,\n" \ +"input data format, Input sample size, and linear PCM parameter.", +&hci_read_voice_settings +}, +{ +"write_voice_settings settings(xxxx)", +"\nThis command will write the values for the Voice_Setting parameter.\n" \ +"The Voice_Setting parameter controls all the various settings for voice\n" \ +"connections. These settings apply to all voice connections, and cannot be\n" \ +"set for individual voice connections. The Voice_Setting parameter controls\n" \ +"the configuration for voice connections: Input Coding, Air coding format,\n" \ +"input data format, Input sample size, and linear PCM parameter.\n\n" \ +"\t<voice_settings> (xxxx) - voice settings", +&hci_write_voice_settings +}, +{ +"read_number_broadcast_retransmissions", +"\nThis command will read the unit's parameter value for the Number of\n" \ +"Broadcast Retransmissions. Broadcast packets are not acknowledged and are\n" \ +"unreliable.", +&hci_read_number_broadcast_retransmissions +}, +{ +"write_number_broadcast_retransmissions count(dd)", +"\nThis command will write the unit's parameter value for the Number of\n" \ +"Broadcast Retransmissions. Broadcast packets are not acknowledged and are\n" \ +"unreliable.\n\n" \ +"\t<count> (dd) - number of broadcast retransimissions", +&hci_write_number_broadcast_retransmissions +}, +{ +"read_hold_mode_activity", +"\nThis command will read the value for the Hold_Mode_Activity parameter.\n" \ +"The Hold_Mode_Activity value is used to determine what activities should\n" \ +"be suspended when the unit is in hold mode.", +&hci_read_hold_mode_activity +}, +{ +"write_hold_mode_activity settings(0|1|2|4)", +"\nThis command will write the value for the Hold_Mode_Activity parameter.\n" \ +"The Hold_Mode_Activity value is used to determine what activities should\n" \ +"be suspended when the unit is in hold mode.\n\n" \ +"\t<settings> (dd) - bit mask:\n" \ +"\t0 - Maintain current Power State. Default\n" \ +"\t1 - Suspend Page Scan.\n" \ +"\t2 - Suspend Inquiry Scan.\n" \ +"\t4 - Suspend Periodic Inquiries.", +&hci_write_hold_mode_activity +}, +{ +"read_sco_flow_control_enable", +"\nThe Read_SCO_Flow_Control_Enable command provides the ability to read\n" \ +"the SCO_Flow_Control_Enable setting. By using this setting, the Host can\n" \ +"decide if the Host Controller will send Number Of Completed Packets events\n" \ +"for SCO Connection Handles. This setting allows the Host to enable and\n" \ +"disable SCO flow control.", +&hci_read_sco_flow_control_enable +}, +{ +"write_sco_flow_control_enable enable(0|1)", +"\nThe Write_SCO_Flow_Control_Enable command provides the ability to write\n" \ +"the SCO_Flow_Control_Enable setting. By using this setting, the Host can\n" \ +"decide if the Host Controller will send Number Of Completed Packets events\n" \ +"for SCO Connection Handles. This setting allows the Host to enable and\n" \ +"disable SCO flow control. The SCO_Flow_Control_Enable setting can only be\n" \ +"changed if no connections exist.", +&hci_write_sco_flow_control_enable +}, +{ +"read_link_supervision_timeout <connection_handle>", +"\nThis command will read the value for the Link_Supervision_Timeout\n" \ +"parameter for the device. The Link_Supervision_Timeout parameter is used\n" \ +"by the master or slave Bluetooth device to monitor link loss. If, for any\n" \ +"reason, no Baseband packets are received from that Connection Handle for a\n" \ +"duration longer than the Link_Supervision_Timeout, the connection is\n" +"disconnected.\n\n" \ +"\t<connection_handle> - dddd; connection handle\n", +&hci_read_link_supervision_timeout +}, +{ +"write_link_supervision_timeout <connection_handle> <timeout>", +"\nThis command will write the value for the Link_Supervision_Timeout\n" \ +"parameter for the device. The Link_Supervision_Timeout parameter is used\n" \ +"by the master or slave Bluetooth device to monitor link loss. If, for any\n" \ +"reason, no Baseband packets are received from that connection handle for a\n" \ +"duration longer than the Link_Supervision_Timeout, the connection is\n" \ +"disconnected.\n\n" \ +"\t<connection_handle> - dddd; connection handle\n" \ +"\t<timeout> - dddd; timeout measured in number of baseband slots\n", +&hci_write_link_supervision_timeout +}, +{ +"read_page_scan_period_mode", +"\nThis command is used to read the mandatory Page_Scan_Period_Mode of the\n" \ +"local Bluetooth device. Every time an inquiry response message is sent, the\n"\ +"Bluetooth device will start a timer (T_mandatory_pscan), the value of which\n"\ +"is dependent on the Page_Scan_Period_Mode. As long as this timer has not\n" \ +"expired, the Bluetooth device will use the Page_Scan_Period_Mode for all\n" \ +"following page scans.", +&hci_read_page_scan_period_mode +}, +{ +"write_page_scan_period_mode <page_scan_period_mode>", +"\nThis command is used to write the mandatory Page_Scan_Period_Mode of the\n" \ +"local Bluetooth device. Every time an inquiry response message is sent, the\n"\ +"Bluetooth device will start a timer (T_mandatory_pscan), the value of which\n"\ +"is dependent on the Page_Scan_Period_Mode. As long as this timer has not\n" \ +"expired, the Bluetooth device will use the Page_Scan_Period_Mode for all\n" \ +"following page scans.\n\n" \ +"\t<page_scan_period_mode> - dd; page scan period mode:\n" \ +"\t0x00 - P0 (Default)\n" \ +"\t0x01 - P1\n" \ +"\t0x02 - P2", +&hci_write_page_scan_period_mode +}, +{ +"read_page_scan_mode", +"\nThis command is used to read the default page scan mode of the local\n" \ +"Bluetooth device. The Page_Scan_Mode parameter indicates the page scan mode\n"\ +"that is used for the default page scan. Currently one mandatory page scan\n"\ +"mode and three optional page scan modes are defined. Following an inquiry\n" \ +"response, if the Baseband timer T_mandatory_pscan has not expired, the\n" \ +"mandatory page scan mode must be applied.", +&hci_read_page_scan_mode +}, +{ +"write_page_scan_mode <page_scan_mode>", +"\nThis command is used to write the default page scan mode of the local\n" \ +"Bluetooth device. The Page_Scan_Mode parameter indicates the page scan mode\n"\ +"that is used for the default page scan. Currently, one mandatory page scan\n"\ +"mode and three optional page scan modes are defined. Following an inquiry\n"\ +"response, if the Baseband timer T_mandatory_pscan has not expired, the\n" \ +"mandatory page scan mode must be applied.\n\n" \ +"\t<page_scan_mode> - dd; page scan mode:\n" \ +"\t0x00 - Mandatory Page Scan Mode (Default)\n" \ +"\t0x01 - Optional Page Scan Mode I\n" \ +"\t0x02 - Optional Page Scan Mode II\n" \ +"\t0x03 - Optional Page Scan Mode III", +&hci_write_page_scan_mode +}, +{ +"read_le_host_support", \ +"Read if this host is in LE supported mode and simultaneous LE supported mode", +&hci_read_le_host_support, +}, +{ +"write_le_host_support", \ +"write_le_host_support le_host[0|1] simultaneous_le[0|1]", +&hci_write_le_host_support, +}, + +{ NULL, } +}; + diff --git a/usr.sbin/bluetooth/hccontrol/info.c b/usr.sbin/bluetooth/hccontrol/info.c new file mode 100644 index 000000000000..b1b2aa36c593 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/info.c @@ -0,0 +1,255 @@ +/*- + * info.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: info.c,v 1.3 2003/08/18 19:19:54 max Exp $ + */ + +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include "hccontrol.h" + +/* Send Read_Local_Version_Information command to the unit */ +static int +hci_read_local_version_information(int s, int argc, char **argv) +{ + ng_hci_read_local_ver_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO, + NG_HCI_OCF_READ_LOCAL_VER), (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + rp.manufacturer = le16toh(rp.manufacturer); + + fprintf(stdout, "HCI version: %s [%#02x]\n", + hci_ver2str(rp.hci_version), rp.hci_version); + fprintf(stdout, "HCI revision: %#04x\n", + le16toh(rp.hci_revision)); + fprintf(stdout, "LMP version: %s [%#02x]\n", + hci_lmpver2str(rp.lmp_version), rp.lmp_version); + fprintf(stdout, "LMP sub-version: %#04x\n", + le16toh(rp.lmp_subversion)); + fprintf(stdout, "Manufacturer: %s [%#04x]\n", + hci_manufacturer2str(rp.manufacturer), rp.manufacturer); + + return (OK); +} /* hci_read_local_version_information */ + +/* Send Read_Local_Supported_Commands command to the unit */ +static int +hci_read_local_supported_commands(int s, int argc, char **argv) +{ + ng_hci_read_local_commands_rp rp; + int n; + char buffer[16384]; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO, + NG_HCI_OCF_READ_LOCAL_COMMANDS), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Supported commands:"); + for (n = 0; n < sizeof(rp.features); n++) { + if (n % 8 == 0) + fprintf(stdout, "\n"); + fprintf(stdout, "%#02x ", rp.features[n]); + } + fprintf(stdout, "\n%s\n", hci_commands2str(rp.features, + buffer, sizeof(buffer))); + + return (OK); +} /* hci_read_local_supported_commands */ + +/* Send Read_Local_Supported_Features command to the unit */ +static int +hci_read_local_supported_features(int s, int argc, char **argv) +{ + ng_hci_read_local_features_rp rp; + int n; + char buffer[2048]; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO, + NG_HCI_OCF_READ_LOCAL_FEATURES), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Features: "); + for (n = 0; n < sizeof(rp.features); n++) + fprintf(stdout, "%#02x ", rp.features[n]); + fprintf(stdout, "\n%s\n", hci_features2str(rp.features, + buffer, sizeof(buffer))); + + return (OK); +} /* hci_read_local_supported_features */ + +/* Sent Read_Buffer_Size command to the unit */ +static int +hci_read_buffer_size(int s, int argc, char **argv) +{ + ng_hci_read_buffer_size_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO, + NG_HCI_OCF_READ_BUFFER_SIZE), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Max. ACL packet size: %d bytes\n", + le16toh(rp.max_acl_size)); + fprintf(stdout, "Number of ACL packets: %d\n", + le16toh(rp.num_acl_pkt)); + fprintf(stdout, "Max. SCO packet size: %d bytes\n", + rp.max_sco_size); + fprintf(stdout, "Number of SCO packets: %d\n", + le16toh(rp.num_sco_pkt)); + + return (OK); +} /* hci_read_buffer_size */ + +/* Send Read_Country_Code command to the unit */ +static int +hci_read_country_code(int s, int argc, char **argv) +{ + ng_hci_read_country_code_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO, + NG_HCI_OCF_READ_COUNTRY_CODE), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Country code: %s [%#02x]\n", + hci_cc2str(rp.country_code), rp.country_code); + + return (OK); +} /* hci_read_country_code */ + +/* Send Read_BD_ADDR command to the unit */ +static int +hci_read_bd_addr(int s, int argc, char **argv) +{ + ng_hci_read_bdaddr_rp rp; + int n; + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO, + NG_HCI_OCF_READ_BDADDR), (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "BD_ADDR: %s\n", bt_ntoa(&rp.bdaddr, NULL)); + + return (OK); +} /* hci_read_bd_addr */ + +struct hci_command info_commands[] = { +{ +"read_local_version_information", +"\nThis command will read the values for the version information for the\n" \ +"local Bluetooth unit.", +&hci_read_local_version_information +}, +{ +"read_local_supported_commands", +"\nThis command will read the commands the local Bluetooth unit supports.\n", +&hci_read_local_supported_commands +}, +{ +"read_local_supported_features", +"\nThis command requests a list of the supported features for the local\n" \ +"unit. This command will return a list of the LMP features.", +&hci_read_local_supported_features +}, +{ +"read_buffer_size", +"\nThe Read_Buffer_Size command is used to read the maximum size of the\n" \ +"data portion of HCI ACL and SCO Data Packets sent from the Host to the\n" \ +"Host Controller.", +&hci_read_buffer_size +}, +{ +"read_country_code", +"\nThis command will read the value for the Country_Code return parameter.\n" \ +"The Country_Code defines which range of frequency band of the ISM 2.4 GHz\n" \ +"band will be used by the unit.", +&hci_read_country_code +}, +{ +"read_bd_addr", +"\nThis command will read the value for the BD_ADDR parameter. The BD_ADDR\n" \ +"is a 48-bit unique identifier for a Bluetooth unit.", +&hci_read_bd_addr +}, +{ +NULL, +}}; + diff --git a/usr.sbin/bluetooth/hccontrol/le.c b/usr.sbin/bluetooth/hccontrol/le.c new file mode 100644 index 000000000000..6d5440643b45 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/le.c @@ -0,0 +1,1370 @@ +/* + * le.c + * + * Copyright (c) 2015 Takanori Watanabe <takawata@freebsd.org> + * 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. + * + * $Id: hccontrol.c,v 1.5 2003/09/05 00:38:24 max Exp $ + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/sysctl.h> +#include <sys/select.h> +#include <assert.h> +#include <bitstring.h> +#include <err.h> +#include <errno.h> +#include <netgraph/ng_message.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdint.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include "hccontrol.h" + +static int le_set_scan_param(int s, int argc, char *argv[]); +static int le_set_scan_enable(int s, int argc, char *argv[]); +static int parse_param(int argc, char *argv[], char *buf, int *len); +static int le_set_scan_response(int s, int argc, char *argv[]); +static int le_read_supported_states(int s, int argc, char *argv[]); +static int le_read_local_supported_features(int s, int argc ,char *argv[]); +static int set_le_event_mask(int s, uint64_t mask); +static int set_event_mask(int s, uint64_t mask); +static int le_enable(int s, int argc, char *argv[]); +static int le_set_advertising_enable(int s, int argc, char *argv[]); +static int le_set_advertising_param(int s, int argc, char *argv[]); +static int le_read_advertising_channel_tx_power(int s, int argc, char *argv[]); +static int le_scan(int s, int argc, char *argv[]); +static void handle_le_event(ng_hci_event_pkt_t* e, bool verbose); +static int le_read_white_list_size(int s, int argc, char *argv[]); +static int le_clear_white_list(int s, int argc, char *argv[]); +static int le_add_device_to_white_list(int s, int argc, char *argv[]); +static int le_remove_device_from_white_list(int s, int argc, char *argv[]); +static int le_connect(int s, int argc, char *argv[]); +static void handle_le_connection_event(ng_hci_event_pkt_t* e, bool verbose); +static int le_read_channel_map(int s, int argc, char *argv[]); +static void handle_le_remote_features_event(ng_hci_event_pkt_t* e); +static int le_rand(int s, int argc, char *argv[]); + +static int +le_set_scan_param(int s, int argc, char *argv[]) +{ + int type; + int interval; + int window; + int adrtype; + int policy; + int n; + + ng_hci_le_set_scan_parameters_cp cp; + ng_hci_le_set_scan_parameters_rp rp; + + if (argc != 5) + return (USAGE); + + if (strcmp(argv[0], "active") == 0) + type = 1; + else if (strcmp(argv[0], "passive") == 0) + type = 0; + else + return (USAGE); + + interval = (int)(atof(argv[1])/0.625); + interval = (interval < 4)? 4: interval; + window = (int)(atof(argv[2])/0.625); + window = (window < 4) ? 4 : interval; + + if (strcmp(argv[3], "public") == 0) + adrtype = 0; + else if (strcmp(argv[3], "random") == 0) + adrtype = 1; + else + return (USAGE); + + if (strcmp(argv[4], "all") == 0) + policy = 0; + else if (strcmp(argv[4], "whitelist") == 0) + policy = 1; + else + return (USAGE); + + cp.le_scan_type = type; + cp.le_scan_interval = interval; + cp.own_address_type = adrtype; + cp.le_scan_window = window; + cp.scanning_filter_policy = policy; + n = sizeof(rp); + + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_SCAN_PARAMETERS), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} + +static int +le_set_scan_enable(int s, int argc, char *argv[]) +{ + ng_hci_le_set_scan_enable_cp cp; + ng_hci_le_set_scan_enable_rp rp; + int n, enable = 0; + + if (argc != 1) + return (USAGE); + + if (strcmp(argv[0], "enable") == 0) + enable = 1; + else if (strcmp(argv[0], "disable") != 0) + return (USAGE); + + n = sizeof(rp); + cp.le_scan_enable = enable; + cp.filter_duplicates = 0; + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_SCAN_ENABLE), + (void *)&cp, sizeof(cp), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "LE Scan: %s\n", + enable? "Enabled" : "Disabled"); + + return (OK); +} + +static int +parse_param(int argc, char *argv[], char *buf, int *len) +{ + char *buflast = buf + (*len); + char *curbuf = buf; + char *token,*lenpos; + int ch; + int datalen; + uint16_t value; + optreset = 1; + optind = 0; + while ((ch = getopt(argc, argv , "n:f:u:")) != -1) { + switch(ch){ + case 'n': + datalen = strlen(optarg); + if ((curbuf + datalen + 2) >= buflast) + goto done; + curbuf[0] = datalen + 1; + curbuf[1] = 8; + curbuf += 2; + memcpy(curbuf, optarg, datalen); + curbuf += datalen; + break; + case 'f': + if (curbuf+3 > buflast) + goto done; + curbuf[0] = 2; + curbuf[1] = 1; + curbuf[2] = (uint8_t)strtol(optarg, NULL, 16); + curbuf += 3; + break; + case 'u': + if ((buf+2) >= buflast) + goto done; + lenpos = curbuf; + curbuf[1] = 2; + *lenpos = 1; + curbuf += 2; + while ((token = strsep(&optarg, ",")) != NULL) { + value = strtol(token, NULL, 16); + if ((curbuf+2) >= buflast) + break; + curbuf[0] = value &0xff; + curbuf[1] = (value>>8)&0xff; + curbuf += 2; + *lenpos += 2; + } + + } + } +done: + *len = curbuf - buf; + + return (OK); +} + +static int +le_set_scan_response(int s, int argc, char *argv[]) +{ + ng_hci_le_set_scan_response_data_cp cp; + ng_hci_le_set_scan_response_data_rp rp; + int n; + int len; + char buf[NG_HCI_ADVERTISING_DATA_SIZE]; + + len = sizeof(buf); + parse_param(argc, argv, buf, &len); + memset(cp.scan_response_data, 0, sizeof(cp.scan_response_data)); + cp.scan_response_data_length = len; + memcpy(cp.scan_response_data, buf, len); + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_SCAN_RESPONSE_DATA), + (void *)&cp, sizeof(cp), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} + +static int +le_read_local_supported_features(int s, int argc ,char *argv[]) +{ + ng_hci_le_read_local_supported_features_rp rp; + int n = sizeof(rp); + + union { + uint64_t raw; + uint8_t octets[8]; + } le_features; + + char buffer[2048]; + + if (hci_simple_request(s, + NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_READ_LOCAL_SUPPORTED_FEATURES), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + le_features.raw = rp.le_features; + + fprintf(stdout, "LE Features: "); + for(int i = 0; i < 8; i++) + fprintf(stdout, " %#02x", le_features.octets[i]); + fprintf(stdout, "\n%s\n", hci_le_features2str(le_features.octets, + buffer, sizeof(buffer))); + fprintf(stdout, "\n"); + + return (OK); +} + +static int +le_read_supported_states(int s, int argc, char *argv[]) +{ + ng_hci_le_read_supported_states_rp rp; + int n = sizeof(rp); + + if (hci_simple_request(s, NG_HCI_OPCODE( + NG_HCI_OGF_LE, + NG_HCI_OCF_LE_READ_SUPPORTED_STATES), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "LE States: %jx\n", rp.le_states); + + return (OK); +} + +static int +set_le_event_mask(int s, uint64_t mask) +{ + ng_hci_le_set_event_mask_cp semc; + ng_hci_le_set_event_mask_rp rp; + int i, n; + + n = sizeof(rp); + + for (i=0; i < NG_HCI_LE_EVENT_MASK_SIZE; i++) { + semc.event_mask[i] = mask&0xff; + mask >>= 8; + } + if(hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_EVENT_MASK), + (void *)&semc, sizeof(semc), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} + +static int +set_event_mask(int s, uint64_t mask) +{ + ng_hci_set_event_mask_cp semc; + ng_hci_set_event_mask_rp rp; + int i, n; + + n = sizeof(rp); + + for (i=0; i < NG_HCI_EVENT_MASK_SIZE; i++) { + semc.event_mask[i] = mask&0xff; + mask >>= 8; + } + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND, + NG_HCI_OCF_SET_EVENT_MASK), + (void *)&semc, sizeof(semc), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} + +static +int le_enable(int s, int argc, char *argv[]) +{ + int result; + + if (argc != 1) + return (USAGE); + + if (strcasecmp(argv[0], "enable") == 0) { + result = set_event_mask(s, NG_HCI_EVENT_MASK_DEFAULT | + NG_HCI_EVENT_MASK_LE); + if (result != OK) + return result; + result = set_le_event_mask(s, NG_HCI_LE_EVENT_MASK_ALL); + if (result == OK) { + fprintf(stdout, "LE enabled\n"); + return (OK); + } else + return result; + } else if (strcasecmp(argv[0], "disable") == 0) { + result = set_event_mask(s, NG_HCI_EVENT_MASK_DEFAULT); + if (result == OK) { + fprintf(stdout, "LE disabled\n"); + return (OK); + } else + return result; + } else + return (USAGE); +} + +static int +le_set_advertising_enable(int s, int argc, char *argv[]) +{ + ng_hci_le_set_advertise_enable_cp cp; + ng_hci_le_set_advertise_enable_rp rp; + int n, enable = 0; + + if (argc != 1) + return USAGE; + + if (strcmp(argv[0], "enable") == 0) + enable = 1; + else if (strcmp(argv[0], "disable") != 0) + return USAGE; + + n = sizeof(rp); + cp.advertising_enable = enable; + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_ADVERTISE_ENABLE), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + fprintf(stdout, "LE Advertising %s\n", (enable ? "enabled" : "disabled")); + + return (OK); +} + +static int +le_set_advertising_param(int s, int argc, char *argv[]) +{ + ng_hci_le_set_advertising_parameters_cp cp; + ng_hci_le_set_advertising_parameters_rp rp; + + int n, ch; + + cp.advertising_interval_min = 0x800; + cp.advertising_interval_max = 0x800; + cp.advertising_type = 0; + cp.own_address_type = 0; + cp.direct_address_type = 0; + + cp.advertising_channel_map = 7; + cp.advertising_filter_policy = 0; + + optreset = 1; + optind = 0; + while ((ch = getopt(argc, argv , "m:M:t:o:p:a:c:f:")) != -1) { + switch(ch) { + case 'm': + cp.advertising_interval_min = + (uint16_t)(strtod(optarg, NULL)/0.625); + break; + case 'M': + cp.advertising_interval_max = + (uint16_t)(strtod(optarg, NULL)/0.625); + break; + case 't': + cp.advertising_type = + (uint8_t)strtod(optarg, NULL); + break; + case 'o': + cp.own_address_type = + (uint8_t)strtod(optarg, NULL); + break; + case 'p': + cp.direct_address_type = + (uint8_t)strtod(optarg, NULL); + break; + case 'a': + if (!bt_aton(optarg, &cp.direct_address)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(optarg)) == NULL) + return (USAGE); + + memcpy(&cp.direct_address, he->h_addr, sizeof(cp.direct_address)); + } + break; + case 'c': + cp.advertising_channel_map = + (uint8_t)strtod(optarg, NULL); + break; + case 'f': + cp.advertising_filter_policy = + (uint8_t)strtod(optarg, NULL); + break; + } + } + + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_ADVERTISING_PARAMETERS), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} + +static int +le_read_advertising_channel_tx_power(int s, int argc, char *argv[]) +{ + ng_hci_le_read_advertising_channel_tx_power_rp rp; + int n; + + n = sizeof(rp); + + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Advertising transmit power level: %d dBm\n", + (int8_t)rp.transmit_power_level); + + return (OK); +} + +static int +le_set_advertising_data(int s, int argc, char *argv[]) +{ + ng_hci_le_set_advertising_data_cp cp; + ng_hci_le_set_advertising_data_rp rp; + int n, len; + + n = sizeof(rp); + + char buf[NG_HCI_ADVERTISING_DATA_SIZE]; + + len = sizeof(buf); + parse_param(argc, argv, buf, &len); + memset(cp.advertising_data, 0, sizeof(cp.advertising_data)); + cp.advertising_data_length = len; + memcpy(cp.advertising_data, buf, len); + + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_ADVERTISING_DATA), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} +static int +le_read_buffer_size(int s, int argc, char *argv[]) +{ + union { + ng_hci_le_read_buffer_size_rp v1; + ng_hci_le_read_buffer_size_rp_v2 v2; + } rp; + + int n, ch; + uint8_t v; + uint16_t cmd; + + optreset = 1; + optind = 0; + + /* Default to version 1*/ + v = 1; + cmd = NG_HCI_OCF_LE_READ_BUFFER_SIZE; + + while ((ch = getopt(argc, argv , "v:")) != -1) { + switch(ch) { + case 'v': + v = (uint8_t)strtol(optarg, NULL, 16); + if (v == 2) + cmd = NG_HCI_OCF_LE_READ_BUFFER_SIZE_V2; + else if (v > 2) + return (USAGE); + break; + default: + v = 1; + } + } + + n = sizeof(rp); + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, cmd), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.v1.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.v1.status), rp.v1.status); + return (FAILED); + } + + fprintf(stdout, "ACL data packet length: %d\n", + rp.v1.hc_le_data_packet_length); + fprintf(stdout, "Number of ACL data packets: %d\n", + rp.v1.hc_total_num_le_data_packets); + + if (v == 2) { + fprintf(stdout, "ISO data packet length: %d\n", + rp.v2.hc_iso_data_packet_length); + fprintf(stdout, "Number of ISO data packets: %d\n", + rp.v2.hc_total_num_iso_data_packets); + } + + return (OK); +} + +static int +le_scan(int s, int argc, char *argv[]) +{ + int n, bufsize, scancount, numscans; + bool verbose; + uint8_t active = 0; + char ch; + + char b[512]; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + ng_hci_le_set_scan_parameters_cp scan_param_cp; + ng_hci_le_set_scan_parameters_rp scan_param_rp; + + ng_hci_le_set_scan_enable_cp scan_enable_cp; + ng_hci_le_set_scan_enable_rp scan_enable_rp; + + optreset = 1; + optind = 0; + verbose = false; + numscans = 1; + + while ((ch = getopt(argc, argv , "an:v")) != -1) { + switch(ch) { + case 'a': + active = 1; + break; + case 'n': + numscans = (uint8_t)strtol(optarg, NULL, 10); + break; + case 'v': + verbose = true; + break; + } + } + + scan_param_cp.le_scan_type = active; + scan_param_cp.le_scan_interval = (uint16_t)(100/0.625); + scan_param_cp.le_scan_window = (uint16_t)(50/0.625); + /* Address type public */ + scan_param_cp.own_address_type = 0; + /* 'All' filter policy */ + scan_param_cp.scanning_filter_policy = 0; + n = sizeof(scan_param_rp); + + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_SCAN_PARAMETERS), + (void *)&scan_param_cp, sizeof(scan_param_cp), + (void *)&scan_param_rp, &n) == ERROR) + return (ERROR); + + if (scan_param_rp.status != 0x00) { + fprintf(stdout, "LE_Set_Scan_Parameters failed. Status: %s [%#02x]\n", + hci_status2str(scan_param_rp.status), + scan_param_rp.status); + return (FAILED); + } + + /* Enable scanning */ + n = sizeof(scan_enable_rp); + scan_enable_cp.le_scan_enable = 1; + scan_enable_cp.filter_duplicates = 1; + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_SCAN_ENABLE), + (void *)&scan_enable_cp, sizeof(scan_enable_cp), + (void *)&scan_enable_rp, &n) == ERROR) + return (ERROR); + + if (scan_enable_rp.status != 0x00) { + fprintf(stdout, "LE_Scan_Enable enable failed. Status: %s [%#02x]\n", + hci_status2str(scan_enable_rp.status), + scan_enable_rp.status); + return (FAILED); + } + + scancount = 0; + while (scancount < numscans) { + /* wait for scan events */ + bufsize = sizeof(b); + if (hci_recv(s, b, &bufsize) == ERROR) { + return (ERROR); + } + + if (bufsize < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + scancount++; + if (e->event == NG_HCI_EVENT_LE) { + fprintf(stdout, "Scan %d\n", scancount); + handle_le_event(e, verbose); + } + } + + fprintf(stdout, "Scan complete\n"); + + /* Disable scanning */ + n = sizeof(scan_enable_rp); + scan_enable_cp.le_scan_enable = 0; + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_SET_SCAN_ENABLE), + (void *)&scan_enable_cp, sizeof(scan_enable_cp), + (void *)&scan_enable_rp, &n) == ERROR) + return (ERROR); + + if (scan_enable_rp.status != 0x00) { + fprintf(stdout, "LE_Scan_Enable disable failed. Status: %s [%#02x]\n", + hci_status2str(scan_enable_rp.status), + scan_enable_rp.status); + return (FAILED); + } + + return (OK); +} + +static void handle_le_event(ng_hci_event_pkt_t* e, bool verbose) +{ + int rc; + ng_hci_le_ep *leer = + (ng_hci_le_ep *)(e + 1); + ng_hci_le_advertising_report_ep *advrep = + (ng_hci_le_advertising_report_ep *)(leer + 1); + ng_hci_le_advreport *reports = + (ng_hci_le_advreport *)(advrep + 1); + + if (leer->subevent_code == NG_HCI_LEEV_ADVREP) { + fprintf(stdout, "Scan result, num_reports: %d\n", + advrep->num_reports); + for(rc = 0; rc < advrep->num_reports; rc++) { + uint8_t length = (uint8_t)reports[rc].length_data; + fprintf(stdout, "\tBD_ADDR %s \n", + hci_bdaddr2str(&reports[rc].bdaddr)); + fprintf(stdout, "\tAddress type: %s\n", + hci_addrtype2str(reports[rc].addr_type)); + if (length > 0 && verbose) { + dump_adv_data(length, reports[rc].data); + print_adv_data(length, reports[rc].data); + fprintf(stdout, + "\tRSSI: %d dBm\n", + (int8_t)reports[rc].data[length]); + fprintf(stdout, "\n"); + } + } + } +} + +static int +le_read_white_list_size(int s, int argc, char *argv[]) +{ + ng_hci_le_read_white_list_size_rp rp; + int n; + + n = sizeof(rp); + + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_READ_WHITE_LIST_SIZE), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "White list size: %d\n", + (uint8_t)rp.white_list_size); + + return (OK); +} + +static int +le_clear_white_list(int s, int argc, char *argv[]) +{ + ng_hci_le_clear_white_list_rp rp; + int n; + + n = sizeof(rp); + + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_CLEAR_WHITE_LIST), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "White list cleared\n"); + + return (OK); +} + +static int +le_add_device_to_white_list(int s, int argc, char *argv[]) +{ + ng_hci_le_add_device_to_white_list_cp cp; + ng_hci_le_add_device_to_white_list_rp rp; + int n; + char ch; + optreset = 1; + optind = 0; + bool addr_set = false; + + n = sizeof(rp); + + cp.address_type = 0x00; + + while ((ch = getopt(argc, argv , "t:a:")) != -1) { + switch(ch) { + case 't': + if (strcmp(optarg, "public") == 0) + cp.address_type = 0x00; + else if (strcmp(optarg, "random") == 0) + cp.address_type = 0x01; + else + return (USAGE); + break; + case 'a': + addr_set = true; + if (!bt_aton(optarg, &cp.address)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(optarg)) == NULL) + return (USAGE); + + memcpy(&cp.address, he->h_addr, + sizeof(cp.address)); + } + break; + } + } + + if (addr_set == false) + return (USAGE); + + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_ADD_DEVICE_TO_WHITE_LIST), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Address added to white list\n"); + + return (OK); +} + +static int +le_remove_device_from_white_list(int s, int argc, char *argv[]) +{ + ng_hci_le_remove_device_from_white_list_cp cp; + ng_hci_le_remove_device_from_white_list_rp rp; + int n; + char ch; + optreset = 1; + optind = 0; + bool addr_set = false; + + n = sizeof(rp); + + cp.address_type = 0x00; + + while ((ch = getopt(argc, argv , "t:a:")) != -1) { + switch(ch) { + case 't': + if (strcmp(optarg, "public") == 0) + cp.address_type = 0x00; + else if (strcmp(optarg, "random") == 0) + cp.address_type = 0x01; + else + return (USAGE); + break; + case 'a': + addr_set = true; + if (!bt_aton(optarg, &cp.address)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(optarg)) == NULL) + return (USAGE); + + memcpy(&cp.address, he->h_addr, + sizeof(cp.address)); + } + break; + } + } + + if (addr_set == false) + return (USAGE); + + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_ADD_DEVICE_TO_WHITE_LIST), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Address removed from white list\n"); + + return (OK); +} + +static int +le_connect(int s, int argc, char *argv[]) +{ + ng_hci_le_create_connection_cp cp; + ng_hci_status_rp rp; + char b[512]; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + int n, scancount, bufsize; + char ch; + bool addr_set = false; + bool verbose = false; + + optreset = 1; + optind = 0; + + /* minimal scan interval (2.5ms) */ + cp.scan_interval = htole16(4); + cp.scan_window = htole16(4); + + /* Don't use the whitelist */ + cp.filter_policy = 0x00; + + /* Default to public peer address */ + cp.peer_addr_type = 0x00; + + /* Own address type public */ + cp.own_address_type = 0x00; + + /* 18.75ms min connection interval */ + cp.conn_interval_min = htole16(0x000F); + /* 18.75ms max connection interval */ + cp.conn_interval_max = htole16(0x000F); + + /* 0 events connection latency */ + cp.conn_latency = htole16(0x0000); + + /* 32s supervision timeout */ + cp.supervision_timeout = htole16(0x0C80); + + /* Min CE Length 0.625 ms */ + cp.min_ce_length = htole16(1); + /* Max CE Length 0.625 ms */ + cp.max_ce_length = htole16(1); + + while ((ch = getopt(argc, argv , "a:t:v")) != -1) { + switch(ch) { + case 't': + if (strcmp(optarg, "public") == 0) + cp.peer_addr_type = 0x00; + else if (strcmp(optarg, "random") == 0) + cp.peer_addr_type = 0x01; + else + return (USAGE); + break; + case 'a': + addr_set = true; + if (!bt_aton(optarg, &cp.peer_addr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(optarg)) == NULL) + return (USAGE); + + memcpy(&cp.peer_addr, he->h_addr, + sizeof(cp.peer_addr)); + } + break; + case 'v': + verbose = true; + break; + } + } + + if (addr_set == false) + return (USAGE); + + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_CREATE_CONNECTION), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, + "Create connection failed. Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + scancount = 0; + while (scancount < 3) { + /* wait for connection events */ + bufsize = sizeof(b); + if (hci_recv(s, b, &bufsize) == ERROR) { + return (ERROR); + } + + if (bufsize < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + scancount++; + if (e->event == NG_HCI_EVENT_LE) { + handle_le_connection_event(e, verbose); + break; + } + } + + return (OK); +} + +static void handle_le_connection_event(ng_hci_event_pkt_t* e, bool verbose) +{ + ng_hci_le_ep *ev_pkt; + ng_hci_le_connection_complete_ep *conn_event; + + ev_pkt = (ng_hci_le_ep *)(e + 1); + + if (ev_pkt->subevent_code == NG_HCI_LEEV_CON_COMPL) { + conn_event =(ng_hci_le_connection_complete_ep *)(ev_pkt + 1); + fprintf(stdout, "Handle: %d\n", le16toh(conn_event->handle)); + if (verbose) { + fprintf(stdout, + "Status: %s\n", + hci_status2str(conn_event->status)); + fprintf(stdout, + "Role: %s\n", + hci_role2str(conn_event->role)); + fprintf(stdout, + "Address Type: %s\n", + hci_addrtype2str(conn_event->address_type)); + fprintf(stdout, + "Address: %s\n", + hci_bdaddr2str(&conn_event->address)); + fprintf(stdout, + "Interval: %.2fms\n", + 6.25 * le16toh(conn_event->interval)); + fprintf(stdout, + "Latency: %d events\n", conn_event->latency); + fprintf(stdout, + "Supervision timeout: %dms\n", + 10 * le16toh(conn_event->supervision_timeout)); + fprintf(stdout, + "Master clock accuracy: %s\n", + hci_mc_accuracy2str( + conn_event->master_clock_accuracy)); + } + } +} + +static int +le_read_channel_map(int s, int argc, char *argv[]) +{ + ng_hci_le_read_channel_map_cp cp; + ng_hci_le_read_channel_map_rp rp; + int n; + char buffer[2048]; + + /* parse command parameters */ + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.connection_handle = (uint16_t) (n & 0x0fff); + cp.connection_handle = htole16(cp.connection_handle); + break; + + default: + return (USAGE); + } + + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_READ_CHANNEL_MAP), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, + "Read channel map failed. Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", + le16toh(rp.connection_handle)); + fprintf(stdout, "Used channels:\n"); + fprintf(stdout, "\n%s\n", hci_le_chanmap2str(rp.le_channel_map, + buffer, sizeof(buffer))); + + return (OK); +} /* le_read_channel_map */ + +static int +le_read_remote_features(int s, int argc, char *argv[]) +{ + ng_hci_le_read_remote_used_features_cp cp; + ng_hci_status_rp rp; + int n, bufsize; + char b[512]; + + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + /* parse command parameters */ + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.connection_handle = (uint16_t) (n & 0x0fff); + cp.connection_handle = htole16(cp.connection_handle); + break; + + default: + return (USAGE); + } + + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_READ_REMOTE_USED_FEATURES), + (void *)&cp, sizeof(cp), (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, + "Read remote features failed. Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + /* wait for connection events */ + bufsize = sizeof(b); + if (hci_recv(s, b, &bufsize) == ERROR) { + return (ERROR); + } + + if (bufsize < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + if (e->event == NG_HCI_EVENT_LE) { + handle_le_remote_features_event(e); + } + + return (OK); +} /* le_read_remote_features */ + +static void handle_le_remote_features_event(ng_hci_event_pkt_t* e) +{ + ng_hci_le_ep *ev_pkt; + ng_hci_le_read_remote_features_ep *feat_event; + char buffer[2048]; + + ev_pkt = (ng_hci_le_ep *)(e + 1); + + if (ev_pkt->subevent_code == NG_HCI_LEEV_READ_REMOTE_FEATURES_COMPL) { + feat_event =(ng_hci_le_read_remote_features_ep *)(ev_pkt + 1); + fprintf(stdout, "Handle: %d\n", + le16toh(feat_event->connection_handle)); + fprintf(stdout, + "Status: %s\n", + hci_status2str(feat_event->status)); + fprintf(stdout, "Features:\n%s\n", + hci_le_features2str(feat_event->features, + buffer, sizeof(buffer))); + } +} /* handle_le_remote_features_event */ + +static int le_rand(int s, int argc, char *argv[]) +{ + ng_hci_le_rand_rp rp; + int n; + + n = sizeof(rp); + + if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE, + NG_HCI_OCF_LE_RAND), + (void *)&rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, + "Random number : %08llx\n", + (unsigned long long)le64toh(rp.random_number)); + + return (OK); +} + + + +struct hci_command le_commands[] = { +{ + "le_enable", + "le_enable [enable|disable] \n" + "Enable LE event ", + &le_enable, +}, + { + "le_read_local_supported_features", + "le_read_local_supported_features\n" + "read local supported features mask", + &le_read_local_supported_features, + }, + { + "le_read_supported_states", + "le_read_supported_states\n" + "read supported status" + , + &le_read_supported_states, + }, + { + "le_set_scan_response", + "le_set_scan_response -n $name -f $flag -u $uuid16,$uuid16 \n" + "set LE scan response data" + , + &le_set_scan_response, + }, + { + "le_set_scan_enable", + "le_set_scan_enable [enable|disable] \n" + "enable or disable LE device scan", + &le_set_scan_enable + }, + { + "le_set_scan_param", + "le_set_scan_param [active|passive] interval(ms) window(ms) [public|random] [all|whitelist] \n" + "set LE device scan parameter", + &le_set_scan_param + }, + { + "le_set_advertising_enable", + "le_set_advertising_enable [enable|disable] \n" + "start or stop advertising", + &le_set_advertising_enable + }, + { + "le_read_advertising_channel_tx_power", + "le_read_advertising_channel_tx_power\n" + "read host advertising transmit poser level (dBm)", + &le_read_advertising_channel_tx_power + }, + { + "le_set_advertising_param", + "le_set_advertising_param [-m min_interval(ms)] [-M max_interval(ms)]\n" + "[-t advertising_type] [-o own_address_type] [-p peer_address_type]\n" + "[-c advertising_channel_map] [-f advertising_filter_policy]\n" + "[-a peer_address]\n" + "set LE device advertising parameters", + &le_set_advertising_param + }, + { + "le_set_advertising_data", + "le_set_advertising_data -n $name -f $flag -u $uuid16,$uuid16 \n" + "set LE device advertising packed data", + &le_set_advertising_data + }, + { + "le_read_buffer_size", + "le_read_buffer_size [-v 1|2]\n" + "Read the maximum size of ACL and ISO data packets", + &le_read_buffer_size + }, + { + "le_scan", + "le_scan [-a] [-v] [-n number_of_scans]\n" + "Do an LE scan", + &le_scan + }, + { + "le_read_white_list_size", + "le_read_white_list_size\n" + "Read total number of white list entries that can be stored", + &le_read_white_list_size + }, + { + "le_clear_white_list", + "le_clear_white_list\n" + "Clear the white list in the controller", + &le_clear_white_list + }, + { + "le_add_device_to_white_list", + "le_add_device_to_white_list\n" + "[-t public|random] -a address\n" + "Add device to the white list", + &le_add_device_to_white_list + }, + { + "le_remove_device_from_white_list", + "le_remove_device_from_white_list\n" + "[-t public|random] -a address\n" + "Remove device from the white list", + &le_remove_device_from_white_list + }, + { + "le_connect", + "le_connect -a address [-t public|random] [-v]\n" + "Connect to an LE device", + &le_connect + }, + { + "le_read_channel_map", + "le_read_channel_map <connection_handle>\n" + "Read the channel map for a connection", + &le_read_channel_map + }, + { + "le_read_remote_features", + "le_read_remote_features <connection_handle>\n" + "Read supported features for the device\n" + "identified by the connection handle", + &le_read_remote_features + }, + { + "le_rand", + "le_rand\n" + "Generate 64 bits of random data", + &le_rand + }, + { + NULL, + } +}; diff --git a/usr.sbin/bluetooth/hccontrol/link_control.c b/usr.sbin/bluetooth/hccontrol/link_control.c new file mode 100644 index 000000000000..b43515aa0d5b --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/link_control.c @@ -0,0 +1,962 @@ +/*- + * link_control.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: link_control.c,v 1.4 2003/08/18 19:19:54 max Exp $ + */ + +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include "hccontrol.h" + +static void hci_inquiry_response (int n, uint8_t **b); + +/* Send Inquiry command to the unit */ +static int +hci_inquiry(int s, int argc, char **argv) +{ + int n0, n1, n2, timo; + char b[512]; + ng_hci_inquiry_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + /* set defaults */ + cp.lap[2] = 0x9e; + cp.lap[1] = 0x8b; + cp.lap[0] = 0x33; + cp.inquiry_length = 5; + cp.num_responses = 8; + + /* parse command parameters */ + switch (argc) { + case 3: + /* number of responses, range 0x00 - 0xff */ + if (sscanf(argv[2], "%d", &n0) != 1 || n0 < 0 || n0 > 0xff) + return (USAGE); + + cp.num_responses = (n0 & 0xff); + + case 2: + /* inquiry length (N * 1.28) sec, range 0x01 - 0x30 */ + if (sscanf(argv[1], "%d", &n0) != 1 || n0 < 0x1 || n0 > 0x30) + return (USAGE); + + cp.inquiry_length = (n0 & 0xff); + + case 1: + /* LAP */ + if (sscanf(argv[0], "%x:%x:%x", &n2, &n1, &n0) != 3) + return (USAGE); + + cp.lap[0] = (n0 & 0xff); + cp.lap[1] = (n1 & 0xff); + cp.lap[2] = (n2 & 0xff); + + case 0: + /* use defaults */ + break; + + default: + return (USAGE); + } + + /* send request and expect status back */ + n0 = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_INQUIRY), (char const *) &cp, sizeof(cp), + b, &n0) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + timo = timeout; + timeout = cp.inquiry_length * 1.28 + 1; + +wait_for_more: + /* wait for inquiry events */ + n0 = sizeof(b); + if (hci_recv(s, b, &n0) == ERROR) { + timeout = timo; + return (ERROR); + } + + if (n0 < sizeof(*e)) { + timeout = timo; + errno = EIO; + return (ERROR); + } + + switch (e->event) { + case NG_HCI_EVENT_INQUIRY_RESULT: { + ng_hci_inquiry_result_ep *ir = + (ng_hci_inquiry_result_ep *)(e + 1); + uint8_t *r = (uint8_t *)(ir + 1); + + fprintf(stdout, "Inquiry result, num_responses=%d\n", + ir->num_responses); + + for (n0 = 0; n0 < ir->num_responses; n0++) + hci_inquiry_response(n0, &r); + + goto wait_for_more; + } + + case NG_HCI_EVENT_INQUIRY_COMPL: + fprintf(stdout, "Inquiry complete. Status: %s [%#02x]\n", + hci_status2str(*(b + sizeof(*e))), *(b + sizeof(*e))); + break; + + default: + goto wait_for_more; + } + + timeout = timo; + + return (OK); +} /* hci_inquiry */ + +/* Print Inquiry_Result event */ +static void +hci_inquiry_response(int n, uint8_t **b) +{ + ng_hci_inquiry_response *ir = (ng_hci_inquiry_response *)(*b); + + fprintf(stdout, "Inquiry result #%d\n", n); + fprintf(stdout, "\tBD_ADDR: %s\n", hci_bdaddr2str(&ir->bdaddr)); + fprintf(stdout, "\tPage Scan Rep. Mode: %#02x\n", + ir->page_scan_rep_mode); + fprintf(stdout, "\tPage Scan Period Mode: %#02x\n", + ir->page_scan_period_mode); + fprintf(stdout, "\tPage Scan Mode: %#02x\n", + ir->page_scan_mode); + fprintf(stdout, "\tClass: %02x:%02x:%02x\n", + ir->uclass[2], ir->uclass[1], ir->uclass[0]); + fprintf(stdout, "\tClock offset: %#04x\n", + le16toh(ir->clock_offset)); + + *b += sizeof(*ir); +} /* hci_inquiry_response */ + +/* Send Create_Connection command to the unit */ +static int +hci_create_connection(int s, int argc, char **argv) +{ + int n0; + char b[512]; + ng_hci_create_con_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + /* Set defaults */ + memset(&cp, 0, sizeof(cp)); + cp.pkt_type = htole16( NG_HCI_PKT_DM1 | NG_HCI_PKT_DH1 | + NG_HCI_PKT_DM3 | NG_HCI_PKT_DH3 | + NG_HCI_PKT_DM5); + cp.page_scan_rep_mode = NG_HCI_SCAN_REP_MODE0; + cp.page_scan_mode = NG_HCI_MANDATORY_PAGE_SCAN_MODE; + cp.clock_offset = 0; + cp.accept_role_switch = 1; + + /* parse command parameters */ + switch (argc) { + case 6: + /* accept role switch */ + if (sscanf(argv[5], "%d", &n0) != 1) + return (USAGE); + + cp.accept_role_switch = n0 ? 1 : 0; + + case 5: + /* clock offset */ + if (sscanf(argv[4], "%d", &n0) != 1) + return (USAGE); + + cp.clock_offset = (n0 & 0xffff); + cp.clock_offset = htole16(cp.clock_offset); + + case 4: + /* page scan mode */ + if (sscanf(argv[3], "%d", &n0) != 1 || n0 < 0 || n0 > 3) + return (USAGE); + + cp.page_scan_mode = (n0 & 0xff); + + case 3: + /* page scan rep mode */ + if (sscanf(argv[2], "%d", &n0) != 1 || n0 < 0 || n0 > 2) + return (USAGE); + + cp.page_scan_rep_mode = (n0 & 0xff); + + case 2: + /* packet type */ + if (sscanf(argv[1], "%x", &n0) != 1) + return (USAGE); + + n0 &= ( NG_HCI_PKT_DM1 | NG_HCI_PKT_DH1 | + NG_HCI_PKT_DM3 | NG_HCI_PKT_DH3 | + NG_HCI_PKT_DM5); + if (n0 == 0) + return (USAGE); + + cp.pkt_type = (n0 & 0xffff); + cp.pkt_type = htole16(cp.pkt_type); + + case 1: + /* BD_ADDR */ + if (!bt_aton(argv[0], &cp.bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(argv[0])) == NULL) + return (USAGE); + + memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr)); + } + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n0 = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_CREATE_CON), + (char const *) &cp, sizeof(cp), b, &n0) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n0 = sizeof(b); + if (hci_recv(s, b, &n0) == ERROR) + return (ERROR); + if (n0 < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_CON_COMPL) { + ng_hci_con_compl_ep *ep = (ng_hci_con_compl_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + fprintf(stdout, "BD_ADDR: %s\n", hci_bdaddr2str(&ep->bdaddr)); + fprintf(stdout, "Connection handle: %d\n", + le16toh(ep->con_handle)); + fprintf(stdout, "Encryption mode: %s [%d]\n", + hci_encrypt2str(ep->encryption_mode, 0), + ep->encryption_mode); + } else + goto again; + + return (OK); +} /* hci_create_connection */ + +/* Send Disconnect command to the unit */ +static int +hci_disconnect(int s, int argc, char **argv) +{ + int n; + char b[512]; + ng_hci_discon_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + /* Set defaults */ + memset(&cp, 0, sizeof(cp)); + cp.reason = 0x13; + + /* parse command parameters */ + switch (argc) { + case 2: + /* reason */ + if (sscanf(argv[1], "%d", &n) != 1 || n <= 0x00 || n > 0xff) + return (USAGE); + + cp.reason = (uint8_t) (n & 0xff); + + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_DISCON), + (char const *) &cp, sizeof(cp), b, &n) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n = sizeof(b); + if (hci_recv(s, b, &n) == ERROR) + return (ERROR); + if (n < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_DISCON_COMPL) { + ng_hci_discon_compl_ep *ep = (ng_hci_discon_compl_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", + le16toh(ep->con_handle)); + fprintf(stdout, "Reason: %s [%#02x]\n", + hci_status2str(ep->reason), ep->reason); + } else + goto again; + + return (OK); +} /* hci_disconnect */ + +/* Send Add_SCO_Connection command to the unit */ +static int +hci_add_sco_connection(int s, int argc, char **argv) +{ + int n; + char b[512]; + ng_hci_add_sco_con_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + /* Set defaults */ + memset(&cp, 0, sizeof(cp)); + cp.pkt_type = htole16(NG_HCI_PKT_HV1 | NG_HCI_PKT_HV2 | NG_HCI_PKT_HV3); + + /* parse command parameters */ + switch (argc) { + case 2: + /* packet type */ + if (sscanf(argv[1], "%x", &n) != 1) + return (USAGE); + + n &= (NG_HCI_PKT_HV1 | NG_HCI_PKT_HV2 | NG_HCI_PKT_HV3); + if (n == 0) + return (USAGE); + + cp.pkt_type = (uint16_t) (n & 0x0fff); + cp.pkt_type = htole16(cp.pkt_type); + + case 1: + /* acl connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_ADD_SCO_CON), + (char const *) &cp, sizeof(cp), b, &n) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n = sizeof(b); + if (hci_recv(s, b, &n) == ERROR) + return (ERROR); + if (n < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_CON_COMPL) { + ng_hci_con_compl_ep *ep = (ng_hci_con_compl_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + fprintf(stdout, "BD_ADDR: %s\n", hci_bdaddr2str(&ep->bdaddr)); + fprintf(stdout, "Connection handle: %d\n", + le16toh(ep->con_handle)); + fprintf(stdout, "Encryption mode: %s [%d]\n", + hci_encrypt2str(ep->encryption_mode, 0), + ep->encryption_mode); + } else + goto again; + + return (OK); +} /* Add_SCO_Connection */ + +/* Send Change_Connection_Packet_Type command to the unit */ +static int +hci_change_connection_packet_type(int s, int argc, char **argv) +{ + int n; + char b[512]; + ng_hci_change_con_pkt_type_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + switch (argc) { + case 2: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + + /* packet type */ + if (sscanf(argv[1], "%x", &n) != 1) + return (USAGE); + + cp.pkt_type = (uint16_t) (n & 0xffff); + cp.pkt_type = htole16(cp.pkt_type); + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_CHANGE_CON_PKT_TYPE), + (char const *) &cp, sizeof(cp), b, &n) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n = sizeof(b); + if (hci_recv(s, b, &n) == ERROR) + return (ERROR); + if (n < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_CON_PKT_TYPE_CHANGED) { + ng_hci_con_pkt_type_changed_ep *ep = + (ng_hci_con_pkt_type_changed_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", + le16toh(ep->con_handle)); + fprintf(stdout, "Packet type: %#04x\n", + le16toh(ep->pkt_type)); + } else + goto again; + + return (OK); +} /* hci_change_connection_packet_type */ + +/* Send Remote_Name_Request command to the unit */ +static int +hci_remote_name_request(int s, int argc, char **argv) +{ + int n0; + char b[512]; + ng_hci_remote_name_req_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + memset(&cp, 0, sizeof(cp)); + cp.page_scan_rep_mode = NG_HCI_SCAN_REP_MODE0; + cp.page_scan_mode = NG_HCI_MANDATORY_PAGE_SCAN_MODE; + + /* parse command parameters */ + switch (argc) { + case 4: + /* clock_offset */ + if (sscanf(argv[3], "%x", &n0) != 1) + return (USAGE); + + cp.clock_offset = (n0 & 0xffff); + cp.clock_offset = htole16(cp.clock_offset); + + case 3: + /* page_scan_mode */ + if (sscanf(argv[2], "%d", &n0) != 1 || n0 < 0x00 || n0 > 0x03) + return (USAGE); + + cp.page_scan_mode = (n0 & 0xff); + + case 2: + /* page_scan_rep_mode */ + if (sscanf(argv[1], "%d", &n0) != 1 || n0 < 0x00 || n0 > 0x02) + return (USAGE); + + cp.page_scan_rep_mode = (n0 & 0xff); + + case 1: + /* BD_ADDR */ + if (!bt_aton(argv[0], &cp.bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(argv[0])) == NULL) + return (USAGE); + + memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr)); + } + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n0 = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_REMOTE_NAME_REQ), + (char const *) &cp, sizeof(cp), b, &n0) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n0 = sizeof(b); + if (hci_recv(s, b, &n0) == ERROR) + return (ERROR); + if (n0 < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_REMOTE_NAME_REQ_COMPL) { + ng_hci_remote_name_req_compl_ep *ep = + (ng_hci_remote_name_req_compl_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + fprintf(stdout, "BD_ADDR: %s\n", hci_bdaddr2str(&ep->bdaddr)); + fprintf(stdout, "Name: %s\n", ep->name); + } else + goto again; + + return (OK); +} /* hci_remote_name_request */ + +/* Send Read_Remote_Supported_Features command to the unit */ +static int +hci_read_remote_supported_features(int s, int argc, char **argv) +{ + int n; + char b[512]; + ng_hci_read_remote_features_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + char buffer[2048]; + + /* parse command parameters */ + switch (argc) { + case 1: + /* connecton handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_READ_REMOTE_FEATURES), + (char const *) &cp, sizeof(cp), b, &n) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n = sizeof(b); + if (hci_recv(s, b, &n) == ERROR) + return (ERROR); + + if (n < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_READ_REMOTE_FEATURES_COMPL) { + ng_hci_read_remote_features_compl_ep *ep = + (ng_hci_read_remote_features_compl_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", + le16toh(ep->con_handle)); + fprintf(stdout, "Features: "); + for (n = 0; n < sizeof(ep->features); n++) + fprintf(stdout, "%#02x ", ep->features[n]); + fprintf(stdout, "\n%s\n", hci_features2str(ep->features, + buffer, sizeof(buffer))); + } else + goto again; + + return (OK); +} /* hci_read_remote_supported_features */ + +/* Send Read_Remote_Version_Information command to the unit */ +static int +hci_read_remote_version_information(int s, int argc, char **argv) +{ + int n; + char b[512]; + ng_hci_read_remote_ver_info_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + /* parse command parameters */ + switch (argc) { + case 1: + /* connecton handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_READ_REMOTE_VER_INFO), + (char const *) &cp, sizeof(cp), b, &n) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n = sizeof(b); + if (hci_recv(s, b, &n) == ERROR) + return (ERROR); + + if (n < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_READ_REMOTE_VER_INFO_COMPL) { + ng_hci_read_remote_ver_info_compl_ep *ep = + (ng_hci_read_remote_ver_info_compl_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + ep->manufacturer = le16toh(ep->manufacturer); + + fprintf(stdout, "Connection handle: %d\n", + le16toh(ep->con_handle)); + fprintf(stdout, "LMP version: %s [%#02x]\n", + hci_lmpver2str(ep->lmp_version), ep->lmp_version); + fprintf(stdout, "LMP sub-version: %#04x\n", + le16toh(ep->lmp_subversion)); + fprintf(stdout, "Manufacturer: %s [%#04x]\n", + hci_manufacturer2str(ep->manufacturer), + ep->manufacturer); + } else + goto again; + + return (OK); +} /* hci_read_remote_version_information */ + +/* Send Read_Clock_Offset command to the unit */ +static int +hci_read_clock_offset(int s, int argc, char **argv) +{ + int n; + char b[512]; + ng_hci_read_clock_offset_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + /* parse command parameters */ + switch (argc) { + case 1: + /* connecton handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_READ_CLOCK_OFFSET), + (char const *) &cp, sizeof(cp), b, &n) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n = sizeof(b); + if (hci_recv(s, b, &n) == ERROR) + return (ERROR); + + if (n < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_READ_CLOCK_OFFSET_COMPL) { + ng_hci_read_clock_offset_compl_ep *ep = + (ng_hci_read_clock_offset_compl_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", + le16toh(ep->con_handle)); + fprintf(stdout, "Clock offset: %#04x\n", + le16toh(ep->clock_offset)); + } else + goto again; + + return (OK); +} /* hci_read_clock_offset */ + +struct hci_command link_control_commands[] = { +{ +"inquiry <LAP> <inquiry_length> <num_reponses>", +"\nThis command will cause the Bluetooth unit to enter Inquiry Mode.\n" \ +"Inquiry Mode is used to discover other nearby Bluetooth units. The LAP\n" \ +"input parameter contains the LAP from which the inquiry access code shall\n" \ +"be derived when the inquiry procedure is made. The Inquiry_Length parameter\n"\ +"specifies the total duration of the Inquiry Mode and, when this time\n" \ +"expires, Inquiry will be halted. The Num_Responses parameter specifies the\n" \ +"number of responses that can be received before the Inquiry is halted.\n\n" \ +"\t<LAP> - xx:xx:xx; 9e:8b:33 (GIAC), 93:8b:00 (LDIAC)\n" \ +"\t<inquiry_length> - dd; total length == dd * 1.28 sec\n" \ +"\t<num_responses> - dd", +&hci_inquiry +}, +{ +"create_connection <BD_ADDR> <pkt> <rep_mode> <ps_mode> <clck_off> <role_sw>", +"" \ +"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name\n\n" \ +"\t<pkt> - xxxx; packet type\n" \ +"" \ +"\t\tACL packets\n" \ +"\t\t-----------\n" \ +"\t\t0x0008 DM1\n" \ +"\t\t0x0010 DH1\n" \ +"\t\t0x0400 DM3\n" \ +"\t\t0x0800 DH3\n" \ +"\t\t0x4000 DM5\n" \ +"\t\t0x8000 DH5\n\n" \ +"" \ +"\trep_mode - d; page scan repetition mode\n" \ +"" \ +"\t\tPage scan repetition modes\n" \ +"\t\t--------------------------\n" \ +"\t\t0 Page scan repetition mode 0\n" \ +"\t\t1 Page scan repetition mode 1\n" \ +"\t\t2 Page scan repetition mode 2\n" \ +"\n" \ +"\tps_mode - d; Page scan mode\n" \ +"" \ +"\t\tPage scan modes\n" \ +"\t\t---------------\n" \ +"\t\t0 Mandatory page scan mode\n" \ +"\t\t1 Optional page scan mode1\n" \ +"\t\t2 Optional page scan mode2\n" \ +"\t\t3 Optional page scan mode3\n" \ +"\n" \ +"\tclck_off - dddd; clock offset. Use 0 if unknown\n\n" \ +"\trole_sw - d; allow (1) or deny role switch\n", +&hci_create_connection +}, +{ +"disconnect <connection_handle> <reason>", +"\nThe Disconnection command is used to terminate an existing connection.\n" \ +"The connection handle command parameter indicates which connection is to\n" \ +"be disconnected. The Reason command parameter indicates the reason for\n" \ +"ending the connection.\n\n" \ +"\t<connection_handle> - dddd; connection handle\n" \ +"\t<reason> - dd; reason; usually 19 (0x13) - user ended;\n" \ +"\t also 0x05, 0x13-0x15, 0x1A, 0x29", +&hci_disconnect +}, +{ +"add_sco_connection <acl connection handle> <packet type>", +"This command will cause the link manager to create a SCO connection using\n" \ +"the ACL connection specified by the connection handle command parameter.\n" \ +"The Link Manager will determine how the new connection is established. This\n"\ +"connection is determined by the current state of the device, its piconet,\n" \ +"and the state of the device to be connected. The packet type command parameter\n" \ +"specifies which packet types the Link Manager should use for the connection.\n"\ +"The Link Manager must only use the packet type(s) specified by the packet\n" \ +"type command parameter for sending HCI SCO data packets. Multiple packet\n" \ +"types may be specified for the packet type command parameter by performing\n" \ +"a bitwise OR operation of the different packet types. Note: An SCO connection\n" \ +"can only be created when an ACL connection already exists and when it is\n" \ +"not put in park mode.\n\n" \ +"\t<connection_handle> - dddd; ACL connection handle\n" \ +"\t<packet_type> - xxxx; packet type\n" \ +"" \ +"\t\tSCO packets\n" \ +"\t\t-----------\n" \ +"\t\t0x0020 HV1\n" \ +"\t\t0x0040 HV2\n" \ +"\t\t0x0080 HV3\n", +&hci_add_sco_connection +}, +{ +"change_connection_packet_type <connection_hande> <packet_type>", +"The Change_Connection_Packet_Type command is used to change which packet\n" \ +"types can be used for a connection that is currently established. This\n" \ +"allows current connections to be dynamically modified to support different\n" \ +"types of user data. The Packet_Type command parameter specifies which\n" \ +"packet types the Link Manager can use for the connection. Multiple packet\n" \ +"types may be specified for the Packet_Type command parameter by bitwise OR\n" \ +"operation of the different packet types.\n\n" \ +"\t<connection_handle> - dddd; connection handle\n" \ +"\t<packet_type> - xxxx; packet type mask\n" \ +"" \ +"\t\tACL packets\n" \ +"\t\t-----------\n" \ +"\t\t0x0008 DM1\n" \ +"\t\t0x0010 DH1\n" \ +"\t\t0x0400 DM3\n" \ +"\t\t0x0800 DH3\n" \ +"\t\t0x4000 DM5\n" \ +"\t\t0x8000 DH5\n\n" \ +"" \ +"\t\tSCO packets\n" \ +"\t\t-----------\n" \ +"\t\t0x0020 HV1\n" \ +"\t\t0x0040 HV2\n" \ +"\t\t0x0080 HV3\n" \ +"", +&hci_change_connection_packet_type +}, +{ +"remote_name_request <BD_ADDR> <ps_rep_mode> <ps_mode> <clock_offset>", +"\nThe Remote_Name_Request command is used to obtain the user-friendly\n" \ +"name of another Bluetooth unit.\n\n" \ +"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name\n" \ +"\t<ps_rep_mode> - dd; page scan repetition mode [0-2]\n" \ +"\t<ps_mode> - dd; page scan mode [0-3]\n" \ +"\t<clock_offset> - xxxx; clock offset [0 - 0xffff]", +&hci_remote_name_request +}, +{ +"read_remote_supported_features <connection_handle>", +"\nThis command requests a list of the supported features for the remote\n" \ +"unit identified by the connection handle parameter. The connection handle\n" \ +"must be a connection handle for an ACL connection.\n\n" \ +"\t<connection_handle> - dddd; connection handle", +&hci_read_remote_supported_features +}, +{ +"read_remote_version_information <connection_handle>", +"\nThis command will obtain the values for the version information for the\n" \ +"remote Bluetooth unit identified by the connection handle parameter. The\n" \ +"connection handle must be a connection handle for an ACL connection.\n\n" \ +"\t<connection_handle> - dddd; connection handle", +&hci_read_remote_version_information +}, +{ +"read_clock_offset <connection_handle>", +"\nThis command allows the Host to read the clock offset from the remote unit.\n" \ +"\t<connection_handle> - dddd; connection handle", +&hci_read_clock_offset +}, +{ +NULL, +}}; + diff --git a/usr.sbin/bluetooth/hccontrol/link_policy.c b/usr.sbin/bluetooth/hccontrol/link_policy.c new file mode 100644 index 000000000000..b99ef2196eb8 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/link_policy.c @@ -0,0 +1,307 @@ +/*- + * link_policy.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: link_policy.c,v 1.3 2003/08/18 19:19:54 max Exp $ + */ + +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include "hccontrol.h" + +/* Send Role Discovery to the unit */ +static int +hci_role_discovery(int s, int argc, char **argv) +{ + ng_hci_role_discovery_cp cp; + ng_hci_role_discovery_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send request */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY, + NG_HCI_OCF_ROLE_DISCOVERY), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle)); + fprintf(stdout, "Role: %s [%#x]\n", + (rp.role == NG_HCI_ROLE_MASTER)? "Master" : "Slave", rp.role); + + return (OK); +} /* hci_role_discovery */ + +/* Send Switch Role to the unit */ +static int +hci_switch_role(int s, int argc, char **argv) +{ + int n0; + char b[512]; + ng_hci_switch_role_cp cp; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b; + + /* parse command parameters */ + switch (argc) { + case 2: + /* bdaddr */ + if (!bt_aton(argv[0], &cp.bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(argv[0])) == NULL) + return (USAGE); + + memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr)); + } + + /* role */ + if (sscanf(argv[1], "%d", &n0) != 1) + return (USAGE); + + cp.role = n0? NG_HCI_ROLE_SLAVE : NG_HCI_ROLE_MASTER; + break; + + default: + return (USAGE); + } + + /* send request and expect status response */ + n0 = sizeof(b); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY, + NG_HCI_OCF_SWITCH_ROLE), + (char const *) &cp, sizeof(cp), b, &n0) == ERROR) + return (ERROR); + + if (*b != 0x00) + return (FAILED); + + /* wait for event */ +again: + n0 = sizeof(b); + if (hci_recv(s, b, &n0) == ERROR) + return (ERROR); + if (n0 < sizeof(*e)) { + errno = EIO; + return (ERROR); + } + + if (e->event == NG_HCI_EVENT_ROLE_CHANGE) { + ng_hci_role_change_ep *ep = (ng_hci_role_change_ep *)(e + 1); + + if (ep->status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(ep->status), ep->status); + return (FAILED); + } + + fprintf(stdout, "BD_ADDR: %s\n", hci_bdaddr2str(&ep->bdaddr)); + fprintf(stdout, "Role: %s [%#x]\n", + (ep->role == NG_HCI_ROLE_MASTER)? "Master" : "Slave", + ep->role); + } else + goto again; + + return (OK); +} /* hci_switch_role */ + +/* Send Read_Link_Policy_Settings command to the unit */ +static int +hci_read_link_policy_settings(int s, int argc, char **argv) +{ + ng_hci_read_link_policy_settings_cp cp; + ng_hci_read_link_policy_settings_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send request */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY, + NG_HCI_OCF_READ_LINK_POLICY_SETTINGS), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle)); + fprintf(stdout, "Link policy settings: %#x\n", le16toh(rp.settings)); + + return (OK); +} /* hci_read_link_policy_settings */ + +/* Send Write_Link_Policy_Settings command to the unit */ +static int +hci_write_link_policy_settings(int s, int argc, char **argv) +{ + ng_hci_write_link_policy_settings_cp cp; + ng_hci_write_link_policy_settings_rp rp; + int n; + + /* parse command parameters */ + switch (argc) { + case 2: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + + /* link policy settings */ + if (sscanf(argv[1], "%x", &n) != 1) + return (USAGE); + + cp.settings = (uint16_t) (n & 0x0ffff); + cp.settings = htole16(cp.settings); + break; + + default: + return (USAGE); + } + + /* send request */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY, + NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_write_link_policy_settings */ + +struct hci_command link_policy_commands[] = { +{ +"role_discovery <connection_handle>", +"\nThe Role_Discovery command is used for a Bluetooth device to determine\n" \ +"which role the device is performing for a particular Connection Handle.\n" \ +"The connection handle must be a connection handle for an ACL connection.\n\n" \ +"\t<connection_handle> - dddd; connection handle", +&hci_role_discovery +}, +{ +"switch_role <BD_ADDR> <role>", +"\nThe Switch_Role command is used for a Bluetooth device to switch the\n" \ +"current role the device is performing for a particular connection with\n" \ +"another specified Bluetooth device. The BD_ADDR command parameter indicates\n"\ +"for which connection the role switch is to be performed. The Role indicates\n"\ +"the requested new role that the local device performs. Note: the BD_ADDR\n" \ +"command parameter must specify a Bluetooth device for which a connection\n" +"already exists.\n\n" \ +"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name\n" \ +"\t<role> - dd; role; 0 - Master, 1 - Slave", +&hci_switch_role +}, +{ +"read_link_policy_settings <connection_handle>", +"\nThis command will read the Link Policy setting for the specified connection\n"\ +"handle. The link policy settings parameter determines the behavior of the\n" \ +"local Link Manager when it receives a request from a remote device or it\n" \ +"determines itself to change the master-slave role or to enter the hold,\n" \ +"sniff, or park mode. The local Link Manager will automatically accept or\n" \ +"reject such a request from the remote device, and may even autonomously\n" \ +"request itself, depending on the value of the link policy settings parameter\n"\ +"for the corresponding connection handle. The connection handle must be a\n" \ +"connection handle for an ACL connection.\n\n" \ +"\t<connection_handle> - dddd; connection handle", +&hci_read_link_policy_settings +}, +{ +"write_link_policy_settings <connection_handle> <settings>", +"\nThis command will write the Link Policy setting for the specified connection\n"\ +"handle. The link policy settings parameter determines the behavior of the\n" \ +"local Link Manager when it receives a request from a remote device or it\n" \ +"determines itself to change the master-slave role or to enter the hold,\n" \ +"sniff, or park mode. The local Link Manager will automatically accept or\n" \ +"reject such a request from the remote device, and may even autonomously\n" \ +"request itself, depending on the value of the link policy settings parameter\n"\ +"for the corresponding connection handle. The connection handle must be a\n" \ +"connection handle for an ACL connection. Multiple Link Manager policies may\n"\ +"be specified for the link policy settings parameter by performing a bitwise\n"\ +"OR operation of the different activity types.\n\n" \ +"\t<connection_handle> - dddd; connection handle\n" \ +"\t<settings> - xxxx; settings\n" \ +"\t\t0x0000 - Disable All LM Modes (Default)\n" \ +"\t\t0x0001 - Enable Master Slave Switch\n" \ +"\t\t0x0002 - Enable Hold Mode\n" \ +"\t\t0x0004 - Enable Sniff Mode\n" \ +"\t\t0x0008 - Enable Park Mode\n", +&hci_write_link_policy_settings +}, +{ +NULL, +}}; + diff --git a/usr.sbin/bluetooth/hccontrol/node.c b/usr.sbin/bluetooth/hccontrol/node.c new file mode 100644 index 000000000000..61b60ba95db5 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/node.c @@ -0,0 +1,620 @@ +/*- + * node.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: node.c,v 1.6 2003/07/22 21:14:02 max Exp $ + */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <netgraph/ng_message.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <uuid.h> +#include "hccontrol.h" + +/* Send Read_Node_State command to the node */ +static int +hci_read_node_state(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_state r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_STATE, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "State: %#x\n", r.state); + + return (OK); +} /* hci_read_node_state */ + +/* Send Intitialize command to the node */ +static int +hci_node_initialize(int s, int argc, char **argv) +{ + if (ioctl(s, SIOC_HCI_RAW_NODE_INIT) < 0) + return (ERROR); + + return (OK); +} /* hci_node_initialize */ + +/* Send Read_Debug_Level command to the node */ +static int +hci_read_debug_level(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_debug r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_DEBUG, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Debug level: %d\n", r.debug); + + return (OK); +} /* hci_read_debug_level */ + +/* Send Write_Debug_Level command to the node */ +static int +hci_write_debug_level(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_debug r; + + memset(&r, 0, sizeof(r)); + switch (argc) { + case 1: + r.debug = atoi(argv[0]); + break; + + default: + return (USAGE); + } + + if (ioctl(s, SIOC_HCI_RAW_NODE_SET_DEBUG, &r, sizeof(r)) < 0) + return (ERROR); + + return (OK); +} /* hci_write_debug_level */ + +/* Send Read_Node_Buffer_Size command to the node */ +static int +hci_read_node_buffer_size(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_buffer r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_BUFFER, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Number of free command buffers: %d\n", + r.buffer.cmd_free); + fprintf(stdout, "Max. ACL packet size: %d\n", + r.buffer.acl_size); + fprintf(stdout, "Numbef of free ACL buffers: %d\n", + r.buffer.acl_free); + fprintf(stdout, "Total number of ACL buffers: %d\n", + r.buffer.acl_pkts); + fprintf(stdout, "Max. SCO packet size: %d\n", + r.buffer.sco_size); + fprintf(stdout, "Numbef of free SCO buffers: %d\n", + r.buffer.sco_free); + fprintf(stdout, "Total number of SCO buffers: %d\n", + r.buffer.sco_pkts); + + return (OK); +} /* hci_read_node_buffer_size */ + +/* Send Read_Node_BD_ADDR command to the node */ +static int +hci_read_node_bd_addr(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_bdaddr r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_BDADDR, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "BD_ADDR: %s\n", bt_ntoa(&r.bdaddr, NULL)); + + return (OK); +} /* hci_read_node_bd_addr */ + +/* Send Read_Node_Features command to the node */ +static int +hci_read_node_features(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_features r; + int n; + char buffer[2048]; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_FEATURES, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Features: "); + for (n = 0; n < nitems(r.features); n++) + fprintf(stdout, "%#02x ", r.features[n]); + fprintf(stdout, "\n%s\n", hci_features2str(r.features, + buffer, sizeof(buffer))); + + return (OK); +} /* hci_read_node_features */ + +/* Send Read_Node_Stat command to the node */ +static int +hci_read_node_stat(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_stat r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_STAT, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Commands sent: %d\n", r.stat.cmd_sent); + fprintf(stdout, "Events received: %d\n", r.stat.evnt_recv); + fprintf(stdout, "ACL packets received: %d\n", r.stat.acl_recv); + fprintf(stdout, "ACL packets sent: %d\n", r.stat.acl_sent); + fprintf(stdout, "SCO packets received: %d\n", r.stat.sco_recv); + fprintf(stdout, "SCO packets sent: %d\n", r.stat.sco_sent); + fprintf(stdout, "Bytes received: %d\n", r.stat.bytes_recv); + fprintf(stdout, "Bytes sent: %d\n", r.stat.bytes_sent); + + return (OK); +} /* hci_read_node_stat */ + +/* Send Reset_Node_Stat command to the node */ +static int +hci_reset_node_stat(int s, int argc, char **argv) +{ + if (ioctl(s, SIOC_HCI_RAW_NODE_RESET_STAT) < 0) + return (ERROR); + + return (OK); +} /* hci_reset_node_stat */ + +/* Send Flush_Neighbor_Cache command to the node */ +static int +hci_flush_neighbor_cache(int s, int argc, char **argv) +{ + if (ioctl(s, SIOC_HCI_RAW_NODE_FLUSH_NEIGHBOR_CACHE) < 0) + return (ERROR); + + return (OK); +} /* hci_flush_neighbor_cache */ + +/* Send Read_Neighbor_Cache command to the node */ +static int +hci_read_neighbor_cache(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_neighbor_cache r; + int n, error = OK; + const char *addrtype2str[] = {"B", "P", "R", "E"}; + + memset(&r, 0, sizeof(r)); + r.num_entries = NG_HCI_MAX_NEIGHBOR_NUM; + r.entries = calloc(NG_HCI_MAX_NEIGHBOR_NUM, + sizeof(ng_hci_node_neighbor_cache_entry_ep)); + if (r.entries == NULL) { + errno = ENOMEM; + return (ERROR); + } + + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_NEIGHBOR_CACHE, &r, + sizeof(r)) < 0) { + error = ERROR; + goto out; + } + + fprintf(stdout, +"T " \ +"BD_ADDR " \ +"Features " \ +"Clock offset " \ +"Page scan " \ +"Rep. scan\n"); + + for (n = 0; n < r.num_entries; n++) { + uint8_t addrtype = r.entries[n].addrtype; + if(addrtype >= nitems(addrtype2str)) + addrtype = nitems(addrtype2str) - 1; + fprintf(stdout, +"%1s %-17.17s " \ +"%02x %02x %02x %02x %02x %02x %02x %02x " \ +"%#12x " \ +"%#9x " \ +"%#9x\n", + addrtype2str[addrtype], + hci_bdaddr2str(&r.entries[n].bdaddr), + r.entries[n].features[0], r.entries[n].features[1], + r.entries[n].features[2], r.entries[n].features[3], + r.entries[n].features[4], r.entries[n].features[5], + r.entries[n].features[6], r.entries[n].features[7], + r.entries[n].clock_offset, r.entries[n].page_scan_mode, + r.entries[n].page_scan_rep_mode); + print_adv_data(r.entries[n].extinq_size, + r.entries[n].extinq_data); + fprintf(stdout,"\n"); + } +out: + free(r.entries); + + return (error); +} /* hci_read_neightbor_cache */ + +/* Send Read_Connection_List command to the node */ +static int +hci_read_connection_list(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_con_list r; + int n, error = OK; + + memset(&r, 0, sizeof(r)); + r.num_connections = NG_HCI_MAX_CON_NUM; + r.connections = calloc(NG_HCI_MAX_CON_NUM, sizeof(ng_hci_node_con_ep)); + if (r.connections == NULL) { + errno = ENOMEM; + return (ERROR); + } + + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_CON_LIST, &r, sizeof(r)) < 0) { + error = ERROR; + goto out; + } + + fprintf(stdout, +"Remote BD_ADDR " \ +"Handle " \ +"Type " \ +"Mode " \ +"Role " \ +"Encrypt " \ +"Pending " \ +"Queue " \ +"State\n"); + + for (n = 0; n < r.num_connections; n++) { + fprintf(stdout, +"%-17.17s " \ +"%6d " \ +"%4.4s " \ +"%4d " \ +"%4.4s " \ +"%7.7s " \ +"%7d " \ +"%5d " \ +"%s\n", + hci_bdaddr2str(&r.connections[n].bdaddr), + r.connections[n].con_handle, + (r.connections[n].link_type == NG_HCI_LINK_ACL)? + "ACL" : "SCO", + r.connections[n].mode, + (r.connections[n].role == NG_HCI_ROLE_MASTER)? + "MAST" : "SLAV", + hci_encrypt2str(r.connections[n].encryption_mode, 1), + r.connections[n].pending, + r.connections[n].queue_len, + hci_con_state2str(r.connections[n].state)); + } +out: + free(r.connections); + + return (error); +} /* hci_read_connection_list */ + +/* Send Read_Node_Link_Policy_Settings_Mask command to the node */ +int +hci_read_node_link_policy_settings_mask(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_link_policy_mask r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_LINK_POLICY_MASK, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Link Policy Settings mask: %#04x\n", r.policy_mask); + + return (OK); +} /* hci_read_node_link_policy_settings_mask */ + +/* Send Write_Node_Link_Policy_Settings_Mask command to the node */ +int +hci_write_node_link_policy_settings_mask(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_link_policy_mask r; + int m; + + memset(&r, 0, sizeof(r)); + + switch (argc) { + case 1: + if (sscanf(argv[0], "%x", &m) != 1) + return (USAGE); + + r.policy_mask = (m & 0xffff); + break; + + default: + return (USAGE); + } + + if (ioctl(s, SIOC_HCI_RAW_NODE_SET_LINK_POLICY_MASK, &r, sizeof(r)) < 0) + return (ERROR); + + return (OK); +} /* hci_write_node_link_policy_settings_mask */ + +/* Send Read_Node_Packet_Mask command to the node */ +int +hci_read_node_packet_mask(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_packet_mask r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_PACKET_MASK, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Packet mask: %#04x\n", r.packet_mask); + + return (OK); +} /* hci_read_node_packet_mask */ + +/* Send Write_Node_Packet_Mask command to the node */ +int +hci_write_node_packet_mask(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_packet_mask r; + int m; + + memset(&r, 0, sizeof(r)); + + switch (argc) { + case 1: + if (sscanf(argv[0], "%x", &m) != 1) + return (USAGE); + + r.packet_mask = (m & 0xffff); + break; + + default: + return (USAGE); + } + + if (ioctl(s, SIOC_HCI_RAW_NODE_SET_PACKET_MASK, &r, sizeof(r)) < 0) + return (ERROR); + + return (OK); +} /* hci_write_node_packet_mask */ + +/* Send Read_Node_Role_Switch command to the node */ +int +hci_read_node_role_switch(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_role_switch r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_HCI_RAW_NODE_GET_ROLE_SWITCH, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Role switch: %d\n", r.role_switch); + + return (OK); +} /* hci_read_node_role_switch */ + +/* Send Write_Node_Role_Switch command to the node */ +int +hci_write_node_role_switch(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_role_switch r; + int m; + + memset(&r, 0, sizeof(r)); + + switch (argc) { + case 1: + if (sscanf(argv[0], "%d", &m) != 1) + return (USAGE); + + r.role_switch = m? 1 : 0; + break; + + default: + return (USAGE); + } + + if (ioctl(s, SIOC_HCI_RAW_NODE_SET_ROLE_SWITCH, &r, sizeof(r)) < 0) + return (ERROR); + + return (OK); +} /* hci_write_node_role_switch */ + +/* Send Read_Node_List command to the node */ +int +hci_read_node_list(int s, int argc, char **argv) +{ + struct ng_btsocket_hci_raw_node_list_names r; + int i; + + r.num_names = MAX_NODE_NUM; + r.names = (struct nodeinfo*)calloc(MAX_NODE_NUM, sizeof(struct nodeinfo)); + if (r.names == NULL) + return (ERROR); + + if (ioctl(s, SIOC_HCI_RAW_NODE_LIST_NAMES, &r, sizeof(r)) < 0) { + free(r.names); + return (ERROR); + } + + fprintf(stdout, "Name ID Num hooks\n"); + for (i = 0; i < r.num_names; ++i) + fprintf(stdout, "%-15s %08x %9d\n", + r.names[i].name, r.names[i].id, r.names[i].hooks); + + free(r.names); + + return (OK); +} /* hci_read_node_list */ + +struct hci_command node_commands[] = { +{ +"read_node_state", +"Get the HCI node state", +&hci_read_node_state +}, +{ +"initialize", +"Initialize the HCI node", +&hci_node_initialize +}, +{ +"read_debug_level", +"Read the HCI node debug level", +&hci_read_debug_level +}, +{ +"write_debug_level <level>", +"Write the HCI node debug level", +&hci_write_debug_level +}, +{ +"read_node_buffer_size", +"Read the HCI node buffer information. This will return current state of the\n"\ +"HCI buffer for the HCI node", +&hci_read_node_buffer_size +}, +{ +"read_node_bd_addr", +"Read the HCI node BD_ADDR. Returns device BD_ADDR as cached by the HCI node", +&hci_read_node_bd_addr +}, +{ +"read_node_features", +"Read the HCI node features. This will return list of supported features as\n" \ +"cached by the HCI node", +&hci_read_node_features +}, +{ +"read_node_stat", +"Read packets and bytes counters for the HCI node", +&hci_read_node_stat +}, +{ +"reset_node_stat", +"Reset packets and bytes counters for the HCI node", +&hci_reset_node_stat +}, +{ +"flush_neighbor_cache", +"Flush content of the HCI node neighbor cache", +&hci_flush_neighbor_cache +}, +{ +"read_neighbor_cache", +"Read content of the HCI node neighbor cache", +&hci_read_neighbor_cache +}, +{ +"read_connection_list", +"Read the baseband connection descriptors list for the HCI node", +&hci_read_connection_list +}, +{ +"read_node_link_policy_settings_mask", +"Read the value of the Link Policy Settinngs mask for the HCI node", +&hci_read_node_link_policy_settings_mask +}, +{ +"write_node_link_policy_settings_mask <policy_mask>", +"Write the value of the Link Policy Settings mask for the HCI node. By default\n" \ +"all supported Link Policy modes (as reported by the local device features) are\n"\ +"enabled. The particular Link Policy mode is enabled if local device supports\n"\ +"it and correspinding bit in the mask was set\n\n" \ +"\t<policy_mask> - xxxx; Link Policy mask\n" \ +"\t\t0x0000 - Disable All LM Modes\n" \ +"\t\t0x0001 - Enable Master Slave Switch\n" \ +"\t\t0x0002 - Enable Hold Mode\n" \ +"\t\t0x0004 - Enable Sniff Mode\n" \ +"\t\t0x0008 - Enable Park Mode\n", +&hci_write_node_link_policy_settings_mask +}, +{ +"read_node_packet_mask", +"Read the value of the Packet mask for the HCI node", +&hci_read_node_packet_mask +}, +{ +"write_node_packet_mask <packet_mask>", +"Write the value of the Packet mask for the HCI node. By default all supported\n" \ +"packet types (as reported by the local device features) are enabled. The\n" \ +"particular packet type is enabled if local device supports it and corresponding\n" \ +"bit in the mask was set\n\n" \ +"\t<packet_mask> - xxxx; packet type mask\n" \ +"" \ +"\t\tACL packets\n" \ +"\t\t-----------\n" \ +"\t\t0x0008 DM1\n" \ +"\t\t0x0010 DH1\n" \ +"\t\t0x0400 DM3\n" \ +"\t\t0x0800 DH3\n" \ +"\t\t0x4000 DM5\n" \ +"\t\t0x8000 DH5\n" \ +"\n" \ +"\t\tSCO packets\n" \ +"\t\t-----------\n" \ +"\t\t0x0020 HV1\n" \ +"\t\t0x0040 HV2\n" \ +"\t\t0x0080 HV3\n", +&hci_write_node_packet_mask +}, +{ +"read_node_role_switch", +"Read the value of the Role Switch parameter for the HCI node", +&hci_read_node_role_switch +}, +{ +"write_node_role_switch {0|1}", +"Write the value of the Role Switch parameter for the HCI node. By default,\n" \ +"if Role Switch is supported, local device will try to perform Role Switch\n" \ +"and become Master on incoming connection. Some devices do not support Role\n" \ +"Switch and thus incoming connections from such devices will fail. Setting\n" \ +"this parameter to zero will prevent Role Switch and thus accepting device\n" \ +"will remain Slave", +&hci_write_node_role_switch +}, +{ +"read_node_list", +"Get a list of HCI nodes, their Netgraph IDs and connected hooks.", +&hci_read_node_list +}, +{ +NULL, +}}; + diff --git a/usr.sbin/bluetooth/hccontrol/send_recv.c b/usr.sbin/bluetooth/hccontrol/send_recv.c new file mode 100644 index 000000000000..4f9bb05aec48 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/send_recv.c @@ -0,0 +1,185 @@ +/*- + * send_recv.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: send_recv.c,v 1.2 2003/05/21 22:40:30 max Exp $ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/endian.h> +#include <assert.h> +#include <errno.h> +#include <netgraph/bluetooth/include/ng_hci.h> +#include <string.h> +#include <unistd.h> +#include "hccontrol.h" + +/* Send HCI request to the unit */ +int +hci_request(int s, int opcode, char const *cp, int cp_size, char *rp, int *rp_size) +{ + char buffer[512]; + int n; + ng_hci_cmd_pkt_t *c = (ng_hci_cmd_pkt_t *) buffer; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buffer; + + assert(rp != NULL); + assert(rp_size != NULL); + assert(*rp_size > 0); + + c->type = NG_HCI_CMD_PKT; + c->opcode = (uint16_t) opcode; + c->opcode = htole16(c->opcode); + + if (cp != NULL) { + assert(0 < cp_size && cp_size <= NG_HCI_CMD_PKT_SIZE); + + c->length = (uint8_t) cp_size; + memcpy(buffer + sizeof(*c), cp, cp_size); + } else + c->length = 0; + + if (hci_send(s, buffer, sizeof(*c) + cp_size) == ERROR) + return (ERROR); + +again: + n = sizeof(buffer); + if (hci_recv(s, buffer, &n) == ERROR) + return (ERROR); + + if (n < sizeof(*e)) { + errno = EMSGSIZE; + return (ERROR); + } + + if (e->type != NG_HCI_EVENT_PKT) { + errno = EIO; + return (ERROR); + } + + switch (e->event) { + case NG_HCI_EVENT_COMMAND_COMPL: { + ng_hci_command_compl_ep *cc = + (ng_hci_command_compl_ep *)(e + 1); + + cc->opcode = le16toh(cc->opcode); + + if (cc->opcode == 0x0000 || cc->opcode != opcode) + goto again; + + n -= (sizeof(*e) + sizeof(*cc)); + if (n < *rp_size) + *rp_size = n; + + memcpy(rp, buffer + sizeof(*e) + sizeof(*cc), *rp_size); + } break; + + case NG_HCI_EVENT_COMMAND_STATUS: { + ng_hci_command_status_ep *cs = + (ng_hci_command_status_ep *)(e + 1); + + cs->opcode = le16toh(cs->opcode); + + if (cs->opcode == 0x0000 || cs->opcode != opcode) + goto again; + + *rp_size = 1; + *rp = cs->status; + } break; + + default: + goto again; + } + + return (OK); +} /* hci_request */ + +/* Send simple HCI request - Just HCI command packet (no parameters) */ +int +hci_simple_request(int s, int opcode, char *rp, int *rp_size) +{ + return (hci_request(s, opcode, NULL, 0, rp, rp_size)); +} /* hci_simple_request */ + +/* Send HCI data to the unit */ +int +hci_send(int s, char const *buffer, int size) +{ + assert(buffer != NULL); + assert(size >= sizeof(ng_hci_cmd_pkt_t)); + assert(size <= sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE); + + if (send(s, buffer, size, 0) < 0) + return (ERROR); + + return (OK); +} /* hci_send */ + +/* Receive HCI data from the unit */ +int +hci_recv(int s, char *buffer, int *size) +{ + struct timeval tv; + fd_set rfd; + int n; + + assert(buffer != NULL); + assert(size != NULL); + assert(*size > sizeof(ng_hci_event_pkt_t)); + +again: + FD_ZERO(&rfd); + FD_SET(s, &rfd); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + n = select(s + 1, &rfd, NULL, NULL, &tv); + if (n <= 0) { + if (n < 0) { + if (errno == EINTR) + goto again; + } else + errno = ETIMEDOUT; + + return (ERROR); + } + + assert(FD_ISSET(s, &rfd)); + + n = recv(s, buffer, *size, 0); + if (n < 0) + return (ERROR); + + *size = n; + + return (OK); +} /* hci_recv */ + diff --git a/usr.sbin/bluetooth/hccontrol/status.c b/usr.sbin/bluetooth/hccontrol/status.c new file mode 100644 index 000000000000..9306c10fec85 --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/status.c @@ -0,0 +1,246 @@ +/*- + * status.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: status.c,v 1.2 2003/05/21 22:40:30 max Exp $ + */ + +#include <sys/types.h> +#include <sys/endian.h> +#include <errno.h> +#include <netgraph/bluetooth/include/ng_hci.h> +#include <stdio.h> +#include "hccontrol.h" + +/* Send Read_Failed_Contact_Counter command to the unit */ +static int +hci_read_failed_contact_counter(int s, int argc, char **argv) +{ + ng_hci_read_failed_contact_cntr_cp cp; + ng_hci_read_failed_contact_cntr_rp rp; + int n; + + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_STATUS, + NG_HCI_OCF_READ_FAILED_CONTACT_CNTR), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle)); + fprintf(stdout, "Failed contact counter: %d\n", le16toh(rp.counter)); + + return (OK); +} /* hci_read_failed_contact_counter */ + +/* Send Reset_Failed_Contact_Counter command to the unit */ +static int +hci_reset_failed_contact_counter(int s, int argc, char **argv) +{ + ng_hci_reset_failed_contact_cntr_cp cp; + ng_hci_reset_failed_contact_cntr_rp rp; + int n; + + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_STATUS, + NG_HCI_OCF_RESET_FAILED_CONTACT_CNTR), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + return (OK); +} /* hci_reset_failed_contact_counter */ + +/* Sent Get_Link_Quality command to the unit */ +static int +hci_get_link_quality(int s, int argc, char **argv) +{ + ng_hci_get_link_quality_cp cp; + ng_hci_get_link_quality_rp rp; + int n; + + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_STATUS, + NG_HCI_OCF_GET_LINK_QUALITY), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle)); + fprintf(stdout, "Link quality: %d\n", le16toh(rp.quality)); + + return (OK); +} /* hci_get_link_quality */ + +/* Send Read_RSSI command to the unit */ +static int +hci_read_rssi(int s, int argc, char **argv) +{ + ng_hci_read_rssi_cp cp; + ng_hci_read_rssi_rp rp; + int n; + + switch (argc) { + case 1: + /* connection handle */ + if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff) + return (USAGE); + + cp.con_handle = (uint16_t) (n & 0x0fff); + cp.con_handle = htole16(cp.con_handle); + break; + + default: + return (USAGE); + } + + /* send command */ + n = sizeof(rp); + if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_STATUS, + NG_HCI_OCF_READ_RSSI), + (char const *) &cp, sizeof(cp), + (char *) &rp, &n) == ERROR) + return (ERROR); + + if (rp.status != 0x00) { + fprintf(stdout, "Status: %s [%#02x]\n", + hci_status2str(rp.status), rp.status); + return (FAILED); + } + + fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle)); + fprintf(stdout, "RSSI: %d dB\n", (int) rp.rssi); + + return (OK); +} /* hci_read_rssi */ + +struct hci_command status_commands[] = { +{ +"read_failed_contact_counter <connection_handle>", +"\nThis command will read the value for the Failed_Contact_Counter\n" \ +"parameter for a particular ACL connection to another device.\n\n" \ +"\t<connection_handle> - dddd; ACL connection handle\n", +&hci_read_failed_contact_counter +}, +{ +"reset_failed_contact_counter <connection_handle>", +"\nThis command will reset the value for the Failed_Contact_Counter\n" \ +"parameter for a particular ACL connection to another device.\n\n" \ +"\t<connection_handle> - dddd; ACL connection handle\n", +&hci_reset_failed_contact_counter +}, +{ +"get_link_quality <connection_handle>", +"\nThis command will return the value for the Link_Quality for the\n" \ +"specified ACL connection handle. This command will return a Link_Quality\n" \ +"value from 0-255, which represents the quality of the link between two\n" \ +"Bluetooth devices. The higher the value, the better the link quality is.\n" \ +"Each Bluetooth module vendor will determine how to measure the link quality." \ +"\n\n" \ +"\t<connection_handle> - dddd; ACL connection handle\n", +&hci_get_link_quality +}, +{ +"read_rssi <connection_handle>", +"\nThis command will read the value for the difference between the\n" \ +"measured Received Signal Strength Indication (RSSI) and the limits of\n" \ +"the Golden Receive Power Range for a ACL connection handle to another\n" \ +"Bluetooth device. Any positive RSSI value returned by the Host Controller\n" \ +"indicates how many dB the RSSI is above the upper limit, any negative\n" \ +"value indicates how many dB the RSSI is below the lower limit. The value\n" \ +"zero indicates that the RSSI is inside the Golden Receive Power Range.\n\n" \ +"\t<connection_handle> - dddd; ACL connection handle\n", +&hci_read_rssi +}, +{ +NULL, +}}; + diff --git a/usr.sbin/bluetooth/hccontrol/util.c b/usr.sbin/bluetooth/hccontrol/util.c new file mode 100644 index 000000000000..029ced6e28fa --- /dev/null +++ b/usr.sbin/bluetooth/hccontrol/util.c @@ -0,0 +1,3366 @@ +/*- + * util.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001 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. + * + * $Id: util.c,v 1.2 2003/05/19 17:29:29 max Exp $ + */ + +#include <sys/param.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <stdio.h> +#include <string.h> + +#define SIZE(x) (sizeof((x))/sizeof((x)[0])) + +char const * +hci_link2str(int link_type) +{ + static char const * const t[] = { + /* NG_HCI_LINK_SCO */ "SCO", + /* NG_HCI_LINK_ACL */ "ACL" + }; + + return (link_type >= SIZE(t)? "?" : t[link_type]); +} /* hci_link2str */ + +char const * +hci_pin2str(int type) +{ + static char const * const t[] = { + /* 0x00 */ "Variable PIN", + /* 0x01 */ "Fixed PIN" + }; + + return (type >= SIZE(t)? "?" : t[type]); +} /* hci_pin2str */ + +char const * +hci_scan2str(int scan) +{ + static char const * const t[] = { + /* 0x00 */ "No Scan enabled", + /* 0x01 */ "Inquiry Scan enabled. Page Scan disabled", + /* 0x02 */ "Inquiry Scan disabled. Page Scan enabled", + /* 0x03 */ "Inquiry Scan enabled. Page Scan enabled" + }; + + return (scan >= SIZE(t)? "?" : t[scan]); +} /* hci_scan2str */ + +char const * +hci_encrypt2str(int encrypt, int brief) +{ + static char const * const t[] = { + /* 0x00 */ "Disabled", + /* 0x01 */ "Only for point-to-point packets", + /* 0x02 */ "Both point-to-point and broadcast packets" + }; + + static char const * const t1[] = { + /* NG_HCI_ENCRYPTION_MODE_NONE */ "NONE", + /* NG_HCI_ENCRYPTION_MODE_P2P */ "P2P", + /* NG_HCI_ENCRYPTION_MODE_ALL */ "ALL", + }; + + if (brief) + return (encrypt >= SIZE(t1)? "?" : t1[encrypt]); + + return (encrypt >= SIZE(t)? "?" : t[encrypt]); +} /* hci_encrypt2str */ + +char const * +hci_coding2str(int coding) +{ + static char const * const t[] = { + /* 0x00 */ "Linear", + /* 0x01 */ "u-law", + /* 0x02 */ "A-law", + /* 0x03 */ "Reserved" + }; + + return (coding >= SIZE(t)? "?" : t[coding]); +} /* hci_coding2str */ + +char const * +hci_vdata2str(int data) +{ + static char const * const t[] = { + /* 0x00 */ "1's complement", + /* 0x01 */ "2's complement", + /* 0x02 */ "Sign-Magnitude", + /* 0x03 */ "Reserved" + }; + + return (data >= SIZE(t)? "?" : t[data]); +} /* hci_vdata2str */ + +char const * +hci_hmode2str(int mode, char *buffer, int size) +{ + static char const * const t[] = { + /* 0x01 */ "Suspend Page Scan ", + /* 0x02 */ "Suspend Inquiry Scan ", + /* 0x04 */ "Suspend Periodic Inquiries " + }; + + if (buffer != NULL && size > 0) { + int n; + + memset(buffer, 0, size); + size--; + for (n = 0; n < SIZE(t); n++) { + int len = strlen(buffer); + + if (len >= size) + break; + if (mode & (1 << n)) + strncat(buffer, t[n], size - len); + } + } + + return (buffer); +} /* hci_hmode2str */ + +char const * +hci_ver2str(int ver) +{ + static char const * const t[] = { + /* 0x00 */ "Bluetooth HCI Specification 1.0B", + /* 0x01 */ "Bluetooth HCI Specification 1.1", + /* 0x02 */ "Bluetooth HCI Specification 1.2", + /* 0x03 */ "Bluetooth HCI Specification 2.0", + /* 0x04 */ "Bluetooth HCI Specification 2.1", + /* 0x05 */ "Bluetooth HCI Specification 3.0", + /* 0x06 */ "Bluetooth HCI Specification 4.0", + /* 0x07 */ "Bluetooth HCI Specification 4.1", + /* 0x08 */ "Bluetooth HCI Specification 4.2", + /* 0x09 */ "Bluetooth HCI Specification 5.0", + /* 0x0a */ "Bluetooth HCI Specification 5.1", + /* 0x0b */ "Bluetooth HCI Specification 5.2" + }; + + return (ver >= SIZE(t)? "?" : t[ver]); +} /* hci_ver2str */ + +char const * +hci_lmpver2str(int ver) +{ + static char const * const t[] = { + /* 0x00 */ "Bluetooth LMP 1.0", + /* 0x01 */ "Bluetooth LMP 1.1", + /* 0x02 */ "Bluetooth LMP 1.2", + /* 0x03 */ "Bluetooth LMP 2.0", + /* 0x04 */ "Bluetooth LMP 2.1", + /* 0x05 */ "Bluetooth LMP 3.0", + /* 0x06 */ "Bluetooth LMP 4.0", + /* 0x07 */ "Bluetooth LMP 4.1", + /* 0x08 */ "Bluetooth LMP 4.2", + /* 0x09 */ "Bluetooth LMP 5.0", + /* 0x0a */ "Bluetooth LMP 5.1", + /* 0x0b */ "Bluetooth LMP 5.2" + }; + + return (ver >= SIZE(t)? "?" : t[ver]); +} /* hci_lmpver2str */ + +char const * +hci_manufacturer2str(int m) +{ + static char const * const t[] = { + /* 0000 */ "Ericsson Technology Licensing", + /* 0001 */ "Nokia Mobile Phones", + /* 0002 */ "Intel Corp.", + /* 0003 */ "IBM Corp.", + /* 0004 */ "Toshiba Corp.", + /* 0005 */ "3Com", + /* 0006 */ "Microsoft", + /* 0007 */ "Lucent", + /* 0008 */ "Motorola", + /* 0009 */ "Infineon Technologies AG", + /* 0010 */ "Qualcomm Technologies International, Ltd. (QTIL)", + /* 0011 */ "Silicon Wave", + /* 0012 */ "Digianswer A/S", + /* 0013 */ "Texas Instruments Inc.", + /* 0014 */ "Parthus Technologies Inc.", + /* 0015 */ "Broadcom Corporation", + /* 0016 */ "Mitel Semiconductor", + /* 0017 */ "Widcomm, Inc.", + /* 0018 */ "Zeevo, Inc.", + /* 0019 */ "Atmel Corporation", + /* 0020 */ "Mitsubishi Electric Corporation", + /* 0021 */ "RTX Telecom A/S", + /* 0022 */ "KC Technology Inc.", + /* 0023 */ "Newlogic", + /* 0024 */ "Transilica, Inc.", + /* 0025 */ "Rohde & Schwarz GmbH & Co. KG", + /* 0026 */ "TTPCom Limited", + /* 0027 */ "Signia Technologies, Inc.", + /* 0028 */ "Conexant Systems Inc.", + /* 0029 */ "Qualcomm", + /* 0030 */ "Inventel", + /* 0031 */ "AVM Berlin", + /* 0032 */ "BandSpeed, Inc.", + /* 0033 */ "Mansella Ltd", + /* 0034 */ "NEC Corporation", + /* 0035 */ "WavePlus Technology Co., Ltd.", + /* 0036 */ "Alcatel", + /* 0037 */ "NXP Semiconductors (formerly Philips Semiconductors)", + /* 0038 */ "C Technologies", + /* 0039 */ "Open Interface", + /* 0040 */ "R F Micro Devices", + /* 0041 */ "Hitachi Ltd", + /* 0042 */ "Symbol Technologies, Inc.", + /* 0043 */ "Tenovis", + /* 0044 */ "Macronix International Co. Ltd.", + /* 0045 */ "GCT Semiconductor", + /* 0046 */ "Norwood Systems", + /* 0047 */ "MewTel Technology Inc.", + /* 0048 */ "ST Microelectronics", + /* 0049 */ "Synopsys, Inc.", + /* 0050 */ "Red-M (Communications) Ltd", + /* 0051 */ "Commil Ltd", + /* 0052 */ "Computer Access Technology Corporation (CATC)", + /* 0053 */ "Eclipse (HQ Espana) S.L.", + /* 0054 */ "Renesas Electronics Corporation", + /* 0055 */ "Mobilian Corporation", + /* 0056 */ "Syntronix Corporation", + /* 0057 */ "Integrated System Solution Corp.", + /* 0058 */ "Panasonic Corporation (formerly Matsushita Electric Industrial Co., Ltd.)", + /* 0059 */ "Gennum Corporation", + /* 0060 */ "BlackBerry Limited (formerly Research In Motion)", + /* 0061 */ "IPextreme, Inc.", + /* 0062 */ "Systems and Chips, Inc", + /* 0063 */ "Bluetooth SIG, Inc", + /* 0064 */ "Seiko Epson Corporation", + /* 0065 */ "Integrated Silicon Solution Taiwan, Inc.", + /* 0066 */ "CONWISE Technology Corporation Ltd", + /* 0067 */ "PARROT AUTOMOTIVE SAS", + /* 0068 */ "Socket Mobile", + /* 0069 */ "Atheros Communications, Inc.", + /* 0070 */ "MediaTek, Inc.", + /* 0071 */ "Bluegiga", + /* 0072 */ "Marvell Technology Group Ltd.", + /* 0073 */ "3DSP Corporation", + /* 0074 */ "Accel Semiconductor Ltd.", + /* 0075 */ "Continental Automotive Systems", + /* 0076 */ "Apple, Inc.", + /* 0077 */ "Staccato Communications, Inc.", + /* 0078 */ "Avago Technologies", + /* 0079 */ "APT Ltd.", + /* 0080 */ "SiRF Technology, Inc.", + /* 0081 */ "Tzero Technologies, Inc.", + /* 0082 */ "J&M Corporation", + /* 0083 */ "Free2move AB", + /* 0084 */ "3DiJoy Corporation", + /* 0085 */ "Plantronics, Inc.", + /* 0086 */ "Sony Ericsson Mobile Communications", + /* 0087 */ "Harman International Industries, Inc.", + /* 0088 */ "Vizio, Inc.", + /* 0089 */ "Nordic Semiconductor ASA", + /* 0090 */ "EM Microelectronic-Marin SA", + /* 0091 */ "Ralink Technology Corporation", + /* 0092 */ "Belkin International, Inc.", + /* 0093 */ "Realtek Semiconductor Corporation", + /* 0094 */ "Stonestreet One, LLC", + /* 0095 */ "Wicentric, Inc.", + /* 0096 */ "RivieraWaves S.A.S", + /* 0097 */ "RDA Microelectronics", + /* 0098 */ "Gibson Guitars", + /* 0099 */ "MiCommand Inc.", + /* 0100 */ "Band XI International, LLC", + /* 0101 */ "Hewlett-Packard Company", + /* 0102 */ "9Solutions Oy", + /* 0103 */ "GN Netcom A/S", + /* 0104 */ "General Motors", + /* 0105 */ "A&D Engineering, Inc.", + /* 0106 */ "MindTree Ltd.", + /* 0107 */ "Polar Electro OY", + /* 0108 */ "Beautiful Enterprise Co., Ltd.", + /* 0109 */ "BriarTek, Inc", + /* 0110 */ "Summit Data Communications, Inc.", + /* 0111 */ "Sound ID", + /* 0112 */ "Monster, LLC", + /* 0113 */ "connectBlue AB", + /* 0114 */ "ShangHai Super Smart Electronics Co. Ltd.", + /* 0115 */ "Group Sense Ltd.", + /* 0116 */ "Zomm, LLC", + /* 0117 */ "Samsung Electronics Co. Ltd.", + /* 0118 */ "Creative Technology Ltd.", + /* 0119 */ "Laird Technologies", + /* 0120 */ "Nike, Inc.", + /* 0121 */ "lesswire AG", + /* 0122 */ "MStar Semiconductor, Inc.", + /* 0123 */ "Hanlynn Technologies", + /* 0124 */ "A & R Cambridge", + /* 0125 */ "Seers Technology Co., Ltd.", + /* 0126 */ "Sports Tracking Technologies Ltd.", + /* 0127 */ "Autonet Mobile", + /* 0128 */ "DeLorme Publishing Company, Inc.", + /* 0129 */ "WuXi Vimicro", + /* 0130 */ "Sennheiser Communications A/S", + /* 0131 */ "TimeKeeping Systems, Inc.", + /* 0132 */ "Ludus Helsinki Ltd.", + /* 0133 */ "BlueRadios, Inc.", + /* 0134 */ "Equinux AG", + /* 0135 */ "Garmin International, Inc.", + /* 0136 */ "Ecotest", + /* 0137 */ "GN ReSound A/S", + /* 0138 */ "Jawbone", + /* 0139 */ "Topcon Positioning Systems, LLC", + /* 0140 */ "Gimbal Inc. (formerly Qualcomm Labs, Inc. and Qualcomm Retail Solutions, Inc.)", + /* 0141 */ "Zscan Software", + /* 0142 */ "Quintic Corp", + /* 0143 */ "Telit Wireless Solutions GmbH (formerly Stollmann E+V GmbH)", + /* 0144 */ "Funai Electric Co., Ltd.", + /* 0145 */ "Advanced PANMOBIL systems GmbH & Co. KG", + /* 0146 */ "ThinkOptics, Inc.", + /* 0147 */ "Universal Electronics, Inc.", + /* 0148 */ "Airoha Technology Corp.", + /* 0149 */ "NEC Lighting, Ltd.", + /* 0150 */ "ODM Technology, Inc.", + /* 0151 */ "ConnecteDevice Ltd.", + /* 0152 */ "zero1.tv GmbH", + /* 0153 */ "i.Tech Dynamic Global Distribution Ltd.", + /* 0154 */ "Alpwise", + /* 0155 */ "Jiangsu Toppower Automotive Electronics Co., Ltd.", + /* 0156 */ "Colorfy, Inc.", + /* 0157 */ "Geoforce Inc.", + /* 0158 */ "Bose Corporation", + /* 0159 */ "Suunto Oy", + /* 0160 */ "Kensington Computer Products Group", + /* 0161 */ "SR-Medizinelektronik", + /* 0162 */ "Vertu Corporation Limited", + /* 0163 */ "Meta Watch Ltd.", + /* 0164 */ "LINAK A/S", + /* 0165 */ "OTL Dynamics LLC", + /* 0166 */ "Panda Ocean Inc.", + /* 0167 */ "Visteon Corporation", + /* 0168 */ "ARP Devices Limited", + /* 0169 */ "MARELLI EUROPE S.P.A. (formerly Magneti Marelli S.p.A.)", + /* 0170 */ "CAEN RFID srl", + /* 0171 */ "Ingenieur-Systemgruppe Zahn GmbH", + /* 0172 */ "Green Throttle Games", + /* 0173 */ "Peter Systemtechnik GmbH", + /* 0174 */ "Omegawave Oy", + /* 0175 */ "Cinetix", + /* 0176 */ "Passif Semiconductor Corp", + /* 0177 */ "Saris Cycling Group, Inc", + /* 0178 */ "Bekey A/S", + /* 0179 */ "Clarinox Technologies Pty. Ltd.", + /* 0180 */ "BDE Technology Co., Ltd.", + /* 0181 */ "Swirl Networks", + /* 0182 */ "Meso international", + /* 0183 */ "TreLab Ltd", + /* 0184 */ "Qualcomm Innovation Center, Inc. (QuIC)", + /* 0185 */ "Johnson Controls, Inc.", + /* 0186 */ "Starkey Laboratories Inc.", + /* 0187 */ "S-Power Electronics Limited", + /* 0188 */ "Ace Sensor Inc", + /* 0189 */ "Aplix Corporation", + /* 0190 */ "AAMP of America", + /* 0191 */ "Stalmart Technology Limited", + /* 0192 */ "AMICCOM Electronics Corporation", + /* 0193 */ "Shenzhen Excelsecu Data Technology Co.,Ltd", + /* 0194 */ "Geneq Inc.", + /* 0195 */ "adidas AG", + /* 0196 */ "LG Electronics", + /* 0197 */ "Onset Computer Corporation", + /* 0198 */ "Selfly BV", + /* 0199 */ "Quuppa Oy.", + /* 0200 */ "GeLo Inc", + /* 0201 */ "Evluma", + /* 0202 */ "MC10", + /* 0203 */ "Binauric SE", + /* 0204 */ "Beats Electronics", + /* 0205 */ "Microchip Technology Inc.", + /* 0206 */ "Elgato Systems GmbH", + /* 0207 */ "ARCHOS SA", + /* 0208 */ "Dexcom, Inc.", + /* 0209 */ "Polar Electro Europe B.V.", + /* 0210 */ "Dialog Semiconductor B.V.", + /* 0211 */ "Taixingbang Technology (HK) Co,. LTD.", + /* 0212 */ "Kawantech", + /* 0213 */ "Austco Communication Systems", + /* 0214 */ "Timex Group USA, Inc.", + /* 0215 */ "Qualcomm Technologies, Inc.", + /* 0216 */ "Qualcomm Connected Experiences, Inc.", + /* 0217 */ "Voyetra Turtle Beach", + /* 0218 */ "txtr GmbH", + /* 0219 */ "Biosentronics", + /* 0220 */ "Procter & Gamble", + /* 0221 */ "Hosiden Corporation", + /* 0222 */ "Muzik LLC", + /* 0223 */ "Misfit Wearables Corp", + /* 0224 */ "Google", + /* 0225 */ "Danlers Ltd", + /* 0226 */ "Semilink Inc", + /* 0227 */ "inMusic Brands, Inc", + /* 0228 */ "L.S. Research Inc.", + /* 0229 */ "Eden Software Consultants Ltd.", + /* 0230 */ "Freshtemp", + /* 0231 */ "KS Technologies", + /* 0232 */ "ACTS Technologies", + /* 0233 */ "Vtrack Systems", + /* 0234 */ "Nielsen-Kellerman Company", + /* 0235 */ "Server Technology Inc.", + /* 0236 */ "BioResearch Associates", + /* 0237 */ "Jolly Logic, LLC", + /* 0238 */ "Above Average Outcomes, Inc.", + /* 0239 */ "Bitsplitters GmbH", + /* 0240 */ "PayPal, Inc.", + /* 0241 */ "Witron Technology Limited", + /* 0242 */ "Morse Project Inc.", + /* 0243 */ "Kent Displays Inc.", + /* 0244 */ "Nautilus Inc.", + /* 0245 */ "Smartifier Oy", + /* 0246 */ "Elcometer Limited", + /* 0247 */ "VSN Technologies, Inc.", + /* 0248 */ "AceUni Corp., Ltd.", + /* 0249 */ "StickNFind", + /* 0250 */ "Crystal Code AB", + /* 0251 */ "KOUKAAM a.s.", + /* 0252 */ "Delphi Corporation", + /* 0253 */ "ValenceTech Limited", + /* 0254 */ "Stanley Black and Decker", + /* 0255 */ "Typo Products, LLC", + /* 0256 */ "TomTom International BV", + /* 0257 */ "Fugoo, Inc.", + /* 0258 */ "Keiser Corporation", + /* 0259 */ "Bang & Olufsen A/S", + /* 0260 */ "PLUS Location Systems Pty Ltd", + /* 0261 */ "Ubiquitous Computing Technology Corporation", + /* 0262 */ "Innovative Yachtter Solutions", + /* 0263 */ "William Demant Holding A/S", + /* 0264 */ "Chicony Electronics Co., Ltd.", + /* 0265 */ "Atus BV", + /* 0266 */ "Codegate Ltd", + /* 0267 */ "ERi, Inc", + /* 0268 */ "Transducers Direct, LLC", + /* 0269 */ "DENSO TEN LIMITED (formerly Fujitsu Ten LImited)", + /* 0270 */ "Audi AG", + /* 0271 */ "HiSilicon Technologies CO., LIMITED", + /* 0272 */ "Nippon Seiki Co., Ltd.", + /* 0273 */ "Steelseries ApS", + /* 0274 */ "Visybl Inc.", + /* 0275 */ "Openbrain Technologies, Co., Ltd.", + /* 0276 */ "Xensr", + /* 0277 */ "e.solutions", + /* 0278 */ "10AK Technologies", + /* 0279 */ "Wimoto Technologies Inc", + /* 0280 */ "Radius Networks, Inc.", + /* 0281 */ "Wize Technology Co., Ltd.", + /* 0282 */ "Qualcomm Labs, Inc.", + /* 0283 */ "Hewlett Packard Enterprise", + /* 0284 */ "Baidu", + /* 0285 */ "Arendi AG", + /* 0286 */ "Skoda Auto a.s.", + /* 0287 */ "Volkswagen AG", + /* 0288 */ "Porsche AG", + /* 0289 */ "Sino Wealth Electronic Ltd.", + /* 0290 */ "AirTurn, Inc.", + /* 0291 */ "Kinsa, Inc", + /* 0292 */ "HID Global", + /* 0293 */ "SEAT es", + /* 0294 */ "Promethean Ltd.", + /* 0295 */ "Salutica Allied Solutions", + /* 0296 */ "GPSI Group Pty Ltd", + /* 0297 */ "Nimble Devices Oy", + /* 0298 */ "Changzhou Yongse Infotech Co., Ltd.", + /* 0299 */ "SportIQ", + /* 0300 */ "TEMEC Instruments B.V.", + /* 0301 */ "Sony Corporation", + /* 0302 */ "ASSA ABLOY", + /* 0303 */ "Clarion Co. Inc.", + /* 0304 */ "Warehouse Innovations", + /* 0305 */ "Cypress Semiconductor", + /* 0306 */ "MADS Inc", + /* 0307 */ "Blue Maestro Limited", + /* 0308 */ "Resolution Products, Ltd.", + /* 0309 */ "Aireware LLC", + /* 0310 */ "Silvair, Inc.", + /* 0311 */ "Prestigio Plaza Ltd.", + /* 0312 */ "NTEO Inc.", + /* 0313 */ "Focus Systems Corporation", + /* 0314 */ "Tencent Holdings Ltd.", + /* 0315 */ "Allegion", + /* 0316 */ "Murata Manufacturing Co., Ltd.", + /* 0317 */ "WirelessWERX", + /* 0318 */ "Nod, Inc.", + /* 0319 */ "B&B Manufacturing Company", + /* 0320 */ "Alpine Electronics (China) Co., Ltd", + /* 0321 */ "FedEx Services", + /* 0322 */ "Grape Systems Inc.", + /* 0323 */ "Bkon Connect", + /* 0324 */ "Lintech GmbH", + /* 0325 */ "Novatel Wireless", + /* 0326 */ "Ciright", + /* 0327 */ "Mighty Cast, Inc.", + /* 0328 */ "Ambimat Electronics", + /* 0329 */ "Perytons Ltd.", + /* 0330 */ "Tivoli Audio, LLC", + /* 0331 */ "Master Lock", + /* 0332 */ "Mesh-Net Ltd", + /* 0333 */ "HUIZHOU DESAY SV AUTOMOTIVE CO., LTD.", + /* 0334 */ "Tangerine, Inc.", + /* 0335 */ "B&W Group Ltd.", + /* 0336 */ "Pioneer Corporation", + /* 0337 */ "OnBeep", + /* 0338 */ "Vernier Software & Technology", + /* 0339 */ "ROL Ergo", + /* 0340 */ "Pebble Technology", + /* 0341 */ "NETATMO", + /* 0342 */ "Accumulate AB", + /* 0343 */ "Anhui Huami Information Technology Co., Ltd.", + /* 0344 */ "Inmite s.r.o.", + /* 0345 */ "ChefSteps, Inc.", + /* 0346 */ "micas AG", + /* 0347 */ "Biomedical Research Ltd.", + /* 0348 */ "Pitius Tec S.L.", + /* 0349 */ "Estimote, Inc.", + /* 0350 */ "Unikey Technologies, Inc.", + /* 0351 */ "Timer Cap Co.", + /* 0352 */ "AwoX", + /* 0353 */ "yikes", + /* 0354 */ "MADSGlobalNZ Ltd.", + /* 0355 */ "PCH International", + /* 0356 */ "Qingdao Yeelink Information Technology Co., Ltd.", + /* 0357 */ "Milwaukee Tool (Formally Milwaukee Electric Tools)", + /* 0358 */ "MISHIK Pte Ltd", + /* 0359 */ "Ascensia Diabetes Care US Inc.", + /* 0360 */ "Spicebox LLC", + /* 0361 */ "emberlight", + /* 0362 */ "Cooper-Atkins Corporation", + /* 0363 */ "Qblinks", + /* 0364 */ "MYSPHERA", + /* 0365 */ "LifeScan Inc", + /* 0366 */ "Volantic AB", + /* 0367 */ "Podo Labs, Inc", + /* 0368 */ "Roche Diabetes Care AG", + /* 0369 */ "Amazon.com Services, LLC (formerly Amazon Fulfillment Service)", + /* 0370 */ "Connovate Technology Private Limited", + /* 0371 */ "Kocomojo, LLC", + /* 0372 */ "Everykey Inc.", + /* 0373 */ "Dynamic Controls", + /* 0374 */ "SentriLock", + /* 0375 */ "I-SYST inc.", + /* 0376 */ "CASIO COMPUTER CO., LTD.", + /* 0377 */ "LAPIS Semiconductor Co., Ltd.", + /* 0378 */ "Telemonitor, Inc.", + /* 0379 */ "taskit GmbH", + /* 0380 */ "Daimler AG", + /* 0381 */ "BatAndCat", + /* 0382 */ "BluDotz Ltd", + /* 0383 */ "XTel Wireless ApS", + /* 0384 */ "Gigaset Communications GmbH", + /* 0385 */ "Gecko Health Innovations, Inc.", + /* 0386 */ "HOP Ubiquitous", + /* 0387 */ "Walt Disney", + /* 0388 */ "Nectar", + /* 0389 */ "bel'apps LLC", + /* 0390 */ "CORE Lighting Ltd", + /* 0391 */ "Seraphim Sense Ltd", + /* 0392 */ "Unico RBC", + /* 0393 */ "Physical Enterprises Inc.", + /* 0394 */ "Able Trend Technology Limited", + /* 0395 */ "Konica Minolta, Inc.", + /* 0396 */ "Wilo SE", + /* 0397 */ "Extron Design Services", + /* 0398 */ "Fitbit, Inc.", + /* 0399 */ "Fireflies Systems", + /* 0400 */ "Intelletto Technologies Inc.", + /* 0401 */ "FDK CORPORATION", + /* 0402 */ "Cloudleaf, Inc", + /* 0403 */ "Maveric Automation LLC", + /* 0404 */ "Acoustic Stream Corporation", + /* 0405 */ "Zuli", + /* 0406 */ "Paxton Access Ltd", + /* 0407 */ "WiSilica Inc.", + /* 0408 */ "VENGIT Korlatolt Felelossegu Tarsasag", + /* 0409 */ "SALTO SYSTEMS S.L.", + /* 0410 */ "TRON Forum (formerly T-Engine Forum)", + /* 0411 */ "CUBETECH s.r.o.", + /* 0412 */ "Cokiya Incorporated", + /* 0413 */ "CVS Health", + /* 0414 */ "Ceruus", + /* 0415 */ "Strainstall Ltd", + /* 0416 */ "Channel Enterprises (HK) Ltd.", + /* 0417 */ "FIAMM", + /* 0418 */ "GIGALANE.CO.,LTD", + /* 0419 */ "EROAD", + /* 0420 */ "Mine Safety Appliances", + /* 0421 */ "Icon Health and Fitness", + /* 0422 */ "Wille Engineering (formerly as Asandoo GmbH)", + /* 0423 */ "ENERGOUS CORPORATION", + /* 0424 */ "Taobao", + /* 0425 */ "Canon Inc.", + /* 0426 */ "Geophysical Technology Inc.", + /* 0427 */ "Facebook, Inc.", + /* 0428 */ "Trividia Health, Inc.", + /* 0429 */ "FlightSafety International", + /* 0430 */ "Earlens Corporation", + /* 0431 */ "Sunrise Micro Devices, Inc.", + /* 0432 */ "Star Micronics Co., Ltd.", + /* 0433 */ "Netizens Sp. z o.o.", + /* 0434 */ "Nymi Inc.", + /* 0435 */ "Nytec, Inc.", + /* 0436 */ "Trineo Sp. z o.o.", + /* 0437 */ "Nest Labs Inc.", + /* 0438 */ "LM Technologies Ltd", + /* 0439 */ "General Electric Company", + /* 0440 */ "i+D3 S.L.", + /* 0441 */ "HANA Micron", + /* 0442 */ "Stages Cycling LLC", + /* 0443 */ "Cochlear Bone Anchored Solutions AB", + /* 0444 */ "SenionLab AB", + /* 0445 */ "Syszone Co., Ltd", + /* 0446 */ "Pulsate Mobile Ltd.", + /* 0447 */ "Hong Kong HunterSun Electronic Limited", + /* 0448 */ "pironex GmbH", + /* 0449 */ "BRADATECH Corp.", + /* 0450 */ "Transenergooil AG", + /* 0451 */ "Bunch", + /* 0452 */ "DME Microelectronics", + /* 0453 */ "Bitcraze AB", + /* 0454 */ "HASWARE Inc.", + /* 0455 */ "Abiogenix Inc.", + /* 0456 */ "Poly-Control ApS", + /* 0457 */ "Avi-on", + /* 0458 */ "Laerdal Medical AS", + /* 0459 */ "Fetch My Pet", + /* 0460 */ "Sam Labs Ltd.", + /* 0461 */ "Chengdu Synwing Technology Ltd", + /* 0462 */ "HOUWA SYSTEM DESIGN, k.k.", + /* 0463 */ "BSH", + /* 0464 */ "Primus Inter Pares Ltd", + /* 0465 */ "August Home, Inc", + /* 0466 */ "Gill Electronics", + /* 0467 */ "Sky Wave Design", + /* 0468 */ "Newlab S.r.l.", + /* 0469 */ "ELAD srl", + /* 0470 */ "G-wearables inc.", + /* 0471 */ "Squadrone Systems Inc.", + /* 0472 */ "Code Corporation", + /* 0473 */ "Savant Systems LLC", + /* 0474 */ "Logitech International SA", + /* 0475 */ "Innblue Consulting", + /* 0476 */ "iParking Ltd.", + /* 0477 */ "Koninklijke Philips Electronics N.V.", + /* 0478 */ "Minelab Electronics Pty Limited", + /* 0479 */ "Bison Group Ltd.", + /* 0480 */ "Widex A/S", + /* 0481 */ "Jolla Ltd", + /* 0482 */ "Lectronix, Inc.", + /* 0483 */ "Caterpillar Inc", + /* 0484 */ "Freedom Innovations", + /* 0485 */ "Dynamic Devices Ltd", + /* 0486 */ "Technology Solutions (UK) Ltd", + /* 0487 */ "IPS Group Inc.", + /* 0488 */ "STIR", + /* 0489 */ "Sano, Inc.", + /* 0490 */ "Advanced Application Design, Inc.", + /* 0491 */ "AutoMap LLC", + /* 0492 */ "Spreadtrum Communications Shanghai Ltd", + /* 0493 */ "CuteCircuit LTD", + /* 0494 */ "Valeo Service", + /* 0495 */ "Fullpower Technologies, Inc.", + /* 0496 */ "KloudNation", + /* 0497 */ "Zebra Technologies Corporation", + /* 0498 */ "Itron, Inc.", + /* 0499 */ "The University of Tokyo", + /* 0500 */ "UTC Fire and Security", + /* 0501 */ "Cool Webthings Limited", + /* 0502 */ "DJO Global", + /* 0503 */ "Gelliner Limited", + /* 0504 */ "Anyka (Guangzhou) Microelectronics Technology Co, LTD", + /* 0505 */ "Medtronic Inc.", + /* 0506 */ "Gozio Inc.", + /* 0507 */ "Form Lifting, LLC", + /* 0508 */ "Wahoo Fitness, LLC", + /* 0509 */ "Kontakt Micro-Location Sp. z o.o.", + /* 0510 */ "Radio Systems Corporation", + /* 0511 */ "Freescale Semiconductor, Inc.", + /* 0512 */ "Verifone Systems Pte Ltd. Taiwan Branch", + /* 0513 */ "AR Timing", + /* 0514 */ "Rigado LLC", + /* 0515 */ "Kemppi Oy", + /* 0516 */ "Tapcentive Inc.", + /* 0517 */ "Smartbotics Inc.", + /* 0518 */ "Otter Products, LLC", + /* 0519 */ "STEMP Inc.", + /* 0520 */ "LumiGeek LLC", + /* 0521 */ "InvisionHeart Inc.", + /* 0522 */ "Macnica Inc.", + /* 0523 */ "Jaguar Land Rover Limited", + /* 0524 */ "CoroWare Technologies, Inc", + /* 0525 */ "Simplo Technology Co., LTD", + /* 0526 */ "Omron Healthcare Co., LTD", + /* 0527 */ "Comodule GMBH", + /* 0528 */ "ikeGPS", + /* 0529 */ "Telink Semiconductor Co. Ltd", + /* 0530 */ "Interplan Co., Ltd", + /* 0531 */ "Wyler AG", + /* 0532 */ "IK Multimedia Production srl", + /* 0533 */ "Lukoton Experience Oy", + /* 0534 */ "MTI Ltd", + /* 0535 */ "Tech4home, Lda", + /* 0536 */ "Hiotech AB", + /* 0537 */ "DOTT Limited", + /* 0538 */ "Blue Speck Labs, LLC", + /* 0539 */ "Cisco Systems, Inc", + /* 0540 */ "Mobicomm Inc", + /* 0541 */ "Edamic", + /* 0542 */ "Goodnet, Ltd", + /* 0543 */ "Luster Leaf Products Inc", + /* 0544 */ "Manus Machina BV", + /* 0545 */ "Mobiquity Networks Inc", + /* 0546 */ "Praxis Dynamics", + /* 0547 */ "Philip Morris Products S.A.", + /* 0548 */ "Comarch SA", + /* 0549 */ "Nestlé Nespresso S.A.", + /* 0550 */ "Merlinia A/S", + /* 0551 */ "LifeBEAM Technologies", + /* 0552 */ "Twocanoes Labs, LLC", + /* 0553 */ "Muoverti Limited", + /* 0554 */ "Stamer Musikanlagen GMBH", + /* 0555 */ "Tesla Motors", + /* 0556 */ "Pharynks Corporation", + /* 0557 */ "Lupine", + /* 0558 */ "Siemens AG", + /* 0559 */ "Huami (Shanghai) Culture Communication CO., LTD", + /* 0560 */ "Foster Electric Company, Ltd", + /* 0561 */ "ETA SA", + /* 0562 */ "x-Senso Solutions Kft", + /* 0563 */ "Shenzhen SuLong Communication Ltd", + /* 0564 */ "FengFan (BeiJing) Technology Co, Ltd", + /* 0565 */ "Qrio Inc", + /* 0566 */ "Pitpatpet Ltd", + /* 0567 */ "MSHeli s.r.l.", + /* 0568 */ "Trakm8 Ltd", + /* 0569 */ "JIN CO, Ltd", + /* 0570 */ "Alatech Tehnology", + /* 0571 */ "Beijing CarePulse Electronic Technology Co, Ltd", + /* 0572 */ "Awarepoint", + /* 0573 */ "ViCentra B.V.", + /* 0574 */ "Raven Industries", + /* 0575 */ "WaveWare Technologies Inc.", + /* 0576 */ "Argenox Technologies", + /* 0577 */ "Bragi GmbH", + /* 0578 */ "16Lab Inc", + /* 0579 */ "Masimo Corp", + /* 0580 */ "Iotera Inc", + /* 0581 */ "Endress+Hauser", + /* 0582 */ "ACKme Networks, Inc.", + /* 0583 */ "FiftyThree Inc.", + /* 0584 */ "Parker Hannifin Corp", + /* 0585 */ "Transcranial Ltd", + /* 0586 */ "Uwatec AG", + /* 0587 */ "Orlan LLC", + /* 0588 */ "Blue Clover Devices", + /* 0589 */ "M-Way Solutions GmbH", + /* 0590 */ "Microtronics Engineering GmbH", + /* 0591 */ "Schneider Schreibgeräte GmbH", + /* 0592 */ "Sapphire Circuits LLC", + /* 0593 */ "Lumo Bodytech Inc.", + /* 0594 */ "UKC Technosolution", + /* 0595 */ "Xicato Inc.", + /* 0596 */ "Playbrush", + /* 0597 */ "Dai Nippon Printing Co., Ltd.", + /* 0598 */ "G24 Power Limited", + /* 0599 */ "AdBabble Local Commerce Inc.", + /* 0600 */ "Devialet SA", + /* 0601 */ "ALTYOR", + /* 0602 */ "University of Applied Sciences Valais/Haute Ecole Valaisanne", + /* 0603 */ "Five Interactive, LLC dba Zendo", + /* 0604 */ "NetEase (Hangzhou) Network co.Ltd.", + /* 0605 */ "Lexmark International Inc.", + /* 0606 */ "Fluke Corporation", + /* 0607 */ "Yardarm Technologies", + /* 0608 */ "SensaRx", + /* 0609 */ "SECVRE GmbH", + /* 0610 */ "Glacial Ridge Technologies", + /* 0611 */ "Identiv, Inc.", + /* 0612 */ "DDS, Inc.", + /* 0613 */ "SMK Corporation", + /* 0614 */ "Schawbel Technologies LLC", + /* 0615 */ "XMI Systems SA", + /* 0616 */ "Cerevo", + /* 0617 */ "Torrox GmbH & Co KG", + /* 0618 */ "Gemalto", + /* 0619 */ "DEKA Research & Development Corp.", + /* 0620 */ "Domster Tadeusz Szydlowski", + /* 0621 */ "Technogym SPA", + /* 0622 */ "FLEURBAEY BVBA", + /* 0623 */ "Aptcode Solutions", + /* 0624 */ "LSI ADL Technology", + /* 0625 */ "Animas Corp", + /* 0626 */ "Alps Electric Co., Ltd.", + /* 0627 */ "OCEASOFT", + /* 0628 */ "Motsai Research", + /* 0629 */ "Geotab", + /* 0630 */ "E.G.O. Elektro-Geraetebau GmbH", + /* 0631 */ "bewhere inc", + /* 0632 */ "Johnson Outdoors Inc", + /* 0633 */ "steute Schaltgerate GmbH & Co. KG", + /* 0634 */ "Ekomini inc.", + /* 0635 */ "DEFA AS", + /* 0636 */ "Aseptika Ltd", + /* 0637 */ "HUAWEI Technologies Co., Ltd.", + /* 0638 */ "HabitAware, LLC", + /* 0639 */ "ruwido austria gmbh", + /* 0640 */ "ITEC corporation", + /* 0641 */ "StoneL", + /* 0642 */ "Sonova AG", + /* 0643 */ "Maven Machines, Inc.", + /* 0644 */ "Synapse Electronics", + /* 0645 */ "Standard Innovation Inc.", + /* 0646 */ "RF Code, Inc.", + /* 0647 */ "Wally Ventures S.L.", + /* 0648 */ "Willowbank Electronics Ltd", + /* 0649 */ "SK Telecom", + /* 0650 */ "Jetro AS", + /* 0651 */ "Code Gears LTD", + /* 0652 */ "NANOLINK APS", + /* 0653 */ "IF, LLC", + /* 0654 */ "RF Digital Corp", + /* 0655 */ "Church & Dwight Co., Inc", + /* 0656 */ "Multibit Oy", + /* 0657 */ "CliniCloud Inc", + /* 0658 */ "SwiftSensors", + /* 0659 */ "Blue Bite", + /* 0660 */ "ELIAS GmbH", + /* 0661 */ "Sivantos GmbH", + /* 0662 */ "Petzl", + /* 0663 */ "storm power ltd", + /* 0664 */ "EISST Ltd", + /* 0665 */ "Inexess Technology Simma KG", + /* 0666 */ "Currant, Inc.", + /* 0667 */ "C2 Development, Inc.", + /* 0668 */ "Blue Sky Scientific, LLC", + /* 0669 */ "ALOTTAZS LABS, LLC", + /* 0670 */ "Kupson spol. s r.o.", + /* 0671 */ "Areus Engineering GmbH", + /* 0672 */ "Impossible Camera GmbH", + /* 0673 */ "InventureTrack Systems", + /* 0674 */ "LockedUp", + /* 0675 */ "Itude", + /* 0676 */ "Pacific Lock Company", + /* 0677 */ "Tendyron Corporation", + /* 0678 */ "Robert Bosch GmbH", + /* 0679 */ "Illuxtron international B.V.", + /* 0680 */ "miSport Ltd.", + /* 0681 */ "Chargelib", + /* 0682 */ "Doppler Lab", + /* 0683 */ "BBPOS Limited", + /* 0684 */ "RTB Elektronik GmbH & Co. KG", + /* 0685 */ "Rx Networks, Inc.", + /* 0686 */ "WeatherFlow, Inc.", + /* 0687 */ "Technicolor USA Inc.", + /* 0688 */ "Bestechnic(Shanghai),Ltd", + /* 0689 */ "Raden Inc", + /* 0690 */ "JouZen Oy", + /* 0691 */ "CLABER S.P.A.", + /* 0692 */ "Hyginex, Inc.", + /* 0693 */ "HANSHIN ELECTRIC RAILWAY CO.,LTD.", + /* 0694 */ "Schneider Electric", + /* 0695 */ "Oort Technologies LLC", + /* 0696 */ "Chrono Therapeutics", + /* 0697 */ "Rinnai Corporation", + /* 0698 */ "Swissprime Technologies AG", + /* 0699 */ "Koha.,Co.Ltd", + /* 0700 */ "Genevac Ltd", + /* 0701 */ "Chemtronics", + /* 0702 */ "Seguro Technology Sp. z o.o.", + /* 0703 */ "Redbird Flight Simulations", + /* 0704 */ "Dash Robotics", + /* 0705 */ "LINE Corporation", + /* 0706 */ "Guillemot Corporation", + /* 0707 */ "Techtronic Power Tools Technology Limited", + /* 0708 */ "Wilson Sporting Goods", + /* 0709 */ "Lenovo (Singapore) Pte Ltd.", + /* 0710 */ "Ayatan Sensors", + /* 0711 */ "Electronics Tomorrow Limited", + /* 0712 */ "VASCO Data Security International, Inc.", + /* 0713 */ "PayRange Inc.", + /* 0714 */ "ABOV Semiconductor", + /* 0715 */ "AINA-Wireless Inc.", + /* 0716 */ "Eijkelkamp Soil & Water", + /* 0717 */ "BMA ergonomics b.v.", + /* 0718 */ "Teva Branded Pharmaceutical Products R&D, Inc.", + /* 0719 */ "Anima", + /* 0720 */ "3M", + /* 0721 */ "Empatica Srl", + /* 0722 */ "Afero, Inc.", + /* 0723 */ "Powercast Corporation", + /* 0724 */ "Secuyou ApS", + /* 0725 */ "OMRON Corporation", + /* 0726 */ "Send Solutions", + /* 0727 */ "NIPPON SYSTEMWARE CO.,LTD.", + /* 0728 */ "Neosfar", + /* 0729 */ "Fliegl Agrartechnik GmbH", + /* 0730 */ "Gilvader", + /* 0731 */ "Digi International Inc (R)", + /* 0732 */ "DeWalch Technologies, Inc.", + /* 0733 */ "Flint Rehabilitation Devices, LLC", + /* 0734 */ "Samsung SDS Co., Ltd.", + /* 0735 */ "Blur Product Development", + /* 0736 */ "University of Michigan", + /* 0737 */ "Victron Energy BV", + /* 0738 */ "NTT docomo", + /* 0739 */ "Carmanah Technologies Corp.", + /* 0740 */ "Bytestorm Ltd.", + /* 0741 */ "Espressif Incorporated", + /* 0742 */ "Unwire", + /* 0743 */ "Connected Yard, Inc.", + /* 0744 */ "American Music Environments", + /* 0745 */ "Sensogram Technologies, Inc.", + /* 0746 */ "Fujitsu Limited", + /* 0747 */ "Ardic Technology", + /* 0748 */ "Delta Systems, Inc", + /* 0749 */ "HTC Corporation", + /* 0750 */ "Citizen Holdings Co., Ltd.", + /* 0751 */ "SMART-INNOVATION.inc", + /* 0752 */ "Blackrat Software", + /* 0753 */ "The Idea Cave, LLC", + /* 0754 */ "GoPro, Inc.", + /* 0755 */ "AuthAir, Inc", + /* 0756 */ "Vensi, Inc.", + /* 0757 */ "Indagem Tech LLC", + /* 0758 */ "Intemo Technologies", + /* 0759 */ "DreamVisions co., Ltd.", + /* 0760 */ "Runteq Oy Ltd", + /* 0761 */ "IMAGINATION TECHNOLOGIES LTD", + /* 0762 */ "CoSTAR TEchnologies", + /* 0763 */ "Clarius Mobile Health Corp.", + /* 0764 */ "Shanghai Frequen Microelectronics Co., Ltd.", + /* 0765 */ "Uwanna, Inc.", + /* 0766 */ "Lierda Science & Technology Group Co., Ltd.", + /* 0767 */ "Silicon Laboratories", + /* 0768 */ "World Moto Inc.", + /* 0769 */ "Giatec Scientific Inc.", + /* 0770 */ "Loop Devices, Inc", + /* 0771 */ "IACA electronique", + /* 0772 */ "Proxy Technologies, Inc.", + /* 0773 */ "Swipp ApS", + /* 0774 */ "Life Laboratory Inc.", + /* 0775 */ "FUJI INDUSTRIAL CO.,LTD.", + /* 0776 */ "Surefire, LLC", + /* 0777 */ "Dolby Labs", + /* 0778 */ "Ellisys", + /* 0779 */ "Magnitude Lighting Converters", + /* 0780 */ "Hilti AG", + /* 0781 */ "Devdata S.r.l.", + /* 0782 */ "Deviceworx", + /* 0783 */ "Shortcut Labs", + /* 0784 */ "SGL Italia S.r.l.", + /* 0785 */ "PEEQ DATA", + /* 0786 */ "Ducere Technologies Pvt Ltd", + /* 0787 */ "DiveNav, Inc.", + /* 0788 */ "RIIG AI Sp. z o.o.", + /* 0789 */ "Thermo Fisher Scientific", + /* 0790 */ "AG Measurematics Pvt. Ltd.", + /* 0791 */ "CHUO Electronics CO., LTD.", + /* 0792 */ "Aspenta International", + /* 0793 */ "Eugster Frismag AG", + /* 0794 */ "Amber wireless GmbH", + /* 0795 */ "HQ Inc", + /* 0796 */ "Lab Sensor Solutions", + /* 0797 */ "Enterlab ApS", + /* 0798 */ "Eyefi, Inc.", + /* 0799 */ "MetaSystem S.p.A.", + /* 0800 */ "SONO ELECTRONICS. CO., LTD", + /* 0801 */ "Jewelbots", + /* 0802 */ "Compumedics Limited", + /* 0803 */ "Rotor Bike Components", + /* 0804 */ "Astro, Inc.", + /* 0805 */ "Amotus Solutions", + /* 0806 */ "Healthwear Technologies (Changzhou)Ltd", + /* 0807 */ "Essex Electronics", + /* 0808 */ "Grundfos A/S", + /* 0809 */ "Eargo, Inc.", + /* 0810 */ "Electronic Design Lab", + /* 0811 */ "ESYLUX", + /* 0812 */ "NIPPON SMT.CO.,Ltd", + /* 0813 */ "BM innovations GmbH", + /* 0814 */ "indoormap", + /* 0815 */ "OttoQ Inc", + /* 0816 */ "North Pole Engineering", + /* 0817 */ "3flares Technologies Inc.", + /* 0818 */ "Electrocompaniet A.S.", + /* 0819 */ "Mul-T-Lock", + /* 0820 */ "Corentium AS", + /* 0821 */ "Enlighted Inc", + /* 0822 */ "GISTIC", + /* 0823 */ "AJP2 Holdings, LLC", + /* 0824 */ "COBI GmbH", + /* 0825 */ "Blue Sky Scientific, LLC", + /* 0826 */ "Appception, Inc.", + /* 0827 */ "Courtney Thorne Limited", + /* 0828 */ "Virtuosys", + /* 0829 */ "TPV Technology Limited", + /* 0830 */ "Monitra SA", + /* 0831 */ "Automation Components, Inc.", + /* 0832 */ "Letsense s.r.l.", + /* 0833 */ "Etesian Technologies LLC", + /* 0834 */ "GERTEC BRASIL LTDA.", + /* 0835 */ "Drekker Development Pty. Ltd.", + /* 0836 */ "Whirl Inc", + /* 0837 */ "Locus Positioning", + /* 0838 */ "Acuity Brands Lighting, Inc", + /* 0839 */ "Prevent Biometrics", + /* 0840 */ "Arioneo", + /* 0841 */ "VersaMe", + /* 0842 */ "Vaddio", + /* 0843 */ "Libratone A/S", + /* 0844 */ "HM Electronics, Inc.", + /* 0845 */ "TASER International, Inc.", + /* 0846 */ "SafeTrust Inc.", + /* 0847 */ "Heartland Payment Systems", + /* 0848 */ "Bitstrata Systems Inc.", + /* 0849 */ "Pieps GmbH", + /* 0850 */ "iRiding(Xiamen)Technology Co.,Ltd.", + /* 0851 */ "Alpha Audiotronics, Inc.", + /* 0852 */ "TOPPAN FORMS CO.,LTD.", + /* 0853 */ "Sigma Designs, Inc.", + /* 0854 */ "Spectrum Brands, Inc.", + /* 0855 */ "Polymap Wireless", + /* 0856 */ "MagniWare Ltd.", + /* 0857 */ "Novotec Medical GmbH", + /* 0858 */ "Medicom Innovation Partner a/s", + /* 0859 */ "Matrix Inc.", + /* 0860 */ "Eaton Corporation", + /* 0861 */ "KYS", + /* 0862 */ "Naya Health, Inc.", + /* 0863 */ "Acromag", + /* 0864 */ "Insulet Corporation", + /* 0865 */ "Wellinks Inc.", + /* 0866 */ "ON Semiconductor", + /* 0867 */ "FREELAP SA", + /* 0868 */ "Favero Electronics Srl", + /* 0869 */ "BioMech Sensor LLC", + /* 0870 */ "BOLTT Sports technologies Private limited", + /* 0871 */ "Saphe International", + /* 0872 */ "Metormote AB", + /* 0873 */ "littleBits", + /* 0874 */ "SetPoint Medical", + /* 0875 */ "BRControls Products BV", + /* 0876 */ "Zipcar", + /* 0877 */ "AirBolt Pty Ltd", + /* 0878 */ "KeepTruckin Inc", + /* 0879 */ "Motiv, Inc.", + /* 0880 */ "Wazombi Labs OU", + /* 0881 */ "ORBCOMM", + /* 0882 */ "Nixie Labs, Inc.", + /* 0883 */ "AppNearMe Ltd", + /* 0884 */ "Holman Industries", + /* 0885 */ "Expain AS", + /* 0886 */ "Electronic Temperature Instruments Ltd", + /* 0887 */ "Plejd AB", + /* 0888 */ "Propeller Health", + /* 0889 */ "Shenzhen iMCO Electronic Technology Co.,Ltd", + /* 0890 */ "Algoria", + /* 0891 */ "Apption Labs Inc.", + /* 0892 */ "Cronologics Corporation", + /* 0893 */ "MICRODIA Ltd.", + /* 0894 */ "lulabytes S.L.", + /* 0895 */ "Societe des Produits Nestle S.A. (formerly Nestec S.A.)", + /* 0896 */ "LLC \"MEGA-F service\"", + /* 0897 */ "Sharp Corporation", + /* 0898 */ "Precision Outcomes Ltd", + /* 0899 */ "Kronos Incorporated", + /* 0900 */ "OCOSMOS Co., Ltd.", + /* 0901 */ "Embedded Electronic Solutions Ltd. dba e2Solutions", + /* 0902 */ "Aterica Inc.", + /* 0903 */ "BluStor PMC, Inc.", + /* 0904 */ "Kapsch TrafficCom AB", + /* 0905 */ "ActiveBlu Corporation", + /* 0906 */ "Kohler Mira Limited", + /* 0907 */ "Noke", + /* 0908 */ "Appion Inc.", + /* 0909 */ "Resmed Ltd", + /* 0910 */ "Crownstone B.V.", + /* 0911 */ "Xiaomi Inc.", + /* 0912 */ "INFOTECH s.r.o.", + /* 0913 */ "Thingsquare AB", + /* 0914 */ "T&D", + /* 0915 */ "LAVAZZA S.p.A.", + /* 0916 */ "Netclearance Systems, Inc.", + /* 0917 */ "SDATAWAY", + /* 0918 */ "BLOKS GmbH", + /* 0919 */ "LEGO System A/S", + /* 0920 */ "Thetatronics Ltd", + /* 0921 */ "Nikon Corporation", + /* 0922 */ "NeST", + /* 0923 */ "South Silicon Valley Microelectronics", + /* 0924 */ "ALE International", + /* 0925 */ "CareView Communications, Inc.", + /* 0926 */ "SchoolBoard Limited", + /* 0927 */ "Molex Corporation", + /* 0928 */ "IVT Wireless Limited", + /* 0929 */ "Alpine Labs LLC", + /* 0930 */ "Candura Instruments", + /* 0931 */ "SmartMovt Technology Co., Ltd", + /* 0932 */ "Token Zero Ltd", + /* 0933 */ "ACE CAD Enterprise Co., Ltd. (ACECAD)", + /* 0934 */ "Medela, Inc", + /* 0935 */ "AeroScout", + /* 0936 */ "Esrille Inc.", + /* 0937 */ "THINKERLY SRL", + /* 0938 */ "Exon Sp. z o.o.", + /* 0939 */ "Meizu Technology Co., Ltd.", + /* 0940 */ "Smablo LTD", + /* 0941 */ "XiQ", + /* 0942 */ "Allswell Inc.", + /* 0943 */ "Comm-N-Sense Corp DBA Verigo", + /* 0944 */ "VIBRADORM GmbH", + /* 0945 */ "Otodata Wireless Network Inc.", + /* 0946 */ "Propagation Systems Limited", + /* 0947 */ "Midwest Instruments & Controls", + /* 0948 */ "Alpha Nodus, inc.", + /* 0949 */ "petPOMM, Inc", + /* 0950 */ "Mattel", + /* 0951 */ "Airbly Inc.", + /* 0952 */ "A-Safe Limited", + /* 0953 */ "FREDERIQUE CONSTANT SA", + /* 0954 */ "Maxscend Microelectronics Company Limited", + /* 0955 */ "Abbott", + /* 0956 */ "ASB Bank Ltd", + /* 0957 */ "amadas", + /* 0958 */ "Applied Science, Inc.", + /* 0959 */ "iLumi Solutions Inc.", + /* 0960 */ "Arch Systems Inc.", + /* 0961 */ "Ember Technologies, Inc.", + /* 0962 */ "Snapchat Inc", + /* 0963 */ "Casambi Technologies Oy", + /* 0964 */ "Pico Technology Inc.", + /* 0965 */ "St. Jude Medical, Inc.", + /* 0966 */ "Intricon", + /* 0967 */ "Structural Health Systems, Inc.", + /* 0968 */ "Avvel International", + /* 0969 */ "Gallagher Group", + /* 0970 */ "In2things Automation Pvt. Ltd.", + /* 0971 */ "SYSDEV Srl", + /* 0972 */ "Vonkil Technologies Ltd", + /* 0973 */ "Wynd Technologies, Inc.", + /* 0974 */ "CONTRINEX S.A.", + /* 0975 */ "MIRA, Inc.", + /* 0976 */ "Watteam Ltd", + /* 0977 */ "Density Inc.", + /* 0978 */ "IOT Pot India Private Limited", + /* 0979 */ "Sigma Connectivity AB", + /* 0980 */ "PEG PEREGO SPA", + /* 0981 */ "Wyzelink Systems Inc.", + /* 0982 */ "Yota Devices LTD", + /* 0983 */ "FINSECUR", + /* 0984 */ "Zen-Me Labs Ltd", + /* 0985 */ "3IWare Co., Ltd.", + /* 0986 */ "EnOcean GmbH", + /* 0987 */ "Instabeat, Inc", + /* 0988 */ "Nima Labs", + /* 0989 */ "Andreas Stihl AG & Co. KG", + /* 0990 */ "Nathan Rhoades LLC", + /* 0991 */ "Grob Technologies, LLC", + /* 0992 */ "Actions (Zhuhai) Technology Co., Limited", + /* 0993 */ "SPD Development Company Ltd", + /* 0994 */ "Sensoan Oy", + /* 0995 */ "Qualcomm Life Inc", + /* 0996 */ "Chip-ing AG", + /* 0997 */ "ffly4u", + /* 0998 */ "IoT Instruments Oy", + /* 0999 */ "TRUE Fitness Technology", + /* 1000 */ "Reiner Kartengeraete GmbH & Co. KG.", + /* 1001 */ "SHENZHEN LEMONJOY TECHNOLOGY CO., LTD.", + /* 1002 */ "Hello Inc.", + /* 1003 */ "Evollve Inc.", + /* 1004 */ "Jigowatts Inc.", + /* 1005 */ "BASIC MICRO.COM,INC.", + /* 1006 */ "CUBE TECHNOLOGIES", + /* 1007 */ "foolography GmbH", + /* 1008 */ "CLINK", + /* 1009 */ "Hestan Smart Cooking Inc.", + /* 1010 */ "WindowMaster A/S", + /* 1011 */ "Flowscape AB", + /* 1012 */ "PAL Technologies Ltd", + /* 1013 */ "WHERE, Inc.", + /* 1014 */ "Iton Technology Corp.", + /* 1015 */ "Owl Labs Inc.", + /* 1016 */ "Rockford Corp.", + /* 1017 */ "Becon Technologies Co.,Ltd.", + /* 1018 */ "Vyassoft Technologies Inc", + /* 1019 */ "Nox Medical", + /* 1020 */ "Kimberly-Clark", + /* 1021 */ "Trimble Navigation Ltd.", + /* 1022 */ "Littelfuse", + /* 1023 */ "Withings", + /* 1024 */ "i-developer IT Beratung UG", + /* 1025 */ "Relations Inc.", + /* 1026 */ "Sears Holdings Corporation", + /* 1027 */ "Gantner Electronic GmbH", + /* 1028 */ "Authomate Inc", + /* 1029 */ "Vertex International, Inc.", + /* 1030 */ "Airtago", + /* 1031 */ "Swiss Audio SA", + /* 1032 */ "ToGetHome Inc.", + /* 1033 */ "AXIS", + /* 1034 */ "Openmatics", + /* 1035 */ "Jana Care Inc.", + /* 1036 */ "Senix Corporation", + /* 1037 */ "NorthStar Battery Company, LLC", + /* 1038 */ "SKF (U.K.) Limited", + /* 1039 */ "CO-AX Technology, Inc.", + /* 1040 */ "Fender Musical Instruments", + /* 1041 */ "Luidia Inc", + /* 1042 */ "SEFAM", + /* 1043 */ "Wireless Cables Inc", + /* 1044 */ "Lightning Protection International Pty Ltd", + /* 1045 */ "Uber Technologies Inc", + /* 1046 */ "SODA GmbH", + /* 1047 */ "Fatigue Science", + /* 1048 */ "Alpine Electronics Inc.", + /* 1049 */ "Novalogy LTD", + /* 1050 */ "Friday Labs Limited", + /* 1051 */ "OrthoAccel Technologies", + /* 1052 */ "WaterGuru, Inc.", + /* 1053 */ "Benning Elektrotechnik und Elektronik GmbH & Co. KG", + /* 1054 */ "Dell Computer Corporation", + /* 1055 */ "Kopin Corporation", + /* 1056 */ "TecBakery GmbH", + /* 1057 */ "Backbone Labs, Inc.", + /* 1058 */ "DELSEY SA", + /* 1059 */ "Chargifi Limited", + /* 1060 */ "Trainesense Ltd.", + /* 1061 */ "Unify Software and Solutions GmbH & Co. KG", + /* 1062 */ "Husqvarna AB", + /* 1063 */ "Focus fleet and fuel management inc", + /* 1064 */ "SmallLoop, LLC", + /* 1065 */ "Prolon Inc.", + /* 1066 */ "BD Medical", + /* 1067 */ "iMicroMed Incorporated", + /* 1068 */ "Ticto N.V.", + /* 1069 */ "Meshtech AS", + /* 1070 */ "MemCachier Inc.", + /* 1071 */ "Danfoss A/S", + /* 1072 */ "SnapStyk Inc.", + /* 1073 */ "Amway Corporation", + /* 1074 */ "Silk Labs, Inc.", + /* 1075 */ "Pillsy Inc.", + /* 1076 */ "Hatch Baby, Inc.", + /* 1077 */ "Blocks Wearables Ltd.", + /* 1078 */ "Drayson Technologies (Europe) Limited", + /* 1079 */ "eBest IOT Inc.", + /* 1080 */ "Helvar Ltd", + /* 1081 */ "Radiance Technologies", + /* 1082 */ "Nuheara Limited", + /* 1083 */ "Appside co., ltd.", + /* 1084 */ "DeLaval", + /* 1085 */ "Coiler Corporation", + /* 1086 */ "Thermomedics, Inc.", + /* 1087 */ "Tentacle Sync GmbH", + /* 1088 */ "Valencell, Inc.", + /* 1089 */ "iProtoXi Oy", + /* 1090 */ "SECOM CO., LTD.", + /* 1091 */ "Tucker International LLC", + /* 1092 */ "Metanate Limited", + /* 1093 */ "Kobian Canada Inc.", + /* 1094 */ "NETGEAR, Inc.", + /* 1095 */ "Fabtronics Australia Pty Ltd", + /* 1096 */ "Grand Centrix GmbH", + /* 1097 */ "1UP USA.com llc", + /* 1098 */ "SHIMANO INC.", + /* 1099 */ "Nain Inc.", + /* 1100 */ "LifeStyle Lock, LLC", + /* 1101 */ "VEGA Grieshaber KG", + /* 1102 */ "Xtrava Inc.", + /* 1103 */ "TTS Tooltechnic Systems AG & Co. KG", + /* 1104 */ "Teenage Engineering AB", + /* 1105 */ "Tunstall Nordic AB", + /* 1106 */ "Svep Design Center AB", + /* 1107 */ "GreenPeak Technologies BV", + /* 1108 */ "Sphinx Electronics GmbH & Co KG", + /* 1109 */ "Atomation", + /* 1110 */ "Nemik Consulting Inc", + /* 1111 */ "RF INNOVATION", + /* 1112 */ "Mini Solution Co., Ltd.", + /* 1113 */ "Lumenetix, Inc", + /* 1114 */ "2048450 Ontario Inc", + /* 1115 */ "SPACEEK LTD", + /* 1116 */ "Delta T Corporation", + /* 1117 */ "Boston Scientific Corporation", + /* 1118 */ "Nuviz, Inc.", + /* 1119 */ "Real Time Automation, Inc.", + /* 1120 */ "Kolibree", + /* 1121 */ "vhf elektronik GmbH", + /* 1122 */ "Bonsai Systems GmbH", + /* 1123 */ "Fathom Systems Inc.", + /* 1124 */ "Bellman & Symfon", + /* 1125 */ "International Forte Group LLC", + /* 1126 */ "CycleLabs Solutions inc.", + /* 1127 */ "Codenex Oy", + /* 1128 */ "Kynesim Ltd", + /* 1129 */ "Palago AB", + /* 1130 */ "INSIGMA INC.", + /* 1131 */ "PMD Solutions", + /* 1132 */ "Qingdao Realtime Technology Co., Ltd.", + /* 1133 */ "BEGA Gantenbrink-Leuchten KG", + /* 1134 */ "Pambor Ltd.", + /* 1135 */ "Develco Products A/S", + /* 1136 */ "iDesign s.r.l.", + /* 1137 */ "TiVo Corp", + /* 1138 */ "Control-J Pty Ltd", + /* 1139 */ "Steelcase, Inc.", + /* 1140 */ "iApartment co., ltd.", + /* 1141 */ "Icom inc.", + /* 1142 */ "Oxstren Wearable Technologies Private Limited", + /* 1143 */ "Blue Spark Technologies", + /* 1144 */ "FarSite Communications Limited", + /* 1145 */ "mywerk system GmbH", + /* 1146 */ "Sinosun Technology Co., Ltd.", + /* 1147 */ "MIYOSHI ELECTRONICS CORPORATION", + /* 1148 */ "POWERMAT LTD", + /* 1149 */ "Occly LLC", + /* 1150 */ "OurHub Dev IvS", + /* 1151 */ "Pro-Mark, Inc.", + /* 1152 */ "Dynometrics Inc.", + /* 1153 */ "Quintrax Limited", + /* 1154 */ "POS Tuning Udo Vosshenrich GmbH & Co. KG", + /* 1155 */ "Multi Care Systems B.V.", + /* 1156 */ "Revol Technologies Inc", + /* 1157 */ "SKIDATA AG", + /* 1158 */ "DEV TECNOLOGIA INDUSTRIA, COMERCIO E MANUTENCAO DE EQUIPAMENTOS LTDA. - ME", + /* 1159 */ "Centrica Connected Home", + /* 1160 */ "Automotive Data Solutions Inc", + /* 1161 */ "Igarashi Engineering", + /* 1162 */ "Taelek Oy", + /* 1163 */ "CP Electronics Limited", + /* 1164 */ "Vectronix AG", + /* 1165 */ "S-Labs Sp. z o.o.", + /* 1166 */ "Companion Medical, Inc.", + /* 1167 */ "BlueKitchen GmbH", + /* 1168 */ "Matting AB", + /* 1169 */ "SOREX - Wireless Solutions GmbH", + /* 1170 */ "ADC Technology, Inc.", + /* 1171 */ "Lynxemi Pte Ltd", + /* 1172 */ "SENNHEISER electronic GmbH & Co. KG", + /* 1173 */ "LMT Mercer Group, Inc", + /* 1174 */ "Polymorphic Labs LLC", + /* 1175 */ "Cochlear Limited", + /* 1176 */ "METER Group, Inc. USA", + /* 1177 */ "Ruuvi Innovations Ltd.", + /* 1178 */ "Situne AS", + /* 1179 */ "nVisti, LLC", + /* 1180 */ "DyOcean", + /* 1181 */ "Uhlmann & Zacher GmbH", + /* 1182 */ "AND!XOR LLC", + /* 1183 */ "tictote AB", + /* 1184 */ "Vypin, LLC", + /* 1185 */ "PNI Sensor Corporation", + /* 1186 */ "ovrEngineered, LLC", + /* 1187 */ "GT-tronics HK Ltd", + /* 1188 */ "Herbert Waldmann GmbH & Co. KG", + /* 1189 */ "Guangzhou FiiO Electronics Technology Co.,Ltd", + /* 1190 */ "Vinetech Co., Ltd", + /* 1191 */ "Dallas Logic Corporation", + /* 1192 */ "BioTex, Inc.", + /* 1193 */ "DISCOVERY SOUND TECHNOLOGY, LLC", + /* 1194 */ "LINKIO SAS", + /* 1195 */ "Harbortronics, Inc.", + /* 1196 */ "Undagrid B.V.", + /* 1197 */ "Shure Inc", + /* 1198 */ "ERM Electronic Systems LTD", + /* 1199 */ "BIOROWER Handelsagentur GmbH", + /* 1200 */ "Weba Sport und Med. Artikel GmbH", + /* 1201 */ "Kartographers Technologies Pvt. Ltd.", + /* 1202 */ "The Shadow on the Moon", + /* 1203 */ "mobike (Hong Kong) Limited", + /* 1204 */ "Inuheat Group AB", + /* 1205 */ "Swiftronix AB", + /* 1206 */ "Diagnoptics Technologies", + /* 1207 */ "Analog Devices, Inc.", + /* 1208 */ "Soraa Inc.", + /* 1209 */ "CSR Building Products Limited", + /* 1210 */ "Crestron Electronics, Inc.", + /* 1211 */ "Neatebox Ltd", + /* 1212 */ "Draegerwerk AG & Co. KGaA", + /* 1213 */ "AlbynMedical", + /* 1214 */ "Averos FZCO", + /* 1215 */ "VIT Initiative, LLC", + /* 1216 */ "Statsports International", + /* 1217 */ "Sospitas, s.r.o.", + /* 1218 */ "Dmet Products Corp.", + /* 1219 */ "Mantracourt Electronics Limited", + /* 1220 */ "TeAM Hutchins AB", + /* 1221 */ "Seibert Williams Glass, LLC", + /* 1222 */ "Insta GmbH", + /* 1223 */ "Svantek Sp. z o.o.", + /* 1224 */ "Shanghai Flyco Electrical Appliance Co., Ltd.", + /* 1225 */ "Thornwave Labs Inc", + /* 1226 */ "Steiner-Optik GmbH", + /* 1227 */ "Novo Nordisk A/S", + /* 1228 */ "Enflux Inc.", + /* 1229 */ "Safetech Products LLC", + /* 1230 */ "GOOOLED S.R.L.", + /* 1231 */ "DOM Sicherheitstechnik GmbH & Co. KG", + /* 1232 */ "Olympus Corporation", + /* 1233 */ "KTS GmbH", + /* 1234 */ "Anloq Technologies Inc.", + /* 1235 */ "Queercon, Inc", + /* 1236 */ "5th Element Ltd", + /* 1237 */ "Gooee Limited", + /* 1238 */ "LUGLOC LLC", + /* 1239 */ "Blincam, Inc.", + /* 1240 */ "FUJIFILM Corporation", + /* 1241 */ "RandMcNally", + /* 1242 */ "Franceschi Marina snc", + /* 1243 */ "Engineered Audio, LLC.", + /* 1244 */ "IOTTIVE (OPC) PRIVATE LIMITED", + /* 1245 */ "4MOD Technology", + /* 1246 */ "Lutron Electronics Co., Inc.", + /* 1247 */ "Emerson", + /* 1248 */ "Guardtec, Inc.", + /* 1249 */ "REACTEC LIMITED", + /* 1250 */ "EllieGrid", + /* 1251 */ "Under Armour", + /* 1252 */ "Woodenshark", + /* 1253 */ "Avack Oy", + /* 1254 */ "Smart Solution Technology, Inc.", + /* 1255 */ "REHABTRONICS INC.", + /* 1256 */ "STABILO International", + /* 1257 */ "Busch Jaeger Elektro GmbH", + /* 1258 */ "Pacific Bioscience Laboratories, Inc", + /* 1259 */ "Bird Home Automation GmbH", + /* 1260 */ "Motorola Solutions", + /* 1261 */ "R9 Technology, Inc.", + /* 1262 */ "Auxivia", + /* 1263 */ "DaisyWorks, Inc", + /* 1264 */ "Kosi Limited", + /* 1265 */ "Theben AG", + /* 1266 */ "InDreamer Techsol Private Limited", + /* 1267 */ "Cerevast Medical", + /* 1268 */ "ZanCompute Inc.", + /* 1269 */ "Pirelli Tyre S.P.A.", + /* 1270 */ "McLear Limited", + /* 1271 */ "Shenzhen Huiding Technology Co.,Ltd.", + /* 1272 */ "Convergence Systems Limited", + /* 1273 */ "Interactio", + /* 1274 */ "Androtec GmbH", + /* 1275 */ "Benchmark Drives GmbH & Co. KG", + /* 1276 */ "SwingLync L. L. C.", + /* 1277 */ "Tapkey GmbH", + /* 1278 */ "Woosim Systems Inc.", + /* 1279 */ "Microsemi Corporation", + /* 1280 */ "Wiliot LTD.", + /* 1281 */ "Polaris IND", + /* 1282 */ "Specifi-Kali LLC", + /* 1283 */ "Locoroll, Inc", + /* 1284 */ "PHYPLUS Inc", + /* 1285 */ "Inplay Technologies LLC", + /* 1286 */ "Hager", + /* 1287 */ "Yellowcog", + /* 1288 */ "Axes System sp. z o. o.", + /* 1289 */ "myLIFTER Inc.", + /* 1290 */ "Shake-on B.V.", + /* 1291 */ "Vibrissa Inc.", + /* 1292 */ "OSRAM GmbH", + /* 1293 */ "TRSystems GmbH", + /* 1294 */ "Yichip Microelectronics (Hangzhou) Co.,Ltd.", + /* 1295 */ "Foundation Engineering LLC", + /* 1296 */ "UNI-ELECTRONICS, INC.", + /* 1297 */ "Brookfield Equinox LLC", + /* 1298 */ "Soprod SA", + /* 1299 */ "9974091 Canada Inc.", + /* 1300 */ "FIBRO GmbH", + /* 1301 */ "RB Controls Co., Ltd.", + /* 1302 */ "Footmarks", + /* 1303 */ "Amtronic Sverige AB (formerly Amcore AB)", + /* 1304 */ "MAMORIO.inc", + /* 1305 */ "Tyto Life LLC", + /* 1306 */ "Leica Camera AG", + /* 1307 */ "Angee Technologies Ltd.", + /* 1308 */ "EDPS", + /* 1309 */ "OFF Line Co., Ltd.", + /* 1310 */ "Detect Blue Limited", + /* 1311 */ "Setec Pty Ltd", + /* 1312 */ "Target Corporation", + /* 1313 */ "IAI Corporation", + /* 1314 */ "NS Tech, Inc.", + /* 1315 */ "MTG Co., Ltd.", + /* 1316 */ "Hangzhou iMagic Technology Co., Ltd", + /* 1317 */ "HONGKONG NANO IC TECHNOLOGIES CO., LIMITED", + /* 1318 */ "Honeywell International Inc.", + /* 1319 */ "Albrecht JUNG", + /* 1320 */ "Lunera Lighting Inc.", + /* 1321 */ "Lumen UAB", + /* 1322 */ "Keynes Controls Ltd", + /* 1323 */ "Novartis AG", + /* 1324 */ "Geosatis SA", + /* 1325 */ "EXFO, Inc.", + /* 1326 */ "LEDVANCE GmbH", + /* 1327 */ "Center ID Corp.", + /* 1328 */ "Adolene, Inc.", + /* 1329 */ "D&M Holdings Inc.", + /* 1330 */ "CRESCO Wireless, Inc.", + /* 1331 */ "Nura Operations Pty Ltd", + /* 1332 */ "Frontiergadget, Inc.", + /* 1333 */ "Smart Component Technologies Limited", + /* 1334 */ "ZTR Control Systems LLC", + /* 1335 */ "MetaLogics Corporation", + /* 1336 */ "Medela AG", + /* 1337 */ "OPPLE Lighting Co., Ltd", + /* 1338 */ "Savitech Corp.,", + /* 1339 */ "prodigy", + /* 1340 */ "Screenovate Technologies Ltd", + /* 1341 */ "TESA SA", + /* 1342 */ "CLIM8 LIMITED", + /* 1343 */ "Silergy Corp", + /* 1344 */ "SilverPlus, Inc", + /* 1345 */ "Sharknet srl", + /* 1346 */ "Mist Systems, Inc.", + /* 1347 */ "MIWA LOCK CO.,Ltd", + /* 1348 */ "OrthoSensor, Inc.", + /* 1349 */ "Candy Hoover Group s.r.l", + /* 1350 */ "Apexar Technologies S.A.", + /* 1351 */ "LOGICDATA d.o.o.", + /* 1352 */ "Knick Elektronische Messgeraete GmbH & Co. KG", + /* 1353 */ "Smart Technologies and Investment Limited", + /* 1354 */ "Linough Inc.", + /* 1355 */ "Advanced Electronic Designs, Inc.", + /* 1356 */ "Carefree Scott Fetzer Co Inc", + /* 1357 */ "Sensome", + /* 1358 */ "FORTRONIK storitve d.o.o.", + /* 1359 */ "Sinnoz", + /* 1360 */ "Versa Networks, Inc.", + /* 1361 */ "Sylero", + /* 1362 */ "Avempace SARL", + /* 1363 */ "Nintendo Co., Ltd.", + /* 1364 */ "National Instruments", + /* 1365 */ "KROHNE Messtechnik GmbH", + /* 1366 */ "Otodynamics Ltd", + /* 1367 */ "Arwin Technology Limited", + /* 1368 */ "benegear, inc.", + /* 1369 */ "Newcon Optik", + /* 1370 */ "CANDY HOUSE, Inc.", + /* 1371 */ "FRANKLIN TECHNOLOGY INC", + /* 1372 */ "Lely", + /* 1373 */ "Valve Corporation", + /* 1374 */ "Hekatron Vertriebs GmbH", + /* 1375 */ "PROTECH S.A.S. DI GIRARDI ANDREA & C.", + /* 1376 */ "Sarita CareTech APS (formerly Sarita CareTech IVS)", + /* 1377 */ "Finder S.p.A.", + /* 1378 */ "Thalmic Labs Inc.", + /* 1379 */ "Steinel Vertrieb GmbH", + /* 1380 */ "Beghelli Spa", + /* 1381 */ "Beijing Smartspace Technologies Inc.", + /* 1382 */ "CORE TRANSPORT TECHNOLOGIES NZ LIMITED", + /* 1383 */ "Xiamen Everesports Goods Co., Ltd", + /* 1384 */ "Bodyport Inc.", + /* 1385 */ "Audionics System, INC.", + /* 1386 */ "Flipnavi Co.,Ltd.", + /* 1387 */ "Rion Co., Ltd.", + /* 1388 */ "Long Range Systems, LLC", + /* 1389 */ "Redmond Industrial Group LLC", + /* 1390 */ "VIZPIN INC.", + /* 1391 */ "BikeFinder AS", + /* 1392 */ "Consumer Sleep Solutions LLC", + /* 1393 */ "PSIKICK, INC.", + /* 1394 */ "AntTail.com", + /* 1395 */ "Lighting Science Group Corp.", + /* 1396 */ "AFFORDABLE ELECTRONICS INC", + /* 1397 */ "Integral Memory Plc", + /* 1398 */ "Globalstar, Inc.", + /* 1399 */ "True Wearables, Inc.", + /* 1400 */ "Wellington Drive Technologies Ltd", + /* 1401 */ "Ensemble Tech Private Limited", + /* 1402 */ "OMNI Remotes", + /* 1403 */ "Duracell U.S. Operations Inc.", + /* 1404 */ "Toor Technologies LLC", + /* 1405 */ "Instinct Performance", + /* 1406 */ "Beco, Inc", + /* 1407 */ "Scuf Gaming International, LLC", + /* 1408 */ "ARANZ Medical Limited", + /* 1409 */ "LYS TECHNOLOGIES LTD", + /* 1410 */ "Breakwall Analytics, LLC", + /* 1411 */ "Code Blue Communications", + /* 1412 */ "Gira Giersiepen GmbH & Co. KG", + /* 1413 */ "Hearing Lab Technology", + /* 1414 */ "LEGRAND", + /* 1415 */ "Derichs GmbH", + /* 1416 */ "ALT-TEKNIK LLC", + /* 1417 */ "Star Technologies", + /* 1418 */ "START TODAY CO.,LTD.", + /* 1419 */ "Maxim Integrated Products", + /* 1420 */ "MERCK Kommanditgesellschaft auf Aktien", + /* 1421 */ "Jungheinrich Aktiengesellschaft", + /* 1422 */ "Oculus VR, LLC", + /* 1423 */ "HENDON SEMICONDUCTORS PTY LTD", + /* 1424 */ "Pur3 Ltd", + /* 1425 */ "Viasat Group S.p.A.", + /* 1426 */ "IZITHERM", + /* 1427 */ "Spaulding Clinical Research", + /* 1428 */ "Kohler Company", + /* 1429 */ "Inor Process AB", + /* 1430 */ "My Smart Blinds", + /* 1431 */ "RadioPulse Inc", + /* 1432 */ "rapitag GmbH", + /* 1433 */ "Lazlo326, LLC.", + /* 1434 */ "Teledyne Lecroy, Inc.", + /* 1435 */ "Dataflow Systems Limited", + /* 1436 */ "Macrogiga Electronics", + /* 1437 */ "Tandem Diabetes Care", + /* 1438 */ "Polycom, Inc.", + /* 1439 */ "Fisher & Paykel Healthcare", + /* 1440 */ "RCP Software Oy", + /* 1441 */ "Shanghai Xiaoyi Technology Co.,Ltd.", + /* 1442 */ "ADHERIUM(NZ) LIMITED", + /* 1443 */ "Axiomware Systems Incorporated", + /* 1444 */ "O. E. M. Controls, Inc.", + /* 1445 */ "Kiiroo BV", + /* 1446 */ "Telecon Mobile Limited", + /* 1447 */ "Sonos Inc", + /* 1448 */ "Tom Allebrandi Consulting", + /* 1449 */ "Monidor", + /* 1450 */ "Tramex Limited", + /* 1451 */ "Nofence AS", + /* 1452 */ "GoerTek Dynaudio Co., Ltd.", + /* 1453 */ "INIA", + /* 1454 */ "CARMATE MFG.CO.,LTD", + /* 1455 */ "ONvocal", + /* 1456 */ "NewTec GmbH", + /* 1457 */ "Medallion Instrumentation Systems", + /* 1458 */ "CAREL INDUSTRIES S.P.A.", + /* 1459 */ "Parabit Systems, Inc.", + /* 1460 */ "White Horse Scientific ltd", + /* 1461 */ "verisilicon", + /* 1462 */ "Elecs Industry Co.,Ltd.", + /* 1463 */ "Beijing Pinecone Electronics Co.,Ltd.", + /* 1464 */ "Ambystoma Labs Inc.", + /* 1465 */ "Suzhou Pairlink Network Technology", + /* 1466 */ "igloohome", + /* 1467 */ "Oxford Metrics plc", + /* 1468 */ "Leviton Mfg. Co., Inc.", + /* 1469 */ "ULC Robotics Inc.", + /* 1470 */ "RFID Global by Softwork SrL", + /* 1471 */ "Real-World-Systems Corporation", + /* 1472 */ "Nalu Medical, Inc.", + /* 1473 */ "P.I.Engineering", + /* 1474 */ "Grote Industries", + /* 1475 */ "Runtime, Inc.", + /* 1476 */ "Codecoup sp. z o.o. sp. k.", + /* 1477 */ "SELVE GmbH & Co. KG", + /* 1478 */ "Smart Animal Training Systems, LLC", + /* 1479 */ "Lippert Components, INC", + /* 1480 */ "SOMFY SAS", + /* 1481 */ "TBS Electronics B.V.", + /* 1482 */ "MHL Custom Inc", + /* 1483 */ "LucentWear LLC", + /* 1484 */ "WATTS ELECTRONICS", + /* 1485 */ "RJ Brands LLC", + /* 1486 */ "V-ZUG Ltd", + /* 1487 */ "Biowatch SA", + /* 1488 */ "Anova Applied Electronics", + /* 1489 */ "Lindab AB", + /* 1490 */ "frogblue TECHNOLOGY GmbH", + /* 1491 */ "Acurable Limited", + /* 1492 */ "LAMPLIGHT Co., Ltd.", + /* 1493 */ "TEGAM, Inc.", + /* 1494 */ "Zhuhai Jieli technology Co.,Ltd", + /* 1495 */ "modum.io AG", + /* 1496 */ "Farm Jenny LLC", + /* 1497 */ "Toyo Electronics Corporation", + /* 1498 */ "Applied Neural Research Corp", + /* 1499 */ "Avid Identification Systems, Inc.", + /* 1500 */ "Petronics Inc.", + /* 1501 */ "essentim GmbH", + /* 1502 */ "QT Medical INC.", + /* 1503 */ "VIRTUALCLINIC.DIRECT LIMITED", + /* 1504 */ "Viper Design LLC", + /* 1505 */ "Human, Incorporated", + /* 1506 */ "stAPPtronics GmbH", + /* 1507 */ "Elemental Machines, Inc.", + /* 1508 */ "Taiyo Yuden Co., Ltd", + /* 1509 */ "INEO ENERGY& SYSTEMS", + /* 1510 */ "Motion Instruments Inc.", + /* 1511 */ "PressurePro", + /* 1512 */ "COWBOY", + /* 1513 */ "iconmobile GmbH", + /* 1514 */ "ACS-Control-System GmbH", + /* 1515 */ "Bayerische Motoren Werke AG", + /* 1516 */ "Gycom Svenska AB", + /* 1517 */ "Fuji Xerox Co., Ltd", + /* 1518 */ "Glide Inc.", + /* 1519 */ "SIKOM AS", + /* 1520 */ "beken", + /* 1521 */ "The Linux Foundation", + /* 1522 */ "Try and E CO.,LTD.", + /* 1523 */ "SeeScan", + /* 1524 */ "Clearity, LLC", + /* 1525 */ "GS TAG", + /* 1526 */ "DPTechnics", + /* 1527 */ "TRACMO, INC.", + /* 1528 */ "Anki Inc.", + /* 1529 */ "Hagleitner Hygiene International GmbH", + /* 1530 */ "Konami Sports Life Co., Ltd.", + /* 1531 */ "Arblet Inc.", + /* 1532 */ "Masbando GmbH", + /* 1533 */ "Innoseis", + /* 1534 */ "Niko nv", + /* 1535 */ "Wellnomics Ltd", + /* 1536 */ "iRobot Corporation", + /* 1537 */ "Schrader Electronics", + /* 1538 */ "Geberit International AG", + /* 1539 */ "Fourth Evolution Inc", + /* 1540 */ "Cell2Jack LLC", + /* 1541 */ "FMW electronic Futterer u. Maier-Wolf OHG", + /* 1542 */ "John Deere", + /* 1543 */ "Rookery Technology Ltd", + /* 1544 */ "KeySafe-Cloud", + /* 1545 */ "BUCHI Labortechnik AG", + /* 1546 */ "IQAir AG", + /* 1547 */ "Triax Technologies Inc", + /* 1548 */ "Vuzix Corporation", + /* 1549 */ "TDK Corporation", + /* 1550 */ "Blueair AB", + /* 1551 */ "Signify Netherlands", + /* 1552 */ "ADH GUARDIAN USA LLC", + /* 1553 */ "Beurer GmbH", + /* 1554 */ "Playfinity AS", + /* 1555 */ "Hans Dinslage GmbH", + /* 1556 */ "OnAsset Intelligence, Inc.", + /* 1557 */ "INTER ACTION Corporation", + /* 1558 */ "OS42 UG (haftungsbeschraenkt)", + /* 1559 */ "WIZCONNECTED COMPANY LIMITED", + /* 1560 */ "Audio-Technica Corporation", + /* 1561 */ "Six Guys Labs, s.r.o.", + /* 1562 */ "R.W. Beckett Corporation", + /* 1563 */ "silex technology, inc.", + /* 1564 */ "Univations Limited", + /* 1565 */ "SENS Innovation ApS", + /* 1566 */ "Diamond Kinetics, Inc.", + /* 1567 */ "Phrame Inc.", + /* 1568 */ "Forciot Oy", + /* 1569 */ "Noordung d.o.o.", + /* 1570 */ "Beam Labs, LLC", + /* 1571 */ "Philadelphia Scientific (U.K.) Limited", + /* 1572 */ "Biovotion AG", + /* 1573 */ "Square Panda, Inc.", + /* 1574 */ "Amplifico", + /* 1575 */ "WEG S.A.", + /* 1576 */ "Ensto Oy", + /* 1577 */ "PHONEPE PVT LTD", + /* 1578 */ "Lunatico Astronomia SL", + /* 1579 */ "MinebeaMitsumi Inc.", + /* 1580 */ "ASPion GmbH", + /* 1581 */ "Vossloh-Schwabe Deutschland GmbH", + /* 1582 */ "Procept", + /* 1583 */ "ONKYO Corporation", + /* 1584 */ "Asthrea D.O.O.", + /* 1585 */ "Fortiori Design LLC", + /* 1586 */ "Hugo Muller GmbH & Co KG", + /* 1587 */ "Wangi Lai PLT", + /* 1588 */ "Fanstel Corp", + /* 1589 */ "Crookwood", + /* 1590 */ "ELECTRONICA INTEGRAL DE SONIDO S.A.", + /* 1591 */ "GiP Innovation Tools GmbH", + /* 1592 */ "LX SOLUTIONS PTY LIMITED", + /* 1593 */ "Shenzhen Minew Technologies Co., Ltd.", + /* 1594 */ "Prolojik Limited", + /* 1595 */ "Kromek Group Plc", + /* 1596 */ "Contec Medical Systems Co., Ltd.", + /* 1597 */ "Xradio Technology Co.,Ltd.", + /* 1598 */ "The Indoor Lab, LLC", + /* 1599 */ "LDL TECHNOLOGY", + /* 1600 */ "Parkifi", + /* 1601 */ "Revenue Collection Systems FRANCE SAS", + /* 1602 */ "Bluetrum Technology Co.,Ltd", + /* 1603 */ "makita corporation", + /* 1604 */ "Apogee Instruments", + /* 1605 */ "BM3", + /* 1606 */ "SGV Group Holding GmbH & Co. KG", + /* 1607 */ "MED-EL", + /* 1608 */ "Ultune Technologies", + /* 1609 */ "Ryeex Technology Co.,Ltd.", + /* 1610 */ "Open Research Institute, Inc.", + /* 1611 */ "Scale-Tec, Ltd", + /* 1612 */ "Zumtobel Group AG", + /* 1613 */ "iLOQ Oy", + /* 1614 */ "KRUXWorks Technologies Private Limited", + /* 1615 */ "Digital Matter Pty Ltd", + /* 1616 */ "Coravin, Inc.", + /* 1617 */ "Stasis Labs, Inc.", + /* 1618 */ "ITZ Innovations- und Technologiezentrum GmbH", + /* 1619 */ "Meggitt SA", + /* 1620 */ "Ledlenser GmbH & Co. KG", + /* 1621 */ "Renishaw PLC", + /* 1622 */ "ZhuHai AdvanPro Technology Company Limited", + /* 1623 */ "Meshtronix Limited", + /* 1624 */ "Payex Norge AS", + /* 1625 */ "UnSeen Technologies Oy", + /* 1626 */ "Zound Industries International AB", + /* 1627 */ "Sesam Solutions BV", + /* 1628 */ "PixArt Imaging Inc.", + /* 1629 */ "Panduit Corp.", + /* 1630 */ "Alo AB", + /* 1631 */ "Ricoh Company Ltd", + /* 1632 */ "RTC Industries, Inc.", + /* 1633 */ "Mode Lighting Limited", + /* 1634 */ "Particle Industries, Inc.", + /* 1635 */ "Advanced Telemetry Systems, Inc.", + /* 1636 */ "RHA TECHNOLOGIES LTD", + /* 1637 */ "Pure International Limited", + /* 1638 */ "WTO Werkzeug-Einrichtungen GmbH", + /* 1639 */ "Spark Technology Labs Inc.", + /* 1640 */ "Bleb Technology srl", + /* 1641 */ "Livanova USA, Inc.", + /* 1642 */ "Brady Worldwide Inc.", + /* 1643 */ "DewertOkin GmbH", + /* 1644 */ "Ztove ApS", + /* 1645 */ "Venso EcoSolutions AB", + /* 1646 */ "Eurotronik Kranj d.o.o.", + /* 1647 */ "Hug Technology Ltd", + /* 1648 */ "Gema Switzerland GmbH", + /* 1649 */ "Buzz Products Ltd.", + /* 1650 */ "Kopi", + /* 1651 */ "Innova Ideas Limited", + /* 1652 */ "BeSpoon", + /* 1653 */ "Deco Enterprises, Inc.", + /* 1654 */ "Expai Solutions Private Limited", + /* 1655 */ "Innovation First, Inc.", + /* 1656 */ "SABIK Offshore GmbH", + /* 1657 */ "4iiii Innovations Inc.", + /* 1658 */ "The Energy Conservatory, Inc.", + /* 1659 */ "I.FARM, INC.", + /* 1660 */ "Tile, Inc.", + /* 1661 */ "Form Athletica Inc.", + /* 1662 */ "MbientLab Inc", + /* 1663 */ "NETGRID S.N.C. DI BISSOLI MATTEO, CAMPOREALE SIMONE, TOGNETTI FEDERICO", + /* 1664 */ "Mannkind Corporation", + /* 1665 */ "Trade FIDES a.s.", + /* 1666 */ "Photron Limited", + /* 1667 */ "Eltako GmbH", + /* 1668 */ "Dermalapps, LLC", + /* 1669 */ "Greenwald Industries", + /* 1670 */ "inQs Co., Ltd.", + /* 1671 */ "Cherry GmbH", + /* 1672 */ "Amsted Digital Solutions Inc.", + /* 1673 */ "Tacx b.v.", + /* 1674 */ "Raytac Corporation", + /* 1675 */ "Jiangsu Teranovo Tech Co., Ltd.", + /* 1676 */ "Changzhou Sound Dragon Electronics and Acoustics Co., Ltd", + /* 1677 */ "JetBeep Inc.", + /* 1678 */ "Razer Inc.", + /* 1679 */ "JRM Group Limited", + /* 1680 */ "Eccrine Systems, Inc.", + /* 1681 */ "Curie Point AB", + /* 1682 */ "Georg Fischer AG", + /* 1683 */ "Hach - Danaher", + /* 1684 */ "T&A Laboratories LLC", + /* 1685 */ "Koki Holdings Co., Ltd.", + /* 1686 */ "Gunakar Private Limited", + /* 1687 */ "Stemco Products Inc", + /* 1688 */ "Wood IT Security, LLC", + /* 1689 */ "RandomLab SAS", + /* 1690 */ "Adero, Inc. (formerly as TrackR, Inc.)", + /* 1691 */ "Dragonchip Limited", + /* 1692 */ "Noomi AB", + /* 1693 */ "Vakaros LLC", + /* 1694 */ "Delta Electronics, Inc.", + /* 1695 */ "FlowMotion Technologies AS", + /* 1696 */ "OBIQ Location Technology Inc.", + /* 1697 */ "Cardo Systems, Ltd", + /* 1698 */ "Globalworx GmbH", + /* 1699 */ "Nymbus, LLC", + /* 1700 */ "Sanyo Techno Solutions Tottori Co., Ltd.", + /* 1701 */ "TEKZITEL PTY LTD", + /* 1702 */ "Roambee Corporation", + /* 1703 */ "Chipsea Technologies (ShenZhen) Corp.", + /* 1704 */ "GD Midea Air-Conditioning Equipment Co., Ltd.", + /* 1705 */ "Soundmax Electronics Limited", + /* 1706 */ "Produal Oy", + /* 1707 */ "HMS Industrial Networks AB", + /* 1708 */ "Ingchips Technology Co., Ltd.", + /* 1709 */ "InnovaSea Systems Inc.", + /* 1710 */ "SenseQ Inc.", + /* 1711 */ "Shoof Technologies", + /* 1712 */ "BRK Brands, Inc.", + /* 1713 */ "SimpliSafe, Inc.", + /* 1714 */ "Tussock Innovation 2013 Limited", + /* 1715 */ "The Hablab ApS", + /* 1716 */ "Sencilion Oy", + /* 1717 */ "Wabilogic Ltd.", + /* 1718 */ "Sociometric Solutions, Inc.", + /* 1719 */ "iCOGNIZE GmbH", + /* 1720 */ "ShadeCraft, Inc", + /* 1721 */ "Beflex Inc.", + /* 1722 */ "Beaconzone Ltd", + /* 1723 */ "Leaftronix Analogic Solutions Private Limited", + /* 1724 */ "TWS Srl", + /* 1725 */ "ABB Oy", + /* 1726 */ "HitSeed Oy", + /* 1727 */ "Delcom Products Inc.", + /* 1728 */ "CAME S.p.A.", + /* 1729 */ "Alarm.com Holdings, Inc", + /* 1730 */ "Measurlogic Inc.", + /* 1731 */ "King I Electronics.Co.,Ltd", + /* 1732 */ "Dream Labs GmbH", + /* 1733 */ "Urban Compass, Inc", + /* 1734 */ "Simm Tronic Limited", + /* 1735 */ "Somatix Inc", + /* 1736 */ "Storz & Bickel GmbH & Co. KG", + /* 1737 */ "MYLAPS B.V.", + /* 1738 */ "Shenzhen Zhongguang Infotech Technology Development Co., Ltd", + /* 1739 */ "Dyeware, LLC", + /* 1740 */ "Dongguan SmartAction Technology Co.,Ltd.", + /* 1741 */ "DIG Corporation", + /* 1742 */ "FIOR & GENTZ", + /* 1743 */ "Belparts N.V.", + /* 1744 */ "Etekcity Corporation", + /* 1745 */ "Meyer Sound Laboratories, Incorporated", + /* 1746 */ "CeoTronics AG", + /* 1747 */ "TriTeq Lock and Security, LLC", + /* 1748 */ "DYNAKODE TECHNOLOGY PRIVATE LIMITED", + /* 1749 */ "Sensirion AG", + /* 1750 */ "JCT Healthcare Pty Ltd", + /* 1751 */ "FUBA Automotive Electronics GmbH", + /* 1752 */ "AW Company", + /* 1753 */ "Shanghai Mountain View Silicon Co.,Ltd.", + /* 1754 */ "Zliide Technologies ApS", + /* 1755 */ "Automatic Labs, Inc.", + /* 1756 */ "Industrial Network Controls, LLC", + /* 1757 */ "Intellithings Ltd.", + /* 1758 */ "Navcast, Inc.", + /* 1759 */ "Hubbell Lighting, Inc.", + /* 1760 */ "Avaya", + /* 1761 */ "Milestone AV Technologies LLC", + /* 1762 */ "Alango Technologies Ltd", + /* 1763 */ "Spinlock Ltd", + /* 1764 */ "Aluna", + /* 1765 */ "OPTEX CO.,LTD.", + /* 1766 */ "NIHON DENGYO KOUSAKU", + /* 1767 */ "VELUX A/S", + /* 1768 */ "Almendo Technologies GmbH", + /* 1769 */ "Zmartfun Electronics, Inc.", + /* 1770 */ "SafeLine Sweden AB", + /* 1771 */ "Houston Radar LLC", + /* 1772 */ "Sigur", + /* 1773 */ "J Neades Ltd", + /* 1774 */ "Avantis Systems Limited", + /* 1775 */ "ALCARE Co., Ltd.", + /* 1776 */ "Chargy Technologies, SL", + /* 1777 */ "Shibutani Co., Ltd.", + /* 1778 */ "Trapper Data AB", + /* 1779 */ "Alfred International Inc.", + /* 1780 */ "Near Field Solutions Ltd", + /* 1781 */ "Vigil Technologies Inc.", + /* 1782 */ "Vitulo Plus BV", + /* 1783 */ "WILKA Schliesstechnik GmbH", + /* 1784 */ "BodyPlus Technology Co.,Ltd", + /* 1785 */ "happybrush GmbH", + /* 1786 */ "Enequi AB", + /* 1787 */ "Sartorius AG", + /* 1788 */ "Tom Communication Industrial Co.,Ltd.", + /* 1789 */ "ESS Embedded System Solutions Inc.", + /* 1790 */ "Mahr GmbH", + /* 1791 */ "Redpine Signals Inc", + /* 1792 */ "TraqFreq LLC", + /* 1793 */ "PAFERS TECH", + /* 1794 */ "Akciju sabiedriba \"SAF TEHNIKA\"", + /* 1795 */ "Beijing Jingdong Century Trading Co., Ltd.", + /* 1796 */ "JBX Designs Inc.", + /* 1797 */ "AB Electrolux", + /* 1798 */ "Wernher von Braun Center for ASdvanced Research", + /* 1799 */ "Essity Hygiene and Health Aktiebolag", + /* 1800 */ "Be Interactive Co., Ltd", + /* 1801 */ "Carewear Corp.", + /* 1802 */ "Huf Hülsbeck & Fürst GmbH & Co. KG", + /* 1803 */ "Element Products, Inc.", + /* 1804 */ "Beijing Winner Microelectronics Co.,Ltd", + /* 1805 */ "SmartSnugg Pty Ltd", + /* 1806 */ "FiveCo Sarl", + /* 1807 */ "California Things Inc.", + /* 1808 */ "Audiodo AB", + /* 1809 */ "ABAX AS", + /* 1810 */ "Bull Group Company Limited", + /* 1811 */ "Respiri Limited", + /* 1812 */ "MindPeace Safety LLC", + /* 1813 */ "Vgyan Solutions", + /* 1814 */ "Altonics", + /* 1815 */ "iQsquare BV", + /* 1816 */ "IDIBAIX enginneering", + /* 1817 */ "ECSG", + /* 1818 */ "REVSMART WEARABLE HK CO LTD", + /* 1819 */ "Precor", + /* 1820 */ "F5 Sports, Inc", + /* 1821 */ "exoTIC Systems", + /* 1822 */ "DONGGUAN HELE ELECTRONICS CO., LTD", + /* 1823 */ "Dongguan Liesheng Electronic Co.Ltd", + /* 1824 */ "Oculeve, Inc.", + /* 1825 */ "Clover Network, Inc.", + /* 1826 */ "Xiamen Eholder Electronics Co.Ltd", + /* 1827 */ "Ford Motor Company", + /* 1828 */ "Guangzhou SuperSound Information Technology Co.,Ltd", + /* 1829 */ "Tedee Sp. z o.o.", + /* 1830 */ "PHC Corporation", + /* 1831 */ "STALKIT AS", + /* 1832 */ "Eli Lilly and Company", + /* 1833 */ "SwaraLink Technologies", + /* 1834 */ "JMR embedded systems GmbH", + /* 1835 */ "Bitkey Inc.", + /* 1836 */ "GWA Hygiene GmbH", + /* 1837 */ "Safera Oy", + /* 1838 */ "Open Platform Systems LLC", + /* 1839 */ "OnePlus Electronics (Shenzhen) Co., Ltd.", + /* 1840 */ "Wildlife Acoustics, Inc.", + /* 1841 */ "ABLIC Inc.", + /* 1842 */ "Dairy Tech, Inc.", + /* 1843 */ "Iguanavation, Inc.", + /* 1844 */ "DiUS Computing Pty Ltd", + /* 1845 */ "UpRight Technologies LTD", + /* 1846 */ "FrancisFund, LLC", + /* 1847 */ "LLC Navitek", + /* 1848 */ "Glass Security Pte Ltd", + /* 1849 */ "Jiangsu Qinheng Co., Ltd.", + /* 1850 */ "Chandler Systems Inc.", + /* 1851 */ "Fantini Cosmi s.p.a.", + /* 1852 */ "Acubit ApS", + /* 1853 */ "Beijing Hao Heng Tian Tech Co., Ltd.", + /* 1854 */ "Bluepack S.R.L.", + /* 1855 */ "Beijing Unisoc Technologies Co., Ltd.", + /* 1856 */ "HITIQ LIMITED", + /* 1857 */ "MAC SRL", + /* 1858 */ "DML LLC", + /* 1859 */ "Sanofi", + /* 1860 */ "SOCOMEC", + /* 1861 */ "WIZNOVA, Inc.", + /* 1862 */ "Seitec Elektronik GmbH", + /* 1863 */ "OR Technologies Pty Ltd", + /* 1864 */ "GuangZhou KuGou Computer Technology Co.Ltd", + /* 1865 */ "DIAODIAO (Beijing) Technology Co., Ltd.", + /* 1866 */ "Illusory Studios LLC", + /* 1867 */ "Sarvavid Software Solutions LLP", + /* 1868 */ "iopool s.a.", + /* 1869 */ "Amtech Systems, LLC", + /* 1870 */ "EAGLE DETECTION SA", + /* 1871 */ "MEDIATECH S.R.L.", + /* 1872 */ "Hamilton Professional Services of Canada Incorporated", + /* 1873 */ "Changsha JEMO IC Design Co.,Ltd", + /* 1874 */ "Elatec GmbH", + /* 1875 */ "JLG Industries, Inc.", + /* 1876 */ "Michael Parkin", + /* 1877 */ "Brother Industries, Ltd", + /* 1878 */ "Lumens For Less, Inc", + /* 1879 */ "ELA Innovation", + /* 1880 */ "umanSense AB", + /* 1881 */ "Shanghai InGeek Cyber Security Co., Ltd.", + /* 1882 */ "HARMAN CO.,LTD.", + /* 1883 */ "Smart Sensor Devices AB", + /* 1884 */ "Antitronics Inc.", + /* 1885 */ "RHOMBUS SYSTEMS, INC.", + /* 1886 */ "Katerra Inc.", + /* 1887 */ "Remote Solution Co., LTD.", + /* 1888 */ "Vimar SpA", + /* 1889 */ "Mantis Tech LLC", + /* 1890 */ "TerOpta Ltd", + /* 1891 */ "PIKOLIN S.L.", + /* 1892 */ "WWZN Information Technology Company Limited", + /* 1893 */ "Voxx International", + /* 1894 */ "ART AND PROGRAM, INC.", + /* 1895 */ "NITTO DENKO ASIA TECHNICAL CENTRE PTE. LTD.", + /* 1896 */ "Peloton Interactive Inc.", + /* 1897 */ "Force Impact Technologies", + /* 1898 */ "Dmac Mobile Developments, LLC", + /* 1899 */ "Engineered Medical Technologies", + /* 1900 */ "Noodle Technology inc", + /* 1901 */ "Graesslin GmbH", + /* 1902 */ "WuQi technologies, Inc.", + /* 1903 */ "Successful Endeavours Pty Ltd", + /* 1904 */ "InnoCon Medical ApS", + /* 1905 */ "Corvex Connected Safety", + /* 1906 */ "Thirdwayv Inc.", + /* 1907 */ "Echoflex Solutions Inc.", + /* 1908 */ "C-MAX Asia Limited", + /* 1909 */ "4eBusiness GmbH", + /* 1910 */ "Cyber Transport Control GmbH", + /* 1911 */ "Cue", + /* 1912 */ "KOAMTAC INC.", + /* 1913 */ "Loopshore Oy", + /* 1914 */ "Niruha Systems Private Limited", + /* 1915 */ "AmaterZ, Inc.", + /* 1916 */ "radius co., ltd.", + /* 1917 */ "Sensority, s.r.o.", + /* 1918 */ "Sparkage Inc.", + /* 1919 */ "Glenview Software Corporation", + /* 1920 */ "Finch Technologies Ltd.", + /* 1921 */ "Qingping Technology (Beijing) Co., Ltd.", + /* 1922 */ "DeviceDrive AS", + /* 1923 */ "ESEMBER LIMITED LIABILITY COMPANY", + /* 1924 */ "audifon GmbH & Co. KG", + /* 1925 */ "O2 Micro, Inc.", + /* 1926 */ "HLP Controls Pty Limited", + /* 1927 */ "Pangaea Solution", + /* 1928 */ "BubblyNet, LLC", + /* 1930 */ "The Wildflower Foundation", + /* 1931 */ "Optikam Tech Inc.", + /* 1932 */ "MINIBREW HOLDING B.V", + /* 1933 */ "Cybex GmbH", + /* 1934 */ "FUJIMIC NIIGATA, INC.", + /* 1935 */ "Hanna Instruments, Inc.", + /* 1936 */ "KOMPAN A/S", + /* 1937 */ "Scosche Industries, Inc.", + /* 1938 */ "Provo Craft", + /* 1939 */ "AEV spol. s r.o.", + /* 1940 */ "The Coca-Cola Company", + /* 1941 */ "GASTEC CORPORATION", + /* 1942 */ "StarLeaf Ltd", + /* 1943 */ "Water-i.d. GmbH", + /* 1944 */ "HoloKit, Inc.", + /* 1945 */ "PlantChoir Inc.", + /* 1946 */ "GuangDong Oppo Mobile Telecommunications Corp., Ltd.", + /* 1947 */ "CST ELECTRONICS (PROPRIETARY) LIMITED", + /* 1948 */ "Sky UK Limited", + /* 1949 */ "Digibale Pty Ltd", + /* 1950 */ "Smartloxx GmbH", + /* 1951 */ "Pune Scientific LLP", + /* 1952 */ "Regent Beleuchtungskorper AG", + /* 1953 */ "Apollo Neuroscience, Inc.", + /* 1954 */ "Roku, Inc.", + /* 1955 */ "Comcast Cable", + /* 1956 */ "Xiamen Mage Information Technology Co., Ltd.", + /* 1957 */ "RAB Lighting, Inc.", + /* 1958 */ "Musen Connect, Inc.", + /* 1959 */ "Zume, Inc.", + /* 1960 */ "conbee GmbH", + /* 1961 */ "Bruel & Kjaer Sound & Vibration", + /* 1962 */ "The Kroger Co.", + /* 1963 */ "Granite River Solutions, Inc.", + /* 1964 */ "LoupeDeck Oy", + /* 1965 */ "New H3C Technologies Co.,Ltd", + /* 1966 */ "Aurea Solucoes Tecnologicas Ltda.", + /* 1967 */ "Hong Kong Bouffalo Lab Limited", + /* 1968 */ "GV Concepts Inc.", + /* 1969 */ "Thomas Dynamics, LLC", + /* 1970 */ "Moeco IOT Inc.", + /* 1971 */ "2N TELEKOMUNIKACE a.s.", + /* 1972 */ "Hormann KG Antriebstechnik", + /* 1973 */ "CRONO CHIP, S.L.", + /* 1974 */ "Soundbrenner Limited", + /* 1975 */ "ETABLISSEMENTS GEORGES RENAULT", + /* 1976 */ "iSwip", + /* 1977 */ "Epona Biotec Limited", + /* 1978 */ "Battery-Biz Inc.", + /* 1979 */ "EPIC S.R.L.", + /* 1980 */ "KD CIRCUITS LLC", + /* 1981 */ "Genedrive Diagnostics Ltd", + /* 1982 */ "Axentia Technologies AB", + /* 1983 */ "REGULA Ltd.", + /* 1984 */ "Biral AG", + /* 1985 */ "A.W. Chesterton Company", + /* 1986 */ "Radinn AB", + /* 1987 */ "CIMTechniques, Inc.", + /* 1988 */ "Johnson Health Tech NA", + /* 1989 */ "June Life, Inc.", + /* 1990 */ "Bluenetics GmbH", + /* 1991 */ "iaconicDesign Inc.", + /* 1992 */ "WRLDS Creations AB", + /* 1993 */ "Skullcandy, Inc.", + /* 1994 */ "Modul-System HH AB", + /* 1995 */ "West Pharmaceutical Services, Inc.", + /* 1996 */ "Barnacle Systems Inc.", + /* 1997 */ "Smart Wave Technologies Canada Inc", + /* 1998 */ "Shanghai Top-Chip Microelectronics Tech. Co., LTD", + /* 1999 */ "NeoSensory, Inc.", + /* 2000 */ "Hangzhou Tuya Information Technology Co., Ltd", + /* 2001 */ "Shanghai Panchip Microelectronics Co., Ltd", + /* 2002 */ "React Accessibility Limited", + /* 2003 */ "LIVNEX Co.,Ltd.", + /* 2004 */ "Kano Computing Limited", + /* 2005 */ "hoots classic GmbH", + /* 2006 */ "ecobee Inc.", + /* 2007 */ "Nanjing Qinheng Microelectronics Co., Ltd", + /* 2008 */ "SOLUTIONS AMBRA INC.", + /* 2009 */ "Micro-Design, Inc.", + /* 2010 */ "STARLITE Co., Ltd.", + /* 2011 */ "Remedee Labs", + /* 2012 */ "ThingOS GmbH", + /* 2013 */ "Linear Circuits", + /* 2014 */ "Unlimited Engineering SL", + /* 2015 */ "Snap-on Incorporated", + /* 2016 */ "Edifier International Limited", + /* 2017 */ "Lucie Labs", + /* 2018 */ "Alfred Kaercher SE & Co. KG", + /* 2019 */ "Audiowise Technology Inc.", + /* 2020 */ "Geeksme S.L.", + /* 2021 */ "Minut, Inc.", + /* 2022 */ "Autogrow Systems Limited", + /* 2023 */ "Komfort IQ, Inc.", + /* 2024 */ "Packetcraft, Inc.", + /* 2025 */ "Häfele GmbH & Co KG", + /* 2026 */ "ShapeLog, Inc.", + /* 2027 */ "NOVABASE S.R.L.", + /* 2028 */ "Frecce LLC", + /* 2029 */ "Joule IQ, INC.", + /* 2030 */ "KidzTek LLC", + /* 2031 */ "Aktiebolaget Sandvik Coromant", + /* 2032 */ "e-moola.com Pty Ltd", + /* 2033 */ "GSM Innovations Pty Ltd", + /* 2034 */ "SERENE GROUP, INC", + /* 2035 */ "DIGISINE ENERGYTECH CO. LTD.", + /* 2036 */ "MEDIRLAB Orvosbiologiai Fejleszto Korlatolt Felelossegu Tarsasag", + /* 2037 */ "Byton North America Corporation", + /* 2038 */ "Shenzhen TonliScience and Technology Development Co.,Ltd", + /* 2039 */ "Cesar Systems Ltd.", + /* 2040 */ "quip NYC Inc.", + /* 2041 */ "Direct Communication Solutions, Inc.", + /* 2042 */ "Klipsch Group, Inc.", + /* 2043 */ "Access Co., Ltd", + /* 2044 */ "Renault SA", + /* 2045 */ "JSK CO., LTD.", + /* 2046 */ "BIROTA", + /* 2047 */ "maxon motor ltd.", + /* 2048 */ "Optek", + /* 2049 */ "CRONUS ELECTRONICS LTD", + /* 2050 */ "NantSound, Inc.", + /* 2051 */ "Domintell s.a.", + /* 2052 */ "Andon Health Co.,Ltd", + /* 2053 */ "Urbanminded Ltd", + /* 2054 */ "TYRI Sweden AB", + /* 2055 */ "ECD Electronic Components GmbH Dresden", + /* 2056 */ "SISTEMAS KERN, SOCIEDAD ANÓMINA", + /* 2057 */ "Trulli Audio", + /* 2058 */ "Altaneos", + /* 2059 */ "Nanoleaf Canada Limited", + /* 2060 */ "Ingy B.V.", + /* 2061 */ "Azbil Co.", + /* 2062 */ "TATTCOM LLC", + /* 2063 */ "Paradox Engineering SA", + /* 2064 */ "LECO Corporation", + /* 2065 */ "Becker Antriebe GmbH", + /* 2066 */ "Mstream Technologies., Inc.", + /* 2067 */ "Flextronics International USA Inc.", + /* 2068 */ "Ossur hf.", + /* 2069 */ "SKC Inc", + /* 2070 */ "SPICA SYSTEMS LLC", + /* 2071 */ "Wangs Alliance Corporation", + /* 2072 */ "tatwah SA", + /* 2073 */ "Hunter Douglas Inc", + /* 2074 */ "Shenzhen Conex", + /* 2075 */ "DIM3", + /* 2076 */ "Bobrick Washroom Equipment, Inc.", + /* 2077 */ "Potrykus Holdings and Development LLC", + /* 2078 */ "iNFORM Technology GmbH", + /* 2079 */ "eSenseLab LTD", + /* 2080 */ "Brilliant Home Technology, Inc.", + /* 2081 */ "INOVA Geophysical, Inc.", + /* 2082 */ "adafruit industries", + /* 2083 */ "Nexite Ltd", + /* 2084 */ "8Power Limited", + /* 2085 */ "CME PTE. LTD.", + /* 2086 */ "Hyundai Motor Company", + /* 2087 */ "Kickmaker", + /* 2088 */ "Shanghai Suisheng Information Technology Co., Ltd.", + /* 2089 */ "HEXAGON", + /* 2090 */ "Mitutoyo Corporation", + /* 2091 */ "shenzhen fitcare electronics Co.,Ltd", + /* 2092 */ "INGICS TECHNOLOGY CO., LTD.", + /* 2093 */ "INCUS PERFORMANCE LTD.", + /* 2094 */ "ABB S.p.A.", + /* 2095 */ "Blippit AB", + /* 2096 */ "Core Health and Fitness LLC", + /* 2097 */ "Foxble, LLC", + /* 2098 */ "Intermotive,Inc.", + /* 2099 */ "Conneqtech B.V.", + /* 2100 */ "RIKEN KEIKI CO., LTD.,", + /* 2101 */ "Canopy Growth Corporation", + /* 2102 */ "Bitwards Oy", + /* 2103 */ "vivo Mobile Communication Co., Ltd.", + /* 2104 */ "Etymotic Research, Inc.", + /* 2105 */ "A puissance 3", + /* 2106 */ "BPW Bergische Achsen Kommanditgesellschaft", + /* 2107 */ "Piaggio Fast Forward", + /* 2108 */ "BeerTech LTD", + /* 2109 */ "Tokenize, Inc.", + /* 2110 */ "Zorachka LTD", + /* 2111 */ "D-Link Corp.", + /* 2112 */ "Down Range Systems LLC", + /* 2113 */ "General Luminaire (Shanghai) Co., Ltd.", + /* 2114 */ "Tangshan HongJia electronic technology co., LTD.", + /* 2115 */ "FRAGRANCE DELIVERY TECHNOLOGIES LTD", + /* 2116 */ "Pepperl + Fuchs GmbH", + /* 2117 */ "Dometic Corporation", + /* 2118 */ "USound GmbH", + /* 2119 */ "DNANUDGE LIMITED", + /* 2120 */ "JUJU JOINTS CANADA CORP.", + /* 2121 */ "Dopple Technologies B.V.", + /* 2122 */ "ARCOM", + /* 2123 */ "Biotechware SRL", + /* 2124 */ "ORSO Inc.", + /* 2125 */ "SafePort", + /* 2126 */ "Carol Cole Company", + /* 2127 */ "Embedded Fitness B.V.", + /* 2128 */ "Yealink (Xiamen) Network Technology Co.,LTD", + /* 2129 */ "Subeca, Inc.", + /* 2130 */ "Cognosos, Inc.", + /* 2131 */ "Pektron Group Limited", + /* 2132 */ "Tap Sound System", + /* 2133 */ "Helios Hockey, Inc.", + /* 2134 */ "Canopy Growth Corporation", + /* 2135 */ "Parsyl Inc", + /* 2136 */ "SOUNDBOKS", + /* 2137 */ "BlueUp", + /* 2138 */ "DAKATECH", + /* 2139 */ "RICOH ELECTRONIC DEVICES CO., LTD.", + /* 2140 */ "ACOS CO.,LTD.", + /* 2141 */ "Guilin Zhishen Information Technology Co.,Ltd.", + /* 2142 */ "Krog Systems LLC", + /* 2143 */ "COMPEGPS TEAM,SOCIEDAD LIMITADA", + /* 2144 */ "Alflex Products B.V.", + /* 2145 */ "SmartSensor Labs Ltd", + /* 2146 */ "SmartDrive Inc.", + /* 2147 */ "Yo-tronics Technology Co., Ltd.", + /* 2148 */ "Rafaelmicro", + /* 2149 */ "Emergency Lighting Products Limited", + /* 2150 */ "LAONZ Co.,Ltd", + /* 2151 */ "Western Digital Techologies, Inc.", + /* 2152 */ "WIOsense GmbH & Co. KG", + /* 2153 */ "EVVA Sicherheitstechnologie GmbH", + /* 2154 */ "Odic Incorporated", + /* 2155 */ "Pacific Track, LLC", + /* 2156 */ "Revvo Technologies, Inc.", + /* 2157 */ "Biometrika d.o.o.", + /* 2158 */ "Vorwerk Elektrowerke GmbH & Co. KG", + /* 2159 */ "Trackunit A/S", + /* 2160 */ "Wyze Labs, Inc", + /* 2161 */ "Dension Elektronikai Kft. (formerly: Dension Audio Systems Ltd.)", + /* 2162 */ "11 Health & Technologies Limited", + /* 2163 */ "Innophase Incorporated", + /* 2164 */ "Treegreen Limited", + /* 2165 */ "Berner International LLC", + /* 2166 */ "SmartResQ ApS", + /* 2167 */ "Tome, Inc.", + /* 2168 */ "The Chamberlain Group, Inc.", + /* 2169 */ "MIZUNO Corporation", + /* 2170 */ "ZRF, LLC", + /* 2171 */ "BYSTAMP", + /* 2172 */ "Crosscan GmbH", + /* 2173 */ "Konftel AB", + /* 2174 */ "1bar.net Limited", + /* 2175 */ "Phillips Connect Technologies LLC", + /* 2176 */ "imagiLabs AB", + /* 2177 */ "Optalert", + /* 2178 */ "PSYONIC, Inc.", + /* 2179 */ "Wintersteiger AG", + /* 2180 */ "Controlid Industria, Comercio de Hardware e Servicos de Tecnologia Ltda", + /* 2181 */ "LEVOLOR, INC.", + /* 2182 */ "Xsens Technologies B.V.", + /* 2183 */ "Hydro-Gear Limited Partnership", + /* 2184 */ "EnPointe Fencing Pty Ltd", + /* 2185 */ "XANTHIO", + /* 2186 */ "sclak s.r.l.", + /* 2187 */ "Tricorder Arraay Technologies LLC", + /* 2188 */ "GB Solution co.,Ltd", + /* 2189 */ "Soliton Systems K.K.", + /* 2190 */ "GIGA-TMS INC", + /* 2191 */ "Tait International Limited", + /* 2192 */ "NICHIEI INTEC CO., LTD.", + /* 2193 */ "SmartWireless GmbH & Co. KG", + /* 2194 */ "Ingenieurbuero Birnfeld UG (haftungsbeschraenkt)", + /* 2195 */ "Maytronics Ltd", + /* 2196 */ "EPIFIT", + /* 2197 */ "Gimer medical", + /* 2198 */ "Nokian Renkaat Oyj", + /* 2199 */ "Current Lighting Solutions LLC", + /* 2200 */ "Sensibo, Inc.", + /* 2201 */ "SFS unimarket AG", + /* 2202 */ "Private limited company \"Teltonika\"", + /* 2203 */ "Saucon Technologies", + /* 2204 */ "Embedded Devices Co. Company", + /* 2205 */ "J-J.A.D.E. Enterprise LLC", + /* 2206 */ "i-SENS, inc.", + /* 2207 */ "Witschi Electronic Ltd", + /* 2208 */ "Aclara Technologies LLC", + /* 2209 */ "EXEO TECH CORPORATION", + /* 2210 */ "Epic Systems Co., Ltd.", + /* 2211 */ "Hoffmann SE", + /* 2212 */ "Realme Chongqing Mobile Telecommunications Corp., Ltd.", + /* 2213 */ "UMEHEAL Ltd", + /* 2214 */ "Intelligenceworks Inc.", + /* 2215 */ "TGR 1.618 Limited", + /* 2216 */ "Shanghai Kfcube Inc", + /* 2217 */ "Fraunhofer IIS", + /* 2218 */ "SZ DJI TECHNOLOGY CO.,LTD", + /* 2219 */ "Coburn Technology, LLC", + /* 2220 */ "Topre Corporation", + /* 2221 */ "Kayamatics Limited", + /* 2222 */ "Moticon ReGo AG", + /* 2223 */ "Polidea Sp. z o.o.", + /* 2224 */ "Trivedi Advanced Technologies LLC", + /* 2225 */ "CORE|vision BV", + /* 2226 */ "PF SCHWEISSTECHNOLOGIE GMBH", + /* 2227 */ "IONIQ Skincare GmbH & Co. KG", + /* 2228 */ "Sengled Co., Ltd.", + /* 2229 */ "TransferFi", + /* 2230 */ "Boehringer Ingelheim Vetmedica GmbH" + }; + + return (m >= SIZE(t)? "?" : t[m]); +} /* hci_manufacturer2str */ + +char const * +hci_commands2str(uint8_t *commands, char *buffer, int size) +{ + static char const * const t[][8] = { + { /* byte 0 */ + /* 0 */ "<HCI_Inquiry> ", + /* 1 */ "<HCI_Inquiry_Cancel> ", + /* 2 */ "<HCI_Periodic_Inquiry_Mode> ", + /* 3 */ "<HCI_Exit_Periodic_Inquiry_Mode> ", + /* 4 */ "<HCI_Create_Connection> ", + /* 5 */ "<HCI_Disconnect> ", + /* 6 */ "<HCI_Add_SCO_Connection (deprecated)> ", + /* 7 */ "<HCI_Create_Connection_Cancel> " + }, + { /* byte 1 */ + /* 0 */ "<HCI_Accept_Connection_Request> ", + /* 1 */ "<HCI_Reject_Connection_Request> ", + /* 2 */ "<HCI_Link_Key_Request_Reply> ", + /* 3 */ "<HCI_Link_Key_Request_Negative_Reply> ", + /* 4 */ "<HCI_PIN_Code_Request_Reply> ", + /* 5 */ "<HCI_PIN_Code_Request_Negative_Reply> ", + /* 6 */ "<HCI_Change_Connection_Packet_Type> ", + /* 7 */ "<HCI_Authentication_Requested> " + }, + { /* byte 2 */ + /* 0 */ "<HCI_Set_Connection_Encryption> ", + /* 1 */ "<HCI_Change_Connection_Link_Key> ", + /* 2 */ "<HCI_Master_Link_Key> ", + /* 3 */ "<HCI_Remote_Name_Request> ", + /* 4 */ "<HCI_Remote_Name_Request_Cancel> ", + /* 5 */ "<HCI_Read_Remote_Supported_Features> ", + /* 6 */ "<HCI_Read_Remote_Extended_Features> ", + /* 7 */ "<HCI_Read_Remote_Version_Information> " + }, + { /* byte 3 */ + /* 0 */ "<HCI_Read_Clock_Offset> ", + /* 1 */ "<HCI_Read_LMP_Handle> ", + /* 2 */ "<Unknown 3.2> ", + /* 3 */ "<Unknown 3.3> ", + /* 4 */ "<Unknown 3.4> ", + /* 5 */ "<Unknown 3.5> ", + /* 6 */ "<Unknown 3.6> ", + /* 7 */ "<Unknown 3.7> " + }, + { /* byte 4 */ + /* 0 */ "<Unknown 4.0> ", + /* 1 */ "<HCI_Hold_Mode> ", + /* 2 */ "<HCI_Sniff_Mode> ", + /* 3 */ "<HCI_Exit_Sniff_Mode> ", + /* 4 */ "<Previously used 4.4> ", + /* 5 */ "<Previously used 4.5> ", + /* 6 */ "<HCI_QoS_Setup> ", + /* 7 */ "<HCI_Role_Discovery> " + }, + { /* byte 5 */ + /* 0 */ "<HCI_Switch_Role> ", + /* 1 */ "<HCI_Read_Link_Policy_Settings> ", + /* 2 */ "<HCI_Write_Link_Policy_Settings> ", + /* 3 */ "<HCI_Read_Default_Link_Policy_Settings> ", + /* 4 */ "<HCI_Write_Default_Link_Policy_Settings> ", + /* 5 */ "<HCI_Flow_Specification> ", + /* 6 */ "<HCI_Set_Event_Mask> ", + /* 7 */ "<HCI_Reset> " + }, + { /* byte 6 */ + /* 0 */ "<HCI_Set_Event_Filter> ", + /* 1 */ "<HCI_Flush> ", + /* 2 */ "<HCI_Read_PIN_Type> ", + /* 3 */ "<HCI_Write_PIN_Type> ", + /* 4 */ "<Previously used 6.4> ", + /* 5 */ "<HCI_Read_Stored_Link_Key> ", + /* 6 */ "<HCI_Write_Stored_Link_Key> ", + /* 7 */ "<HCI_Delete_Stored_Link_Key> " + }, + { /* byte 7 */ + /* 0 */ "<HCI_Write_Local_Name> ", + /* 1 */ "<HCI_Read_Local_Name> ", + /* 2 */ "<HCI_Read_Connection_Accept_Timeout> ", + /* 3 */ "<HCI_Write_Connection_Accept_Timeout> ", + /* 4 */ "<HCI_Read_Page_Timeout> ", + /* 5 */ "<HCI_Write_Page_Timeout> ", + /* 6 */ "<HCI_Read_Scan_Enable> ", + /* 7 */ "<HCI_Write_Scan_Enable> " + }, + { /* byte 8 */ + /* 0 */ "<HCI_Read_Page_Scan_Activity> ", + /* 1 */ "<HCI_Write_Page_Scan_Activity> ", + /* 2 */ "<HCI_Read_Inquiry_Scan_Activity> ", + /* 3 */ "<HCI_Write_Inquiry_Scan_Activity> ", + /* 4 */ "<HCI_Read_Authentication_Enable> ", + /* 5 */ "<HCI_Write_Authentication_Enable> ", + /* 6 */ "<HCI_Read_Encryption_Mode (deprecated)> ", + /* 7 */ "<HCI_Write_Encryption_Mode (deprecated)> " + }, + { /* byte 9 */ + /* 0 */ "<HCI_Read_Class_Of_Device> ", + /* 1 */ "<HCI_Write_Class_Of_Device> ", + /* 2 */ "<HCI_Read_Voice_Setting> ", + /* 3 */ "<HCI_Write_Voice_Setting> ", + /* 4 */ "<HCI_Read_Automatic_Flush_Timeout> ", + /* 5 */ "<HCI_Write_Automatic_Flush_Timeout> ", + /* 6 */ "<HCI_Read_Num_Broadcast_Retransmissions> ", + /* 7 */ "<HCI_Write_Num_Broadcast_Retransmissions> " + }, + { /* byte 10 */ + /* 0 */ "<HCI_Read_Hold_Mode_Activity> ", + /* 1 */ "<HCI_Write_Hold_Mode_Activity> ", + /* 2 */ "<HCI_Read_Transmit_Power_Level> ", + /* 3 */ "<HCI_Read_Synchronous_Flow_Control_Enable> ", + /* 4 */ "<HCI_Write_Synchronous_Flow_Control_Enable> ", + /* 5 */ "<HCI_Set_Controller_To_Host_Flow_Control> ", + /* 6 */ "<HCI_Host_Buffer_Size> ", + /* 7 */ "<HCI_Host_Number_Of_Completed_Packets> " + }, + { /* byte 11 */ + /* 0 */ "<HCI_Read_Link_Supervision_Timeout> ", + /* 1 */ "<HCI_Write_Link_Supervision_Timeout> ", + /* 2 */ "<HCI_Read_Number_Of_Supported_IAC> ", + /* 3 */ "<HCI_Read_Current_IAC_LAP> ", + /* 4 */ "<HCI_Write_Current_IAC_LAP> ", + /* 5 */ "<HCI_Read_Page_Scan_Mode_Period (deprecated)> ", + /* 6 */ "<HCI_Write_Page_Scan_Mode_Period (deprecated)> ", + /* 7 */ "<HCI_Read_Page_Scan_Mode (deprecated)> " + }, + { /* byte 12 */ + /* 0 */ "<HCI_Write_Page_Scan_Mode (deprecated)> ", + /* 1 */ "<HCI_Set_AFH_Host_Channel_Classification> ", + /* 2 */ "<Unknown 12.2> ", + /* 3 */ "<Unknown 12.3> ", + /* 4 */ "<HCI_Read_Inquiry_Scan_Type> ", + /* 5 */ "<HCI_Write_Inquiry_Scan_Type> ", + /* 6 */ "<HCI_Read_Inquiry_Mode> ", + /* 7 */ "<HCI_Write_Inquiry_Mode> " + }, + { /* byte 13 */ + /* 0 */ "<HCI_Read_Page_Scan_Type> ", + /* 1 */ "<HCI_Write_Page_Scan_Type> ", + /* 2 */ "<HCI_Read_AFH_Channel_Assessment_Mode> ", + /* 3 */ "<HCI_Write_AFH_Channel_Assessment_Mode> ", + /* 4 */ "<Unknown 13.4> ", + /* 5 */ "<Unknown 13.5> ", + /* 6 */ "<Unknown 13.6> ", + /* 7 */ "<Unknown 13.7> " + }, + { /* byte 14 */ + /* 0 */ "<Unknown 14.0> ", + /* 1 */ "<Unknown 14.1>", + /* 2 */ "<Unknown 14.2> ", + /* 3 */ "<HCI_Read_Local_Version_Information> ", + /* 4 */ "<Unknown 14.4> ", + /* 5 */ "<HCI_Read_Local_Supported_Features> ", + /* 6 */ "<HCI_Read_Local_Extended_Features> ", + /* 7 */ "<HCI_Read_Buffer_Size> " + }, + { /* byte 15 */ + /* 0 */ "<HCI_Read_Country_Code (deprecated)> ", + /* 1 */ "<HCI_Read_BD_ADDR> ", + /* 2 */ "<HCI_Read_Failed_Contact_Counter> ", + /* 3 */ "<HCI_Reset_Failed_Contact_Counter> ", + /* 4 */ "<HCI_Read_Link_Quality> ", + /* 5 */ "<HCI_Read_RSSI> ", + /* 6 */ "<HCI_Read_AFH_Channel_Map> ", + /* 7 */ "<HCI_Read_Clock> " + }, + { /* byte 16 */ + /* 0 */ "<HCI_Read_Loopback_Mode> ", + /* 1 */ "<HCI_Write_Loopback_Mode> ", + /* 2 */ "<HCI_Enable_Device_Under_Test_Mode> ", + /* 3 */ "<HCI_Setup_Synchronous_Connection_Request> ", + /* 4 */ "<HCI_Accept_Synchronous_Connection_Request> ", + /* 5 */ "<HCI_Reject_Synchronous_Connection_Request> ", + /* 6 */ "<Unknown 16.6> ", + /* 7 */ "<Unknown 16,7> " + }, + { /* byte 17 */ + /* 0 */ "<HCI_Read_Extended_Inquiry_Response> ", + /* 1 */ "<HCI_Write_Extended_Inquiry_Response> ", + /* 2 */ "<HCI_Refresh_Encryption_Key> ", + /* 3 */ "<Unknown 17.3> ", + /* 4 */ "<HCI_Sniff_Subrating> ", + /* 5 */ "<HCI_Read_Simple_Pairing_Mode> ", + /* 6 */ "<HCI_Write_Simple_Pairing_Mode> ", + /* 7 */ "<HCI_Read_Local_OOB_Data> " + }, + { /* byte 18 */ + /* 0 */ "<HCI_Read_Inquiry_Response_Transmit_Power_Level> ", + /* 1 */ "<HCI_Write_Inquiry_Transmit_Power_Level> ", + /* 2 */ "<HCI_Read_Default_Erroneous_Data_Reporting> ", + /* 3 */ "<HCI_Write_Default_Erroneous_Data_Reporting> ", + /* 4 */ "<Unknown 18.4> ", + /* 5 */ "<Unknown 18.5> ", + /* 6 */ "<Unknown 18.6> ", + /* 7 */ "<HCI_IO_Capability_Request_Reply> " + }, + { /* byte 19 */ + /* 0 */ "<HCI_User_Confirmation_Request_Reply> ", + /* 1 */ "<HCI_User_Confirmation_Request_Negative_Reply> ", + /* 2 */ "<HCI_User_Passkey_Request_Reply> ", + /* 3 */ "<HCI_User_Passkey_Request_Negative_Reply> ", + /* 4 */ "<HCI_Remote_OOB_Data_Request_Reply> ", + /* 5 */ "<HCI_Write_Simple_Pairing_Debug_Mode> ", + /* 6 */ "<HCI_Enhanced_Flush> ", + /* 7 */ "<HCI_Remote_OOB_Data_Request_Negative_Reply> " + }, + { /* byte 20 */ + /* 0 */ "<Unknown 20.0> ", + /* 1 */ "<Unknown 20.1> ", + /* 2 */ "<HCI_Send_Keypress_Notification> ", + /* 3 */ "<HCI_IO_Capability_Request_Negative_Reply> ", + /* 4 */ "<HCI_Read_Encryption_Key_Size> ", + /* 5 */ "<Unknown 20.5> ", + /* 6 */ "<Unknown 20.6> ", + /* 7 */ "<Unknown 20.7> " + }, + { /* byte 21 */ + /* 0 */ "<HCI_Create_Physical_Link> ", + /* 1 */ "<HCI_Accept_Physical_Link> ", + /* 2 */ "<HCI_Disconnect_Physical_Link> ", + /* 3 */ "<HCI_Create_Logical_Link> ", + /* 4 */ "<HCI_Accept_Logical_Link> ", + /* 5 */ "<HCI_Disconnect_Logical_Link> ", + /* 6 */ "<HCI_Logical_Link_Cancel> ", + /* 7 */ "<HCI_Flow_Spec_Modify> " + }, + { /* byte 22 */ + /* 0 */ "<HCI_Read_Logical_Link_Accept_Timeout> ", + /* 1 */ "<HCI_Write_Logical_Link_Accept_Timeout> ", + /* 2 */ "<HCI_Set_Event_Mask_Page_2> ", + /* 3 */ "<HCI_Read_Location_Data> ", + /* 4 */ "<HCI_Write_Location_Data> ", + /* 5 */ "<HCI_Read_Local_AMP_Info> ", + /* 6 */ "<HCI_Read_Local_AMP_ASSOC> ", + /* 7 */ "<HCI_Write_Remote_AMP_ASSOC> " + }, + { /* byte 23 */ + /* 0 */ "<HCI_Read_Flow_Control_Mode> ", + /* 1 */ "<HCI_Write_Flow_Control_Mode> ", + /* 2 */ "<HCI_Read_Data_Block_Size> ", + /* 3 */ "<Unknown 23.3> ", + /* 4 */ "<Unknown 23.4> ", + /* 5 */ "<HCI_Enable_AMP_Receiver_Reports> ", + /* 6 */ "<HCI_AMP_Test_End> ", + /* 7 */ "<HCI_AMP_Test> " + }, + { /* byte 24 */ + /* 0 */ "<HCI_Read_Enhanced_Transmit_Power_Level> ", + /* 1 */ "<Unknown 24.1> ", + /* 2 */ "<HCI_Read_Best_Effort_Flush_Timeout> ", + /* 3 */ "<HCI_Write_Best_Effort_Flush_Timeout> ", + /* 4 */ "<HCI_Short_Range_Mode> ", + /* 5 */ "<HCI_Read_LE_Host_Support> ", + /* 6 */ "<HCI_Write_LE_Host_Support> ", + /* 7 */ "<Unknown 24.7> " + }, + { /* byte 25 */ + /* 0 */ "<HCI_LE_Set_Event_Mask> ", + /* 1 */ "<HCI_LE_Read_Buffer_Size [v1]> ", + /* 2 */ "<HCI_LE_Read_Local_Supported_Features> ", + /* 3 */ "<Unknown 25.3> ", + /* 4 */ "<HCI_LE_Set_Random_Address> ", + /* 5 */ "<HCI_LE_Set_Advertising_Parameters> ", + /* 6 */ "<HCI_LE_Read_Advertising_Physical_Channel_Tx_Power> ", + /* 7 */ "<HCI_LE_Set_Advertising_Data> " + }, + { /* byte 26 */ + /* 0 */ "<HCI_LE_Set_Scan_Response_Data> ", + /* 1 */ "<HCI_LE_Set_Advertising_Enable> ", + /* 2 */ "<HCI_LE_Set_Scan_Parameters> ", + /* 3 */ "<HCI_LE_Set_Scan_Enable> ", + /* 4 */ "<HCI_LE_Create_Connection> ", + /* 5 */ "<HCI_LE_Create_Connection_Cancel> ", + /* 6 */ "<HCI_LE_Read_White_List_Size> ", + /* 7 */ "<HCI_LE_Clear_White_List> " + }, + { /* byte 27 */ + /* 0 */ "<HCI_LE_Add_Device_To_White_List> ", + /* 1 */ "<HCI_LE_Remove_Device_From_White_List> ", + /* 2 */ "<HCI_LE_Connection_Update> ", + /* 3 */ "<HCI_LE_Set_Host_Channel_Classification> ", + /* 4 */ "<HCI_LE_Read_Channel_Map> ", + /* 5 */ "<HCI_LE_Read_Remote_Features> ", + /* 6 */ "<HCI_LE_Encrypt> ", + /* 7 */ "<HCI_LE_Rand> " + }, + { /* byte 28 */ + /* 0 */ "<HCI_LE_Enable_Encryption> ", + /* 1 */ "<HCI_LE_Long_Term_Key_Request_Reply> ", + /* 2 */ "<HCI_LE_Long_Term_Key_Request_Negative_Reply> ", + /* 3 */ "<HCI_LE_Read_Supported_States> ", + /* 4 */ "<HCI_LE_Receiver_Test [v1]> ", + /* 5 */ "<HCI_LE_Transmitter_Test [v1]> ", + /* 6 */ "<HCI_LE_Test_End> ", + /* 7 */ "<Unknown 28.7> " + }, + { /* byte 29 */ + /* 0 */ "<Unknown 29.0> ", + /* 1 */ "<Unknown 29.1> ", + /* 2 */ "<Unknown 29.2> ", + /* 3 */ "<HCI_Enhanced_Setup_Synchronous_Connection> ", + /* 4 */ "<HCI_Enhanced_Accept_Synchronous_Connection> ", + /* 5 */ "<HCI_Read_Local_Supported_Codecs> ", + /* 6 */ "<HCI_Set_MWS_Channel_Parameters> ", + /* 7 */ "<HCI_Set_External_Frame_Configuration> " + }, + { /* byte 30 */ + /* 0 */ "<HCI_Set_MWS_Signaling> ", + /* 1 */ "<HCI_Set_MWS_Transport_Layer> ", + /* 2 */ "<HCI_Set_MWS_Scan_Frequency_Table> ", + /* 3 */ "<HCI_Get_MWS_Transport_Layer_Configuration> ", + /* 4 */ "<HCI_Set_MWS_PATTERN_Configuration> ", + /* 5 */ "<HCI_Set_Triggered_Clock_Capture> ", + /* 6 */ "<HCI_Truncated_Page> ", + /* 7 */ "<HCI_Truncated_Page_Cancel> " + }, + { /* byte 31 */ + /* 0 */ "<HCI_Set_Connectionless_Slave_Broadcast> ", + /* 1 */ "<HCI_Set_Connectionless_Slave_Broadcast_Receive> ", + /* 2 */ "<HCI_Start_Synchronization_Train> ", + /* 3 */ "<HCI_Receive_Synchronization_Train> ", + /* 4 */ "<HCI_Set_Reserved_LT_ADDR> ", + /* 5 */ "<HCI_Delete_Reserved_LT_ADDR> ", + /* 6 */ "<HCI_Set_Connectionless_Slave_Broadcast_Data> ", + /* 7 */ "<HCI_Read_Synchronization_Train_Parameters> " + }, + { /* byte 32 */ + /* 0 */ "<HCI_Write_Synchronization_Train_Parameters> ", + /* 1 */ "<HCI_Remote_OOB_Extended_Data_Request_Reply> ", + /* 2 */ "<HCI_Read_Secure_Connections_Host_Support> ", + /* 3 */ "<HCI_Write_Secure_Connections_Host_Support> ", + /* 4 */ "<HCI_Read_Authenticated_Payload_Timeout> ", + /* 5 */ "<HCI_Write_Authenticated_Payload_Timeout> ", + /* 6 */ "<HCI_Read_Local_OOB_Extended_Data> ", + /* 7 */ "<HCI_Write_Secure_Connections_Test_Mode> " + }, + { /* byte 33 */ + /* 0 */ "<HCI_Read_Extended_Page_Timeout> ", + /* 1 */ "<HCI_Write_Extended_Page_Timeout> ", + /* 2 */ "<HCI_Read_Extended_Inquiry_Length> ", + /* 3 */ "<HCI_Write_Extended_Inquiry_Length> ", + /* 4 */ "<HCI_LE_Remote_Connection_Parameter_Request_Reply> ", + /* 5 */ "<HCI_LE_Remote_Connection_Parameter_Request_Negative_Reply> ", + /* 6 */ "<HCI_LE_Set_Data_Length> ", + /* 7 */ "<HCI_LE_Read_Suggested_Default_Data_Length> " + }, + { /* byte 34 */ + /* 0 */ "<HCI_LE_Write_Suggested_Default_Data_Length> ", + /* 1 */ "<HCI_LE_Read_Local_P-256_Public_Key> ", + /* 2 */ "<HCI_LE_Generate_DHKey [v1]> ", + /* 3 */ "<HCI_LE_Add_Device_To_Resolving_List> ", + /* 4 */ "<HCI_LE_Remove_Device_From_Resolving_List> ", + /* 5 */ "<HCI_LE_Clear_Resolving_List> ", + /* 6 */ "<HCI_LE_Read_Resolving_List_Size> ", + /* 7 */ "<HCI_LE_Read_Peer_Resolvable_Address> " + }, + { /* byte 35 */ + /* 0 */ "<HCI_LE_Read_Local_Resolvable_Address> ", + /* 1 */ "<HCI_LE_Set_Address_Resolution_Enable> ", + /* 2 */ "<HCI_LE_Set_Resolvable_Private_Address_Timeout> ", + /* 3 */ "<HCI_LE_Read_Maximum_Data_Length> ", + /* 4 */ "<HCI_LE_Read_PHY> ", + /* 5 */ "<HCI_LE_Set_Default_PHY> ", + /* 6 */ "<HCI_LE_Set_PHY> ", + /* 7 */ "<HCI_LE_Receiver_Test [v2]> " + }, + { /* byte 36 */ + /* 0 */ "<HCI_LE_Transmitter_Test [v2]> ", + /* 1 */ "<HCI_LE_Set_Advertising_Set_Random_Address> ", + /* 2 */ "<HCI_LE_Set_Extended_Advertising_Parameters> ", + /* 3 */ "<HCI_LE_Set_Extended_Advertising_Data> ", + /* 4 */ "<HCI_LE_Set_Extended_Scan_Response_Data> ", + /* 5 */ "<HCI_LE_Set_Extended_Advertising_Enable> ", + /* 6 */ "<HCI_LE_Read_Maximum_Advertising_Data_Length> ", + /* 7 */ "<HCI_LE_Read_Number_of_Supported_Advertising_Sets> " + }, + { /* byte 37 */ + /* 0 */ "<HCI_LE_Remove_Advertising_Set> ", + /* 1 */ "<HCI_LE_Clear_Advertising_Sets> ", + /* 2 */ "<HCI_LE_Set_Periodic_Advertising_Parameters> ", + /* 3 */ "<HCI_LE_Set_Periodic_Advertising_Data> ", + /* 4 */ "<HCI_LE_Set_Periodic_Advertising_Enable> ", + /* 5 */ "<HCI_LE_Set_Extended_Scan_Parameters> ", + /* 6 */ "<HCI_LE_Set_Extended_Scan_Enable> ", + /* 7 */ "<HCI_LE_Extended_Create_Connection> " + }, + { /* byte 38 */ + /* 0 */ "<HCI_LE_Periodic_Advertising_Create_Sync> ", + /* 1 */ "<HCI_LE_Periodic_Advertising_Create_Sync_Cancel> ", + /* 2 */ "<HCI_LE_Periodic_Advertising_Terminate_Sync> ", + /* 3 */ "<HCI_LE_Add_Device_To_Periodic_Advertiser_List> ", + /* 4 */ "<HCI_LE_Remove_Device_From_Periodic_Advertiser_List> ", + /* 5 */ "<HCI_LE_Clear_Periodic_Advertiser_List> ", + /* 6 */ "<HCI_LE_Read_Periodic_Advertiser_List_Size> ", + /* 7 */ "<HCI_LE_Read_Transmit_Power> " + }, + { /* byte 39 */ + /* 0 */ "<HCI_LE_Read_RF_Path_Compensation> ", + /* 1 */ "<HCI_LE_Write_RF_Path_Compensation> ", + /* 2 */ "<HCI_LE_Set_Privacy_Mode> ", + /* 3 */ "<HCI_LE_Receiver_Test [v3]> ", + /* 4 */ "<HCI_LE_Transmitter_Test [v3]> ", + /* 5 */ "<HCI_LE_Set_Connectionless_CTE_Transmit_Parameters> ", + /* 6 */ "<HCI_LE_Set_Connectionless_CTE_Transmit_Enable> ", + /* 7 */ "<HCI_LE_Set_Connectionless_IQ_Sampling_Enable> " + }, + { /* byte 40 */ + /* 0 */ "<HCI_LE_Set_Connection_CTE_Receive_Parameters> ", + /* 1 */ "<HCI_LE_Set_Connection_CTE_Transmit_Parameters> ", + /* 2 */ "<HCI_LE_Connection_CTE_Request_Enable> ", + /* 3 */ "<HCI_LE_Connection_CTE_Response_Enable> ", + /* 4 */ "<HCI_LE_Read_Antenna_Information> ", + /* 5 */ "<HCI_LE_Set_Periodic_Advertising_Receive_Enable> ", + /* 6 */ "<HCI_LE_Periodic_Advertising_Sync_Transfer> ", + /* 7 */ "<HCI_LE_Periodic_Advertising_Set_Info_Transfer> " + }, + { /* byte 41 */ + /* 0 */ "<HCI_LE_Set_Periodic_Advertising_Sync_Transfer_Parameters> ", + /* 1 */ "<HCI_LE_Set_Default_Periodic_Advertising_Sync_Transfer_- Parameters> ", + /* 2 */ "<HCI_LE_Generate_DHKey [v2]> ", + /* 3 */ "<HCI_Read_Local_Simple_Pairing_Options> ", + /* 4 */ "<HCI_LE_Modify_Sleep_Clock_Accuracy> ", + /* 5 */ "<HCI_LE_Read_Buffer_Size [v2]> ", + /* 6 */ "<HCI_LE_Read_ISO_TX_Sync> ", + /* 7 */ "<HCI_LE_Set_CIG_Parameters> " + }, + { /* byte 42 */ + /* 0 */ "<HCI_LE_Set_CIG_Parameters_Test> ", + /* 1 */ "<HCI_LE_Create_CIS> ", + /* 2 */ "<HCI_LE_Remove_CIG> ", + /* 3 */ "<HCI_LE_Accept_CIS_Request> ", + /* 4 */ "<HCI_LE_Reject_CIS_Request> ", + /* 5 */ "<HCI_LE_Create_BIG> ", + /* 6 */ "<HCI_LE_Create_BIG_Test> ", + /* 7 */ "<HCI_LE_Terminate_BIG> " + }, + { /* byte 43 */ + /* 0 */ "<HCI_LE_BIG_Create_Sync> ", + /* 1 */ "<HCI_LE_BIG_Terminate_Sync> ", + /* 2 */ "<HCI_LE_Request_Peer_SCA> ", + /* 3 */ "<HCI_LE_Setup_ISO_Data_Path> ", + /* 4 */ "<HCI_LE_Remove_ISO_Data_Path> ", + /* 5 */ "<HCI_LE_ISO_Transmit_Test> ", + /* 6 */ "<HCI_LE_ISO_Receive_Test> ", + /* 7 */ "<HCI_LE_ISO_Read_Test_Counters> " + }, + { /* byte 44 */ + /* 0 */ "<HCI_LE_ISO_Test_End> ", + /* 1 */ "<HCI_LE_Set_Host_Feature> ", + /* 2 */ "<HCI_LE_Read_ISO_Link_Quality> ", + /* 3 */ "<HCI_LE_Enhanced_Read_Transmit_Power_Level> ", + /* 4 */ "<HCI_LE_Read_Remote_Transmit_Power_Level> ", + /* 5 */ "<HCI_LE_Set_Path_Loss_Reporting_Parameters> ", + /* 6 */ "<HCI_LE_Set_Path_Loss_Reporting_Enable> ", + /* 7 */ "<HCI_LE_Set_Transmit_Power_Reporting_Enable> " + }, + { /* byte 45 */ + /* 0 */ "<HCI_LE_Transmitter_Test [v4]> ", + /* 1 */ "<HCI_Set_Ecosystem_Base_Interval> ", + /* 2 */ "<HCI_Read_Local_Supported_Codecs [v2]> ", + /* 3 */ "<HCI_Read_Local_Supported_Codec_Capabilities> ", + /* 4 */ "<HCI_Read_Local_Supported_Controller_Delay> ", + /* 5 */ "<HCI_Configure_Data_Path> ", + /* 6 */ "<Unknown 45.6> ", + /* 7 */ "<Unknown 45.7> " + }}; + + if (buffer != NULL && size > 0) { + int n, i, len0, len1; + + memset(buffer, 0, size); + size--; + + + for (n = 0; n < SIZE(t); n++) { + for (i = 0; i < SIZE(t[n]); i++) { + len0 = strlen(buffer); + if (len0 >= size) + goto done; + + if (commands[n] & (1 << i)) { + if (len1 + strlen(t[n][i]) > 60) { + len1 = 0; + buffer[len0 - 1] = '\n'; + } + + len1 += strlen(t[n][i]); + strncat(buffer, t[n][i], size - len0); + } + + } + } + } +done: + return (buffer); +} /* hci_commands2str */ + +char const * +hci_features2str(uint8_t *features, char *buffer, int size) +{ + static char const * const t[][8] = { + { /* byte 0 */ + /* 0 */ "<3-Slot> ", + /* 1 */ "<5-Slot> ", + /* 2 */ "<Encryption> ", + /* 3 */ "<Slot offset> ", + /* 4 */ "<Timing accuracy> ", + /* 5 */ "<Switch> ", + /* 6 */ "<Hold mode> ", + /* 7 */ "<Sniff mode> " + }, + { /* byte 1 */ + /* 0 */ "<Park mode> ", + /* 1 */ "<RSSI> ", + /* 2 */ "<Channel quality> ", + /* 3 */ "<SCO link> ", + /* 4 */ "<HV2 packets> ", + /* 5 */ "<HV3 packets> ", + /* 6 */ "<u-law log> ", + /* 7 */ "<A-law log> " + }, + { /* byte 2 */ + /* 0 */ "<CVSD> ", + /* 1 */ "<Paging scheme> ", + /* 2 */ "<Power control> ", + /* 3 */ "<Transparent SCO data> ", + /* 4 */ "<Flow control lag (bit0)> ", + /* 5 */ "<Flow control lag (bit1)> ", + /* 6 */ "<Flow control lag (bit2)> ", + /* 7 */ "<Broadcast Encryption> " + }, + { /* byte 3 */ + /* 0 */ "<Unknown 3.0> ", + /* 1 */ "<EDR ACL 2 Mb/s> ", + /* 2 */ "<EDR ACL 3 Mb/s> ", + /* 3 */ "<Enhanced inquiry scan> ", + /* 4 */ "<Interlaced inquiry scan> ", + /* 5 */ "<Interlaced page scan> ", + /* 6 */ "<RSSI with inquiry results> ", + /* 7 */ "<Extended SCO link (EV3 packets)> " + }, + { /* byte 4 */ + /* 0 */ "<EV4 packets> ", + /* 1 */ "<EV5 packets> ", + /* 2 */ "<Unknown 4.2> ", + /* 3 */ "<AFH capable slave> ", + /* 4 */ "<AFH classification slave> ", + /* 5 */ "<BR/EDR Not Supported> ", + /* 6 */ "<LE Supported (Controller)> ", + /* 7 */ "<3-Slot EDR ACL packets> " + }, + { /* byte 5 */ + /* 0 */ "<5-Slot EDR ACL packets> ", + /* 1 */ "<Sniff subrating> ", + /* 2 */ "<Pause encryption> ", + /* 3 */ "<AFH capable master> ", + /* 4 */ "<AFH classification master> ", + /* 5 */ "<EDR eSCO 2 Mb/s mode> ", + /* 6 */ "<EDR eSCO 3 Mb/s mode> ", + /* 7 */ "<3-Slot EDR eSCO packets> " + }, + { /* byte 6 */ + /* 0 */ "<Enhanced Inquiry Response> ", + /* 1 */ "<Simultaneous LE and BR/EDR (Controller)> ", + /* 2 */ "<Unknown 6.2> ", + /* 3 */ "<Secure Simple Pairing (Controller Support)> ", + /* 4 */ "<Encapsulated PDU> ", + /* 5 */ "<Erroneous Data Reporting> ", + /* 6 */ "<Non-flushable Packed Boundary Flag> ", + /* 7 */ "<Unknown 6.7> " + }, + { /* byte 7 */ + /* 0 */ "<HCI_Link_Supervision_Timeout_Changed event> ", + /* 1 */ "<Variable Inquiry TX Power Level> ", + /* 2 */ "<Enhanced Power Control> ", + /* 3 */ "<Unknown 7.3> ", + /* 4 */ "<Unknown 7.4> ", + /* 5 */ "<Unknown 7.5> ", + /* 6 */ "<Unknown 7.6> ", + /* 7 */ "<Extended features> " + }}; + + if (buffer != NULL && size > 0) { + int n, i, len0, len1; + + memset(buffer, 0, size); + len1 = 0; + size--; + + for (n = 0; n < SIZE(t); n++) { + for (i = 0; i < SIZE(t[n]); i++) { + len0 = strlen(buffer); + if (len0 >= size) + goto done; + + if (features[n] & (1 << i)) { + if (len1 + strlen(t[n][i]) > 60) { + len1 = 0; + buffer[len0 - 1] = '\n'; + } + + len1 += strlen(t[n][i]); + strncat(buffer, t[n][i], size - len0); + } + } + } + } +done: + return (buffer); +} /* hci_features2str */ + +char const * +hci_le_features2str(uint8_t *features, char *buffer, int size) +{ + static char const * const t[][8] = { + { /* byte 0 */ + /* 0 */ "<LE Encryption> ", + /* 1 */ "<Connection Parameters Request Procedure> ", + /* 2 */ "<Extended Reject Indication> ", + /* 3 */ "<Slave-initiated Features Exchange> ", + /* 4 */ "<LE Ping> ", + /* 5 */ "<LE Data Packet Length Extension> ", + /* 6 */ "<LL Privacy> ", + /* 7 */ "<Extended Scanner Filter Policies> " + }, + { /* byte 1 */ + /* 0 */ "<LE 2M PHY> ", + /* 1 */ "<Stable Modulation Index - Transmitter> ", + /* 2 */ "<Stable Modulation Index - Receiver> ", + /* 3 */ "<LE Coded PHY> ", + /* 4 */ "<LE Extended Advertising> ", + /* 5 */ "<LE Periodic Advertising> ", + /* 6 */ "<Channel Selection Algorithm #2> ", + /* 7 */ "<LE Power Class 1> " + }, + { /* byte 2 */ + /* 0 */ "<Minimum Number of Used Channels Procedure> ", + /* 1 */ "<Connection CTE Request> ", + /* 2 */ "<Connection CTE Response> ", + /* 3 */ "<Connectionless CTE Transmitter> ", + /* 4 */ "<Connectionless CTE Receiver> ", + /* 5 */ "<Antenna Switching During CTE Transmission (AoD)> ", + /* 6 */ "<Antenna Switching During CTE Reception (AoA)> ", + /* 7 */ "<Receiving Constant Tone Extensions> " + }, + { /* byte 3 */ + /* 0 */ "<Periodic Advertising Sync Transfer - Sender> ", + /* 1 */ "<Periodic Advertising Sync Transfer - Recipient> ", + /* 2 */ "<Sleep Clock Accuracy Updates> ", + /* 3 */ "<Remote Public Key Validation> ", + /* 4 */ "<Connected Isochronous Stream - Master> ", + /* 5 */ "<Connected Isochronous Stream - Slave> ", + /* 6 */ "<Isochronous Broadcaster> ", + /* 7 */ "<Synchronized Receiver> " + }, + { /* byte 4 */ + /* 0 */ "<Isochronous Channels (Host Support)> ", + /* 1 */ "<LE Power Control Request> ", + /* 2 */ "<LE Power Change Indication> ", + /* 3 */ "<LE Path Loss Monitoring> ", + /* 4 */ "<Reserved for future use> ", + /* 5 */ "<Unknown 4.5> ", + /* 6 */ "<Unknown 4.6> ", + /* 7 */ "<Unknown 4.7> " + }, + { /* byte 5 */ + /* 0 */ "<Unknown 5.0> ", + /* 1 */ "<Unknown 5.1> ", + /* 2 */ "<Unknown 5.2> ", + /* 3 */ "<Unknown 5.3> ", + /* 4 */ "<Unknown 5.4> ", + /* 5 */ "<Unknown 5.5> ", + /* 6 */ "<Unknown 5.6> ", + /* 7 */ "<Unknown 5.7> " + }, + { /* byte 6 */ + /* 0 */ "<Unknown 6.0> ", + /* 1 */ "<Unknown 6.1> ", + /* 2 */ "<Unknown 6.2> ", + /* 3 */ "<Unknown 6.3> ", + /* 4 */ "<Unknown 6.4> ", + /* 5 */ "<Unknown 6.5> ", + /* 6 */ "<Unknown 6.6> ", + /* 7 */ "<Unknown 6.7> " + }, + { /* byte 7 */ + /* 0 */ "<Unknown 7.0> ", + /* 1 */ "<Unknown 7.1> ", + /* 2 */ "<Unknown 7.2> ", + /* 3 */ "<Unknown 7.3> ", + /* 4 */ "<Unknown 7.4> ", + /* 5 */ "<Unknown 7.5> ", + /* 6 */ "<Unknown 7.6> ", + /* 7 */ "<Unknown 7.7> " + }}; + + if (buffer != NULL && size > 0) { + int n, i, len0, len1; + + memset(buffer, 0, size); + len1 = 0; + size--; + + for (n = 0; n < SIZE(t); n++) { + for (i = 0; i < SIZE(t[n]); i++) { + len0 = strlen(buffer); + if (len0 >= size) + goto done; + + if (features[n] & (1 << i)) { + if (len1 + strlen(t[n][i]) > 60) { + len1 = 0; + buffer[len0 - 1] = '\n'; + } + + len1 += strlen(t[n][i]); + strncat(buffer, t[n][i], size - len0); + } + } + } + } +done: + return (buffer); +} + +char const * +hci_cc2str(int cc) +{ + static char const * const t[] = { + /* 0x00 */ "North America, Europe, Japan", + /* 0x01 */ "France" + }; + + return (cc >= SIZE(t)? "?" : t[cc]); +} /* hci_cc2str */ + +char const * +hci_con_state2str(int state) +{ + static char const * const t[] = { + /* NG_HCI_CON_CLOSED */ "CLOSED", + /* NG_HCI_CON_W4_LP_CON_RSP */ "W4_LP_CON_RSP", + /* NG_HCI_CON_W4_CONN_COMPLETE */ "W4_CONN_COMPLETE", + /* NG_HCI_CON_OPEN */ "OPEN" + }; + + return (state >= SIZE(t)? "UNKNOWN" : t[state]); +} /* hci_con_state2str */ + +char const * +hci_status2str(int status) +{ + static char const * const t[] = { + /* 0x00 */ "No error", + /* 0x01 */ "Unknown HCI command", + /* 0x02 */ "No connection", + /* 0x03 */ "Hardware failure", + /* 0x04 */ "Page timeout", + /* 0x05 */ "Authentication failure", + /* 0x06 */ "Key missing", + /* 0x07 */ "Memory full", + /* 0x08 */ "Connection timeout", + /* 0x09 */ "Max number of connections", + /* 0x0a */ "Max number of SCO connections to a unit", + /* 0x0b */ "ACL connection already exists", + /* 0x0c */ "Command disallowed", + /* 0x0d */ "Host rejected due to limited resources", + /* 0x0e */ "Host rejected due to security reasons", + /* 0x0f */ "Host rejected due to remote unit is a personal unit", + /* 0x10 */ "Host timeout", + /* 0x11 */ "Unsupported feature or parameter value", + /* 0x12 */ "Invalid HCI command parameter", + /* 0x13 */ "Other end terminated connection: User ended connection", + /* 0x14 */ "Other end terminated connection: Low resources", + /* 0x15 */ "Other end terminated connection: About to power off", + /* 0x16 */ "Connection terminated by local host", + /* 0x17 */ "Repeated attempts", + /* 0x18 */ "Pairing not allowed", + /* 0x19 */ "Unknown LMP PDU", + /* 0x1a */ "Unsupported remote feature", + /* 0x1b */ "SCO offset rejected", + /* 0x1c */ "SCO interval rejected", + /* 0x1d */ "SCO air mode rejected", + /* 0x1e */ "Invalid LMP parameters", + /* 0x1f */ "Unspecified error", + /* 0x20 */ "Unsupported LMP parameter value", + /* 0x21 */ "Role change not allowed", + /* 0x22 */ "LMP response timeout", + /* 0x23 */ "LMP error transaction collision", + /* 0x24 */ "LMP PSU not allowed", + /* 0x25 */ "Encryption mode not acceptable", + /* 0x26 */ "Unit key used", + /* 0x27 */ "QoS is not supported", + /* 0x28 */ "Instant passed", + /* 0x29 */ "Pairing with unit key not supported", + /* 0x2a */ "Different Transaction Collision", + /* 0x2b */ "Unknown error (Reserved for future use)", + /* 0x2c */ "QoS Unacceptable Parameter", + /* 0x2d */ "QoS Rejected", + /* 0x2e */ "Channel Classification Not Supported", + /* 0x2f */ "Insufficient Security", + /* 0x30 */ "Parameter Out Of Mandatory Range", + /* 0x31 */ "Unknown error (Reserved for future use)", + /* 0x32 */ "Role Switch Pending", + /* 0x33 */ "Unknown error (Reserved for future use)", + /* 0x34 */ "Reserved Slot Violation", + /* 0x35 */ "Role Switch Failed", + /* 0x36 */ "Extended Inquiry Response Too Large", + /* 0x37 */ "Secure Simple Pairing Not Supported By Host", + /* 0x38 */ "Host Busy - Pairing", + /* 0x39 */ "Connection Rejected due to No Suitable Channel Found", + /* 0x3a */ "Controller Busy", + /* 0x3b */ "Unacceptable Connection Parameters", + /* 0x3c */ "Advertising Timeout", + /* 0x3d */ "Connection Terminated due to MIC Failure", + /* 0x3e */ "Connection Failed to be Established / Synchronization Timeout", + /* 0x3f */ "MAC Connection Failed", + /* 0x40 */ "Coarse Clock Adjustment Rejected but Will Try to Adjust Using Clock Dragging", + /* 0x41 */ "Type0 Submap Not Defined", + /* 0x42 */ "Unknown Advertising Identifier", + /* 0x43 */ "Limit Reached", + /* 0x44 */ "Operation Cancelled by Host", + /* 0x45 */ "Packet Too Long" + }; + + return (status >= SIZE(t)? "Unknown error" : t[status]); +} /* hci_status2str */ + +char const * +hci_bdaddr2str(bdaddr_t const *ba) +{ + extern int numeric_bdaddr; + static char buffer[MAXHOSTNAMELEN]; + struct hostent *he = NULL; + + if (memcmp(ba, NG_HCI_BDADDR_ANY, sizeof(*ba)) == 0) { + buffer[0] = '*'; + buffer[1] = 0; + + return (buffer); + } + + if (!numeric_bdaddr && + (he = bt_gethostbyaddr((char *)ba, sizeof(*ba), AF_BLUETOOTH)) != NULL) { + strlcpy(buffer, he->h_name, sizeof(buffer)); + + return (buffer); + } + + bt_ntoa(ba, buffer); + + return (buffer); +} /* hci_bdaddr2str */ + + +char const * +hci_addrtype2str(int type) +{ + static char const * const t[] = { + /* 0x00 */ "Public Device Address", + /* 0x01 */ "Random Device Address", + /* 0x02 */ "Public Identity Address", + /* 0x03 */ "Random (static) Identity Address" + }; + + return (type >= SIZE(t)? "?" : t[type]); +} /* hci_addrtype2str */ + +char const * +hci_role2str(int role) +{ + static char const * const roles[] = { + /* 0x00 */ "Master", + /* 0x01 */ "Slave", + }; + + return (role >= SIZE(roles)? "Unknown role" : roles[role]); +} /* hci_role2str */ + +char const * +hci_mc_accuracy2str(int accuracy) +{ + static char const * const acc[] = { + /* 0x00 */ "500 ppm", + /* 0x01 */ "250 ppm", + /* 0x02 */ "150 ppm", + /* 0x03 */ "100 ppm", + /* 0x04 */ "75 ppm", + /* 0x05 */ "50 ppm", + /* 0x06 */ "30 ppm", + /* 0x07 */ "20 ppm", + }; + + return (accuracy >= SIZE(acc)? "Unknown accuracy" : acc[accuracy]); +} /* hci_mc_accuracy2str */ + +char const * +hci_le_chanmap2str(uint8_t *map, char *buffer, int size) +{ + char chantxt[4]; + if (buffer != NULL && size > 0) { + int n, i, len0, len1; + + memset(buffer, 0, size); + len1 = 0; + size--; + + for (n = 0; n < 5; n++) { + fprintf(stdout, "%02x ", map[n]); + for (i = 0; i < 8; i++) { + len0 = strlen(buffer); + if (len0 >= size) + goto done; + + if (map[n] & (1 << i)) { + if (len1 + 3 > 60) { + len1 = 0; + buffer[len0 - 1] = '\n'; + } + + len1 += 3; + snprintf( + chantxt, + sizeof(chantxt), + "%02d ", + (n * 8 + i)); + strncat( + buffer, + chantxt, + size - len0); + } + } + } + fprintf(stdout, "\n"); + } +done: + return (buffer); +} diff --git a/usr.sbin/bluetooth/hcsecd/Makefile b/usr.sbin/bluetooth/hcsecd/Makefile new file mode 100644 index 000000000000..b32e3c670aa1 --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/Makefile @@ -0,0 +1,15 @@ +# $Id: Makefile,v 1.8 2003/08/14 20:06:20 max Exp $ + +PACKAGE= bluetooth +CONFS= hcsecd.conf +CONFSDIR= /etc/bluetooth +CONFSMODE_hcsecd.conf= 600 +PROG= hcsecd +MAN= hcsecd.8 hcsecd.conf.5 +SRCS= hcsecd.c lexer.l parser.y +WARNS?= 2 +CFLAGS+= -I${.CURDIR} + +LIBADD= bluetooth + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/hcsecd/Makefile.depend b/usr.sbin/bluetooth/hcsecd/Makefile.depend new file mode 100644 index 000000000000..5cf06f846a48 --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + usr.bin/yacc.host \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.8 b/usr.sbin/bluetooth/hcsecd/hcsecd.8 new file mode 100644 index 000000000000..9f8d3c7bf971 --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/hcsecd.8 @@ -0,0 +1,126 @@ +.\" Copyright (c) 2001-2002 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. +.\" +.\" $Id: hcsecd.8,v 1.8 2003/09/08 18:54:20 max Exp $ +.\" +.Dd November 16, 2002 +.Dt HCSECD 8 +.Os +.Sh NAME +.Nm hcsecd +.Nd control link keys and PIN codes for Bluetooth devices +.Sh SYNOPSIS +.Nm +.Op Fl dh +.Fl f Ar configfile +.Sh DESCRIPTION +The +.Nm +daemon controls link keys and PIN codes for Bluetooth devices. +It opens a raw HCI socket and listens for +.Dv Link_Key_Request , +.Dv PIN_Code_Request +and +.Dv Link_Key_Notification +HCI events. +.Pp +Once a +.Dv Link_Key_Request +or +.Dv PIN_Code_Request +HCI event is received, the daemon scans the configuration file for a +matching entry. +The remote device BD_ADDR is used as a key. +If no matching entry was found, the default entry will be used. +If no default entry was found then it is assumed that no link key and no +PIN code exists. +For any given entry, the link key takes precedence over the PIN code. +If a link key was not specified, the device must generate the link key from +the PIN code. +If an entry was found and the link key (or PIN code) exists, the +.Dv Link_Key_Request_Reply +(or +.Dv PIN_Code_Request_Reply ) +command will be sent back to the device. +Otherwise, the +.Dv Link_Key_Request_Negative_Reply +(or +.Dv PIN_Code_Request_Negative_Reply ) +command will be sent back to the device. +.Pp +The +.Nm +daemon also handles HCI +.Dv Link_Key_Notification +events and caches link keys created from the PIN codes in memory. +To preserve link keys between restarts the +.Nm +daemon dumps link keys for all entries in the +.Pa /var/db/hcsecd.keys +link keys file. +If it exists, the link keys file gets processed by the +.Nm +daemon after it processes its main configuration file. +The link keys file gets written every time the +.Nm +daemon shuts down gracefully. +It is possible to force the +.Nm +daemon to re-read its main configuration file and dump the link keys file by +sending the +.Dv HUP +signal to the +.Nm +process. +The user is expected to not modify the link keys file by hand. +.Pp +The command line options are as follows: +.Bl -tag -width indent +.It Fl d +Do not detach from the controlling terminal. +.It Fl f Ar configfile +Specify the name of the configuration file. +The default is +.Pa /etc/bluetooth/hcsecd.conf . +.It Fl h +Display usage message and exit. +.El +.Sh FILES +.Bl -tag -width ".Pa /etc/bluetooth/hcsecd.conf" -compact +.It Pa /etc/bluetooth/hcsecd.conf +.It Pa /var/db/hcsecd.keys +.It Pa /var/run/hcsecd.pid +.El +.Sh SEE ALSO +.Xr ng_btsocket 4 , +.Xr ng_hci 4 , +.Xr hcsecd.conf 5 , +.Xr hccontrol 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +.Sh BUGS +Currently there is no way to select the link key or the PIN code based on +which local device received the request. +Everything is based on the remote device BD_ADDR. +An interface for external helpers to obtain link keys and PIN codes is missing. diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.c b/usr.sbin/bluetooth/hcsecd/hcsecd.c new file mode 100644 index 000000000000..824b0ba6971b --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/hcsecd.c @@ -0,0 +1,448 @@ +/*- + * hcsecd.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: hcsecd.c,v 1.6 2003/08/18 19:19:55 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include "hcsecd.h" + +static int done = 0; + +static int process_pin_code_request_event + (int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr); +static int process_link_key_request_event + (int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr); +static int send_pin_code_reply + (int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr, char const *pin); +static int send_link_key_reply + (int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr, uint8_t *key); +static int process_link_key_notification_event + (int sock, struct sockaddr_hci *addr, ng_hci_link_key_notification_ep *ep); +static void sighup + (int s); +static void sigint + (int s); +static void usage + (void); + +/* Main */ +int +main(int argc, char *argv[]) +{ + int n, detach, sock; + socklen_t size; + struct sigaction sa; + struct sockaddr_hci addr; + struct ng_btsocket_hci_raw_filter filter; + char buffer[HCSECD_BUFFER_SIZE]; + ng_hci_event_pkt_t *event = NULL; + + detach = 1; + + while ((n = getopt(argc, argv, "df:h")) != -1) { + switch (n) { + case 'd': + detach = 0; + break; + + case 'f': + config_file = optarg; + break; + + case 'h': + default: + usage(); + /* NOT REACHED */ + } + } + + if (config_file == NULL) + usage(); + /* NOT REACHED */ + + if (getuid() != 0) + errx(1, "** ERROR: You should run %s as privileged user!", + HCSECD_IDENT); + + /* Set signal handlers */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigint; + sa.sa_flags = SA_NOCLDWAIT; + if (sigaction(SIGINT, &sa, NULL) < 0) + err(1, "Could not sigaction(SIGINT)"); + if (sigaction(SIGTERM, &sa, NULL) < 0) + err(1, "Could not sigaction(SIGINT)"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sighup; + if (sigaction(SIGHUP, &sa, NULL) < 0) + err(1, "Could not sigaction(SIGHUP)"); + + /* Open socket and set filter */ + sock = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (sock < 0) + err(1, "Could not create HCI socket"); + + memset(&filter, 0, sizeof(filter)); + bit_set(filter.event_mask, NG_HCI_EVENT_PIN_CODE_REQ - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_LINK_KEY_REQ - 1); + bit_set(filter.event_mask, NG_HCI_EVENT_LINK_KEY_NOTIFICATION - 1); + + if (setsockopt(sock, SOL_HCI_RAW, SO_HCI_RAW_FILTER, + (void * const) &filter, sizeof(filter)) < 0) + err(1, "Could not set HCI socket filter"); + + if (detach && daemon(0, 0) < 0) + err(1, "Could not daemon()ize"); + + openlog(HCSECD_IDENT, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_DAEMON); + + read_config_file(); + read_keys_file(); + + if (detach) { + FILE *pid = NULL; + + if ((pid = fopen(HCSECD_PIDFILE, "w")) == NULL) { + syslog(LOG_ERR, "Could not create PID file %s. %s (%d)", + HCSECD_PIDFILE, strerror(errno), errno); + exit(1); + } + + fprintf(pid, "%d", getpid()); + fclose(pid); + } + + event = (ng_hci_event_pkt_t *) buffer; + while (!done) { + size = sizeof(addr); + n = recvfrom(sock, buffer, sizeof(buffer), 0, + (struct sockaddr *) &addr, &size); + if (n < 0) { + if (errno == EINTR) + continue; + + syslog(LOG_ERR, "Could not receive from HCI socket. " \ + "%s (%d)", strerror(errno), errno); + exit(1); + } + + if (event->type != NG_HCI_EVENT_PKT) { + syslog(LOG_ERR, "Received unexpected HCI packet, " \ + "type=%#x", event->type); + continue; + } + + switch (event->event) { + case NG_HCI_EVENT_PIN_CODE_REQ: + process_pin_code_request_event(sock, &addr, + (bdaddr_p)(event + 1)); + break; + + case NG_HCI_EVENT_LINK_KEY_REQ: + process_link_key_request_event(sock, &addr, + (bdaddr_p)(event + 1)); + break; + + case NG_HCI_EVENT_LINK_KEY_NOTIFICATION: + process_link_key_notification_event(sock, &addr, + (ng_hci_link_key_notification_ep *)(event + 1)); + break; + + default: + syslog(LOG_ERR, "Received unexpected HCI event, " \ + "event=%#x", event->event); + break; + } + } + + if (detach) + if (remove(HCSECD_PIDFILE) < 0) + syslog(LOG_ERR, "Could not remove PID file %s. %s (%d)", + HCSECD_PIDFILE, strerror(errno), errno); + + dump_keys_file(); + clean_config(); + closelog(); + close(sock); + + return (0); +} + +/* Process PIN_Code_Request event */ +static int +process_pin_code_request_event(int sock, struct sockaddr_hci *addr, + bdaddr_p bdaddr) +{ + link_key_p key = NULL; + + syslog(LOG_DEBUG, "Got PIN_Code_Request event from '%s', " \ + "remote bdaddr %s", addr->hci_node, + bt_ntoa(bdaddr, NULL)); + + if ((key = get_key(bdaddr, 0)) != NULL) { + syslog(LOG_DEBUG, "Found matching entry, " \ + "remote bdaddr %s, name '%s', PIN code %s", + bt_ntoa(&key->bdaddr, NULL), + (key->name != NULL)? key->name : "No name", + (key->pin != NULL)? "exists" : "doesn't exist"); + + return (send_pin_code_reply(sock, addr, bdaddr, key->pin)); + } + + syslog(LOG_DEBUG, "Could not PIN code for remote bdaddr %s", + bt_ntoa(bdaddr, NULL)); + + return (send_pin_code_reply(sock, addr, bdaddr, NULL)); +} + +/* Process Link_Key_Request event */ +static int +process_link_key_request_event(int sock, struct sockaddr_hci *addr, + bdaddr_p bdaddr) +{ + link_key_p key = NULL; + + syslog(LOG_DEBUG, "Got Link_Key_Request event from '%s', " \ + "remote bdaddr %s", addr->hci_node, + bt_ntoa(bdaddr, NULL)); + + if ((key = get_key(bdaddr, 0)) != NULL) { + syslog(LOG_DEBUG, "Found matching entry, " \ + "remote bdaddr %s, name '%s', link key %s", + bt_ntoa(&key->bdaddr, NULL), + (key->name != NULL)? key->name : "No name", + (key->key != NULL)? "exists" : "doesn't exist"); + + return (send_link_key_reply(sock, addr, bdaddr, key->key)); + } + + syslog(LOG_DEBUG, "Could not find link key for remote bdaddr %s", + bt_ntoa(bdaddr, NULL)); + + return (send_link_key_reply(sock, addr, bdaddr, NULL)); +} + +/* Send PIN_Code_[Negative]_Reply */ +static int +send_pin_code_reply(int sock, struct sockaddr_hci *addr, + bdaddr_p bdaddr, char const *pin) +{ + uint8_t buffer[HCSECD_BUFFER_SIZE]; + ng_hci_cmd_pkt_t *cmd = NULL; + + memset(buffer, 0, sizeof(buffer)); + + cmd = (ng_hci_cmd_pkt_t *) buffer; + cmd->type = NG_HCI_CMD_PKT; + + if (pin != NULL) { + ng_hci_pin_code_rep_cp *cp = NULL; + + cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_PIN_CODE_REP)); + cmd->length = sizeof(*cp); + + cp = (ng_hci_pin_code_rep_cp *)(cmd + 1); + memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr)); + strncpy((char *) cp->pin, pin, sizeof(cp->pin)); + cp->pin_size = strlen((char const *) cp->pin); + + syslog(LOG_DEBUG, "Sending PIN_Code_Reply to '%s' " \ + "for remote bdaddr %s", + addr->hci_node, bt_ntoa(bdaddr, NULL)); + } else { + ng_hci_pin_code_neg_rep_cp *cp = NULL; + + cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_PIN_CODE_NEG_REP)); + cmd->length = sizeof(*cp); + + cp = (ng_hci_pin_code_neg_rep_cp *)(cmd + 1); + memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr)); + + syslog(LOG_DEBUG, "Sending PIN_Code_Negative_Reply to '%s' " \ + "for remote bdaddr %s", + addr->hci_node, bt_ntoa(bdaddr, NULL)); + } + +again: + if (sendto(sock, buffer, sizeof(*cmd) + cmd->length, 0, + (struct sockaddr *) addr, sizeof(*addr)) < 0) { + if (errno == EINTR) + goto again; + + syslog(LOG_ERR, "Could not send PIN code reply to '%s' " \ + "for remote bdaddr %s. %s (%d)", + addr->hci_node, bt_ntoa(bdaddr, NULL), + strerror(errno), errno); + return (-1); + } + + return (0); +} + +/* Send Link_Key_[Negative]_Reply */ +static int +send_link_key_reply(int sock, struct sockaddr_hci *addr, + bdaddr_p bdaddr, uint8_t *key) +{ + uint8_t buffer[HCSECD_BUFFER_SIZE]; + ng_hci_cmd_pkt_t *cmd = NULL; + + memset(buffer, 0, sizeof(buffer)); + + cmd = (ng_hci_cmd_pkt_t *) buffer; + cmd->type = NG_HCI_CMD_PKT; + + if (key != NULL) { + ng_hci_link_key_rep_cp *cp = NULL; + + cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_LINK_KEY_REP)); + cmd->length = sizeof(*cp); + + cp = (ng_hci_link_key_rep_cp *)(cmd + 1); + memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr)); + memcpy(&cp->key, key, sizeof(cp->key)); + + syslog(LOG_DEBUG, "Sending Link_Key_Reply to '%s' " \ + "for remote bdaddr %s", + addr->hci_node, bt_ntoa(bdaddr, NULL)); + } else { + ng_hci_link_key_neg_rep_cp *cp = NULL; + + cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_LINK_KEY_NEG_REP)); + cmd->length = sizeof(*cp); + + cp = (ng_hci_link_key_neg_rep_cp *)(cmd + 1); + memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr)); + + syslog(LOG_DEBUG, "Sending Link_Key_Negative_Reply to '%s' " \ + "for remote bdaddr %s", + addr->hci_node, bt_ntoa(bdaddr, NULL)); + } + +again: + if (sendto(sock, buffer, sizeof(*cmd) + cmd->length, 0, + (struct sockaddr *) addr, sizeof(*addr)) < 0) { + if (errno == EINTR) + goto again; + + syslog(LOG_ERR, "Could not send link key reply to '%s' " \ + "for remote bdaddr %s. %s (%d)", + addr->hci_node, bt_ntoa(bdaddr, NULL), + strerror(errno), errno); + return (-1); + } + + return (0); +} + +/* Process Link_Key_Notification event */ +static int +process_link_key_notification_event(int sock, struct sockaddr_hci *addr, + ng_hci_link_key_notification_ep *ep) +{ + link_key_p key = NULL; + + syslog(LOG_DEBUG, "Got Link_Key_Notification event from '%s', " \ + "remote bdaddr %s", addr->hci_node, + bt_ntoa(&ep->bdaddr, NULL)); + + if ((key = get_key(&ep->bdaddr, 1)) == NULL) { + syslog(LOG_ERR, "Could not find entry for remote bdaddr %s", + bt_ntoa(&ep->bdaddr, NULL)); + return (-1); + } + + syslog(LOG_DEBUG, "Updating link key for the entry, " \ + "remote bdaddr %s, name '%s', link key %s", + bt_ntoa(&key->bdaddr, NULL), + (key->name != NULL)? key->name : "No name", + (key->key != NULL)? "exists" : "doesn't exist"); + + if (key->key == NULL) { + key->key = (uint8_t *) malloc(NG_HCI_KEY_SIZE); + if (key->key == NULL) { + syslog(LOG_ERR, "Could not allocate link key"); + exit(1); + } + } + + memcpy(key->key, &ep->key, NG_HCI_KEY_SIZE); + + return (0); +} + +/* Signal handlers */ +static void +sighup(int s) +{ + syslog(LOG_DEBUG, "Got SIGHUP (%d)", s); + + dump_keys_file(); + read_config_file(); + read_keys_file(); +} + +static void +sigint(int s) +{ + syslog(LOG_DEBUG, "Got signal %d, total number of signals %d", + s, ++ done); +} + +/* Display usage and exit */ +static void +usage(void) +{ + fprintf(stderr, +"Usage: %s [-d] -f config_file [-h]\n" \ +"Where:\n" \ +"\t-d do not detach from terminal\n" \ +"\t-f config_file use <config_file>\n" \ +"\t-h display this message\n", HCSECD_IDENT); + + exit(255); +} + diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.conf b/usr.sbin/bluetooth/hcsecd/hcsecd.conf new file mode 100644 index 000000000000..36ec516ae019 --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/hcsecd.conf @@ -0,0 +1,55 @@ +# $Id: hcsecd.conf,v 1.1 2003/05/26 22:50:47 max Exp $ +# +# HCI security daemon configuration file +# +# Format: +# +# device { +# option value ; +# } +# +# Possible options and values +# +# Options Values +# ---------------------------------- +# bdaddr xx:xx:xx:xx:xx:xx ; - remote device BD_ADDR +# name "any char" ; - to set user friendly device name +# key 0x11223344 | nokey ; - to set link key for the device +# pin "secret" | nopin ; - to PIN code for the device +# +# Notes: +# +# Currently there is no way to select keys/PIN code based on which +# local device received the request. Everything is based on remote +# device BD_ADDR. +# +# "nokey" means that no link key has been defined and we should +# send Link_Key_Negative_Reply command to the device. +# +# "nopin" means that no PIN code has been defined and we should +# send PIN_Code_Negative_Reply command to the device +# + +# Default entry is applied if no better match found +# It MUST have 00:00:00:00:00:00 as bdaddr +device { + bdaddr 00:00:00:00:00:00; + name "Default entry"; + key nokey; + pin nopin; +} + +device { + bdaddr 00:01:02:03:04:05; + name "Dummy"; + key nokey; + pin "0000"; +} + +device { + bdaddr 00:11:22:33:44:55; + name "Dummy"; + key 0x00112233445566778899aabbccddeeff; # 16 bytes key (hex string) + pin nopin; +} + diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.conf.5 b/usr.sbin/bluetooth/hcsecd/hcsecd.conf.5 new file mode 100644 index 000000000000..306aea6cbb5b --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/hcsecd.conf.5 @@ -0,0 +1,130 @@ +.\" Copyright (c) 2001-2002 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. +.\" +.\" $Id: hcsecd.conf.5,v 1.1 2003/05/26 22:49:23 max Exp $ +.\" +.Dd May 26, 2003 +.Dt HCSECD.CONF 5 +.Os +.Sh NAME +.Nm hcsecd.conf +.Nd +.Xr hcsecd 8 +configuration file +.Sh DESCRIPTION +The +.Nm +file is the configuration file for the +.Xr hcsecd 8 +Bluetooth link keys/PIN codes management daemon. +.Pp +The +.Nm +file is a free-form +.Tn ASCII +text file. +It is parsed by the recursive-descent parser built into +.Xr hcsecd 8 . +The file may contain extra tabs and newlines for formatting purposes. +Keywords in the file are case-sensitive. +Comments may be placed anywhere within the file (except within quotes). +Comments begin with the +.Ql # +character and end at the end of the line. +.Sh FILE FORMAT +The +.Nm +file consists of a list of +.Cm device +entries. +Each +.Cm device +entry defines a link key or PIN code for a remote Bluetooth device. +Each remote Bluetooth device is identified by its unique BD_ADDR. +.Pp +The +.Cm device +entry +.Pp +.Cm device +{ +.Cm option Ar argument ; +.Oo +.Cm option Ar argument ; +.Oc +} +.Pp +The following section describes all supported options and arguments. +.Bl -tag -width indent +.It Cm bdaddr Ar BD_ADDR +Specify remote device BD_ADDR for the entry. +.It Cm name Ar device_name +Specify user friendly name for the entry. +Name is a string in straight double quotes. +.It Cm key Ar link_key +Specify link key for the entry. +Link key is hexadecimal string up to 32 characters in length starting with +.Ql 0x . +.It Cm key nokey +Specify no link key for the entry. +.It Cm pin Ar PIN_code +Specify PIN code for the entry. +PIN code is a string up to 16 characters in length in straight double quotes. +.It Cm pin nopin +Specify no PIN code for the entry. +.El +.Sh EXAMPLES +A sample +.Nm +file: +.Bd -literal +# Default entry is applied if no better match found +# It MUST have 00:00:00:00:00:00 as bdaddr +device { + bdaddr 00:00:00:00:00:00; + name "Default entry"; + key nokey; + pin nopin; +} + +# Ericsson T68 phone +device { + bdaddr 00:80:37:5e:4d:d4; + name "Ericsson T68 phone"; + key nokey; + pin "0000"; # PIN code +} + +# Dummy device +device { + bdaddr 00:11:22:33:44:55; + name "Dummy"; + key 0x00112233445566778899aabbccddeeff; # 16 bytes key + pin nopin; +} +.Ed +.Sh SEE ALSO +.Xr hcsecd 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.h b/usr.sbin/bluetooth/hcsecd/hcsecd.h new file mode 100644 index 000000000000..e13139e7cf1a --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/hcsecd.h @@ -0,0 +1,66 @@ +/*- + * hcsecd.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: hcsecd.h,v 1.3 2003/09/08 18:54:21 max Exp $ + */ + +#ifndef _HCSECD_H_ +#define _HCSECD_H_ 1 + +#define HCSECD_BUFFER_SIZE 512 +#define HCSECD_IDENT "hcsecd" +#define HCSECD_PIDFILE "/var/run/" HCSECD_IDENT ".pid" +#define HCSECD_KEYSFILE "/var/db/" HCSECD_IDENT ".keys" + +struct link_key +{ + bdaddr_t bdaddr; /* remote device BDADDR */ + char *name; /* remote device name */ + uint8_t *key; /* link key (or NULL if no key) */ + char *pin; /* pin (or NULL if no pin) */ + LIST_ENTRY(link_key) next; /* link to the next */ +}; +typedef struct link_key link_key_t; +typedef struct link_key * link_key_p; + +extern char *config_file; + +#if __config_debug__ +void dump_config (void); +#endif + +void read_config_file(void); +void clean_config (void); +link_key_p get_key (bdaddr_p bdaddr, int exact_match); + +int read_keys_file (void); +int dump_keys_file (void); + +#endif /* ndef _HCSECD_H_ */ + diff --git a/usr.sbin/bluetooth/hcsecd/lexer.l b/usr.sbin/bluetooth/hcsecd/lexer.l new file mode 100644 index 000000000000..71bfd7d5c7b1 --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/lexer.l @@ -0,0 +1,96 @@ +%{ +/*- + * lexer.l + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: lexer.l,v 1.1 2002/11/24 20:22:39 max Exp $ + */ + +#include <string.h> +#include "parser.h" +%} + +%option yylineno noyywrap nounput noinput + +delim [ \t\n] +ws {delim}+ +empty {delim}* +comment \#.* + +hexdigit [0-9a-fA-F] +hexbyte {hexdigit}{hexdigit} + +device_word device +bdaddr_word bdaddr +name_word name +key_word key +nokey_word nokey +pin_word pin +nopin_word nopin + +bdaddrstring {hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte} +hexstring 0x{hexbyte}+ +string \".+\" + +%% + +\; return (';'); +\: return (':'); +\{ return ('{'); +\} return ('}'); + +{ws} ; +{empty} ; +{comment} ; + +{device_word} return (T_DEVICE); +{bdaddr_word} return (T_BDADDR); +{name_word} return (T_NAME); +{key_word} return (T_KEY); +{nokey_word} return (T_NOKEY); +{pin_word} return (T_PIN); +{nopin_word} return (T_NOPIN); + +{bdaddrstring} { + yylval.string = yytext; + return (T_BDADDRSTRING); + } + +{hexstring} { + yylval.string = &yytext[2]; + return (T_HEXSTRING); + } + +{string} { + yytext[strlen(yytext) - 1] = 0; + yylval.string = &yytext[1]; + return (T_STRING); + } + +%% + diff --git a/usr.sbin/bluetooth/hcsecd/parser.y b/usr.sbin/bluetooth/hcsecd/parser.y new file mode 100644 index 000000000000..bf8d33f565bc --- /dev/null +++ b/usr.sbin/bluetooth/hcsecd/parser.y @@ -0,0 +1,436 @@ +%{ +/*- + * parser.y + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: parser.y,v 1.5 2003/06/07 21:22:30 max Exp $ + */ + +#include <sys/fcntl.h> +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include "hcsecd.h" + + int yyparse (void); + int yylex (void); + +static void free_key (link_key_p key); +static int hexa2int4(char *a); +static int hexa2int8(char *a); + +extern int yylineno; +static LIST_HEAD(, link_key) link_keys; + char *config_file = "/etc/bluetooth/hcsecd.conf"; + +static link_key_p key = NULL; +%} + +%union { + char *string; +} + +%token <string> T_BDADDRSTRING T_HEXSTRING T_STRING +%token T_DEVICE T_BDADDR T_NAME T_KEY T_PIN T_NOKEY T_NOPIN T_JUNK + +%% + +config: line + | config line + ; + +line: T_DEVICE + { + key = (link_key_p) malloc(sizeof(*key)); + if (key == NULL) { + syslog(LOG_ERR, "Could not allocate new " \ + "config entry"); + exit(1); + } + + memset(key, 0, sizeof(*key)); + } + '{' options '}' + { + if (get_key(&key->bdaddr, 1) != NULL) { + syslog(LOG_ERR, "Ignoring duplicated entry " \ + "for bdaddr %s", + bt_ntoa(&key->bdaddr, NULL)); + free_key(key); + } else + LIST_INSERT_HEAD(&link_keys, key, next); + + key = NULL; + } + ; + +options: option ';' + | options option ';' + ; + +option: bdaddr + | name + | key + | pin + ; + +bdaddr: T_BDADDR T_BDADDRSTRING + { + if (!bt_aton($2, &key->bdaddr)) { + syslog(LOG_ERR, "Cound not parse BD_ADDR " \ + "'%s'", $2); + exit(1); + } + } + ; + +name: T_NAME T_STRING + { + if (key->name != NULL) + free(key->name); + + key->name = strdup($2); + if (key->name == NULL) { + syslog(LOG_ERR, "Could not allocate new " \ + "device name"); + exit(1); + } + } + ; + +key: T_KEY T_HEXSTRING + { + int i, len; + + if (key->key != NULL) + free(key->key); + + key->key = (uint8_t *) malloc(NG_HCI_KEY_SIZE); + if (key->key == NULL) { + syslog(LOG_ERR, "Could not allocate new " \ + "link key"); + exit(1); + } + + memset(key->key, 0, NG_HCI_KEY_SIZE); + + len = strlen($2) / 2; + if (len > NG_HCI_KEY_SIZE) + len = NG_HCI_KEY_SIZE; + + for (i = 0; i < len; i ++) + key->key[i] = hexa2int8((char *)($2) + 2*i); + } + | T_KEY T_NOKEY + { + if (key->key != NULL) + free(key->key); + + key->key = NULL; + } + ; + +pin: T_PIN T_STRING + { + if (key->pin != NULL) + free(key->pin); + + key->pin = strdup($2); + if (key->pin == NULL) { + syslog(LOG_ERR, "Could not allocate new " \ + "PIN code"); + exit(1); + } + } + | T_PIN T_NOPIN + { + if (key->pin != NULL) + free(key->pin); + + key->pin = NULL; + } + ; + +%% + +/* Display parser error message */ +void +yyerror(char const *message) +{ + syslog(LOG_ERR, "%s in line %d", message, yylineno); +} + +/* Re-read config file */ +void +read_config_file(void) +{ + extern FILE *yyin; + + if (config_file == NULL) { + syslog(LOG_ERR, "Unknown config file name!"); + exit(1); + } + + if ((yyin = fopen(config_file, "r")) == NULL) { + syslog(LOG_ERR, "Could not open config file '%s'. %s (%d)", + config_file, strerror(errno), errno); + exit(1); + } + + clean_config(); + if (yyparse() < 0) { + syslog(LOG_ERR, "Could not parse config file '%s'",config_file); + exit(1); + } + + fclose(yyin); + yyin = NULL; + +#if __config_debug__ + dump_config(); +#endif +} + +/* Clean config */ +void +clean_config(void) +{ + link_key_p key = NULL; + + while ((key = LIST_FIRST(&link_keys)) != NULL) { + LIST_REMOVE(key, next); + free_key(key); + } +} + +/* Find link key entry in the list. Return exact or default match */ +link_key_p +get_key(bdaddr_p bdaddr, int exact_match) +{ + link_key_p key = NULL, defkey = NULL; + + LIST_FOREACH(key, &link_keys, next) { + if (memcmp(bdaddr, &key->bdaddr, sizeof(key->bdaddr)) == 0) + break; + + if (!exact_match) + if (memcmp(NG_HCI_BDADDR_ANY, &key->bdaddr, + sizeof(key->bdaddr)) == 0) + defkey = key; + } + + return ((key != NULL)? key : defkey); +} + +#if __config_debug__ +/* Dump config */ +void +dump_config(void) +{ + link_key_p key = NULL; + char buffer[64]; + + LIST_FOREACH(key, &link_keys, next) { + if (key->key != NULL) + snprintf(buffer, sizeof(buffer), +"0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + key->key[0], key->key[1], key->key[2], + key->key[3], key->key[4], key->key[5], + key->key[6], key->key[7], key->key[8], + key->key[9], key->key[10], key->key[11], + key->key[12], key->key[13], key->key[14], + key->key[15]); + + syslog(LOG_DEBUG, +"device %s " \ +"bdaddr %s " \ +"pin %s " \ +"key %s", + (key->name != NULL)? key->name : "noname", + bt_ntoa(&key->bdaddr, NULL), + (key->pin != NULL)? key->pin : "nopin", + (key->key != NULL)? buffer : "nokey"); + } +} +#endif + +/* Read keys file */ +int +read_keys_file(void) +{ + FILE *f = NULL; + link_key_t *key = NULL; + char buf[HCSECD_BUFFER_SIZE], *p = NULL, *cp = NULL; + bdaddr_t bdaddr; + int i, len; + + if ((f = fopen(HCSECD_KEYSFILE, "r")) == NULL) { + if (errno == ENOENT) + return (0); + + syslog(LOG_ERR, "Could not open keys file %s. %s (%d)\n", + HCSECD_KEYSFILE, strerror(errno), errno); + + return (-1); + } + + while ((p = fgets(buf, sizeof(buf), f)) != NULL) { + if (*p == '#') + continue; + if ((cp = strpbrk(p, " ")) == NULL) + continue; + + *cp++ = '\0'; + + if (!bt_aton(p, &bdaddr)) + continue; + + if ((key = get_key(&bdaddr, 1)) == NULL) + continue; + + if (key->key == NULL) { + key->key = (uint8_t *) malloc(NG_HCI_KEY_SIZE); + if (key->key == NULL) { + syslog(LOG_ERR, "Could not allocate link key"); + exit(1); + } + } + + memset(key->key, 0, NG_HCI_KEY_SIZE); + + len = strlen(cp) / 2; + if (len > NG_HCI_KEY_SIZE) + len = NG_HCI_KEY_SIZE; + + for (i = 0; i < len; i ++) + key->key[i] = hexa2int8(cp + 2*i); + + syslog(LOG_DEBUG, "Restored link key for the entry, " \ + "remote bdaddr %s, name '%s'", + bt_ntoa(&key->bdaddr, NULL), + (key->name != NULL)? key->name : "No name"); + } + + fclose(f); + + return (0); +} + +/* Dump keys file */ +int +dump_keys_file(void) +{ + link_key_p key = NULL; + char tmp[PATH_MAX], buf[HCSECD_BUFFER_SIZE]; + int f; + + snprintf(tmp, sizeof(tmp), "%s.tmp", HCSECD_KEYSFILE); + if ((f = open(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600)) < 0) { + syslog(LOG_ERR, "Could not create temp keys file %s. %s (%d)\n", + tmp, strerror(errno), errno); + return (-1); + } + + LIST_FOREACH(key, &link_keys, next) { + if (key->key == NULL) + continue; + + snprintf(buf, sizeof(buf), +"%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + bt_ntoa(&key->bdaddr, NULL), + key->key[0], key->key[1], key->key[2], key->key[3], + key->key[4], key->key[5], key->key[6], key->key[7], + key->key[8], key->key[9], key->key[10], key->key[11], + key->key[12], key->key[13], key->key[14], key->key[15]); + + if (write(f, buf, strlen(buf)) < 0) { + syslog(LOG_ERR, "Could not write temp keys file. " \ + "%s (%d)\n", strerror(errno), errno); + break; + } + } + + close(f); + + if (rename(tmp, HCSECD_KEYSFILE) < 0) { + syslog(LOG_ERR, "Could not rename(%s, %s). %s (%d)\n", + tmp, HCSECD_KEYSFILE, strerror(errno), errno); + unlink(tmp); + return (-1); + } + + return (0); +} + +/* Free key entry */ +static void +free_key(link_key_p key) +{ + if (key->name != NULL) + free(key->name); + if (key->key != NULL) + free(key->key); + if (key->pin != NULL) + free(key->pin); + + memset(key, 0, sizeof(*key)); + free(key); +} + +/* Convert hex ASCII to int4 */ +static int +hexa2int4(char *a) +{ + if ('0' <= *a && *a <= '9') + return (*a - '0'); + + if ('A' <= *a && *a <= 'F') + return (*a - 'A' + 0xa); + + if ('a' <= *a && *a <= 'f') + return (*a - 'a' + 0xa); + + syslog(LOG_ERR, "Invalid hex character: '%c' (%#x)", *a, *a); + exit(1); +} + +/* Convert hex ASCII to int8 */ +static int +hexa2int8(char *a) +{ + return ((hexa2int4(a) << 4) | hexa2int4(a + 1)); +} + diff --git a/usr.sbin/bluetooth/iwmbtfw/Makefile b/usr.sbin/bluetooth/iwmbtfw/Makefile new file mode 100644 index 000000000000..c5cf037eac06 --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/Makefile @@ -0,0 +1,11 @@ +PACKAGE= bluetooth +CONFS= iwmbtfw.conf +CONFSDIR= /etc/devd +PROG= iwmbtfw +MAN= iwmbtfw.8 +LIBADD+= usb +# Not having NDEBUG defined will enable assertions +CFLAGS+= -DNDEBUG +SRCS= main.c iwmbt_fw.c iwmbt_hw.c + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/iwmbtfw/Makefile.depend b/usr.sbin/bluetooth/iwmbtfw/Makefile.depend new file mode 100644 index 000000000000..34fbfadfcfb6 --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libusb \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h new file mode 100644 index 000000000000..9cfded6f33cf --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h @@ -0,0 +1,45 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * + * 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. + */ +#ifndef __IWMBT_DEBUG_H__ +#define __IWMBT_DEBUG_H__ + +extern int iwmbt_do_debug; +extern int iwmbt_do_info; + +#define iwmbt_err(fmt, ...) \ + fprintf(stderr, "iwmbtfw: %s: "fmt"\n", __func__, ##__VA_ARGS__) +#define iwmbt_info(fmt, ...) do { \ + if (iwmbt_do_info) \ + fprintf(stderr, "%s: "fmt"\n", __func__, ##__VA_ARGS__);\ +} while (0) +#define iwmbt_debug(fmt, ...) do { \ + if (iwmbt_do_debug) \ + fprintf(stderr, "%s: "fmt"\n", __func__, ##__VA_ARGS__);\ +} while (0) + +#endif diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c new file mode 100644 index 000000000000..3a5cd9d42658 --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c @@ -0,0 +1,194 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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/types.h> +#include <sys/endian.h> +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "iwmbt_fw.h" +#include "iwmbt_dbg.h" + +int +iwmbt_fw_read(struct iwmbt_firmware *fw, const char *fwname) +{ + int fd; + struct stat sb; + unsigned char *buf; + ssize_t r; + + fd = open(fwname, O_RDONLY); + if (fd < 0) { + warn("%s: open: %s", __func__, fwname); + return (0); + } + + if (fstat(fd, &sb) != 0) { + warn("%s: stat: %s", __func__, fwname); + close(fd); + return (0); + } + + buf = calloc(1, sb.st_size); + if (buf == NULL) { + warn("%s: calloc", __func__); + close(fd); + return (0); + } + + /* XXX handle partial reads */ + r = read(fd, buf, sb.st_size); + if (r < 0) { + warn("%s: read", __func__); + free(buf); + close(fd); + return (0); + } + + if (r != sb.st_size) { + iwmbt_err("read len %d != file size %d", + (int) r, + (int) sb.st_size); + free(buf); + close(fd); + return (0); + } + + /* We have everything, so! */ + + memset(fw, 0, sizeof(*fw)); + + fw->fwname = strdup(fwname); + fw->len = sb.st_size; + fw->buf = buf; + + close(fd); + return (1); +} + +void +iwmbt_fw_free(struct iwmbt_firmware *fw) +{ + if (fw->fwname) + free(fw->fwname); + if (fw->buf) + free(fw->buf); + memset(fw, 0, sizeof(*fw)); +} + +char * +iwmbt_get_fwname(struct iwmbt_version *ver, struct iwmbt_boot_params *params, + const char *prefix, const char *suffix) +{ + struct stat sb; + char *fwname; + + switch (ver->hw_variant) { + case 0x07: /* 7260 */ + case 0x08: /* 7265 */ + // NB: don't use params, they are NULL for 7xxx + asprintf(&fwname, "%s/ibt-hw-%x.%x.%x-fw-%x.%x.%x.%x.%x.%s", + prefix, + le16toh(ver->hw_platform), + le16toh(ver->hw_variant), + le16toh(ver->hw_revision), + le16toh(ver->fw_variant), + le16toh(ver->fw_revision), + le16toh(ver->fw_build_num), + le16toh(ver->fw_build_ww), + le16toh(ver->fw_build_yy), + suffix); + /* + * Fallback to the default firmware patch if + * the correct firmware patch file is not found. + */ + if (stat(fwname, &sb) != 0 && errno == ENOENT) { + free(fwname); + asprintf(&fwname, "%s/ibt-hw-%x.%x.%s", + prefix, + le16toh(ver->hw_platform), + le16toh(ver->hw_variant), + suffix); + } + break; + + case 0x0b: /* 8260 */ + case 0x0c: /* 8265 */ + asprintf(&fwname, "%s/ibt-%u-%u.%s", + prefix, + le16toh(ver->hw_variant), + le16toh(params->dev_revid), + suffix); + break; + + case 0x11: /* 9560 */ + case 0x12: /* 9260 */ + case 0x13: + case 0x14: /* 22161 */ + asprintf(&fwname, "%s/ibt-%u-%u-%u.%s", + prefix, + le16toh(ver->hw_variant), + le16toh(ver->hw_revision), + le16toh(ver->fw_revision), + suffix); + break; + + default: + fwname = NULL; + } + + return (fwname); +} + +char * +iwmbt_get_fwname_tlv(struct iwmbt_version_tlv *ver, const char *prefix, + const char *suffix) +{ + char *fwname; + +#define IWMBT_PACK_CNVX_TOP(cnvx_top) ((uint16_t)( \ + ((cnvx_top) & 0x0f000000) >> 16 | \ + ((cnvx_top) & 0x0000000f) << 12 | \ + ((cnvx_top) & 0x00000ff0) >> 4)) + + asprintf(&fwname, "%s/ibt-%04x-%04x.%s", + prefix, + IWMBT_PACK_CNVX_TOP(ver->cnvi_top), + IWMBT_PACK_CNVX_TOP(ver->cnvr_top), + suffix); + + return (fwname); +} diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h new file mode 100644 index 000000000000..eb6909a1f91d --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h @@ -0,0 +1,156 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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. + */ + +#ifndef __IWMBT_FW_H__ +#define __IWMBT_FW_H__ + +#include <sys/types.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> + +#define RSA_HEADER_LEN 644 +#define ECDSA_HEADER_LEN 320 +#define ECDSA_OFFSET RSA_HEADER_LEN +#define CSS_HEADER_OFFSET 8 + +struct iwmbt_version { + uint8_t status; + uint8_t hw_platform; + uint8_t hw_variant; + uint8_t hw_revision; + uint8_t fw_variant; + uint8_t fw_revision; + uint8_t fw_build_num; + uint8_t fw_build_ww; + uint8_t fw_build_yy; + uint8_t fw_patch_num; +} __attribute__ ((packed)); + +/* Known values for fw_variant */ +#define FW_VARIANT_BOOTLOADER 0x06 /* Bootloader mode */ +#define FW_VARIANT_OPERATIONAL 0x23 /* Operational mode */ + +struct iwmbt_boot_params { + uint8_t status; + uint8_t otp_format; + uint8_t otp_content; + uint8_t otp_patch; + uint16_t dev_revid; + uint8_t secure_boot; + uint8_t key_from_hdr; + uint8_t key_type; + uint8_t otp_lock; + uint8_t api_lock; + uint8_t debug_lock; + uint8_t otp_bdaddr[6]; + uint8_t min_fw_build_nn; + uint8_t min_fw_build_cw; + uint8_t min_fw_build_yy; + uint8_t limited_cce; + uint8_t unlocked_state; +} __attribute__ ((packed)); + +enum { + IWMBT_TLV_CNVI_TOP = 0x10, + IWMBT_TLV_CNVR_TOP, + IWMBT_TLV_CNVI_BT, + IWMBT_TLV_CNVR_BT, + IWMBT_TLV_CNVI_OTP, + IWMBT_TLV_CNVR_OTP, + IWMBT_TLV_DEV_REV_ID, + IWMBT_TLV_USB_VENDOR_ID, + IWMBT_TLV_USB_PRODUCT_ID, + IWMBT_TLV_PCIE_VENDOR_ID, + IWMBT_TLV_PCIE_DEVICE_ID, + IWMBT_TLV_PCIE_SUBSYSTEM_ID, + IWMBT_TLV_IMAGE_TYPE, + IWMBT_TLV_TIME_STAMP, + IWMBT_TLV_BUILD_TYPE, + IWMBT_TLV_BUILD_NUM, + IWMBT_TLV_FW_BUILD_PRODUCT, + IWMBT_TLV_FW_BUILD_HW, + IWMBT_TLV_FW_STEP, + IWMBT_TLV_BT_SPEC, + IWMBT_TLV_MFG_NAME, + IWMBT_TLV_HCI_REV, + IWMBT_TLV_LMP_SUBVER, + IWMBT_TLV_OTP_PATCH_VER, + IWMBT_TLV_SECURE_BOOT, + IWMBT_TLV_KEY_FROM_HDR, + IWMBT_TLV_OTP_LOCK, + IWMBT_TLV_API_LOCK, + IWMBT_TLV_DEBUG_LOCK, + IWMBT_TLV_MIN_FW, + IWMBT_TLV_LIMITED_CCE, + IWMBT_TLV_SBE_TYPE, + IWMBT_TLV_OTP_BDADDR, + IWMBT_TLV_UNLOCKED_STATE +}; + +struct iwmbt_version_tlv { + uint32_t cnvi_top; + uint32_t cnvr_top; + uint32_t cnvi_bt; + uint32_t cnvr_bt; + uint16_t dev_rev_id; + uint8_t img_type; + uint16_t timestamp; + uint8_t build_type; + uint32_t build_num; + uint8_t secure_boot; + uint8_t otp_lock; + uint8_t api_lock; + uint8_t debug_lock; + uint8_t min_fw_build_nn; + uint8_t min_fw_build_cw; + uint8_t min_fw_build_yy; + uint8_t limited_cce; + uint8_t sbe_type; + bdaddr_t otp_bd_addr; +}; + +/* Known TLV img_type values */ +#define TLV_IMG_TYPE_BOOTLOADER 0x01 /* Bootloader mode */ +#define TLV_IMG_TYPE_OPERATIONAL 0x03 /* Operational mode */ + +struct iwmbt_firmware { + char *fwname; + int len; + unsigned char *buf; +}; + +extern int iwmbt_fw_read(struct iwmbt_firmware *fw, const char *fwname); +extern void iwmbt_fw_free(struct iwmbt_firmware *fw); +extern char *iwmbt_get_fwname(struct iwmbt_version *ver, + struct iwmbt_boot_params *params, const char *prefix, + const char *suffix); +extern char *iwmbt_get_fwname_tlv(struct iwmbt_version_tlv *ver, + const char *prefix, const char *suffix); + +#endif diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c new file mode 100644 index 000000000000..255181b8f4bc --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c @@ -0,0 +1,777 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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/param.h> +#include <sys/endian.h> +#include <sys/stat.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <libusb.h> + +#include <netgraph/bluetooth/include/ng_hci.h> + +#include "iwmbt_fw.h" +#include "iwmbt_hw.h" +#include "iwmbt_dbg.h" + +#define XMIN(x, y) ((x) < (y) ? (x) : (y)) + +static int +iwmbt_send_fragment(struct libusb_device_handle *hdl, + uint8_t fragment_type, const void *data, uint8_t len, int timeout) +{ + int ret, transferred; + uint8_t buf[IWMBT_HCI_MAX_CMD_SIZE]; + struct iwmbt_hci_cmd *cmd = (struct iwmbt_hci_cmd *) buf; + + memset(buf, 0, sizeof(buf)); + cmd->opcode = htole16(0xfc09), + cmd->length = len + 1, + cmd->data[0] = fragment_type; + memcpy(cmd->data + 1, data, len); + + ret = libusb_bulk_transfer(hdl, + IWMBT_BULK_OUT_ENDPOINT_ADDR, + (uint8_t *)cmd, + IWMBT_HCI_CMD_SIZE(cmd), + &transferred, + timeout); + + if (ret < 0 || transferred != (int)IWMBT_HCI_CMD_SIZE(cmd)) { + iwmbt_err("libusb_bulk_transfer() failed: err=%s, size=%zu", + libusb_strerror(ret), + IWMBT_HCI_CMD_SIZE(cmd)); + return (-1); + } + + ret = libusb_bulk_transfer(hdl, + IWMBT_BULK_IN_ENDPOINT_ADDR, + buf, + sizeof(buf), + &transferred, + timeout); + + if (ret < 0) { + iwmbt_err("libusb_bulk_transfer() failed: err=%s", + libusb_strerror(ret)); + return (-1); + } + + return (0); +} + +static int +iwmbt_hci_command(struct libusb_device_handle *hdl, struct iwmbt_hci_cmd *cmd, + void *event, int size, int *transferred, int timeout) +{ + struct timespec to, now, remains; + int ret; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE, + 0, + 0, + 0, + (uint8_t *)cmd, + IWMBT_HCI_CMD_SIZE(cmd), + timeout); + + if (ret < 0) { + iwmbt_err("libusb_control_transfer() failed: err=%s", + libusb_strerror(ret)); + return (ret); + } + + clock_gettime(CLOCK_MONOTONIC, &now); + to = IWMBT_MSEC2TS(timeout); + timespecadd(&to, &now, &to); + + do { + timespecsub(&to, &now, &remains); + ret = libusb_interrupt_transfer(hdl, + IWMBT_INTERRUPT_ENDPOINT_ADDR, + event, + size, + transferred, + IWMBT_TS2MSEC(remains) + 1); + + if (ret < 0) { + iwmbt_err("libusb_interrupt_transfer() failed: err=%s", + libusb_strerror(ret)); + return (ret); + } + + switch (((struct iwmbt_hci_event *)event)->header.event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (*transferred < + (int)offsetof(struct iwmbt_hci_event_cmd_compl, data)) + break; + if (cmd->opcode != + ((struct iwmbt_hci_event_cmd_compl *)event)->opcode) + break; + /* FALLTHROUGH */ + case 0xFF: + return (0); + default: + break; + } + iwmbt_debug("Stray HCI event: %x", + ((struct iwmbt_hci_event *)event)->header.event); + } while (timespeccmp(&to, &now, >)); + + iwmbt_err("libusb_interrupt_transfer() failed: err=%s", + libusb_strerror(LIBUSB_ERROR_TIMEOUT)); + + return (LIBUSB_ERROR_TIMEOUT); +} + +int +iwmbt_patch_fwfile(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw) +{ + int ret, transferred; + struct iwmbt_firmware fw_job = *fw; + uint16_t cmd_opcode; + uint8_t cmd_length; + struct iwmbt_hci_cmd *cmd_buf; + uint8_t evt_code; + uint8_t evt_length; + uint8_t evt_buf[IWMBT_HCI_MAX_EVENT_SIZE]; + int activate_patch = 0; + + while (fw_job.len > 0) { + if (fw_job.len < 4) { + iwmbt_err("Invalid firmware, unexpected EOF in HCI " + "command header. Remains=%d", fw_job.len); + return (-1); + } + + if (fw_job.buf[0] != 0x01) { + iwmbt_err("Invalid firmware, expected HCI command (%d)", + fw_job.buf[0]); + return (-1); + } + + /* Advance by one. */ + fw_job.buf++; + fw_job.len--; + + /* Load in the HCI command to perform. */ + cmd_opcode = le16dec(fw_job.buf); + cmd_length = fw_job.buf[2]; + cmd_buf = (struct iwmbt_hci_cmd *)fw_job.buf; + + iwmbt_debug("opcode=%04x, len=%02x", cmd_opcode, cmd_length); + + /* + * If there is a command that loads a patch in the + * firmware file, then activate the patch upon success, + * otherwise just disable the manufacturer mode. + */ + if (cmd_opcode == 0xfc8e) + activate_patch = 1; + + /* Advance by three. */ + fw_job.buf += 3; + fw_job.len -= 3; + + if (fw_job.len < cmd_length) { + iwmbt_err("Invalid firmware, unexpected EOF in HCI " + "command data. len=%d, remains=%d", + cmd_length, fw_job.len); + return (-1); + } + + /* Advance by data length. */ + fw_job.buf += cmd_length; + fw_job.len -= cmd_length; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE, + 0, + 0, + 0, + (uint8_t *)cmd_buf, + IWMBT_HCI_CMD_SIZE(cmd_buf), + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) { + iwmbt_err("libusb_control_transfer() failed: err=%s", + libusb_strerror(ret)); + return (-1); + } + + /* + * Every command has its associated event: data must match + * what is recorded in the firmware file. Perform that check + * now. + */ + + while (fw_job.len > 0 && fw_job.buf[0] == 0x02) { + /* Is this the end of the file? */ + if (fw_job.len < 3) { + iwmbt_err("Invalid firmware, unexpected EOF in" + "event header. remains=%d", fw_job.len); + return (-1); + } + + /* Advance by one. */ + fw_job.buf++; + fw_job.len--; + + /* Load in the HCI event. */ + evt_code = fw_job.buf[0]; + evt_length = fw_job.buf[1]; + + /* Advance by two. */ + fw_job.buf += 2; + fw_job.len -= 2; + + /* Prepare HCI event buffer. */ + memset(evt_buf, 0, IWMBT_HCI_MAX_EVENT_SIZE); + + iwmbt_debug("event=%04x, len=%02x", + evt_code, evt_length); + + if (fw_job.len < evt_length) { + iwmbt_err("Invalid firmware, unexpected EOF in" + " event data. len=%d, remains=%d", + evt_length, fw_job.len); + return (-1); + } + + ret = libusb_interrupt_transfer(hdl, + IWMBT_INTERRUPT_ENDPOINT_ADDR, + evt_buf, + IWMBT_HCI_MAX_EVENT_SIZE, + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) { + iwmbt_err("libusb_interrupt_transfer() failed:" + " err=%s", libusb_strerror(ret)); + return (-1); + } + + if ((int)evt_length + 2 != transferred || + memcmp(evt_buf + 2, fw_job.buf, evt_length) != 0) { + iwmbt_err("event does not match firmware"); + return (-1); + } + + /* Advance by data length. */ + fw_job.buf += evt_length; + fw_job.len -= evt_length; + } + } + + return (activate_patch); +} + +#define IWMBT_SEND_FRAGMENT(fragment_type, size, msg) do { \ + iwmbt_debug("transferring %d bytes, offset %d", size, sent); \ + \ + ret = iwmbt_send_fragment(hdl, \ + fragment_type, \ + fw->buf + sent, \ + XMIN(size, fw->len - sent), \ + IWMBT_HCI_CMD_TIMEOUT); \ + \ + if (ret < 0) { \ + iwmbt_debug("Failed to send "msg": code=%d", ret); \ + return (-1); \ + } \ + sent += size; \ +} while (0) + +int +iwmbt_load_rsa_header(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw) +{ + int ret, sent = 0; + + IWMBT_SEND_FRAGMENT(0x00, 0x80, "CCS segment"); + IWMBT_SEND_FRAGMENT(0x03, 0x80, "public key / part 1"); + IWMBT_SEND_FRAGMENT(0x03, 0x80, "public key / part 2"); + + /* skip 4 bytes */ + sent += 4; + + IWMBT_SEND_FRAGMENT(0x02, 0x80, "signature / part 1"); + IWMBT_SEND_FRAGMENT(0x02, 0x80, "signature / part 2"); + + return (0); +} + +int +iwmbt_load_ecdsa_header(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw) +{ + int ret, sent = ECDSA_OFFSET; + + IWMBT_SEND_FRAGMENT(0x00, 0x80, "CCS segment"); + IWMBT_SEND_FRAGMENT(0x03, 0x60, "public key"); + IWMBT_SEND_FRAGMENT(0x02, 0x60, "signature"); + + return (0); +} + +int +iwmbt_load_fwfile(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw, uint32_t *boot_param, int offset) +{ + int ready = 0, sent = offset; + int ret, transferred; + struct iwmbt_hci_cmd *cmd; + struct iwmbt_hci_event *event; + uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; + + /* + * Send firmware chunks. Chunk len must be 4 byte aligned. + * multiple commands can be combined + */ + while (fw->len - sent - ready >= (int) sizeof(struct iwmbt_hci_cmd)) { + cmd = (struct iwmbt_hci_cmd *)(fw->buf + sent + ready); + /* Parse firmware for Intel Reset HCI command parameter */ + if (cmd->opcode == htole16(0xfc0e)) { + *boot_param = le32dec(cmd->data); + iwmbt_debug("boot_param=0x%08x", *boot_param); + } + ready += IWMBT_HCI_CMD_SIZE(cmd); + while (ready >= 0xFC) { + IWMBT_SEND_FRAGMENT(0x01, 0xFC, "firmware chunk"); + ready -= 0xFC; + } + if (ready > 0 && ready % 4 == 0) { + IWMBT_SEND_FRAGMENT(0x01, ready, "firmware chunk"); + ready = 0; + } + } + + /* Wait for firmware download completion event */ + ret = libusb_interrupt_transfer(hdl, + IWMBT_INTERRUPT_ENDPOINT_ADDR, + buf, + sizeof(buf), + &transferred, + IWMBT_LOADCMPL_TIMEOUT); + + if (ret < 0 || transferred < (int)sizeof(struct iwmbt_hci_event) + 1) { + iwmbt_err("libusb_interrupt_transfer() failed: " + "err=%s, size=%d", + libusb_strerror(ret), + transferred); + return (-1); + } + + /* Expect Vendor Specific Event 0x06 */ + event = (struct iwmbt_hci_event *)buf; + if (event->header.event != 0xFF || event->data[0] != 0x06) { + iwmbt_err("firmware download completion event missed"); + return (-1); + } + + return (0); +} + +int +iwmbt_enter_manufacturer(struct libusb_device_handle *hdl) +{ + int ret, transferred; + static struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc11), + .length = 2, + .data = { 0x01, 0x00 }, + }; + uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) { + iwmbt_debug("Can't enter manufacturer mode: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + return (0); +} + +int +iwmbt_exit_manufacturer(struct libusb_device_handle *hdl, + enum iwmbt_mm_exit mode) +{ + int ret, transferred; + static struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc11), + .length = 2, + .data = { 0x00, 0x00 }, + }; + uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; + + cmd.data[1] = (uint8_t)mode; + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) { + iwmbt_debug("Can't exit manufacturer mode: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + return (0); +} + +int +iwmbt_get_version(struct libusb_device_handle *hdl, + struct iwmbt_version *version) +{ + int ret, transferred; + struct iwmbt_hci_event_cmd_compl*event; + struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc05), + .length = 0, + }; + uint8_t buf[IWMBT_HCI_EVT_COMPL_SIZE(struct iwmbt_version)]; + + memset(buf, 0, sizeof(buf)); + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0 || transferred != sizeof(buf)) { + iwmbt_debug("Can't get version: : code=%d, size=%d", + ret, + transferred); + return (-1); + } + + event = (struct iwmbt_hci_event_cmd_compl *)buf; + memcpy(version, event->data, sizeof(struct iwmbt_version)); + + return (0); +} + +int +iwmbt_get_version_tlv(struct libusb_device_handle *hdl, + struct iwmbt_version_tlv *version) +{ + int ret, transferred; + struct iwmbt_hci_event_cmd_compl *event; + static struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc05), + .length = 1, + .data = { 0xff }, + }; + uint8_t status, datalen, type, len; + uint8_t *data; + uint8_t buf[255]; + + memset(buf, 0, sizeof(buf)); + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0 || transferred < (int)IWMBT_HCI_EVT_COMPL_SIZE(uint16_t)) { + iwmbt_debug("Can't get version: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + event = (struct iwmbt_hci_event_cmd_compl *)buf; + memcpy(version, event->data, sizeof(struct iwmbt_version)); + + datalen = event->header.length - IWMBT_HCI_EVENT_COMPL_HEAD_SIZE; + data = event->data; + status = *data++; + if (status != 0) + return (-1); + datalen--; + + while (datalen >= 2) { + type = *data++; + len = *data++; + datalen -= 2; + + if (datalen < len) + return (-1); + + switch (type) { + case IWMBT_TLV_CNVI_TOP: + assert(len == 4); + version->cnvi_top = le32dec(data); + break; + case IWMBT_TLV_CNVR_TOP: + assert(len == 4); + version->cnvr_top = le32dec(data); + break; + case IWMBT_TLV_CNVI_BT: + assert(len == 4); + version->cnvi_bt = le32dec(data); + break; + case IWMBT_TLV_CNVR_BT: + assert(len == 4); + version->cnvr_bt = le32dec(data); + break; + case IWMBT_TLV_DEV_REV_ID: + assert(len == 2); + version->dev_rev_id = le16dec(data); + break; + case IWMBT_TLV_IMAGE_TYPE: + assert(len == 1); + version->img_type = *data; + break; + case IWMBT_TLV_TIME_STAMP: + assert(len == 2); + version->min_fw_build_cw = data[0]; + version->min_fw_build_yy = data[1]; + version->timestamp = le16dec(data); + break; + case IWMBT_TLV_BUILD_TYPE: + assert(len == 1); + version->build_type = *data; + break; + case IWMBT_TLV_BUILD_NUM: + assert(len == 4); + version->min_fw_build_nn = *data; + version->build_num = le32dec(data); + break; + case IWMBT_TLV_SECURE_BOOT: + assert(len == 1); + version->secure_boot = *data; + break; + case IWMBT_TLV_OTP_LOCK: + assert(len == 1); + version->otp_lock = *data; + break; + case IWMBT_TLV_API_LOCK: + assert(len == 1); + version->api_lock = *data; + break; + case IWMBT_TLV_DEBUG_LOCK: + assert(len == 1); + version->debug_lock = *data; + break; + case IWMBT_TLV_MIN_FW: + assert(len == 3); + version->min_fw_build_nn = data[0]; + version->min_fw_build_cw = data[1]; + version->min_fw_build_yy = data[2]; + break; + case IWMBT_TLV_LIMITED_CCE: + assert(len == 1); + version->limited_cce = *data; + break; + case IWMBT_TLV_SBE_TYPE: + assert(len == 1); + version->sbe_type = *data; + break; + case IWMBT_TLV_OTP_BDADDR: + memcpy(&version->otp_bd_addr, data, sizeof(bdaddr_t)); + break; + default: + /* Ignore other types */ + break; + } + + datalen -= len; + data += len; + } + + return (0); +} + +int +iwmbt_get_boot_params(struct libusb_device_handle *hdl, + struct iwmbt_boot_params *params) +{ + int ret, transferred = 0; + struct iwmbt_hci_event_cmd_compl *event; + struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc0d), + .length = 0, + }; + uint8_t buf[IWMBT_HCI_EVT_COMPL_SIZE(struct iwmbt_boot_params)]; + + memset(buf, 0, sizeof(buf)); + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0 || transferred != sizeof(buf)) { + iwmbt_debug("Can't get boot params: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + event = (struct iwmbt_hci_event_cmd_compl *)buf; + memcpy(params, event->data, sizeof(struct iwmbt_boot_params)); + + return (0); +} + +int +iwmbt_intel_reset(struct libusb_device_handle *hdl, uint32_t boot_param) +{ + int ret, transferred = 0; + struct iwmbt_hci_event *event; + static struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc01), + .length = 8, + .data = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 }, + }; + uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; + + le32enc(cmd.data + 4, boot_param); + memset(buf, 0, sizeof(buf)); + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0 || transferred < (int)sizeof(struct iwmbt_hci_event) + 1) { + iwmbt_debug("Intel Reset command failed: code=%d, size=%d", + ret, + transferred); + return (ret); + } + + /* expect Vendor Specific Event 0x02 */ + event = (struct iwmbt_hci_event *)buf; + if (event->header.event != 0xFF || event->data[0] != 0x02) { + iwmbt_err("Intel Reset completion event missed"); + return (-1); + } + + return (0); +} + +int +iwmbt_load_ddc(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *ddc) +{ + int size, sent = 0; + int ret, transferred; + uint8_t buf[IWMBT_HCI_MAX_CMD_SIZE]; + uint8_t evt[IWMBT_HCI_MAX_CMD_SIZE]; + struct iwmbt_hci_cmd *cmd = (struct iwmbt_hci_cmd *)buf; + + size = ddc->len; + + iwmbt_debug("file=%s, size=%d", ddc->fwname, size); + + while (size > 0) { + + memset(buf, 0, sizeof(buf)); + cmd->opcode = htole16(0xfc8b); + cmd->length = ddc->buf[sent] + 1; + memcpy(cmd->data, ddc->buf + sent, XMIN(ddc->buf[sent], size)); + + iwmbt_debug("transferring %d bytes, offset %d", + cmd->length, + sent); + + size -= cmd->length; + sent += cmd->length; + + ret = iwmbt_hci_command(hdl, + cmd, + evt, + sizeof(evt), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) { + iwmbt_debug("Intel Write DDC failed: code=%d", ret); + return (-1); + } + } + + return (0); +} + +int +iwmbt_set_event_mask(struct libusb_device_handle *hdl) +{ + int ret, transferred = 0; + static struct iwmbt_hci_cmd cmd = { + .opcode = htole16(0xfc52), + .length = 8, + .data = { 0x87, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + }; + uint8_t buf[IWMBT_HCI_MAX_EVENT_SIZE]; + + ret = iwmbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + IWMBT_HCI_CMD_TIMEOUT); + + if (ret < 0) + iwmbt_debug("Intel Set Event Mask failed: code=%d", ret); + + return (ret); +} diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h new file mode 100644 index 000000000000..aac885dfd153 --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h @@ -0,0 +1,119 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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. + */ +#ifndef __IWMBT_HW_H__ +#define __IWMBT_HW_H__ + +/* USB control request (HCI command) structure */ +struct iwmbt_hci_cmd { + uint16_t opcode; + uint8_t length; + uint8_t data[]; +} __attribute__ ((packed)); + +#define IWMBT_HCI_CMD_SIZE(cmd) \ + ((cmd)->length + offsetof(struct iwmbt_hci_cmd, data)) + +/* USB interrupt transfer HCI event header structure */ +struct iwmbt_hci_evhdr { + uint8_t event; + uint8_t length; +} __attribute__ ((packed)); + +/* USB interrupt transfer (generic HCI event) structure */ +struct iwmbt_hci_event { + struct iwmbt_hci_evhdr header; + uint8_t data[]; +} __attribute__ ((packed)); + +/* USB interrupt transfer (HCI command completion event) structure */ +struct iwmbt_hci_event_cmd_compl { + struct iwmbt_hci_evhdr header; + uint8_t numpkt; + uint16_t opcode; + uint8_t data[]; +} __attribute__ ((packed)); + +/* + * Manufacturer mode exit type: selects reset type, + * 0x00: simply exit manufacturer mode without a reset. + * 0x01: exit manufacturer mode with a reset and patches disabled + * 0x02: exit manufacturer mode with a reset and patches enabled + */ +enum iwmbt_mm_exit { + IWMBT_MM_EXIT_ONLY = 0x00, + IWMBT_MM_EXIT_COLD_RESET = 0x01, + IWMBT_MM_EXIT_WARM_RESET = 0x02, +}; + +#define IWMBT_HCI_EVT_COMPL_SIZE(payload) \ + (offsetof(struct iwmbt_hci_event_cmd_compl, data) + sizeof(payload)) +#define IWMBT_HCI_EVENT_COMPL_HEAD_SIZE \ + (offsetof(struct iwmbt_hci_event_cmd_compl, data) - \ + offsetof(struct iwmbt_hci_event_cmd_compl, numpkt)) + +#define IWMBT_CONTROL_ENDPOINT_ADDR 0x00 +#define IWMBT_INTERRUPT_ENDPOINT_ADDR 0x81 +#define IWMBT_BULK_IN_ENDPOINT_ADDR 0x82 +#define IWMBT_BULK_OUT_ENDPOINT_ADDR 0x02 + +#define IWMBT_HCI_MAX_CMD_SIZE 256 +#define IWMBT_HCI_MAX_EVENT_SIZE 16 + +#define IWMBT_MSEC2TS(msec) \ + (struct timespec) { \ + .tv_sec = (msec) / 1000, \ + .tv_nsec = ((msec) % 1000) * 1000000 \ + }; +#define IWMBT_TS2MSEC(ts) ((ts).tv_sec * 1000 + (ts).tv_nsec / 1000000) +#define IWMBT_HCI_CMD_TIMEOUT 2000 /* ms */ +#define IWMBT_LOADCMPL_TIMEOUT 5000 /* ms */ + +extern int iwmbt_patch_fwfile(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw); +extern int iwmbt_load_rsa_header(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw); +extern int iwmbt_load_ecdsa_header(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw); +extern int iwmbt_load_fwfile(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *fw, uint32_t *boot_param, int offset); +extern int iwmbt_enter_manufacturer(struct libusb_device_handle *hdl); +extern int iwmbt_exit_manufacturer(struct libusb_device_handle *hdl, + enum iwmbt_mm_exit mode); +extern int iwmbt_get_version(struct libusb_device_handle *hdl, + struct iwmbt_version *version); +extern int iwmbt_get_version_tlv(struct libusb_device_handle *hdl, + struct iwmbt_version_tlv *version); +extern int iwmbt_get_boot_params(struct libusb_device_handle *hdl, + struct iwmbt_boot_params *params); +extern int iwmbt_intel_reset(struct libusb_device_handle *hdl, + uint32_t boot_param); +extern int iwmbt_load_ddc(struct libusb_device_handle *hdl, + const struct iwmbt_firmware *ddc); +extern int iwmbt_set_event_mask(struct libusb_device_handle *hdl); + +#endif diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 new file mode 100644 index 000000000000..ac32a675aa63 --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8 @@ -0,0 +1,101 @@ +.\"- +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2013, 2016 Adrian Chadd <adrian@freebsd.org> +.\" Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> +.\" Copyright (c) 2021 Philippe Michaud-Boudreault <pitwuu@gmail.com> +.\" +.\" 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. +.\" +.Dd July 15, 2025 +.Dt IWMBTFW 8 +.Os +.Sh NAME +.Nm iwmbtfw +.Nd load firmware for Intel Wireless AC Bluetooth USB devices +.Sh SYNOPSIS +.Nm +.Op Fl DI +.Fl d Ar device_name +.Op Fl f Ar firmware_path +.Nm +.Fl h +.Sh DESCRIPTION +The +.Nm +utility downloads the specified firmware file to the specified +.Xr ugen 4 +device. +.Pp +This utility will +.Em only +work with Intel Wireless 7260/8260/9260 chip based Bluetooth USB devices +and some of their successors. +The identification is currently based on USB vendor ID/product ID pair. +The vendor ID should be 0x8087 +.Pq Dv USB_VENDOR_INTEL2 +and the product ID should be one of the supported devices. +.Pp +Firmware files are available in the +.Pa comms/iwmbt-firmware +port. +.Pp +The +.Nm +utility will query the device to determine which firmware image and board +configuration to load in at runtime. +.Pp +The options are as follows: +.Bl -tag -width "-f firmware_path" +.It Fl I +Enable informational debugging. +.It Fl D +Enable verbose debugging. +.It Fl d Ar device_name +Specify +.Xr ugen 4 +device name. +.It Fl f Ar firmware_path +Specify the directory containing the firmware files to search and upload. +.It Fl h +Display usage message and exit. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr libusb 3 , +.Xr ugen 4 , +.Xr devd 8 +.Sh AUTHORS +.Nm +is based on +.Xr ath3kfw 8 +utility used as firmware downloader template and on Linux btintel driver +source code. +It is written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . +.Pp +Support for the 7260 card added by +.An Philippe Michaud-Boudreault Aq Mt pitwuu@gmail.com . +.Sh BUGS +Most likely. +Please report if found. diff --git a/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf new file mode 100644 index 000000000000..e30a3c15ccaa --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf @@ -0,0 +1,12 @@ +# +# Download Intel Wireless bluetooth adaptor firmware +# + +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x8087"; + match "product" "(0x07dc|0x0a2a|0x0aa7|0x0a2b|0x0aaa|0x0025|0x0026|0x0029|0x0032|0x0033)"; + action "/usr/sbin/iwmbtfw -d $cdev -f /usr/local/share/iwmbt-firmware"; +}; diff --git a/usr.sbin/bluetooth/iwmbtfw/main.c b/usr.sbin/bluetooth/iwmbtfw/main.c new file mode 100644 index 000000000000..b27c5ad62239 --- /dev/null +++ b/usr.sbin/bluetooth/iwmbtfw/main.c @@ -0,0 +1,770 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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/param.h> +#include <sys/stat.h> +#include <sys/endian.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libusb.h> + +#include "iwmbt_fw.h" +#include "iwmbt_hw.h" +#include "iwmbt_dbg.h" + +#define _DEFAULT_IWMBT_FIRMWARE_PATH "/usr/share/firmware/intel" + +int iwmbt_do_debug = 0; +int iwmbt_do_info = 0; + +enum iwmbt_device { + IWMBT_DEVICE_UNKNOWN, + IWMBT_DEVICE_7260, + IWMBT_DEVICE_8260, + IWMBT_DEVICE_9260, +}; + +struct iwmbt_devid { + uint16_t product_id; + uint16_t vendor_id; + enum iwmbt_device device; +}; + +static struct iwmbt_devid iwmbt_list[] = { + + /* Intel Wireless 7260/7265 and successors */ + { .vendor_id = 0x8087, .product_id = 0x07dc, .device = IWMBT_DEVICE_7260 }, + { .vendor_id = 0x8087, .product_id = 0x0a2a, .device = IWMBT_DEVICE_7260 }, + { .vendor_id = 0x8087, .product_id = 0x0aa7, .device = IWMBT_DEVICE_7260 }, + + /* Intel Wireless 8260/8265 and successors */ + { .vendor_id = 0x8087, .product_id = 0x0a2b, .device = IWMBT_DEVICE_8260 }, + { .vendor_id = 0x8087, .product_id = 0x0aaa, .device = IWMBT_DEVICE_8260 }, + { .vendor_id = 0x8087, .product_id = 0x0025, .device = IWMBT_DEVICE_8260 }, + { .vendor_id = 0x8087, .product_id = 0x0026, .device = IWMBT_DEVICE_8260 }, + { .vendor_id = 0x8087, .product_id = 0x0029, .device = IWMBT_DEVICE_8260 }, + + /* Intel Wireless 9260/9560 and successors */ + { .vendor_id = 0x8087, .product_id = 0x0032, .device = IWMBT_DEVICE_9260 }, + { .vendor_id = 0x8087, .product_id = 0x0033, .device = IWMBT_DEVICE_9260 }, +}; + +static enum iwmbt_device +iwmbt_is_supported(struct libusb_device_descriptor *d) +{ + int i; + + /* Search looking for whether it's an 7260/7265 */ + for (i = 0; i < (int) nitems(iwmbt_list); i++) { + if ((iwmbt_list[i].product_id == d->idProduct) && + (iwmbt_list[i].vendor_id == d->idVendor)) { + iwmbt_info("found iwmbtfw compatible"); + return (iwmbt_list[i].device); + } + } + + /* Not found */ + return (IWMBT_DEVICE_UNKNOWN); +} + +static libusb_device * +iwmbt_find_device(libusb_context *ctx, int bus_id, int dev_id, + enum iwmbt_device *iwmbt_device) +{ + libusb_device **list, *dev = NULL, *found = NULL; + struct libusb_device_descriptor d; + enum iwmbt_device device; + ssize_t cnt, i; + int r; + + cnt = libusb_get_device_list(ctx, &list); + if (cnt < 0) { + iwmbt_err("libusb_get_device_list() failed: code %lld", + (long long int) cnt); + return (NULL); + } + + /* + * Scan through USB device list. + */ + for (i = 0; i < cnt; i++) { + dev = list[i]; + if (bus_id == libusb_get_bus_number(dev) && + dev_id == libusb_get_device_address(dev)) { + /* Get the device descriptor for this device entry */ + r = libusb_get_device_descriptor(dev, &d); + if (r != 0) { + iwmbt_err("libusb_get_device_descriptor: %s", + libusb_strerror(r)); + break; + } + + /* Match on the vendor/product id */ + device = iwmbt_is_supported(&d); + if (device != IWMBT_DEVICE_UNKNOWN) { + /* + * Take a reference so it's not freed later on. + */ + found = libusb_ref_device(dev); + *iwmbt_device = device; + break; + } + } + } + + libusb_free_device_list(list, 1); + return (found); +} + +static void +iwmbt_dump_version(struct iwmbt_version *ver) +{ + iwmbt_info("status 0x%02x", ver->status); + iwmbt_info("hw_platform 0x%02x", ver->hw_platform); + iwmbt_info("hw_variant 0x%02x", ver->hw_variant); + iwmbt_info("hw_revision 0x%02x", ver->hw_revision); + iwmbt_info("fw_variant 0x%02x", ver->fw_variant); + iwmbt_info("fw_revision 0x%02x", ver->fw_revision); + iwmbt_info("fw_build_num 0x%02x", ver->fw_build_num); + iwmbt_info("fw_build_ww 0x%02x", ver->fw_build_ww); + iwmbt_info("fw_build_yy 0x%02x", ver->fw_build_yy); + iwmbt_info("fw_patch_num 0x%02x", ver->fw_patch_num); +} + +static void +iwmbt_dump_boot_params(struct iwmbt_boot_params *params) +{ + iwmbt_info("Device revision: %u", le16toh(params->dev_revid)); + iwmbt_info("Secure Boot: %s", params->secure_boot ? "on" : "off"); + iwmbt_info("OTP lock: %s", params->otp_lock ? "on" : "off"); + iwmbt_info("API lock: %s", params->api_lock ? "on" : "off"); + iwmbt_info("Debug lock: %s", params->debug_lock ? "on" : "off"); + iwmbt_info("Minimum firmware build %u week %u year %u", + params->min_fw_build_nn, + params->min_fw_build_cw, + 2000 + params->min_fw_build_yy); + iwmbt_info("OTC BD_ADDR: %02x:%02x:%02x:%02x:%02x:%02x", + params->otp_bdaddr[5], + params->otp_bdaddr[4], + params->otp_bdaddr[3], + params->otp_bdaddr[2], + params->otp_bdaddr[1], + params->otp_bdaddr[0]); +} + +static void +iwmbt_dump_version_tlv(struct iwmbt_version_tlv *ver) +{ + iwmbt_info("cnvi_top 0x%08x", ver->cnvi_top); + iwmbt_info("cnvr_top 0x%08x", ver->cnvr_top); + iwmbt_info("cnvi_bt 0x%08x", ver->cnvi_bt); + iwmbt_info("cnvr_bt 0x%08x", ver->cnvr_bt); + iwmbt_info("dev_rev_id 0x%04x", ver->dev_rev_id); + iwmbt_info("img_type 0x%02x", ver->img_type); + iwmbt_info("timestamp 0x%04x", ver->timestamp); + iwmbt_info("build_type 0x%02x", ver->build_type); + iwmbt_info("build_num 0x%08x", ver->build_num); + iwmbt_info("Secure Boot: %s", ver->secure_boot ? "on" : "off"); + iwmbt_info("OTP lock: %s", ver->otp_lock ? "on" : "off"); + iwmbt_info("API lock: %s", ver->api_lock ? "on" : "off"); + iwmbt_info("Debug lock: %s", ver->debug_lock ? "on" : "off"); + iwmbt_info("Minimum firmware build %u week %u year %u", + ver->min_fw_build_nn, + ver->min_fw_build_cw, + 2000 + ver->min_fw_build_yy); + iwmbt_info("limited_cce 0x%02x", ver->limited_cce); + iwmbt_info("sbe_type 0x%02x", ver->sbe_type); + iwmbt_info("OTC BD_ADDR: %02x:%02x:%02x:%02x:%02x:%02x", + ver->otp_bd_addr.b[5], + ver->otp_bd_addr.b[4], + ver->otp_bd_addr.b[3], + ver->otp_bd_addr.b[2], + ver->otp_bd_addr.b[1], + ver->otp_bd_addr.b[0]); + if (ver->img_type == TLV_IMG_TYPE_BOOTLOADER || + ver->img_type == TLV_IMG_TYPE_OPERATIONAL) + iwmbt_info("%s timestamp %u.%u buildtype %u build %u", + (ver->img_type == TLV_IMG_TYPE_BOOTLOADER ? + "Bootloader" : "Firmware"), + 2000 + (ver->timestamp >> 8), + ver->timestamp & 0xff, + ver->build_type, + ver->build_num); +} + + +static int +iwmbt_init_firmware(libusb_device_handle *hdl, const char *firmware_path, + uint32_t *boot_param, uint8_t hw_variant, uint8_t sbe_type) +{ + struct iwmbt_firmware fw; + int header_len, ret = -1; + + iwmbt_debug("loading %s", firmware_path); + + /* Read in the firmware */ + if (iwmbt_fw_read(&fw, firmware_path) <= 0) { + iwmbt_debug("iwmbt_fw_read() failed"); + return (-1); + } + + iwmbt_debug("Firmware file size=%d", fw.len); + + if (hw_variant <= 0x14) { + /* + * Hardware variants 0x0b, 0x0c, 0x11 - 0x14 .sfi file have + * a RSA header of 644 bytes followed by Command Buffer. + */ + header_len = RSA_HEADER_LEN; + if (fw.len < header_len) { + iwmbt_err("Invalid size of firmware file (%d)", fw.len); + ret = -1; + goto exit; + } + + /* Check if the CSS Header version is RSA(0x00010000) */ + if (le32dec(fw.buf + CSS_HEADER_OFFSET) != 0x00010000) { + iwmbt_err("Invalid CSS Header version"); + ret = -1; + goto exit; + } + + /* Only RSA secure boot engine supported */ + if (sbe_type != 0x00) { + iwmbt_err("Invalid SBE type for hardware variant (%d)", + hw_variant); + ret = -1; + goto exit; + } + + } else if (hw_variant >= 0x17) { + /* + * Hardware variants 0x17, 0x18 onwards support both RSA and + * ECDSA secure boot engine. As a result, the corresponding sfi + * file will have RSA header of 644, ECDSA header of 320 bytes + * followed by Command Buffer. + */ + header_len = ECDSA_OFFSET + ECDSA_HEADER_LEN; + if (fw.len < header_len) { + iwmbt_err("Invalid size of firmware file (%d)", fw.len); + ret = -1; + goto exit; + } + + /* Check if CSS header for ECDSA follows the RSA header */ + if (fw.buf[ECDSA_OFFSET] != 0x06) { + ret = -1; + goto exit; + } + + /* Check if the CSS Header version is ECDSA(0x00020000) */ + if (le32dec(fw.buf + ECDSA_OFFSET + CSS_HEADER_OFFSET) != 0x00020000) { + iwmbt_err("Invalid CSS Header version"); + ret = -1; + goto exit; + } + } + + /* Load in the CSS header */ + if (sbe_type == 0x00) + ret = iwmbt_load_rsa_header(hdl, &fw); + else if (sbe_type == 0x01) + ret = iwmbt_load_ecdsa_header(hdl, &fw); + if (ret < 0) + goto exit; + + /* Load in the Command Buffer */ + ret = iwmbt_load_fwfile(hdl, &fw, boot_param, header_len); + +exit: + /* free firmware */ + iwmbt_fw_free(&fw); + + return (ret); +} + +static int +iwmbt_init_ddc(libusb_device_handle *hdl, const char *ddc_path) +{ + struct iwmbt_firmware ddc; + int ret; + + iwmbt_debug("loading %s", ddc_path); + + /* Read in the DDC file */ + if (iwmbt_fw_read(&ddc, ddc_path) <= 0) { + iwmbt_debug("iwmbt_fw_read() failed"); + return (-1); + } + + /* Load in the DDC file */ + ret = iwmbt_load_ddc(hdl, &ddc); + if (ret < 0) + iwmbt_debug("Loading DDC file failed"); + + /* free it */ + iwmbt_fw_free(&ddc); + + return (ret); +} + +/* + * Parse ugen name and extract device's bus and address + */ + +static int +parse_ugen_name(char const *ugen, uint8_t *bus, uint8_t *addr) +{ + char *ep; + + if (strncmp(ugen, "ugen", 4) != 0) + return (-1); + + *bus = (uint8_t) strtoul(ugen + 4, &ep, 10); + if (*ep != '.') + return (-1); + + *addr = (uint8_t) strtoul(ep + 1, &ep, 10); + if (*ep != '\0') + return (-1); + + return (0); +} + +static void +usage(void) +{ + fprintf(stderr, + "Usage: iwmbtfw [-DI] -d ugenX.Y [-f firmware path]\n"); + fprintf(stderr, " -D: enable debugging\n"); + fprintf(stderr, " -d: device to operate upon\n"); + fprintf(stderr, " -f: firmware path (defaults to %s)\n", + _DEFAULT_IWMBT_FIRMWARE_PATH); + fprintf(stderr, " -I: enable informational output\n"); + exit(127); +} + + + +/* + * Returns 0 on success. + */ +static int +handle_7260(libusb_device_handle *hdl, char *firmware_dir) +{ + int r; + char *firmware_path; + struct iwmbt_version ver; + struct iwmbt_firmware fw; + + r = iwmbt_get_version(hdl, &ver); + if (r < 0) { + iwmbt_debug("iwmbt_get_version() failed code %d", r); + return 1; + } + iwmbt_dump_version(&ver); + iwmbt_debug("fw_patch_num=0x%02x", (int) ver.fw_patch_num); + + /* fw_patch_num = >0 operational mode */ + if (ver.fw_patch_num > 0x00) { + iwmbt_info("Firmware has already been downloaded"); + return 0; + } + + firmware_path = iwmbt_get_fwname(&ver, NULL, firmware_dir, "bseq"); + if (firmware_path == NULL) + return 1; + iwmbt_debug("firmware_path = %s", firmware_path); + + r = iwmbt_fw_read(&fw, firmware_path); + free(firmware_path); + if (r <= 0) { + iwmbt_debug("iwmbt_fw_read() failed"); + return 1; + } + + r = iwmbt_enter_manufacturer(hdl); + if (r < 0) { + iwmbt_debug("iwmbt_enter_manufacturer() failed code %d", r); + iwmbt_fw_free(&fw); + return 1; + } + + /* Download firmware */ + r = iwmbt_patch_fwfile(hdl, &fw); + iwmbt_fw_free(&fw); + if (r < 0) { + iwmbt_debug("Loading firmware file failed"); + (void)iwmbt_exit_manufacturer(hdl, IWMBT_MM_EXIT_COLD_RESET); + return 1; + } + + iwmbt_info("Firmware download complete"); + + r = iwmbt_exit_manufacturer(hdl, + (r == 0 ? IWMBT_MM_EXIT_ONLY : IWMBT_MM_EXIT_WARM_RESET)); + if (r < 0) { + iwmbt_debug("iwmbt_exit_manufacturer() failed code %d", r); + return 1; + } + + /* Once device is running in operational mode we can ignore failures */ + + /* Dump actual controller version */ + r = iwmbt_get_version(hdl, &ver); + if (r == 0) + iwmbt_dump_version(&ver); + + if (iwmbt_enter_manufacturer(hdl) < 0) + return 0; + r = iwmbt_set_event_mask(hdl); + if (r == 0) + iwmbt_info("Intel Event Mask is set"); + (void)iwmbt_exit_manufacturer(hdl, IWMBT_MM_EXIT_ONLY); + + return 0; +} + + +/* + * Returns 0 on success. + */ +static int +handle_8260(libusb_device_handle *hdl, char *firmware_dir) +{ + int r; + uint32_t boot_param; + struct iwmbt_version ver; + struct iwmbt_boot_params params; + char *firmware_path = NULL; + + r = iwmbt_get_version(hdl, &ver); + if (r < 0) { + iwmbt_debug("iwmbt_get_version() failed code %d", r); + return 1; + } + iwmbt_dump_version(&ver); + iwmbt_debug("fw_variant=0x%02x", (int) ver.fw_variant); + + if (ver.fw_variant == FW_VARIANT_OPERATIONAL) { + iwmbt_info("Firmware has already been downloaded"); + return 0; + } + + if (ver.fw_variant != FW_VARIANT_BOOTLOADER){ + iwmbt_err("unknown fw_variant 0x%02x", (int) ver.fw_variant); + return 1; + } + + /* Read Intel Secure Boot Params */ + r = iwmbt_get_boot_params(hdl, ¶ms); + if (r < 0) { + iwmbt_debug("iwmbt_get_boot_params() failed!"); + return 1; + } + iwmbt_dump_boot_params(¶ms); + + /* Check if firmware fragments are ACKed with a cmd complete event */ + if (params.limited_cce != 0x00) { + iwmbt_err("Unsupported Intel firmware loading method (%u)", + params.limited_cce); + return 1; + } + + firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "sfi"); + if (firmware_path == NULL) + return 1; + iwmbt_debug("firmware_path = %s", firmware_path); + + /* Download firmware and parse it for magic Intel Reset parameter */ + r = iwmbt_init_firmware(hdl, firmware_path, &boot_param, 0, 0); + free(firmware_path); + if (r < 0) + return 1; + + iwmbt_info("Firmware download complete"); + + r = iwmbt_intel_reset(hdl, boot_param); + if (r < 0) { + iwmbt_debug("iwmbt_intel_reset() failed!"); + return 1; + } + + iwmbt_info("Firmware operational"); + + /* Once device is running in operational mode we can ignore failures */ + + /* Dump actual controller version */ + r = iwmbt_get_version(hdl, &ver); + if (r == 0) + iwmbt_dump_version(&ver); + + /* Apply the device configuration (DDC) parameters */ + firmware_path = iwmbt_get_fwname(&ver, ¶ms, firmware_dir, "ddc"); + iwmbt_debug("ddc_path = %s", firmware_path); + if (firmware_path != NULL) { + r = iwmbt_init_ddc(hdl, firmware_path); + if (r == 0) + iwmbt_info("DDC download complete"); + free(firmware_path); + } + + r = iwmbt_set_event_mask(hdl); + if (r == 0) + iwmbt_info("Intel Event Mask is set"); + + return 0; +} + + +static int +handle_9260(libusb_device_handle *hdl, char *firmware_dir) +{ + int r; + uint32_t boot_param; + struct iwmbt_version vl; + struct iwmbt_version_tlv vt; + char *firmware_path = NULL; + + r = iwmbt_get_version_tlv(hdl, &vt); + if (r < 0) { + iwmbt_debug("iwmbt_get_version_tlv() failed code %d", r); + return 1; + } + iwmbt_dump_version_tlv(&vt); + iwmbt_debug("img_type=0x%02x", (int) vt.img_type); + + if (vt.img_type == TLV_IMG_TYPE_OPERATIONAL) { + iwmbt_info("Firmware has already been downloaded"); + return 0; + } + + if (vt.img_type != TLV_IMG_TYPE_BOOTLOADER) { + iwmbt_err("unknown img_type 0x%02x", (int) vt.img_type); + return 1; + } + + /* Check if firmware fragments are ACKed with a cmd complete event */ + if (vt.limited_cce != 0x00) { + iwmbt_err("Unsupported Intel firmware loading method (%u)", + vt.limited_cce); + return 1; + } + + /* Check if secure boot engine is supported: 1 (ECDSA) or 0 (RSA) */ + if (vt.sbe_type > 0x01) { + iwmbt_err("Unsupported secure boot engine (%u)", + vt.sbe_type); + return 1; + } + + firmware_path = iwmbt_get_fwname_tlv(&vt, firmware_dir, "sfi"); + if (firmware_path == NULL) + return 1; + iwmbt_debug("firmware_path = %s", firmware_path); + + /* Download firmware and parse it for magic Intel Reset parameter */ + r = iwmbt_init_firmware(hdl, firmware_path, &boot_param, + vt.cnvi_bt >> 16 & 0x3f, vt.sbe_type); + free(firmware_path); + if (r < 0) + return 1; + + iwmbt_info("Firmware download complete"); + + r = iwmbt_intel_reset(hdl, boot_param); + if (r < 0) { + iwmbt_debug("iwmbt_intel_reset() failed!"); + return 1; + } + + iwmbt_info("Firmware operational"); + + /* Once device is running in operational mode we can ignore failures */ + + r = iwmbt_get_version(hdl, &vl); + if (r == 0) + iwmbt_dump_version(&vl); + + /* Apply the device configuration (DDC) parameters */ + firmware_path = iwmbt_get_fwname_tlv(&vt, firmware_dir, "ddc"); + iwmbt_debug("ddc_path = %s", firmware_path); + if (firmware_path != NULL) { + r = iwmbt_init_ddc(hdl, firmware_path); + if (r == 0) + iwmbt_info("DDC download complete"); + free(firmware_path); + } + + r = iwmbt_set_event_mask(hdl); + if (r == 0) + iwmbt_info("Intel Event Mask is set"); + + return 0; +} + + +int +main(int argc, char *argv[]) +{ + libusb_context *ctx = NULL; + libusb_device *dev = NULL; + libusb_device_handle *hdl = NULL; + int r; + uint8_t bus_id = 0, dev_id = 0; + int devid_set = 0; + int n; + char *firmware_dir = NULL; + int retcode = 1; + enum iwmbt_device iwmbt_device; + + /* Parse command line arguments */ + while ((n = getopt(argc, argv, "Dd:f:hI")) != -1) { + switch (n) { + case 'd': /* ugen device name */ + devid_set = 1; + if (parse_ugen_name(optarg, &bus_id, &dev_id) < 0) + usage(); + break; + case 'D': + iwmbt_do_debug = 1; + break; + case 'f': /* firmware dir */ + if (firmware_dir) + free(firmware_dir); + firmware_dir = strdup(optarg); + break; + case 'I': + iwmbt_do_info = 1; + break; + case 'h': + default: + usage(); + break; + /* NOT REACHED */ + } + } + + /* Ensure the devid was given! */ + if (devid_set == 0) { + usage(); + /* NOTREACHED */ + } + + /* Default the firmware path */ + if (firmware_dir == NULL) + firmware_dir = strdup(_DEFAULT_IWMBT_FIRMWARE_PATH); + + /* libusb setup */ + r = libusb_init(&ctx); + if (r != 0) { + iwmbt_err("libusb_init failed: code %d", r); + exit(127); + } + + iwmbt_debug("opening dev %d.%d", (int) bus_id, (int) dev_id); + + /* Find a device based on the bus/dev id */ + dev = iwmbt_find_device(ctx, bus_id, dev_id, &iwmbt_device); + if (dev == NULL) { + iwmbt_err("device not found"); + goto shutdown; + } + + /* XXX enforce that bInterfaceNumber is 0 */ + + /* XXX enforce the device/product id if they're non-zero */ + + /* Grab device handle */ + r = libusb_open(dev, &hdl); + if (r != 0) { + iwmbt_err("libusb_open() failed: code %d", r); + goto shutdown; + } + + /* Check if ng_ubt is attached */ + r = libusb_kernel_driver_active(hdl, 0); + if (r < 0) { + iwmbt_err("libusb_kernel_driver_active() failed: code %d", r); + goto shutdown; + } + if (r > 0) { + iwmbt_info("Firmware has already been downloaded"); + retcode = 0; + goto shutdown; + } + + switch(iwmbt_device) { + case IWMBT_DEVICE_7260: + retcode = handle_7260(hdl, firmware_dir); + break; + case IWMBT_DEVICE_8260: + retcode = handle_8260(hdl, firmware_dir); + break; + case IWMBT_DEVICE_9260: + retcode = handle_9260(hdl, firmware_dir); + break; + default: + iwmbt_err("FIXME: unknown iwmbt type %d", (int)iwmbt_device); + retcode = 1; + } + + if (retcode == 0) { + /* Ask kernel driver to probe and attach device again */ + r = libusb_reset_device(hdl); + if (r != 0) + iwmbt_err("libusb_reset_device() failed: %s", + libusb_strerror(r)); + } + +shutdown: + if (hdl != NULL) + libusb_close(hdl); + + if (dev != NULL) + libusb_unref_device(dev); + + if (ctx != NULL) + libusb_exit(ctx); + + if (retcode == 0) + iwmbt_info("Firmware download is successful!"); + else + iwmbt_err("Firmware download failed!"); + + return (retcode); +} diff --git a/usr.sbin/bluetooth/l2control/Makefile b/usr.sbin/bluetooth/l2control/Makefile new file mode 100644 index 000000000000..e81bed589420 --- /dev/null +++ b/usr.sbin/bluetooth/l2control/Makefile @@ -0,0 +1,11 @@ +# $Id: Makefile,v 1.7 2003/08/14 20:06:22 max Exp $ + +PACKAGE= bluetooth +PROG= l2control +MAN= l2control.8 +SRCS= l2cap.c l2control.c +WARNS?= 2 + +LIBADD= bluetooth + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/l2control/Makefile.depend b/usr.sbin/bluetooth/l2control/Makefile.depend new file mode 100644 index 000000000000..5d0531350f25 --- /dev/null +++ b/usr.sbin/bluetooth/l2control/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/l2control/l2cap.c b/usr.sbin/bluetooth/l2control/l2cap.c new file mode 100644 index 000000000000..0f410d3c6a85 --- /dev/null +++ b/usr.sbin/bluetooth/l2control/l2cap.c @@ -0,0 +1,315 @@ +/*- + * l2cap.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: l2cap.c,v 1.5 2003/05/16 19:52:37 max Exp $ + */ + +#include <sys/ioctl.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "l2control.h" + +#define SIZE(x) (sizeof((x))/sizeof((x)[0])) + +/* Print BDADDR */ +static char * +bdaddrpr(bdaddr_t const *ba) +{ + extern int numeric_bdaddr; + static char str[24]; + struct hostent *he = NULL; + + if (memcmp(ba, NG_HCI_BDADDR_ANY, sizeof(*ba)) == 0) { + str[0] = '*'; + str[1] = 0; + + return (str); + } + + if (!numeric_bdaddr && + (he = bt_gethostbyaddr((char *)ba, sizeof(*ba), AF_BLUETOOTH)) != NULL) { + strlcpy(str, he->h_name, sizeof(str)); + + return (str); + } + + bt_ntoa(ba, str); + + return (str); +} /* bdaddrpr */ + +/* Send read_node_flags command to the node */ +static int +l2cap_read_node_flags(int s, int argc, char **argv) +{ + struct ng_btsocket_l2cap_raw_node_flags r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_L2CAP_NODE_GET_FLAGS, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Connectionless traffic flags:\n"); + fprintf(stdout, "\tSDP: %s\n", + (r.flags & NG_L2CAP_CLT_SDP_DISABLED)? "disabled" : "enabled"); + fprintf(stdout, "\tRFCOMM: %s\n", + (r.flags & NG_L2CAP_CLT_RFCOMM_DISABLED)? "disabled":"enabled"); + fprintf(stdout, "\tTCP: %s\n", + (r.flags & NG_L2CAP_CLT_TCP_DISABLED)? "disabled" : "enabled"); + + return (OK); +} /* l2cap_read_node_flags */ + +/* Send read_debug_level command to the node */ +static int +l2cap_read_debug_level(int s, int argc, char **argv) +{ + struct ng_btsocket_l2cap_raw_node_debug r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_L2CAP_NODE_GET_DEBUG, &r, sizeof(r)) < 0) + return (ERROR); + + fprintf(stdout, "Debug level: %d\n", r.debug); + + return (OK); +} /* l2cap_read_debug_level */ + +/* Send write_debug_level command to the node */ +static int +l2cap_write_debug_level(int s, int argc, char **argv) +{ + struct ng_btsocket_l2cap_raw_node_debug r; + + memset(&r, 0, sizeof(r)); + switch (argc) { + case 1: + r.debug = atoi(argv[0]); + break; + + default: + return (USAGE); + } + + if (ioctl(s, SIOC_L2CAP_NODE_SET_DEBUG, &r, sizeof(r)) < 0) + return (ERROR); + + return (OK); +} /* l2cap_write_debug_level */ + +/* Send read_connection_list command to the node */ +static int +l2cap_read_connection_list(int s, int argc, char **argv) +{ + static char const * const state[] = { + /* NG_L2CAP_CON_CLOSED */ "CLOSED", + /* NG_L2CAP_W4_LP_CON_CFM */ "W4_LP_CON_CFM", + /* NG_L2CAP_CON_OPEN */ "OPEN" + }; +#define con_state2str(x) ((x) >= SIZE(state)? "UNKNOWN" : state[(x)]) + + struct ng_btsocket_l2cap_raw_con_list r; + int n, error = OK; + + memset(&r, 0, sizeof(r)); + r.num_connections = NG_L2CAP_MAX_CON_NUM; + r.connections = calloc(NG_L2CAP_MAX_CON_NUM, + sizeof(ng_l2cap_node_con_ep)); + if (r.connections == NULL) { + errno = ENOMEM; + return (ERROR); + } + + if (ioctl(s, SIOC_L2CAP_NODE_GET_CON_LIST, &r, sizeof(r)) < 0) { + error = ERROR; + goto out; + } + + fprintf(stdout, "L2CAP connections:\n"); + fprintf(stdout, +"Remote BD_ADDR Handle Flags Pending State\n"); + for (n = 0; n < r.num_connections; n++) { + fprintf(stdout, + "%-17.17s " \ + "%6d " \ + "%c%c%c%c%c " \ + "%7d " \ + "%s\n", + bdaddrpr(&r.connections[n].remote), + r.connections[n].con_handle, + ((r.connections[n].flags & NG_L2CAP_CON_OUTGOING)? 'O' : 'I'), + ((r.connections[n].flags & NG_L2CAP_CON_LP_TIMO)? 'L' : ' '), + ((r.connections[n].flags & NG_L2CAP_CON_AUTO_DISCON_TIMO)? 'D' : ' '), + ((r.connections[n].flags & NG_L2CAP_CON_TX)? 'T' : ' '), + ((r.connections[n].flags & NG_L2CAP_CON_RX)? 'R' : ' '), + r.connections[n].pending, + con_state2str(r.connections[n].state)); + } +out: + free(r.connections); + + return (error); +} /* l2cap_read_connection_list */ + +/* Send read_channel_list command to the node */ +static int +l2cap_read_channel_list(int s, int argc, char **argv) +{ + static char const * const state[] = { + /* NG_L2CAP_CLOSED */ "CLOSED", + /* NG_L2CAP_W4_L2CAP_CON_RSP */ "W4_L2CAP_CON_RSP", + /* NG_L2CAP_W4_L2CA_CON_RSP */ "W4_L2CA_CON_RSP", + /* NG_L2CAP_CONFIG */ "CONFIG", + /* NG_L2CAP_OPEN */ "OPEN", + /* NG_L2CAP_W4_L2CAP_DISCON_RSP */ "W4_L2CAP_DISCON_RSP", + /* NG_L2CAP_W4_L2CA_DISCON_RSP */ "W4_L2CA_DISCON_RSP" + }; +#define ch_state2str(x) ((x) >= SIZE(state)? "UNKNOWN" : state[(x)]) + + struct ng_btsocket_l2cap_raw_chan_list r; + int n, error = OK; + + memset(&r, 0, sizeof(r)); + r.num_channels = NG_L2CAP_MAX_CHAN_NUM; + r.channels = calloc(NG_L2CAP_MAX_CHAN_NUM, + sizeof(ng_l2cap_node_chan_ep)); + if (r.channels == NULL) { + errno = ENOMEM; + return (ERROR); + } + + if (ioctl(s, SIOC_L2CAP_NODE_GET_CHAN_LIST, &r, sizeof(r)) < 0) { + error = ERROR; + goto out; + } + + fprintf(stdout, "L2CAP channels:\n"); + fprintf(stdout, +"Remote BD_ADDR SCID/ DCID PSM IMTU/ OMTU State\n"); + for (n = 0; n < r.num_channels; n++) { + fprintf(stdout, + "%-17.17s " \ + "%5d/%5d %5d " \ + "%5d/%5d " \ + "%s\n", + bdaddrpr(&r.channels[n].remote), + r.channels[n].scid, r.channels[n].dcid, + r.channels[n].psm, r.channels[n].imtu, + r.channels[n].omtu, + ch_state2str(r.channels[n].state)); + } +out: + free(r.channels); + + return (error); +} /* l2cap_read_channel_list */ + +/* Send read_auto_disconnect_timeout command to the node */ +static int +l2cap_read_auto_disconnect_timeout(int s, int argc, char **argv) +{ + struct ng_btsocket_l2cap_raw_auto_discon_timo r; + + memset(&r, 0, sizeof(r)); + if (ioctl(s, SIOC_L2CAP_NODE_GET_AUTO_DISCON_TIMO, &r, sizeof(r)) < 0) + return (ERROR); + + if (r.timeout != 0) + fprintf(stdout, "Auto disconnect timeout: %d sec\n", r.timeout); + else + fprintf(stdout, "Auto disconnect disabled\n"); + + return (OK); +} /* l2cap_read_auto_disconnect_timeout */ + +/* Send write_auto_disconnect_timeout command to the node */ +static int +l2cap_write_auto_disconnect_timeout(int s, int argc, char **argv) +{ + struct ng_btsocket_l2cap_raw_auto_discon_timo r; + + memset(&r, 0, sizeof(r)); + switch (argc) { + case 1: + r.timeout = atoi(argv[0]); + break; + + default: + return (USAGE); + } + + if (ioctl(s, SIOC_L2CAP_NODE_SET_AUTO_DISCON_TIMO, &r, sizeof(r)) < 0) + return (ERROR); + + return (OK); +} /* l2cap_write_auto_disconnect_timeout */ + +struct l2cap_command l2cap_commands[] = { +{ +"read_node_flags", +"Get L2CAP node flags", +&l2cap_read_node_flags +}, +{ +"read_debug_level", +"Get L2CAP node debug level", +&l2cap_read_debug_level +}, +{ +"write_debug_level <level>", +"Set L2CAP node debug level", +&l2cap_write_debug_level +}, +{ +"read_connection_list", +"Read list of the L2CAP connections", +&l2cap_read_connection_list +}, +{ +"read_channel_list", +"Read list of the L2CAP channels", +&l2cap_read_channel_list +}, +{ +"read_auto_disconnect_timeout", +"Get L2CAP node auto disconnect timeout (in sec)", +&l2cap_read_auto_disconnect_timeout +}, +{ +"write_auto_disconnect_timeout <timeout>", +"Set L2CAP node auto disconnect timeout (in sec)", +&l2cap_write_auto_disconnect_timeout +}, +{ +NULL, +}}; + diff --git a/usr.sbin/bluetooth/l2control/l2control.8 b/usr.sbin/bluetooth/l2control/l2control.8 new file mode 100644 index 000000000000..478a97f89d99 --- /dev/null +++ b/usr.sbin/bluetooth/l2control/l2control.8 @@ -0,0 +1,96 @@ +.\" Copyright (c) 2001-2002 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. +.\" +.\" $Id: l2control.8,v 1.5 2003/05/21 00:53:00 max Exp $ +.\" +.Dd April 9, 2011 +.Dt L2CONTROL 8 +.Os +.Sh NAME +.Nm l2control +.Nd L2CAP configuration utility +.Sh SYNOPSIS +.Nm +.Op Fl hn +.Fl a Ar local +.Ar command +.Op Ar parameters ... +.Sh DESCRIPTION +The +.Nm +utility connects to the local device with the specified BD_ADDR or name +and attempts to send the specified command. +The +.Nm +utility will print results to the standard output and error messages to +the standard error output. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar local +Connect to the local device with the specified BD_ADDR or name. +Example: +.Fl a Li 00:01:02:03:04:05 +or +.Fl a Li bt_device . +.It Fl h +Display usage message and exit. +.It Fl n +Show Bluetooth addresses as numbers. +Normally +.Nm +attempts to resolve Bluetooth addresses, and display them symbolically. +.It Ar command +One of the supported commands (see below). +The special command +.Cm help +can be used to obtain a list of all supported commands. +To get more information about a specific command use +.Cm help Ar command . +.It Ar parameters +One or more optional space separated command parameters. +.El +.Sh COMMANDS +The currently supported node commands in +.Nm +are: +.Pp +.Bl -tag -width "Write_Auto_Disconnect_Timeout" -offset indent -compact +.It Cm Read_Node_Flags +.It Cm Read_Debug_Level +.It Cm Write_Debug_Level +.It Cm Read_Connection_List +.It Cm Read_Channel_List +.It Cm Read_Auto_Disconnect_Timeout +.It Cm Write_Auto_Disconnect_Timeout +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr netgraph 3 , +.Xr netgraph 4 , +.Xr ng_l2cap 4 , +.Xr l2ping 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt emax@FreeBSD.org diff --git a/usr.sbin/bluetooth/l2control/l2control.c b/usr.sbin/bluetooth/l2control/l2control.c new file mode 100644 index 000000000000..7312371345ec --- /dev/null +++ b/usr.sbin/bluetooth/l2control/l2control.c @@ -0,0 +1,222 @@ +/*- + * l2control.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: l2control.c,v 1.6 2003/09/05 00:38:25 max Exp $ + */ + +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "l2control.h" + +/* Prototypes */ +static int do_l2cap_command (bdaddr_p, int, char **); +static struct l2cap_command * find_l2cap_command (char const *, + struct l2cap_command *); +static void print_l2cap_command (struct l2cap_command *); +static void usage (void); + +/* Main */ + +int numeric_bdaddr = 0; + +int +main(int argc, char *argv[]) +{ + int n; + bdaddr_t bdaddr; + + memset(&bdaddr, 0, sizeof(bdaddr)); + + /* Process command line arguments */ + while ((n = getopt(argc, argv, "a:nh")) != -1) { + switch (n) { + case 'a': + if (!bt_aton(optarg, &bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(1, "%s: %s", optarg, hstrerror(h_errno)); + + memcpy(&bdaddr, he->h_addr, sizeof(bdaddr)); + } + break; + + case 'n': + numeric_bdaddr = 1; + break; + + case 'h': + default: + usage(); + break; + } + } + + argc -= optind; + argv += optind; + + if (*argv == NULL) + usage(); + + return (do_l2cap_command(&bdaddr, argc, argv)); +} /* main */ + +/* Execute commands */ +static int +do_l2cap_command(bdaddr_p bdaddr, int argc, char **argv) +{ + char *cmd = argv[0]; + struct l2cap_command *c = NULL; + struct sockaddr_l2cap sa; + int s, e, help; + + help = 0; + if (strcasecmp(cmd, "help") == 0) { + argc --; + argv ++; + + if (argc <= 0) { + fprintf(stdout, "Supported commands:\n"); + print_l2cap_command(l2cap_commands); + fprintf(stdout, "\nFor more information use " \ + "'help command'\n"); + + return (OK); + } + + help = 1; + cmd = argv[0]; + } + + c = find_l2cap_command(cmd, l2cap_commands); + if (c == NULL) { + fprintf(stdout, "Unknown command: \"%s\"\n", cmd); + return (ERROR); + } + + if (!help) { + if (memcmp(bdaddr, NG_HCI_BDADDR_ANY, sizeof(*bdaddr)) == 0) + usage(); + + memset(&sa, 0, sizeof(sa)); + sa.l2cap_len = sizeof(sa); + sa.l2cap_family = AF_BLUETOOTH; + memcpy(&sa.l2cap_bdaddr, bdaddr, sizeof(sa.l2cap_bdaddr)); + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_L2CAP); + if (s < 0) + err(1, "Could not create socket"); + + if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) + err(2, +"Could not bind socket, bdaddr=%s", bt_ntoa(&sa.l2cap_bdaddr, NULL)); + + e = 0x0ffff; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &e, sizeof(e)) < 0) + err(3, "Could not setsockopt(RCVBUF, %d)", e); + + e = (c->handler)(s, -- argc, ++ argv); + + close(s); + } else + e = USAGE; + + switch (e) { + case OK: + case FAILED: + break; + + case ERROR: + fprintf(stdout, "Could not execute command \"%s\". %s\n", + cmd, strerror(errno)); + break; + + case USAGE: + fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description); + break; + + default: assert(0); break; + } + + return (e); +} /* do_l2cap_command */ + +/* Try to find command in specified category */ +static struct l2cap_command * +find_l2cap_command(char const *command, struct l2cap_command *category) +{ + struct l2cap_command *c = NULL; + + for (c = category; c->command != NULL; c++) { + char *c_end = strchr(c->command, ' '); + + if (c_end != NULL) { + int len = c_end - c->command; + + if (strncasecmp(command, c->command, len) == 0) + return (c); + } else if (strcasecmp(command, c->command) == 0) + return (c); + } + + return (NULL); +} /* find_l2cap_command */ + +/* Print commands in specified category */ +static void +print_l2cap_command(struct l2cap_command *category) +{ + struct l2cap_command *c = NULL; + + for (c = category; c->command != NULL; c++) + fprintf(stdout, "\t%s\n", c->command); +} /* print_l2cap_command */ + +/* Usage */ +static void +usage(void) +{ + fprintf(stderr, "Usage: l2control [-hn] -a local cmd [params ..]\n"); + fprintf(stderr, "Where:\n"); + fprintf(stderr, " -a local Specify local device to connect to\n"); + fprintf(stderr, " -h Display this message\n"); + fprintf(stderr, " -n Show addresses as numbers\n"); + fprintf(stderr, " cmd Supported command " \ + "(see l2control help)\n"); + fprintf(stderr, " params Optional command parameters\n"); + exit(255); +} /* usage */ + diff --git a/usr.sbin/bluetooth/l2control/l2control.h b/usr.sbin/bluetooth/l2control/l2control.h new file mode 100644 index 000000000000..e18691d84ca3 --- /dev/null +++ b/usr.sbin/bluetooth/l2control/l2control.h @@ -0,0 +1,50 @@ +/*- + * l2control.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: l2control.h,v 1.1 2002/11/24 20:22:41 max Exp $ + */ + +#ifndef _L2CONTROL_H_ +#define _L2CONTROL_H_ + +#define OK 0 /* everything was OK */ +#define ERROR 1 /* could not execute command */ +#define FAILED 2 /* error was reported */ +#define USAGE 3 /* invalid parameters */ + +struct l2cap_command { + char const *command; + char const *description; + int (*handler)(int, int, char **); +}; + +extern struct l2cap_command l2cap_commands[]; + +#endif /* _L2CONTROL_H_ */ + diff --git a/usr.sbin/bluetooth/l2ping/Makefile b/usr.sbin/bluetooth/l2ping/Makefile new file mode 100644 index 000000000000..70b8d19bc609 --- /dev/null +++ b/usr.sbin/bluetooth/l2ping/Makefile @@ -0,0 +1,10 @@ +# $Id: Makefile,v 1.6 2003/08/14 20:06:24 max Exp $ + +PACKAGE= bluetooth +PROG= l2ping +MAN= l2ping.8 +WARNS?= 2 + +LIBADD= bluetooth + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/l2ping/Makefile.depend b/usr.sbin/bluetooth/l2ping/Makefile.depend new file mode 100644 index 000000000000..a1fc9c8c3375 --- /dev/null +++ b/usr.sbin/bluetooth/l2ping/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/arpa \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/l2ping/l2ping.8 b/usr.sbin/bluetooth/l2ping/l2ping.8 new file mode 100644 index 000000000000..07ba6547a424 --- /dev/null +++ b/usr.sbin/bluetooth/l2ping/l2ping.8 @@ -0,0 +1,113 @@ +.\" Copyright (c) 2001-2002 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. +.\" +.\" $Id: l2ping.8,v 1.3 2003/05/21 01:00:19 max Exp $ +.\" +.Dd March 29, 2011 +.Dt L2PING 8 +.Os +.Sh NAME +.Nm l2ping +.Nd send L2CAP ECHO_REQUEST to remote devices +.Sh SYNOPSIS +.Nm +.Op Fl fhn +.Fl a Ar remote +.Op Fl c Ar count +.Op Fl i Ar wait +.Op Fl S Ar source +.Op Fl s Ar size +.Sh DESCRIPTION +The +.Nm +utility uses L2CAP +.Dv ECHO_REQUEST +datagram to elicit an L2CAP +.Dv ECHO_RESPONSE +datagram from a remote device. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar remote +Specify the remote device to ping. +The remote device can be specified by either its BD_ADDR or name. +If name was specified then the +.Nm +utility will attempt to resolve the name via +.Xr bt_gethostbyname 3 . +.It Fl c Ar count +Number of packets to send. +If this option is not specified, +.Nm +will operate until interrupted. +.It Fl f +Do not wait between sending each packet. +.It Fl h +Display usage message and exit. +.It Fl i Ar wait +Wait +.Ar wait +seconds between sending each packet. +The default is to wait for one second between each packet. +This option is ignored if +.Fl f +has been specified. +.It Fl n +Numeric output only. +No attempt will be made to look up symbolic names for host addresses. +.It Fl S Ar source +Specify the local device which should be used to send L2CAP +.Dv ECHO_REQUEST +datagrams. +The local device can be specified by either its BD_ADDR or name. +If name was specified then the +.Nm +utility will attempt to resolve the name via +.Xr bt_gethostbyname 3 . +.It Fl s Ar size +Specify the number of payload bytes to be sent. +The default size is 44 bytes. +It is calculated as minimum L2CAP MTU (48 bytes) minus the size of the L2CAP +signalling command header (4 bytes). +The maximum size is 65531 bytes. +Is is calculated as maximum L2CAP MTU +(65535 bytes) minus four bytes of payload reserved for +.Nm +internal use. +Use this option with caution. +Some implementations may not like large sizes and may hang or even crash. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr bluetooth 3 , +.Xr netgraph 3 , +.Xr netgraph 4 , +.Xr ng_l2cap 4 , +.Xr l2control 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt emax@FreeBSD.org +.Sh BUGS +Could collect more statistic. +Could check for duplicated, corrupted and lost packets. diff --git a/usr.sbin/bluetooth/l2ping/l2ping.c b/usr.sbin/bluetooth/l2ping/l2ping.c new file mode 100644 index 000000000000..25ddf06e8532 --- /dev/null +++ b/usr.sbin/bluetooth/l2ping/l2ping.c @@ -0,0 +1,294 @@ +/*- + * l2ping.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2002 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. + * + * $Id: l2ping.c,v 1.5 2003/05/16 19:54:40 max Exp $ + */ + +#include <sys/ioctl.h> +#include <sys/time.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static void usage (void); +static void tv_sub (struct timeval *, struct timeval const *); +static double tv2msec (struct timeval const *); + +#undef min +#define min(x, y) (((x) > (y))? (y) : (x)) + +static char const pattern[] = "1234567890-"; +#define PATTERN_SIZE (sizeof(pattern) - 1) + +/* + * Main + */ + +int +main(int argc, char *argv[]) +{ + bdaddr_t src, dst; + struct hostent *he; + uint8_t *echo_data; + struct sockaddr_l2cap sa; + int32_t n, s, count, wait, flood, echo_size, numeric; + char *endp, *rname; + + /* Set defaults */ + memcpy(&src, NG_HCI_BDADDR_ANY, sizeof(src)); + memcpy(&dst, NG_HCI_BDADDR_ANY, sizeof(dst)); + + echo_data = (uint8_t *) calloc(NG_L2CAP_MAX_ECHO_SIZE, sizeof(uint8_t)); + if (echo_data == NULL) { + fprintf(stderr, "Failed to allocate echo data buffer"); + exit(1); + } + + /* + * Set default echo size to the NG_L2CAP_MTU_MINIMUM minus + * the size of the L2CAP signalling command header. + */ + + echo_size = NG_L2CAP_MTU_MINIMUM - sizeof(ng_l2cap_cmd_hdr_t); + count = -1; /* unimited */ + wait = 1; /* sec */ + flood = 0; + numeric = 0; + + /* Parse command line arguments */ + while ((n = getopt(argc, argv, "a:c:fi:nS:s:h")) != -1) { + switch (n) { + case 'a': + if (!bt_aton(optarg, &dst)) { + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(1, "%s: %s", optarg, hstrerror(h_errno)); + + memcpy(&dst, he->h_addr, sizeof(dst)); + } + break; + + case 'c': + count = strtol(optarg, &endp, 10); + if (count <= 0 || *endp != '\0') + usage(); + break; + + case 'f': + flood = 1; + break; + + case 'i': + wait = strtol(optarg, &endp, 10); + if (wait <= 0 || *endp != '\0') + usage(); + break; + + case 'n': + numeric = 1; + break; + + case 'S': + if (!bt_aton(optarg, &src)) { + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(1, "%s: %s", optarg, hstrerror(h_errno)); + + memcpy(&src, he->h_addr, sizeof(src)); + } + break; + + case 's': + echo_size = strtol(optarg, &endp, 10); + if (echo_size < sizeof(int32_t) || + echo_size > NG_L2CAP_MAX_ECHO_SIZE || + *endp != '\0') + usage(); + break; + + case 'h': + default: + usage(); + break; + } + } + + if (memcmp(&dst, NG_HCI_BDADDR_ANY, sizeof(dst)) == 0) + usage(); + + he = bt_gethostbyaddr((const char *)&dst, sizeof(dst), AF_BLUETOOTH); + if (he == NULL || he->h_name == NULL || he->h_name[0] == '\0' || numeric) + asprintf(&rname, "%s", bt_ntoa(&dst, NULL)); + else + rname = strdup(he->h_name); + + if (rname == NULL) + errx(1, "Failed to create remote hostname"); + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_L2CAP); + if (s < 0) + err(2, "Could not create socket"); + + memset(&sa, 0, sizeof(sa)); + sa.l2cap_len = sizeof(sa); + sa.l2cap_family = AF_BLUETOOTH; + memcpy(&sa.l2cap_bdaddr, &src, sizeof(sa.l2cap_bdaddr)); + + if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) + err(3, +"Could not bind socket, src bdaddr=%s", bt_ntoa(&sa.l2cap_bdaddr, NULL)); + + memset(&sa, 0, sizeof(sa)); + sa.l2cap_len = sizeof(sa); + sa.l2cap_family = AF_BLUETOOTH; + memcpy(&sa.l2cap_bdaddr, &dst, sizeof(sa.l2cap_bdaddr)); + + if (connect(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) + err(4, +"Could not connect socket, dst bdaddr=%s", bt_ntoa(&sa.l2cap_bdaddr, NULL)); + + /* Fill pattern */ + for (n = 0; n < echo_size; ) { + int32_t avail = min(echo_size - n, PATTERN_SIZE); + + memcpy(echo_data + n, pattern, avail); + n += avail; + } + + /* Start ping'ing */ + for (n = 0; count == -1 || count > 0; n ++) { + struct ng_btsocket_l2cap_raw_ping r; + struct timeval a, b; + int32_t fail; + + if (gettimeofday(&a, NULL) < 0) + err(5, "Could not gettimeofday(a)"); + + fail = 0; + *((int32_t *) echo_data) = htonl(n); + + r.result = 0; + r.echo_size = echo_size; + r.echo_data = echo_data; + if (ioctl(s, SIOC_L2CAP_L2CA_PING, &r, sizeof(r)) < 0) { + r.result = errno; + fail = 1; +/* + warn("Could not ping, dst bdaddr=%s", + bt_ntoa(&r.echo_dst, NULL)); +*/ + } + + if (gettimeofday(&b, NULL) < 0) + err(7, "Could not gettimeofday(b)"); + + tv_sub(&b, &a); + + fprintf(stdout, +"%d bytes from %s seq_no=%d time=%.3f ms result=%#x %s\n", + r.echo_size, + rname, + ntohl(*((int32_t *)(r.echo_data))), + tv2msec(&b), r.result, + ((fail == 0)? "" : strerror(errno))); + + if (!flood) { + /* Wait */ + a.tv_sec = wait; + a.tv_usec = 0; + select(0, NULL, NULL, NULL, &a); + } + + if (count != -1) + count --; + } + + free(rname); + free(echo_data); + close(s); + + return (0); +} /* main */ + +/* + * a -= b, for timevals + */ + +static void +tv_sub(struct timeval *a, struct timeval const *b) +{ + if (a->tv_usec < b->tv_usec) { + a->tv_usec += 1000000; + a->tv_sec -= 1; + } + + a->tv_usec -= b->tv_usec; + a->tv_sec -= b->tv_sec; +} /* tv_sub */ + +/* + * convert tv to msec + */ + +static double +tv2msec(struct timeval const *tvp) +{ + return(((double)tvp->tv_usec)/1000.0 + ((double)tvp->tv_sec)*1000.0); +} /* tv2msec */ + +/* + * Usage + */ + +static void +usage(void) +{ + fprintf(stderr, "Usage: l2ping [-fhn] -a remote " \ + "[-c count] [-i wait] [-S source] [-s size]\n"); + fprintf(stderr, "Where:\n"); + fprintf(stderr, " -a remote Specify remote device to ping\n"); + fprintf(stderr, " -c count Number of packets to send\n"); + fprintf(stderr, " -f No delay between packets\n"); + fprintf(stderr, " -h Display this message\n"); + fprintf(stderr, " -i wait Delay between packets (sec)\n"); + fprintf(stderr, " -n Numeric output only\n"); + fprintf(stderr, " -S source Specify source device\n"); + fprintf(stderr, " -s size Packet size (bytes), " \ + "between %zd and %zd\n", sizeof(int32_t), NG_L2CAP_MAX_ECHO_SIZE); + + exit(255); +} /* usage */ + diff --git a/usr.sbin/bluetooth/rfcomm_pppd/Makefile b/usr.sbin/bluetooth/rfcomm_pppd/Makefile new file mode 100644 index 000000000000..ea3f211ae7dc --- /dev/null +++ b/usr.sbin/bluetooth/rfcomm_pppd/Makefile @@ -0,0 +1,13 @@ +# $Id: Makefile,v 1.7 2003/09/07 18:32:11 max Exp $ + +.PATH: ${SRCTOP}/usr.bin/bluetooth/rfcomm_sppd + +PACKAGE= bluetooth +PROG= rfcomm_pppd +MAN= rfcomm_pppd.8 +SRCS= rfcomm_pppd.c rfcomm_sdp.c +WARNS?= 2 + +LIBADD= bluetooth sdp + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/rfcomm_pppd/Makefile.depend b/usr.sbin/bluetooth/rfcomm_pppd/Makefile.depend new file mode 100644 index 000000000000..ccc9ef8b7fa3 --- /dev/null +++ b/usr.sbin/bluetooth/rfcomm_pppd/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + lib/libsdp \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.8 b/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.8 new file mode 100644 index 000000000000..804070c7e31b --- /dev/null +++ b/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.8 @@ -0,0 +1,353 @@ +.\" Copyright (c) 2001-2003 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. +.\" +.\" $Id: rfcomm_pppd.8,v 1.7 2003/09/07 18:32:11 max Exp $ +.\" +.Dd February 4, 2003 +.Dt RFCOMM_PPPD 8 +.Os +.Sh NAME +.Nm rfcomm_pppd +.Nd RFCOMM PPP daemon +.Sh SYNOPSIS +.Nm +.Fl c +.Op Fl dh +.Fl a Ar address +.Fl C Ar channel +.Fl l Ar label +.Fl u Ar N +.Nm +.Fl s +.Op Fl dDhS +.Op Fl a Ar address +.Fl C Ar channel +.Fl l Ar label +.Sh DESCRIPTION +The +.Nm +daemon is a simple wrapper daemon that allows the use of +.Xr ppp 8 +via an RFCOMM connection. +It can operate in two modes: client and server. +.Pp +In client mode, +.Nm +opens an RFCOMM connection to the specified server's +.Ar BD_ADRR +and +.Ar channel . +Once the RFCOMM connection is established, +.Nm +executes +.Xr ppp 8 +in +.Fl direct +mode with the specified +.Ar label . +Likewise, +.Xr ppp 8 +operates over the RFCOMM connection just like it would over a standard serial +port, thus allowing a user to +.Dq "dial out" +and connect to the Internet. +.Pp +In server mode, +.Nm +opens an RFCOMM socket and listens for incoming connections from remote clients. +Once the new incoming connection is accepted, +.Nm +forks and executes +.Xr ppp 8 +in +.Fl direct +mode with the specified +.Ar label . +Likewise, +.Xr ppp 8 +operates over the RFCOMM connection just like it would over a standard serial +port, thus providing network connectivity to remote clients. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar address +In client mode, this required option specifies the address of the remote +RFCOMM server. +In server mode, this option can be used to specify the local +address to listen on. +By default, in server mode, the daemon will listen on +.Dv ANY +address. +The address can be specified as BD_ADDR or name. +If a name was specified, the +.Nm +utility will attempt to resolve the name via +.Xr bt_gethostbyname 3 . +.It Fl C Ar channel +In both client and server mode, this required option specifies the RFCOMM +channel to connect to or listen on. +In server mode, the channel should be a number between 1 and 30. +In client mode, the channel could either be a number between 1 and 30 +or a service name. +Supported service names are: +.Cm DUN +(Dial-Up Networking) and +.Cm LAN +(LAN Access Using PPP). +If a service name is used instead of a numeric channel number, then +.Nm +will try to obtain an RFCOMM channel number via SDP +(Service Discovery Protocol). +.It Fl c +Act as an RFCOMM client. +This is the default mode. +.It Fl d +Do not detach from the controlling terminal, i.e., run in foreground. +.It Fl D +In server mode, register the +.Cm DUN +(Dial-Up Networking) service in addition to the +.Cm LAN +(LAN Access Using PPP) service. +AT-command exchange can be faked with +.Xr ppp 8 +chat script. +.It Fl h +Display usage message and exit. +.It Fl l Ar label +In both client and server mode, this required option specifies which +.Xr ppp 8 +label will be used. +.It Fl S +In server mode, register the +.Cm SP +(Serial Port) service in addition to the +.Cm LAN +(LAN Access Using PPP) service. +.Pp +It appears that some cell phones are using the so-called +.Dq "callback mechanism" . +In this scenario, the user is trying to connect his cell phone to the Internet, +while the user's host computer is acting as the gateway server. +It seems that it is not possible to tell the phone to just connect and start +using the +.Cm LAN +service. +Instead, the user's host computer must +.Dq "jump start" +the phone by connecting to the phone's +.Cm SP +service. +What happens next is the phone kills the existing connection and opens another +connection back to the user's host computer. +The phone really wants to use the +.Cm LAN +service, but for whatever reason it looks for the +.Cm SP +service on the user's host computer. +This brain-damaged behavior was reported for the Nokia 6600 and the +Sony/Ericsson P900. +.It Fl s +Act as an RFCOMM server. +.It Fl u Ar N +This option maps directly to the +.Fl unit +.Xr ppp 8 +command-line option and tells +.Nm +to instruct +.Xr ppp 8 +to only attempt to open +.Pa /dev/tun Ns Ar N . +This option only works in client mode. +.El +.Sh PPP CONFIGURATION +.Ss Important Notes on PPP Configuration +Special attention is required when adding new RFCOMM configurations to the +existing PPP configuration. +Please keep in mind that PPP will +.Em always +execute commands in the +.Dq Li default +label of your +.Pa /etc/ppp/ppp.conf +file. +Please make sure that the +.Dq Li default +label +.Em only +contains commands that apply to +.Em every +other label. +If you need to use PPP for both dialing out and accepting incoming +RFCOMM connections, please make sure you have moved all commands related to +dialing out from the +.Dq Li default +section into an appropriate outgoing label. +.Ss RFCOMM Server +One of the typical examples is the LAN access. +In this example, an RFCOMM connection +is used as a null-modem connection between a client and a server. +Both client and server will start talking PPP right after the RFCOMM +connection has been established. +.Bd -literal -offset indent +rfcomm-server: + set timeout 0 + set lqrperiod 10 + set ifaddr 10.0.0.1 10.0.0.2 255.255.255.0 + enable lqr + accept lqr + accept dns + # Do not use PPP authentication. Assume that + # Bluetooth connection was authenticated already + disable pap + deny pap + disable chap + deny chap +.Ed +.Ss RFCOMM Client +The +.Nm +utility supports both +.Cm LAN +(LAN Access Using PPP) and +.Cm DUN +(Dial-Up Networking) access. +The client's configuration for +.Cm LAN +access is very similar to the server's and might look like this: +.Bd -literal -offset indent +rfcomm-client: + enable lqr + accept lqr + set dial + set timeout 0 + disable iface-alias + set ifaddr 10.0.0.1/0 10.0.0.2/0 255.255.255.0 0.0.0.0 + # Do not use PPP authentication. Assume that + # Bluetooth connection was authenticated already + deny pap + disable pap + deny chap + disable chap +.Ed +.Pp +The client's configuration for +.Cm DUN +access is different. +In this scenario, the client gets connected to the virtual serial port on the +server. +To open a PPP session, the client must dial a number. +Note that by default +.Xr ppp 8 +will not execute any configured chat scripts. +The +.Ic force-scripts +option can be used to override this behavior. +An example configuration is shown below: +.Bd -literal -offset indent +rfcomm-dialup: + # This is IMPORTANT option + enable force-scripts + + # You might want to change these + set authname + set authkey + set phone "*99***1#" + + # You might want to adjust dial string as well + set dial "ABORT BUSY ABORT NO\\\\sCARRIER TIMEOUT 5 \\ + \\"\\" AT OK-AT-OK ATE1Q0 OK \\\\dATDT\\\\T TIMEOUT 40 CONNECT" + set login + set timeout 30 + enable dns + resolv rewrite + + set ifaddr 10.0.0.1/0 10.0.0.2/0 255.255.255.0 0.0.0.0 + add default HISADDR +.Ed +.Pp +Note that by adjusting the initialization string, one can make a CSD (Circuit +Switched Data), HSCSD (High Speed Circuit Switched Data) or GPRS (General +Packet Radio Service) connection. +The availability of the particular connection +type depends on the phone model and service plan activated on the phone. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Dl "rfcomm_pppd -s -a 00:01:02:03:04:05 -C 1 -l rfcomm-server" +.Pp +This command will start +.Nm +in the server mode. +The RFCOMM server will listen on local address +.Li 00:01:02:03:04:05 +and channel +.Li 1 . +Once the incoming connection has been accepted, +.Nm +will execute +.Xr ppp 8 +in +.Fl direct +mode with the +.Dq Li rfcomm-server +label. +.Pp +.Dl "rfcomm_pppd -c -a 00:01:02:03:04:05 -C 1 -l rfcomm-client" +.Pp +This command will start +.Nm +in the client mode. +.Nm +will try to connect to the RFCOMM server at +.Li 00:01:02:03:04:05 +address and channel +.Li 1 . +Once connected, +.Nm +will execute +.Xr ppp 8 +in +.Fl direct +mode with the +.Dq Li rfcomm-client +label. +.Sh SEE ALSO +.Xr rfcomm_sppd 1 , +.Xr bluetooth 3 , +.Xr ng_btsocket 4 , +.Xr ppp 8 , +.Xr sdpcontrol 8 , +.Xr sdpd 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +.Sh CAVEATS +The +.Nm +utility in server mode will try to register the Bluetooth LAN Access Over PPP +service with the local SDP daemon. +If the local SDP daemon is not running, +.Nm +will exit with an error. diff --git a/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.c b/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.c new file mode 100644 index 000000000000..439b4157f59e --- /dev/null +++ b/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.c @@ -0,0 +1,472 @@ +/* + * rfcomm_pppd.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2008 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. + * + * $Id: rfcomm_pppd.c,v 1.5 2003/09/07 18:32:11 max Exp $ + */ +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <sdp.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#define RFCOMM_PPPD "rfcomm_pppd" + +int rfcomm_channel_lookup (bdaddr_t const *local, + bdaddr_t const *remote, + int service, int *channel, int *error); + +static void exec_ppp (int s, char *unit, char *label); +static void sighandler (int s); +static void usage (void); + +static int done; + +/* Main */ +int +main(int argc, char *argv[]) +{ + struct sockaddr_rfcomm sock_addr; + char *label = NULL, *unit = NULL, *ep = NULL; + bdaddr_t addr; + int s, channel, detach, server, service, + regdun, regsp; + pid_t pid; + + memcpy(&addr, NG_HCI_BDADDR_ANY, sizeof(addr)); + channel = 0; + detach = 1; + server = 0; + service = 0; + regdun = 0; + regsp = 0; + + /* Parse command line arguments */ + while ((s = getopt(argc, argv, "a:cC:dDhl:sSu:")) != -1) { + switch (s) { + case 'a': /* BDADDR */ + if (!bt_aton(optarg, &addr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(1, "%s: %s", optarg, hstrerror(h_errno)); + + memcpy(&addr, he->h_addr, sizeof(addr)); + } + break; + + case 'c': /* client */ + server = 0; + break; + + case 'C': /* RFCOMM channel */ + channel = strtoul(optarg, &ep, 10); + if (*ep != '\0') { + channel = 0; + switch (tolower(optarg[0])) { + case 'd': /* DialUp Networking */ + service = SDP_SERVICE_CLASS_DIALUP_NETWORKING; + break; + + case 'l': /* LAN Access Using PPP */ + service = SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP; + break; + } + } + break; + + case 'd': /* do not detach */ + detach = 0; + break; + + case 'D': /* Register DUN service as well as LAN service */ + regdun = 1; + break; + + case 'l': /* PPP label */ + label = optarg; + break; + + case 's': /* server */ + server = 1; + break; + + case 'S': /* Register SP service as well as LAN service */ + regsp = 1; + break; + + case 'u': /* PPP -unit option */ + strtoul(optarg, &ep, 10); + if (*ep != '\0') + usage(); + /* NOT REACHED */ + + unit = optarg; + break; + + case 'h': + default: + usage(); + /* NOT REACHED */ + } + } + + /* Check if we got everything we wanted */ + if (label == NULL) + errx(1, "Must specify PPP label"); + + if (!server) { + if (memcmp(&addr, NG_HCI_BDADDR_ANY, sizeof(addr)) == 0) + errx(1, "Must specify server BD_ADDR"); + + /* Check channel, if was not set then obtain it via SDP */ + if (channel == 0 && service != 0) + if (rfcomm_channel_lookup(NULL, &addr, service, + &channel, &s) != 0) + errc(1, s, "Could not obtain RFCOMM channel"); + } + + if (channel <= 0 || channel > 30) + errx(1, "Invalid RFCOMM channel number %d", channel); + + openlog(RFCOMM_PPPD, LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_USER); + + if (detach && daemon(0, 0) < 0) { + syslog(LOG_ERR, "Could not daemon(0, 0). %s (%d)", + strerror(errno), errno); + exit(1); + } + + s = socket(PF_BLUETOOTH, SOCK_STREAM, BLUETOOTH_PROTO_RFCOMM); + if (s < 0) { + syslog(LOG_ERR, "Could not create socket. %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (server) { + struct sigaction sa; + void *ss = NULL; + sdp_lan_profile_t lan; + + /* Install signal handler */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sighandler; + + if (sigaction(SIGTERM, &sa, NULL) < 0) { + syslog(LOG_ERR, "Could not sigaction(SIGTERM). %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (sigaction(SIGHUP, &sa, NULL) < 0) { + syslog(LOG_ERR, "Could not sigaction(SIGHUP). %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (sigaction(SIGINT, &sa, NULL) < 0) { + syslog(LOG_ERR, "Could not sigaction(SIGINT). %s (%d)", + strerror(errno), errno); + exit(1); + } + + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDWAIT; + + if (sigaction(SIGCHLD, &sa, NULL) < 0) { + syslog(LOG_ERR, "Could not sigaction(SIGCHLD). %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* bind socket and listen for incoming connections */ + sock_addr.rfcomm_len = sizeof(sock_addr); + sock_addr.rfcomm_family = AF_BLUETOOTH; + memcpy(&sock_addr.rfcomm_bdaddr, &addr, + sizeof(sock_addr.rfcomm_bdaddr)); + sock_addr.rfcomm_channel = channel; + + if (bind(s, (struct sockaddr *) &sock_addr, + sizeof(sock_addr)) < 0) { + syslog(LOG_ERR, "Could not bind socket. %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (listen(s, 10) < 0) { + syslog(LOG_ERR, "Could not listen on socket. %s (%d)", + strerror(errno), errno); + exit(1); + } + + ss = sdp_open_local(NULL); + if (ss == NULL) { + syslog(LOG_ERR, "Unable to create local SDP session"); + exit(1); + } + + if (sdp_error(ss) != 0) { + syslog(LOG_ERR, "Unable to open local SDP session. " \ + "%s (%d)", strerror(sdp_error(ss)), + sdp_error(ss)); + exit(1); + } + + memset(&lan, 0, sizeof(lan)); + lan.server_channel = channel; + + if (sdp_register_service(ss, + SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP, + &addr, (void *) &lan, sizeof(lan), NULL) != 0) { + syslog(LOG_ERR, "Unable to register LAN service with " \ + "local SDP daemon. %s (%d)", + strerror(sdp_error(ss)), sdp_error(ss)); + exit(1); + } + + /* + * Register DUN (Dial-Up Networking) service on the same + * RFCOMM channel if requested. There is really no good reason + * to not to support this. AT-command exchange can be faked + * with chat script in ppp.conf + */ + + if (regdun) { + sdp_dun_profile_t dun; + + memset(&dun, 0, sizeof(dun)); + dun.server_channel = channel; + + if (sdp_register_service(ss, + SDP_SERVICE_CLASS_DIALUP_NETWORKING, + &addr, (void *) &dun, sizeof(dun), + NULL) != 0) { + syslog(LOG_ERR, "Unable to register DUN " \ + "service with local SDP daemon. " \ + "%s (%d)", strerror(sdp_error(ss)), + sdp_error(ss)); + exit(1); + } + } + + /* + * Register SP (Serial Port) service on the same RFCOMM channel + * if requested. It appears that some cell phones are using so + * called "callback mechanism". In this scenario user is trying + * to connect his cell phone to the Internet, and, user's host + * computer is acting as the gateway server. It seems that it + * is not possible to tell the phone to just connect and start + * using the LAN service. Instead the user's host computer must + * "jump start" the phone by connecting to the phone's SP + * service. What happens next is the phone kills the existing + * connection and opens another connection back to the user's + * host computer. The phone really wants to use LAN service, + * but for whatever reason it looks for SP service on the + * user's host computer. This brain damaged behavior was + * reported for Nokia 6600 and Sony/Ericsson P900. Both phones + * are Symbian-based phones. Perhaps this is a Symbian problem? + */ + + if (regsp) { + sdp_sp_profile_t sp; + + memset(&sp, 0, sizeof(sp)); + sp.server_channel = channel; + + if (sdp_register_service(ss, + SDP_SERVICE_CLASS_SERIAL_PORT, + &addr, (void *) &sp, sizeof(sp), + NULL) != 0) { + syslog(LOG_ERR, "Unable to register SP " \ + "service with local SDP daemon. " \ + "%s (%d)", strerror(sdp_error(ss)), + sdp_error(ss)); + exit(1); + } + } + + for (done = 0; !done; ) { + socklen_t len = sizeof(sock_addr); + int s1 = accept(s, (struct sockaddr *) &sock_addr, &len); + + if (s1 < 0) { + syslog(LOG_ERR, "Could not accept connection " \ + "on socket. %s (%d)", strerror(errno), + errno); + exit(1); + } + + pid = fork(); + if (pid == (pid_t) -1) { + syslog(LOG_ERR, "Could not fork(). %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (pid == 0) { + sdp_close(ss); + close(s); + + /* Reset signal handler */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGCHLD, &sa, NULL); + + /* Become daemon */ + daemon(0, 0); + + /* + * XXX Make sure user does not shoot himself + * in the foot. Do not pass unit option to the + * PPP when operating in the server mode. + */ + + exec_ppp(s1, NULL, label); + } else + close(s1); + } + } else { + sock_addr.rfcomm_len = sizeof(sock_addr); + sock_addr.rfcomm_family = AF_BLUETOOTH; + memcpy(&sock_addr.rfcomm_bdaddr, NG_HCI_BDADDR_ANY, + sizeof(sock_addr.rfcomm_bdaddr)); + sock_addr.rfcomm_channel = 0; + + if (bind(s, (struct sockaddr *) &sock_addr, + sizeof(sock_addr)) < 0) { + syslog(LOG_ERR, "Could not bind socket. %s (%d)", + strerror(errno), errno); + exit(1); + } + + memcpy(&sock_addr.rfcomm_bdaddr, &addr, + sizeof(sock_addr.rfcomm_bdaddr)); + sock_addr.rfcomm_channel = channel; + + if (connect(s, (struct sockaddr *) &sock_addr, + sizeof(sock_addr)) < 0) { + syslog(LOG_ERR, "Could not connect socket. %s (%d)", + strerror(errno), errno); + exit(1); + } + + exec_ppp(s, unit, label); + } + + exit(0); +} /* main */ + +/* + * Redirects stdin/stdout to s, stderr to /dev/null and exec + * 'ppp -direct -quiet [-unit N] label'. Never returns. + */ + +static void +exec_ppp(int s, char *unit, char *label) +{ + char ppp[] = "/usr/sbin/ppp"; + char *ppp_args[] = { ppp, "-direct", "-quiet", + NULL, NULL, NULL, NULL }; + + close(0); + if (dup(s) < 0) { + syslog(LOG_ERR, "Could not dup(0). %s (%d)", + strerror(errno), errno); + exit(1); + } + + close(1); + if (dup(s) < 0) { + syslog(LOG_ERR, "Could not dup(1). %s (%d)", + strerror(errno), errno); + exit(1); + } + + close(2); + open("/dev/null", O_RDWR); + + if (unit != NULL) { + ppp_args[3] = "-unit"; + ppp_args[4] = unit; + ppp_args[5] = label; + } else + ppp_args[3] = label; + + if (execv(ppp, ppp_args) < 0) { + syslog(LOG_ERR, "Could not exec(%s -direct -quiet%s%s %s). " \ + "%s (%d)", ppp, (unit != NULL)? " -unit " : "", + (unit != NULL)? unit : "", label, + strerror(errno), errno); + exit(1); + } +} /* run_ppp */ + +/* Signal handler */ +static void +sighandler(int s) +{ + done = 1; +} /* sighandler */ + +/* Display usage and exit */ +static void +usage(void) +{ + fprintf(stdout, +"Usage: %s options\n" \ +"Where options are:\n" \ +"\t-a address Address to listen on or connect to (required for client)\n" \ +"\t-c Act as a clinet (default)\n" \ +"\t-C channel RFCOMM channel to listen on or connect to (required)\n" \ +"\t-d Run in foreground\n" \ +"\t-D Register Dial-Up Networking service (server mode only)\n" \ +"\t-l label Use PPP label (required)\n" \ +"\t-s Act as a server\n" \ +"\t-S Register Serial Port service (server mode only)\n" \ +"\t-u N Tell PPP to operate on /dev/tunN (client mode only)\n" \ +"\t-h Display this message\n", RFCOMM_PPPD); + + exit(255); +} /* usage */ + diff --git a/usr.sbin/bluetooth/rtlbtfw/Makefile b/usr.sbin/bluetooth/rtlbtfw/Makefile new file mode 100644 index 000000000000..f9c5dfd12b1f --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/Makefile @@ -0,0 +1,9 @@ +PACKAGE= bluetooth +CONFS= rtlbtfw.conf +CONFSDIR= /etc/devd +PROG= rtlbtfw +MAN= rtlbtfw.8 +LIBADD+= usb +SRCS= main.c rtlbt_fw.c rtlbt_hw.c + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/rtlbtfw/main.c b/usr.sbin/bluetooth/rtlbtfw/main.c new file mode 100644 index 000000000000..58503b8087b5 --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/main.c @@ -0,0 +1,554 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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/param.h> +#include <sys/stat.h> +#include <sys/endian.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libusb.h> + +#include "rtlbt_fw.h" +#include "rtlbt_hw.h" +#include "rtlbt_dbg.h" + +#define _DEFAULT_RTLBT_FIRMWARE_PATH "/usr/share/firmware/rtlbt" + +int rtlbt_do_debug = 0; +int rtlbt_do_info = 0; + +struct rtlbt_devid { + uint16_t product_id; + uint16_t vendor_id; +}; + +static struct rtlbt_devid rtlbt_list[] = { + /* Realtek 8821CE Bluetooth devices */ + { .vendor_id = 0x13d3, .product_id = 0x3529 }, + + /* Realtek 8822CE Bluetooth devices */ + { .vendor_id = 0x0bda, .product_id = 0xb00c }, + { .vendor_id = 0x0bda, .product_id = 0xc822 }, + + /* Realtek 8851BE Bluetooth devices */ + { .vendor_id = 0x13d3, .product_id = 0x3600 }, + + /* Realtek 8852AE Bluetooth devices */ + { .vendor_id = 0x0bda, .product_id = 0x2852 }, + { .vendor_id = 0x0bda, .product_id = 0xc852 }, + { .vendor_id = 0x0bda, .product_id = 0x385a }, + { .vendor_id = 0x0bda, .product_id = 0x4852 }, + { .vendor_id = 0x04c5, .product_id = 0x165c }, + { .vendor_id = 0x04ca, .product_id = 0x4006 }, + { .vendor_id = 0x0cb8, .product_id = 0xc549 }, + + /* Realtek 8852CE Bluetooth devices */ + { .vendor_id = 0x04ca, .product_id = 0x4007 }, + { .vendor_id = 0x04c5, .product_id = 0x1675 }, + { .vendor_id = 0x0cb8, .product_id = 0xc558 }, + { .vendor_id = 0x13d3, .product_id = 0x3587 }, + { .vendor_id = 0x13d3, .product_id = 0x3586 }, + { .vendor_id = 0x13d3, .product_id = 0x3592 }, + { .vendor_id = 0x0489, .product_id = 0xe122 }, + + /* Realtek 8852BE Bluetooth devices */ + { .vendor_id = 0x0cb8, .product_id = 0xc559 }, + { .vendor_id = 0x0bda, .product_id = 0x4853 }, + { .vendor_id = 0x0bda, .product_id = 0x887b }, + { .vendor_id = 0x0bda, .product_id = 0xb85b }, + { .vendor_id = 0x13d3, .product_id = 0x3570 }, + { .vendor_id = 0x13d3, .product_id = 0x3571 }, + { .vendor_id = 0x13d3, .product_id = 0x3572 }, + { .vendor_id = 0x13d3, .product_id = 0x3591 }, + { .vendor_id = 0x0489, .product_id = 0xe123 }, + { .vendor_id = 0x0489, .product_id = 0xe125 }, + + /* Realtek 8852BT/8852BE-VT Bluetooth devices */ + { .vendor_id = 0x0bda, .product_id = 0x8520 }, + + /* Realtek 8922AE Bluetooth devices */ + { .vendor_id = 0x0bda, .product_id = 0x8922 }, + { .vendor_id = 0x13d3, .product_id = 0x3617 }, + { .vendor_id = 0x13d3, .product_id = 0x3616 }, + { .vendor_id = 0x0489, .product_id = 0xe130 }, + + /* Realtek 8723AE Bluetooth devices */ + { .vendor_id = 0x0930, .product_id = 0x021d }, + { .vendor_id = 0x13d3, .product_id = 0x3394 }, + + /* Realtek 8723BE Bluetooth devices */ + { .vendor_id = 0x0489, .product_id = 0xe085 }, + { .vendor_id = 0x0489, .product_id = 0xe08b }, + { .vendor_id = 0x04f2, .product_id = 0xb49f }, + { .vendor_id = 0x13d3, .product_id = 0x3410 }, + { .vendor_id = 0x13d3, .product_id = 0x3416 }, + { .vendor_id = 0x13d3, .product_id = 0x3459 }, + { .vendor_id = 0x13d3, .product_id = 0x3494 }, + + /* Realtek 8723BU Bluetooth devices */ + { .vendor_id = 0x7392, .product_id = 0xa611 }, + + /* Realtek 8723DE Bluetooth devices */ + { .vendor_id = 0x0bda, .product_id = 0xb009 }, + { .vendor_id = 0x2ff8, .product_id = 0xb011 }, + + /* Realtek 8761BUV Bluetooth devices */ + { .vendor_id = 0x2c4e, .product_id = 0x0115 }, + { .vendor_id = 0x2357, .product_id = 0x0604 }, + { .vendor_id = 0x0b05, .product_id = 0x190e }, + { .vendor_id = 0x2550, .product_id = 0x8761 }, + { .vendor_id = 0x0bda, .product_id = 0x8771 }, + { .vendor_id = 0x6655, .product_id = 0x8771 }, + { .vendor_id = 0x7392, .product_id = 0xc611 }, + { .vendor_id = 0x2b89, .product_id = 0x8761 }, + + /* Realtek 8821AE Bluetooth devices */ + { .vendor_id = 0x0b05, .product_id = 0x17dc }, + { .vendor_id = 0x13d3, .product_id = 0x3414 }, + { .vendor_id = 0x13d3, .product_id = 0x3458 }, + { .vendor_id = 0x13d3, .product_id = 0x3461 }, + { .vendor_id = 0x13d3, .product_id = 0x3462 }, + + /* Realtek 8822BE Bluetooth devices */ + { .vendor_id = 0x13d3, .product_id = 0x3526 }, + { .vendor_id = 0x0b05, .product_id = 0x185c }, + + /* Realtek 8822CE Bluetooth devices */ + { .vendor_id = 0x04ca, .product_id = 0x4005 }, + { .vendor_id = 0x04c5, .product_id = 0x161f }, + { .vendor_id = 0x0b05, .product_id = 0x18ef }, + { .vendor_id = 0x13d3, .product_id = 0x3548 }, + { .vendor_id = 0x13d3, .product_id = 0x3549 }, + { .vendor_id = 0x13d3, .product_id = 0x3553 }, + { .vendor_id = 0x13d3, .product_id = 0x3555 }, + { .vendor_id = 0x2ff8, .product_id = 0x3051 }, + { .vendor_id = 0x1358, .product_id = 0xc123 }, + { .vendor_id = 0x0bda, .product_id = 0xc123 }, + { .vendor_id = 0x0cb5, .product_id = 0xc547 }, +}; + +static int +rtlbt_is_realtek(struct libusb_device_descriptor *d) +{ + int i; + + /* Search looking for whether it's a Realtek-based device */ + for (i = 0; i < (int) nitems(rtlbt_list); i++) { + if ((rtlbt_list[i].product_id == d->idProduct) && + (rtlbt_list[i].vendor_id == d->idVendor)) { + rtlbt_info("found USB Realtek"); + return (1); + } + } + + /* Not found */ + return (0); +} + +static int +rtlbt_is_bluetooth(struct libusb_device *dev) +{ + struct libusb_config_descriptor *cfg; + const struct libusb_interface *ifc; + const struct libusb_interface_descriptor *d; + int r; + + r = libusb_get_active_config_descriptor(dev, &cfg); + if (r < 0) { + rtlbt_err("Cannot retrieve config descriptor: %s", + libusb_error_name(r)); + return (0); + } + + if (cfg->bNumInterfaces != 0) { + /* Only 0-th HCI/ACL interface is supported by downloader */ + ifc = &cfg->interface[0]; + if (ifc->num_altsetting != 0) { + /* BT HCI/ACL interface has no altsettings */ + d = &ifc->altsetting[0]; + /* Check if interface is a bluetooth */ + if (d->bInterfaceClass == LIBUSB_CLASS_WIRELESS && + d->bInterfaceSubClass == 0x01 && + d->bInterfaceProtocol == 0x01) { + rtlbt_info("found USB Realtek"); + libusb_free_config_descriptor(cfg); + return (1); + } + } + } + libusb_free_config_descriptor(cfg); + + /* Not found */ + return (0); +} + +static libusb_device * +rtlbt_find_device(libusb_context *ctx, int bus_id, int dev_id) +{ + libusb_device **list, *dev = NULL, *found = NULL; + struct libusb_device_descriptor d; + ssize_t cnt, i; + int r; + + cnt = libusb_get_device_list(ctx, &list); + if (cnt < 0) { + rtlbt_err("libusb_get_device_list() failed: code %lld", + (long long int) cnt); + return (NULL); + } + + /* + * Scan through USB device list. + */ + for (i = 0; i < cnt; i++) { + dev = list[i]; + if (bus_id == libusb_get_bus_number(dev) && + dev_id == libusb_get_device_address(dev)) { + /* Get the device descriptor for this device entry */ + r = libusb_get_device_descriptor(dev, &d); + if (r != 0) { + rtlbt_err("libusb_get_device_descriptor: %s", + libusb_strerror(r)); + break; + } + + /* For non-Realtek match on the vendor/product id */ + if (rtlbt_is_realtek(&d)) { + /* + * Take a reference so it's not freed later on. + */ + found = libusb_ref_device(dev); + break; + } + /* For Realtek vendor match on the interface class */ + if (d.idVendor == 0x0bda && rtlbt_is_bluetooth(dev)) { + /* + * Take a reference so it's not freed later on. + */ + found = libusb_ref_device(dev); + break; + } + } + } + + libusb_free_device_list(list, 1); + return (found); +} + +static void +rtlbt_dump_version(ng_hci_read_local_ver_rp *ver) +{ + rtlbt_info("hci_version 0x%02x", ver->hci_version); + rtlbt_info("hci_revision 0x%04x", le16toh(ver->hci_revision)); + rtlbt_info("lmp_version 0x%02x", ver->lmp_version); + rtlbt_info("lmp_subversion 0x%04x", le16toh(ver->lmp_subversion)); +} + +/* + * Parse ugen name and extract device's bus and address + */ + +static int +parse_ugen_name(char const *ugen, uint8_t *bus, uint8_t *addr) +{ + char *ep; + + if (strncmp(ugen, "ugen", 4) != 0) + return (-1); + + *bus = (uint8_t) strtoul(ugen + 4, &ep, 10); + if (*ep != '.') + return (-1); + + *addr = (uint8_t) strtoul(ep + 1, &ep, 10); + if (*ep != '\0') + return (-1); + + return (0); +} + +static void +usage(void) +{ + fprintf(stderr, + "Usage: rtlbtfw (-D) -d ugenX.Y (-f firmware path) (-I)\n"); + fprintf(stderr, " -D: enable debugging\n"); + fprintf(stderr, " -d: device to operate upon\n"); + fprintf(stderr, " -f: firmware path, if not default\n"); + fprintf(stderr, " -I: enable informational output\n"); + exit(127); +} + +int +main(int argc, char *argv[]) +{ + libusb_context *ctx = NULL; + libusb_device *dev = NULL; + libusb_device_handle *hdl = NULL; + ng_hci_read_local_ver_rp ver; + int r; + uint8_t bus_id = 0, dev_id = 0; + int devid_set = 0; + int n; + char *firmware_dir = NULL; + char *firmware_path = NULL; + char *config_path = NULL; + const char *fw_suffix; + int retcode = 1; + const struct rtlbt_id_table *ic; + uint8_t rom_version; + struct rtlbt_firmware fw, cfg; + enum rtlbt_fw_type fw_type; + uint16_t fw_lmp_subversion; + + /* Parse command line arguments */ + while ((n = getopt(argc, argv, "Dd:f:hIm:p:v:")) != -1) { + switch (n) { + case 'd': /* ugen device name */ + devid_set = 1; + if (parse_ugen_name(optarg, &bus_id, &dev_id) < 0) + usage(); + break; + case 'D': + rtlbt_do_debug = 1; + break; + case 'f': /* firmware dir */ + if (firmware_dir) + free(firmware_dir); + firmware_dir = strdup(optarg); + break; + case 'I': + rtlbt_do_info = 1; + break; + case 'h': + default: + usage(); + break; + /* NOT REACHED */ + } + } + + /* Ensure the devid was given! */ + if (devid_set == 0) { + usage(); + /* NOTREACHED */ + } + + /* libusb setup */ + r = libusb_init(&ctx); + if (r != 0) { + rtlbt_err("libusb_init failed: code %d", r); + exit(127); + } + + rtlbt_debug("opening dev %d.%d", (int) bus_id, (int) dev_id); + + /* Find a device based on the bus/dev id */ + dev = rtlbt_find_device(ctx, bus_id, dev_id); + if (dev == NULL) { + rtlbt_err("device not found"); + goto shutdown; + } + + /* XXX enforce that bInterfaceNumber is 0 */ + + /* XXX enforce the device/product id if they're non-zero */ + + /* Grab device handle */ + r = libusb_open(dev, &hdl); + if (r != 0) { + rtlbt_err("libusb_open() failed: code %d", r); + goto shutdown; + } + + /* Check if ng_ubt is attached */ + r = libusb_kernel_driver_active(hdl, 0); + if (r < 0) { + rtlbt_err("libusb_kernel_driver_active() failed: code %d", r); + goto shutdown; + } + if (r > 0) { + rtlbt_info("Firmware has already been downloaded"); + retcode = 0; + goto shutdown; + } + + /* Get local version */ + r = rtlbt_read_local_ver(hdl, &ver); + if (r < 0) { + rtlbt_err("rtlbt_read_local_ver() failed code %d", r); + goto shutdown; + } + rtlbt_dump_version(&ver); + + ic = rtlbt_get_ic(ver.lmp_subversion, ver.hci_revision, + ver.hci_version); + if (ic == NULL) { + rtlbt_err("rtlbt_get_ic() failed: Unknown IC"); + goto shutdown; + } + + /* Default the firmware path */ + if (firmware_dir == NULL) + firmware_dir = strdup(_DEFAULT_RTLBT_FIRMWARE_PATH); + + fw_suffix = ic->fw_suffix == NULL ? "_fw.bin" : ic->fw_suffix; + firmware_path = rtlbt_get_fwname(ic->fw_name, firmware_dir, fw_suffix); + if (firmware_path == NULL) + goto shutdown; + + rtlbt_debug("firmware_path = %s", firmware_path); + + rtlbt_info("loading firmware %s", firmware_path); + + /* Read in the firmware */ + if (rtlbt_fw_read(&fw, firmware_path) <= 0) { + rtlbt_debug("rtlbt_fw_read() failed"); + return (-1); + } + + fw_type = rtlbt_get_fw_type(&fw, &fw_lmp_subversion); + if (fw_type == RTLBT_FW_TYPE_UNKNOWN && + (ic->flags & RTLBT_IC_FLAG_SIMPLE) == 0) { + rtlbt_debug("Unknown firmware type"); + goto shutdown; + } + + if (fw_type != RTLBT_FW_TYPE_UNKNOWN) { + + /* Match hardware and firmware lmp_subversion */ + if (fw_lmp_subversion != ver.lmp_subversion) { + rtlbt_err("firmware is for %x but this is a %x", + fw_lmp_subversion, ver.lmp_subversion); + goto shutdown; + } + + /* Query a ROM version */ + r = rtlbt_read_rom_ver(hdl, &rom_version); + if (r < 0) { + rtlbt_err("rtlbt_read_rom_ver() failed code %d", r); + goto shutdown; + } + rtlbt_debug("rom_version = %d", rom_version); + + /* Load in the firmware */ + if (fw_type == RTLBT_FW_TYPE_V2) { + uint8_t key_id, reg_val[2]; + r = rtlbt_read_reg16(hdl, RTLBT_SEC_PROJ, reg_val); + if (r < 0) { + rtlbt_err("rtlbt_read_reg16() failed code %d", r); + goto shutdown; + } + key_id = reg_val[0]; + rtlbt_debug("key_id = %d", key_id); + r = rtlbt_parse_fwfile_v2(&fw, rom_version, key_id); + } else + r = rtlbt_parse_fwfile_v1(&fw, rom_version); + if (r < 0) { + rtlbt_err("Parsing firmware file failed"); + goto shutdown; + } + + config_path = rtlbt_get_fwname(ic->fw_name, firmware_dir, + "_config.bin"); + if (config_path == NULL) + goto shutdown; + + rtlbt_info("loading config %s", config_path); + + /* Read in the config file */ + if (rtlbt_fw_read(&cfg, config_path) <= 0) { + rtlbt_err("rtlbt_fw_read() failed"); + if ((ic->flags & RTLBT_IC_FLAG_CONFIG) != 0) + goto shutdown; + } else { + r = rtlbt_append_fwfile(&fw, &cfg); + rtlbt_fw_free(&cfg); + if (r < 0) { + rtlbt_err("Appending config file failed"); + goto shutdown; + } + } + } + + r = rtlbt_load_fwfile(hdl, &fw); + if (r < 0) { + rtlbt_debug("Loading firmware file failed"); + goto shutdown; + } + + /* free it */ + rtlbt_fw_free(&fw); + + rtlbt_info("Firmware download complete"); + + /* Execute Read Local Version one more time */ + r = rtlbt_read_local_ver(hdl, &ver); + if (r < 0) { + rtlbt_err("rtlbt_read_local_ver() failed code %d", r); + goto shutdown; + } + rtlbt_dump_version(&ver); + + retcode = 0; + + /* Ask kernel driver to probe and attach device again */ + r = libusb_reset_device(hdl); + if (r != 0) + rtlbt_err("libusb_reset_device() failed: %s", + libusb_strerror(r)); + +shutdown: + + /* Shutdown */ + + if (hdl != NULL) + libusb_close(hdl); + + if (dev != NULL) + libusb_unref_device(dev); + + if (ctx != NULL) + libusb_exit(ctx); + + if (retcode == 0) + rtlbt_info("Firmware download is successful!"); + else + rtlbt_err("Firmware download failed!"); + + return (retcode); +} diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h new file mode 100644 index 000000000000..54c982119d40 --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h @@ -0,0 +1,46 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * + * 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. + */ + +#ifndef __RTLBT_DEBUG_H__ +#define __RTLBT_DEBUG_H__ + +extern int rtlbt_do_debug; +extern int rtlbt_do_info; + +#define rtlbt_err(fmt, ...) \ + fprintf(stderr, "rtlbtfw: %s: "fmt"\n", __func__, ##__VA_ARGS__) +#define rtlbt_info(fmt, ...) do { \ + if (rtlbt_do_info) \ + fprintf(stderr, "%s: "fmt"\n", __func__, ##__VA_ARGS__);\ +} while (0) +#define rtlbt_debug(fmt, ...) do { \ + if (rtlbt_do_debug) \ + fprintf(stderr, "%s: "fmt"\n", __func__, ##__VA_ARGS__);\ +} while (0) + +#endif diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c new file mode 100644 index 000000000000..d7e9f2f939c6 --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c @@ -0,0 +1,582 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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/param.h> +#include <sys/endian.h> +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "rtlbt_fw.h" +#include "rtlbt_dbg.h" + +static const struct rtlbt_id_table rtlbt_ic_id_table[] = { + { /* 8723A */ + .lmp_subversion = RTLBT_ROM_LMP_8723A, + .hci_revision = 0xb, + .hci_version = 0x6, + .flags = RTLBT_IC_FLAG_SIMPLE, + .fw_name = "rtl8723a", + }, { /* 8723B */ + .lmp_subversion = RTLBT_ROM_LMP_8723B, + .hci_revision = 0xb, + .hci_version = 0x6, + .fw_name = "rtl8723b", + }, { /* 8723D */ + .lmp_subversion = RTLBT_ROM_LMP_8723B, + .hci_revision = 0xd, + .hci_version = 0x8, + .flags = RTLBT_IC_FLAG_CONFIG, + .fw_name = "rtl8723d", + }, { /* 8821A */ + .lmp_subversion = RTLBT_ROM_LMP_8821A, + .hci_revision = 0xa, + .hci_version = 0x6, + .fw_name = "rtl8821a", + }, { /* 8821C */ + .lmp_subversion = RTLBT_ROM_LMP_8821A, + .hci_revision = 0xc, + .hci_version = 0x8, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8821c", + }, { /* 8761A */ + .lmp_subversion = RTLBT_ROM_LMP_8761A, + .hci_revision = 0xa, + .hci_version = 0x6, + .fw_name = "rtl8761a", + }, { /* 8761BU */ + .lmp_subversion = RTLBT_ROM_LMP_8761A, + .hci_revision = 0xb, + .hci_version = 0xa, + .fw_name = "rtl8761bu", + }, { /* 8822C with USB interface */ + .lmp_subversion = RTLBT_ROM_LMP_8822B, + .hci_revision = 0xc, + .hci_version = 0xa, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8822cu", + }, { /* 8822B */ + .lmp_subversion = RTLBT_ROM_LMP_8822B, + .hci_revision = 0xb, + .hci_version = 0x7, + .flags = RTLBT_IC_FLAG_CONFIG | RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8822b", + }, { /* 8852A */ + .lmp_subversion = RTLBT_ROM_LMP_8852A, + .hci_revision = 0xa, + .hci_version = 0xb, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8852au", + }, { /* 8852B */ + .lmp_subversion = RTLBT_ROM_LMP_8852A, + .hci_revision = 0xb, + .hci_version = 0xb, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8852bu", + }, { /* 8852C */ + .lmp_subversion = RTLBT_ROM_LMP_8852A, + .hci_revision = 0xc, + .hci_version = 0xc, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8852cu", + .fw_suffix = "_fw_v2.bin", + }, { /* 8851B */ + .lmp_subversion = RTLBT_ROM_LMP_8851B, + .hci_revision = 0xb, + .hci_version = 0xc, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8851bu", + }, { /* 8922A */ + .lmp_subversion = RTLBT_ROM_LMP_8922A, + .hci_revision = 0xa, + .hci_version = 0xc, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8922au", + }, { /* 8852BT/8852BE-VT */ + .lmp_subversion = RTLBT_ROM_LMP_8852A, + .hci_revision = 0x87, + .hci_version = 0xc, + .flags = RTLBT_IC_FLAG_MSFT, + .fw_name = "rtl8852btu", + }, +}; + +static const uint16_t project_ids[] = { + [ 0 ] = RTLBT_ROM_LMP_8723A, + [ 1 ] = RTLBT_ROM_LMP_8723B, + [ 2 ] = RTLBT_ROM_LMP_8821A, + [ 3 ] = RTLBT_ROM_LMP_8761A, + [ 7 ] = RTLBT_ROM_LMP_8703B, + [ 8 ] = RTLBT_ROM_LMP_8822B, + [ 9 ] = RTLBT_ROM_LMP_8723B, /* 8723DU */ + [ 10 ] = RTLBT_ROM_LMP_8821A, /* 8821CU */ + [ 13 ] = RTLBT_ROM_LMP_8822B, /* 8822CU */ + [ 14 ] = RTLBT_ROM_LMP_8761A, /* 8761BU */ + [ 18 ] = RTLBT_ROM_LMP_8852A, /* 8852AU */ + [ 19 ] = RTLBT_ROM_LMP_8723B, /* 8723FU */ + [ 20 ] = RTLBT_ROM_LMP_8852A, /* 8852BU */ + [ 25 ] = RTLBT_ROM_LMP_8852A, /* 8852CU */ + [ 33 ] = RTLBT_ROM_LMP_8822B, /* 8822EU */ + [ 36 ] = RTLBT_ROM_LMP_8851B, /* 8851BU */ + [ 44 ] = RTLBT_ROM_LMP_8922A, /* 8922A */ + [ 47 ] = RTLBT_ROM_LMP_8852A, /* 8852BT */ +}; + +/* Signatures */ +static const uint8_t fw_header_sig_v1[8] = + {0x52, 0x65, 0x61, 0x6C, 0x74, 0x65, 0x63, 0x68}; /* Realtech */ +static const uint8_t fw_header_sig_v2[8] = + {0x52, 0x54, 0x42, 0x54, 0x43, 0x6F, 0x72, 0x65}; /* RTBTCore */ +static const uint8_t fw_ext_sig[4] = {0x51, 0x04, 0xFD, 0x77}; + +int +rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname) +{ + int fd; + struct stat sb; + unsigned char *buf; + ssize_t r; + + fd = open(fwname, O_RDONLY); + if (fd < 0) { + warn("%s: open: %s", __func__, fwname); + return (0); + } + + if (fstat(fd, &sb) != 0) { + warn("%s: stat: %s", __func__, fwname); + close(fd); + return (0); + } + + buf = calloc(1, sb.st_size); + if (buf == NULL) { + warn("%s: calloc", __func__); + close(fd); + return (0); + } + + /* XXX handle partial reads */ + r = read(fd, buf, sb.st_size); + if (r < 0) { + warn("%s: read", __func__); + free(buf); + close(fd); + return (0); + } + + if (r != sb.st_size) { + rtlbt_err("read len %d != file size %d", + (int) r, + (int) sb.st_size); + free(buf); + close(fd); + return (0); + } + + /* We have everything, so! */ + + memset(fw, 0, sizeof(*fw)); + + fw->fwname = strdup(fwname); + fw->len = sb.st_size; + fw->buf = buf; + + close(fd); + return (1); +} + +void +rtlbt_fw_free(struct rtlbt_firmware *fw) +{ + if (fw->fwname) + free(fw->fwname); + if (fw->buf) + free(fw->buf); + memset(fw, 0, sizeof(*fw)); +} + +char * +rtlbt_get_fwname(const char *fw_name, const char *prefix, const char *suffix) +{ + char *fwname; + + asprintf(&fwname, "%s/%s%s", prefix, fw_name, suffix); + + return (fwname); +} + +const struct rtlbt_id_table * +rtlbt_get_ic(uint16_t lmp_subversion, uint16_t hci_revision, + uint8_t hci_version) +{ + unsigned int i; + + for (i = 0; i < nitems(rtlbt_ic_id_table); i++) { + if (rtlbt_ic_id_table[i].lmp_subversion == lmp_subversion && + rtlbt_ic_id_table[i].hci_revision == hci_revision && + rtlbt_ic_id_table[i].hci_version == hci_version) + return (rtlbt_ic_id_table + i); + } + + return (NULL); +} + +enum rtlbt_fw_type +rtlbt_get_fw_type(struct rtlbt_firmware *fw, uint16_t *fw_lmp_subversion) +{ + enum rtlbt_fw_type fw_type; + size_t fw_header_len; + uint8_t *ptr; + uint8_t opcode, oplen, project_id; + + if (fw->len < 8) { + rtlbt_err("firmware file too small"); + return (RTLBT_FW_TYPE_UNKNOWN); + } + + if (memcmp(fw->buf, fw_header_sig_v1, sizeof(fw_header_sig_v1)) == 0) { + fw_type = RTLBT_FW_TYPE_V1; + fw_header_len = sizeof(struct rtlbt_fw_header_v1); + } else + if (memcmp(fw->buf, fw_header_sig_v2, sizeof(fw_header_sig_v2)) == 0) { + fw_type = RTLBT_FW_TYPE_V2; + fw_header_len = sizeof(struct rtlbt_fw_header_v2); + } else + return (RTLBT_FW_TYPE_UNKNOWN); + + if (fw->len < fw_header_len + sizeof(fw_ext_sig) + 4) { + rtlbt_err("firmware file too small"); + return (RTLBT_FW_TYPE_UNKNOWN); + } + + ptr = fw->buf + fw->len - sizeof(fw_ext_sig); + if (memcmp(ptr, fw_ext_sig, sizeof(fw_ext_sig)) != 0) { + rtlbt_err("invalid extension section signature"); + return (RTLBT_FW_TYPE_UNKNOWN); + } + + do { + opcode = *--ptr; + oplen = *--ptr; + ptr -= oplen; + + rtlbt_debug("code=%x len=%x", opcode, oplen); + + if (opcode == 0x00) { + if (oplen != 1) { + rtlbt_err("invalid instruction length"); + return (RTLBT_FW_TYPE_UNKNOWN); + } + project_id = *ptr; + rtlbt_debug("project_id=%x", project_id); + if (project_id >= nitems(project_ids) || + project_ids[project_id] == 0) { + rtlbt_err("unknown project id %x", project_id); + return (RTLBT_FW_TYPE_UNKNOWN); + } + *fw_lmp_subversion = project_ids[project_id]; + return (fw_type); + } + } while (opcode != 0xff && ptr > fw->buf + fw_header_len); + + rtlbt_err("can not find project id instruction"); + return (RTLBT_FW_TYPE_UNKNOWN); +}; + +int +rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version) +{ + struct rtlbt_fw_header_v1 *fw_header; + uint8_t *patch_buf; + unsigned int i; + const uint8_t *chip_id_base; + uint32_t patch_offset; + uint16_t patch_length, num_patches; + + fw_header = (struct rtlbt_fw_header_v1 *)fw->buf; + num_patches = le16toh(fw_header->num_patches); + rtlbt_debug("fw_version=%x, num_patches=%d", + le32toh(fw_header->fw_version), num_patches); + + /* Find a right patch for the chip. */ + if (fw->len < sizeof(struct rtlbt_fw_header_v1) + + sizeof(fw_ext_sig) + 4 + 8 * num_patches) { + errno = EINVAL; + return (-1); + } + + chip_id_base = fw->buf + sizeof(struct rtlbt_fw_header_v1); + for (i = 0; i < num_patches; i++) { + if (le16dec(chip_id_base + i * 2) != rom_version + 1) + continue; + patch_length = le16dec(chip_id_base + 2 * (num_patches + i)); + patch_offset = le32dec(chip_id_base + 4 * (num_patches + i)); + break; + } + + if (i >= num_patches) { + rtlbt_err("can not find patch for chip id %d", rom_version); + errno = EINVAL; + return (-1); + } + + rtlbt_debug( + "index=%d length=%x offset=%x", i, patch_length, patch_offset); + if (fw->len < patch_offset + patch_length) { + errno = EINVAL; + return (-1); + } + + patch_buf = malloc(patch_length); + if (patch_buf == NULL) { + errno = ENOMEM; + return (-1); + } + + memcpy(patch_buf, fw->buf + patch_offset, patch_length - 4); + memcpy(patch_buf + patch_length - 4, &fw_header->fw_version, 4); + + free(fw->buf); + fw->buf = patch_buf; + fw->len = patch_length; + + return (0); +} + +static void * +rtlbt_iov_fetch(struct rtlbt_iov *iov, uint32_t len) +{ + void *data = NULL; + + if (iov->len >= len) { + data = iov->data; + iov->data += len; + iov->len -= len; + } + + return (data); +} + +static int +rtlbt_patch_entry_cmp(struct rtlbt_patch_entry *a, struct rtlbt_patch_entry *b, + void *thunk __unused) +{ + return ((a->prio > b->prio) - (a->prio < b->prio)); +} + +static int +rtlbt_parse_section(struct rtlbt_patch_list *patch_list, uint32_t opcode, + uint8_t *data, uint32_t len, uint8_t rom_version, uint8_t key_id) +{ + struct rtlbt_sec_hdr *hdr; + struct rtlbt_patch_entry *entry; + struct rtlbt_subsec_hdr *subsec_hdr; + struct rtlbt_subsec_security_hdr *subsec_security_hdr; + uint16_t num_subsecs; + uint8_t *subsec_data; + uint32_t subsec_len; + int i, sec_len = 0; + struct rtlbt_iov iov = { + .data = data, + .len = len, + }; + + hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr)); + if (hdr == NULL) { + errno = EINVAL; + return (-1); + } + num_subsecs = le16toh(hdr->num); + + for (i = 0; i < num_subsecs; i++) { + subsec_hdr = rtlbt_iov_fetch(&iov, sizeof(*subsec_hdr)); + if (subsec_hdr == NULL) + break; + subsec_len = le32toh(subsec_hdr->len); + + rtlbt_debug("subsection, eco 0x%02x, prio 0x%02x, len 0x%x", + subsec_hdr->eco, subsec_hdr->prio, subsec_len); + + subsec_data = rtlbt_iov_fetch(&iov, subsec_len); + if (subsec_data == NULL) + break; + + if (subsec_hdr->eco == rom_version + 1) { + if (opcode == RTLBT_PATCH_SECURITY_HEADER) { + subsec_security_hdr = (void *)subsec_hdr; + if (subsec_security_hdr->key_id == key_id) + break; + continue; + } + + entry = calloc(1, sizeof(*entry)); + if (entry == NULL) { + errno = ENOMEM; + return (-1); + } + *entry = (struct rtlbt_patch_entry) { + .opcode = opcode, + .len = subsec_len, + .prio = subsec_hdr->prio, + .data = subsec_data, + }; + SLIST_INSERT_HEAD(patch_list, entry, next); + sec_len += subsec_len; + } + } + + return (sec_len); +} + +int +rtlbt_parse_fwfile_v2(struct rtlbt_firmware *fw, uint8_t rom_version, + uint8_t key_id) +{ + struct rtlbt_fw_header_v2 *hdr; + struct rtlbt_section *section; + struct rtlbt_patch_entry *entry; + uint32_t num_sections; + uint32_t section_len; + uint32_t opcode; + int seclen, len = 0, patch_len = 0; + uint32_t i; + uint8_t *section_data, *patch_buf; + struct rtlbt_patch_list patch_list = + SLIST_HEAD_INITIALIZER(patch_list); + struct rtlbt_iov iov = { + .data = fw->buf, + .len = fw->len - 7, + }; + + hdr = rtlbt_iov_fetch(&iov, sizeof(*hdr)); + if (hdr == NULL) { + errno = EINVAL; + return (-1); + } + num_sections = le32toh(hdr->num_sections); + + rtlbt_debug("FW version %02x%02x%02x%02x-%02x%02x%02x%02x", + hdr->fw_version[0], hdr->fw_version[1], + hdr->fw_version[2], hdr->fw_version[3], + hdr->fw_version[4], hdr->fw_version[5], + hdr->fw_version[6], hdr->fw_version[7]); + + for (i = 0; i < num_sections; i++) { + section = rtlbt_iov_fetch(&iov, sizeof(*section)); + if (section == NULL) + break; + section_len = le32toh(section->len); + opcode = le32toh(section->opcode); + + rtlbt_debug("section, opcode 0x%08x", section->opcode); + + section_data = rtlbt_iov_fetch(&iov, section_len); + if (section_data == NULL) + break; + + seclen = 0; + switch (opcode) { + case RTLBT_PATCH_SECURITY_HEADER: + if (key_id == 0) + break; + /* FALLTHROUGH */ + case RTLBT_PATCH_SNIPPETS: + case RTLBT_PATCH_DUMMY_HEADER: + seclen = rtlbt_parse_section(&patch_list, opcode, + section_data, section_len, rom_version, key_id); + break; + default: + break; + } + if (seclen < 0) { + rtlbt_err("Section parse (0x%08x) failed. err %d", + opcode, errno); + return (-1); + } + len += seclen; + } + + if (len == 0) { + errno = ENOMSG; + return (-1); + } + + patch_buf = calloc(1, len); + if (patch_buf == NULL) { + errno = ENOMEM; + return (-1); + } + + SLIST_MERGESORT(&patch_list, NULL, + rtlbt_patch_entry_cmp, rtlbt_patch_entry, next); + while (!SLIST_EMPTY(&patch_list)) { + entry = SLIST_FIRST(&patch_list); + rtlbt_debug("opcode 0x%08x, addr 0x%p, len 0x%x", + entry->opcode, entry->data, entry->len); + memcpy(patch_buf + patch_len, entry->data, entry->len); + patch_len += entry->len; + SLIST_REMOVE_HEAD(&patch_list, next); + free(entry); + } + + if (patch_len == 0) { + errno = EPERM; + return (-1); + } + + free(fw->buf); + fw->buf = patch_buf; + fw->len = patch_len; + + return (0); +} + +int +rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt) +{ + uint8_t *buf; + int len; + + len = fw->len + opt->len; + buf = realloc(fw->buf, len); + if (buf == NULL) + return (-1); + memcpy(buf + fw->len, opt->buf, opt->len); + fw->buf = buf; + fw->len = len; + + return (0); +} diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h new file mode 100644 index 000000000000..e9af6c93950e --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h @@ -0,0 +1,140 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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. + */ + +#ifndef __RTLBT_FW_H__ +#define __RTLBT_FW_H__ + +#include <sys/queue.h> +#include <sys/queue_mergesort.h> + +#define RTLBT_ROM_LMP_8703B 0x8703 +#define RTLBT_ROM_LMP_8723A 0x1200 +#define RTLBT_ROM_LMP_8723B 0x8723 +#define RTLBT_ROM_LMP_8821A 0x8821 +#define RTLBT_ROM_LMP_8761A 0x8761 +#define RTLBT_ROM_LMP_8822B 0x8822 +#define RTLBT_ROM_LMP_8852A 0x8852 +#define RTLBT_ROM_LMP_8851B 0x8851 +#define RTLBT_ROM_LMP_8922A 0x8922 + +#define RTLBT_PATCH_SNIPPETS 0x01 +#define RTLBT_PATCH_DUMMY_HEADER 0x02 +#define RTLBT_PATCH_SECURITY_HEADER 0x03 + +enum rtlbt_fw_type { + RTLBT_FW_TYPE_UNKNOWN, + RTLBT_FW_TYPE_V1, + RTLBT_FW_TYPE_V2, +}; + +struct rtlbt_id_table { + uint16_t lmp_subversion; + uint16_t hci_revision; + uint8_t hci_version; + uint8_t flags; +#define RTLBT_IC_FLAG_SIMPLE (0 << 1) +#define RTLBT_IC_FLAG_CONFIG (1 << 1) +#define RTLBT_IC_FLAG_MSFT (2 << 1) + const char *fw_name; + const char *fw_suffix; +}; + +struct rtlbt_firmware { + char *fwname; + size_t len; + unsigned char *buf; +}; + +SLIST_HEAD(rtlbt_patch_list, rtlbt_patch_entry); + +struct rtlbt_patch_entry { + SLIST_ENTRY(rtlbt_patch_entry) next; + uint32_t opcode; + uint32_t len; + uint8_t prio; + uint8_t *data; +}; + +struct rtlbt_iov { + uint8_t *data; + uint32_t len; +}; + +struct rtlbt_fw_header_v1 { + uint8_t signature[8]; + uint32_t fw_version; + uint16_t num_patches; +} __attribute__ ((packed)); + +struct rtlbt_fw_header_v2 { + uint8_t signature[8]; + uint8_t fw_version[8]; + uint32_t num_sections; +} __attribute__ ((packed)); + +struct rtlbt_section { + uint32_t opcode; + uint32_t len; + uint8_t data[]; +} __attribute__ ((packed)); + +struct rtlbt_sec_hdr { + uint16_t num; + uint16_t reserved; +} __attribute__ ((packed)); + +struct rtlbt_subsec_hdr { + uint8_t eco; + uint8_t prio; + uint8_t cb[2]; + uint32_t len; +} __attribute__ ((packed)); + +struct rtlbt_subsec_security_hdr { + uint8_t eco; + uint8_t prio; + uint8_t key_id; + uint8_t reserved; + uint32_t len; +} __attribute__ ((packed)); + +int rtlbt_fw_read(struct rtlbt_firmware *fw, const char *fwname); +void rtlbt_fw_free(struct rtlbt_firmware *fw); +char *rtlbt_get_fwname(const char *fw_name, const char *prefix, + const char *suffix); +const struct rtlbt_id_table *rtlbt_get_ic(uint16_t lmp_subversion, + uint16_t hci_revision, uint8_t hci_version); +enum rtlbt_fw_type rtlbt_get_fw_type(struct rtlbt_firmware *fw, + uint16_t *fw_lmp_subversion); +int rtlbt_parse_fwfile_v1(struct rtlbt_firmware *fw, uint8_t rom_version); +int rtlbt_parse_fwfile_v2(struct rtlbt_firmware *fw, uint8_t rom_version, + uint8_t reg_id); +int rtlbt_append_fwfile(struct rtlbt_firmware *fw, struct rtlbt_firmware *opt); + +#endif diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c new file mode 100644 index 000000000000..82e22d406ea9 --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c @@ -0,0 +1,270 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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/param.h> +#include <sys/endian.h> +#include <sys/stat.h> + +#include <netgraph/bluetooth/include/ng_hci.h> + +#include <err.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <libusb.h> + +#include "rtlbt_fw.h" +#include "rtlbt_hw.h" +#include "rtlbt_dbg.h" + +static int +rtlbt_hci_command(struct libusb_device_handle *hdl, struct rtlbt_hci_cmd *cmd, + void *event, int size, int *transferred, int timeout) +{ + struct timespec to, now, remains; + int ret; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_DEVICE, + 0, + 0, + 0, + (uint8_t *)cmd, + RTLBT_HCI_CMD_SIZE(cmd), + timeout); + + if (ret < 0) { + rtlbt_err("libusb_control_transfer() failed: err=%s", + libusb_strerror(ret)); + return (ret); + } + + clock_gettime(CLOCK_MONOTONIC, &now); + to = RTLBT_MSEC2TS(timeout); + timespecadd(&to, &now, &to); + + do { + timespecsub(&to, &now, &remains); + ret = libusb_interrupt_transfer(hdl, + RTLBT_INTERRUPT_ENDPOINT_ADDR, + event, + size, + transferred, + RTLBT_TS2MSEC(remains) + 1); + + if (ret < 0) { + rtlbt_err("libusb_interrupt_transfer() failed: err=%s", + libusb_strerror(ret)); + return (ret); + } + + switch (((struct rtlbt_hci_event *)event)->header.event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (*transferred < + (int)offsetof(struct rtlbt_hci_event_cmd_compl, data)) + break; + if (cmd->opcode != + ((struct rtlbt_hci_event_cmd_compl *)event)->opcode) + break; + return (0); + default: + break; + } + rtlbt_debug("Stray HCI event: %x", + ((struct rtlbt_hci_event *)event)->header.event); + } while (timespeccmp(&to, &now, >)); + + rtlbt_err("libusb_interrupt_transfer() failed: err=%s", + libusb_strerror(LIBUSB_ERROR_TIMEOUT)); + + return (LIBUSB_ERROR_TIMEOUT); +} + +int +rtlbt_read_local_ver(struct libusb_device_handle *hdl, + ng_hci_read_local_ver_rp *ver) +{ + int ret, transferred; + struct rtlbt_hci_event_cmd_compl *event; + struct rtlbt_hci_cmd cmd = { + .opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_INFO, + NG_HCI_OCF_READ_LOCAL_VER)), + .length = 0, + }; + uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(ng_hci_read_local_ver_rp)]; + + memset(buf, 0, sizeof(buf)); + + ret = rtlbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + RTLBT_HCI_CMD_TIMEOUT); + + if (ret < 0 || transferred != sizeof(buf)) { + rtlbt_debug("Can't read local version: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + event = (struct rtlbt_hci_event_cmd_compl *)buf; + memcpy(ver, event->data, sizeof(ng_hci_read_local_ver_rp)); + + return (0); +} + +int +rtlbt_read_rom_ver(struct libusb_device_handle *hdl, uint8_t *ver) +{ + int ret, transferred; + struct rtlbt_hci_event_cmd_compl *event; + struct rtlbt_hci_cmd cmd = { + .opcode = htole16(0xfc6d), + .length = 0, + }; + uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_rom_ver_rp)]; + + memset(buf, 0, sizeof(buf)); + + ret = rtlbt_hci_command(hdl, + &cmd, + buf, + sizeof(buf), + &transferred, + RTLBT_HCI_CMD_TIMEOUT); + + if (ret < 0 || transferred != sizeof(buf)) { + rtlbt_debug("Can't read ROM version: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + event = (struct rtlbt_hci_event_cmd_compl *)buf; + *ver = ((struct rtlbt_rom_ver_rp *)event->data)->version; + + return (0); +} + +int +rtlbt_read_reg16(struct libusb_device_handle *hdl, + struct rtlbt_vendor_cmd *vcmd, uint8_t *resp) +{ + int ret, transferred; + struct rtlbt_hci_event_cmd_compl *event; + uint8_t cmd_buf[offsetof(struct rtlbt_hci_cmd, data) + sizeof(*vcmd)]; + struct rtlbt_hci_cmd *cmd = (struct rtlbt_hci_cmd *)cmd_buf; + cmd->opcode = htole16(0xfc61); + cmd->length = sizeof(struct rtlbt_vendor_cmd); + memcpy(cmd->data, vcmd, sizeof(struct rtlbt_vendor_cmd)); + uint8_t buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_vendor_rp)]; + + memset(buf, 0, sizeof(buf)); + + ret = rtlbt_hci_command(hdl, + cmd, + buf, + sizeof(buf), + &transferred, + RTLBT_HCI_CMD_TIMEOUT); + + if (ret < 0 || transferred != sizeof(buf)) { + rtlbt_debug("Can't read reg16: code=%d, size=%d", + ret, + transferred); + return (-1); + } + + event = (struct rtlbt_hci_event_cmd_compl *)buf; + memcpy(resp, &(((struct rtlbt_vendor_rp *)event->data)->data), 2); + + return (0); +} + +int +rtlbt_load_fwfile(struct libusb_device_handle *hdl, + const struct rtlbt_firmware *fw) +{ + uint8_t cmd_buf[RTLBT_HCI_MAX_CMD_SIZE]; + struct rtlbt_hci_cmd *cmd = (struct rtlbt_hci_cmd *)cmd_buf; + struct rtlbt_hci_dl_cmd *dl_cmd = (struct rtlbt_hci_dl_cmd *)cmd->data; + uint8_t evt_buf[RTLBT_HCI_EVT_COMPL_SIZE(struct rtlbt_hci_dl_rp)]; + uint8_t *data = fw->buf; + int frag_num = fw->len / RTLBT_MAX_CMD_DATA_LEN + 1; + int frag_len = RTLBT_MAX_CMD_DATA_LEN; + int i, j; + int ret, transferred; + + for (i = 0, j = 0; i < frag_num; i++, j++) { + + rtlbt_debug("download fw (%d/%d)", i + 1, frag_num); + + memset(cmd_buf, 0, sizeof(cmd_buf)); + cmd->opcode = htole16(0xfc20); + if (j > 0x7f) + j = 1; + dl_cmd->index = j; + + if (i == (frag_num - 1)) { + dl_cmd->index |= 0x80; /* data end */ + frag_len = fw->len % RTLBT_MAX_CMD_DATA_LEN; + } + cmd->length = frag_len + 1; + memcpy(dl_cmd->data, data, frag_len); + + /* Send download command */ + ret = rtlbt_hci_command(hdl, + cmd, + evt_buf, + sizeof(evt_buf), + &transferred, + RTLBT_HCI_CMD_TIMEOUT); + if (ret < 0) { + rtlbt_err("download fw command failed (%d)", errno); + goto out; + } + if (transferred != sizeof(evt_buf)) { + rtlbt_err("download fw event length mismatch"); + errno = EIO; + ret = -1; + goto out; + } + + data += RTLBT_MAX_CMD_DATA_LEN; + } + +out: + return (ret); +} diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h new file mode 100644 index 000000000000..a7200a440272 --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h @@ -0,0 +1,117 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> + * Copyright (c) 2023 Future Crew LLC. + * + * 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. + */ +#ifndef __RTLBT_HW_H__ +#define __RTLBT_HW_H__ + +#include <netgraph/bluetooth/include/ng_hci.h> + +/* USB control request (HCI command) structure */ +struct rtlbt_hci_cmd { + uint16_t opcode; + uint8_t length; + uint8_t data[]; +} __attribute__ ((packed)); + +#define RTLBT_HCI_CMD_SIZE(cmd) \ + ((cmd)->length + offsetof(struct rtlbt_hci_cmd, data)) + +/* USB interrupt transfer HCI event header structure */ +struct rtlbt_hci_evhdr { + uint8_t event; + uint8_t length; +} __attribute__ ((packed)); + +/* USB interrupt transfer (generic HCI event) structure */ +struct rtlbt_hci_event { + struct rtlbt_hci_evhdr header; + uint8_t data[]; +} __attribute__ ((packed)); + +/* USB interrupt transfer (HCI command completion event) structure */ +struct rtlbt_hci_event_cmd_compl { + struct rtlbt_hci_evhdr header; + uint8_t numpkt; + uint16_t opcode; + uint8_t data[]; +} __attribute__ ((packed)); + +#define RTLBT_HCI_EVT_COMPL_SIZE(payload) \ + (offsetof(struct rtlbt_hci_event_cmd_compl, data) + sizeof(payload)) + +#define RTLBT_CONTROL_ENDPOINT_ADDR 0x00 +#define RTLBT_INTERRUPT_ENDPOINT_ADDR 0x81 + +#define RTLBT_HCI_MAX_CMD_SIZE 256 +#define RTLBT_HCI_MAX_EVENT_SIZE 16 + +#define RTLBT_MSEC2TS(msec) \ + (struct timespec) { \ + .tv_sec = (msec) / 1000, \ + .tv_nsec = ((msec) % 1000) * 1000000 \ + }; +#define RTLBT_TS2MSEC(ts) ((ts).tv_sec * 1000 + (ts).tv_nsec / 1000000) +#define RTLBT_HCI_CMD_TIMEOUT 2000 /* ms */ +#define RTLBT_LOADCMPL_TIMEOUT 5000 /* ms */ + +#define RTLBT_MAX_CMD_DATA_LEN 252 + +struct rtlbt_rom_ver_rp { + uint8_t status; + uint8_t version; +} __attribute__ ((packed)); + +struct rtlbt_hci_dl_cmd { + uint8_t index; + uint8_t data[RTLBT_MAX_CMD_DATA_LEN]; +} __attribute__ ((packed)); + +struct rtlbt_hci_dl_rp { + uint8_t status; + uint8_t index; +} __attribute__ ((packed)); + +/* Vendor USB request payload */ +struct rtlbt_vendor_cmd { + uint8_t data[5]; +} __attribute__ ((packed)); +#define RTLBT_SEC_PROJ (&(struct rtlbt_vendor_cmd) {{0x10, 0xA4, 0x0D, 0x00, 0xb0}}) + +struct rtlbt_vendor_rp { + uint8_t status; + uint8_t data[2]; +}; + +int rtlbt_read_local_ver(struct libusb_device_handle *hdl, + ng_hci_read_local_ver_rp *ver); +int rtlbt_read_rom_ver(struct libusb_device_handle *hdl, uint8_t *ver); +int rtlbt_read_reg16(struct libusb_device_handle *hdl, + struct rtlbt_vendor_cmd *cmd, uint8_t *resp); +int rtlbt_load_fwfile(struct libusb_device_handle *hdl, + const struct rtlbt_firmware *fw); + +#endif diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8 b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8 new file mode 100644 index 000000000000..5cae9c9d288d --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.8 @@ -0,0 +1,99 @@ +.\" Copyright (c) 2013, 2016 Adrian Chadd <adrian@freebsd.org> +.\" Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org> +.\" Copyright (c) 2023 Future Crew LLC. +.\" +.\" 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. +.\" +.Dd July 15, 2025 +.Dt RTLBTFW 8 +.Os +.Sh NAME +.Nm rtlbtfw +.Nd load firmware for Realtek 87XX/88XX Bluetooth USB devices +.Sh SYNOPSIS +.Nm +.Fl d Ar device_name +.Fl f Ar firmware_path +.Nm +.Fl h +.Sh DESCRIPTION +The +.Nm +utility downloads the specified firmware file to the specified +.Xr ugen 4 +device. +.Pp +This utility will +.Em only +work with Realtek 87XX/88XX chip based Bluetooth USB devices and some of +their successors. +The identification is currently based on USB vendor ID/product ID pair and +interface class. +For Realtek devices the vendor ID should be 0x0bda +.Pq Dv USB_VENDOR_REALTEK +and the 0-th interface class/subclass/protocol should be a Bluetooth RF +Wireless Controller. +Non-Realtek devices are identified based on USB vendor ID/product ID pair. +.Pp +Firmware files are available in the +.Pa comms/rtlbt-firmware +port. +.Pp +The +.Nm +utility will query the device to determine which firmware image and board +configuration to load in at runtime. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl D +Enable verbose debugging. +.It Fl d Ar device_name +Specify +.Xr ugen 4 +device name. +.It Fl I +Enable informational debugging. +.It Fl f Ar firmware_path +Specify the directory containing the firmware files to search and upload. +.It Fl h +Display usage message and exit. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr libusb 3 , +.Xr ng_ubt 4 , +.Xr ugen 4 , +.Xr devd 8 +.Sh AUTHORS +.Nm +is based on +.Xr ath3kfw 8 +utility used as firmware downloader template and on Linux btrtl driver +source code. +It is written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org +under sponsorship from Future Crew LLC. +.Sh BUGS +Most likely. +Please report if found. diff --git a/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf new file mode 100644 index 000000000000..2ef56d2af93a --- /dev/null +++ b/usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf @@ -0,0 +1,433 @@ +# +# Download Realtek 87XX/88XX bluetooth adaptor firmware +# + +# Generic Realtek vendor Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "INTERFACE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + # only interface 0 is supported by rtlbtfw + match "interface" "0"; + match "intclass" "0xe0"; + match "intsubclass" "0x01"; + match "intprotocol" "0x01"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8821CE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "0x3529"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8822CE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + match "product" "(0xb00c|0xc822)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8851BE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "0x3600"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8852AE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + match "product" "(0x2852|0xc852|0x385a|0x4852)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x04c5"; + match "product" "0x165c"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x04ca"; + match "product" "0x4006"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0cb8"; + match "product" "0xc549"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8852CE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x04ca"; + match "product" "0x4007"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x04c5"; + match "product" "0x1675"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0cb8"; + match "product" "0xc558"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "(0x3587|0x3586|0x3592)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0489"; + match "product" "0xe122"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8852BE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0cb8"; + match "product" "0xc559"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + match "product" "(0x4853|0x887b|0xb85b)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "(0x3570|0x3571|0x3572|0x3591)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0489"; + match "product" "(0xe123|0xe125)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8852BT/8852BE-VT Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + match "product" "0x8520"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8922AE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + match "product" "0x8922"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "(0x3617|0x3616)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0489"; + match "product" "0xe130"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8723AE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0930"; + match "product" "0x021d"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "0x3394"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8723BE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0489"; + match "product" "(0xe085|0xe08b)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x04f2"; + match "product" "0xb49f"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "(0x3410|0x3416|0x3459|0x3494)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8723BU Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x7392"; + match "product" "0xa611"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8723DE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + match "product" "0xb009"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x2ff8"; + match "product" "0xb011"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8761BUV Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x2c4e"; + match "product" "0x0115"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x2357"; + match "product" "0x0604"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0b05"; + match "product" "0x190e"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x2550"; + match "product" "0x8761"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + match "product" "0x8771"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x6655"; + match "product" "0x8771"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x7392"; + match "product" "0xc611"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x2b89"; + match "product" "0x8761"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8821AE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0b05"; + match "product" "0x17dc"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "(0x3414|0x3458|0x3461|0x3462)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8822BE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "0x3526"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0b05"; + match "product" "0x185c"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; + +# Realtek 8822CE Bluetooth devices +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x04ca"; + match "product" "0x4005"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x04c5"; + match "product" "0x161f"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0b05"; + match "product" "0x18ef"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x13d3"; + match "product" "(0x3548|0x3549|0x3553|0x3555)"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x2ff8"; + match "product" "0x3051"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x1358"; + match "product" "0xc123"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0bda"; + match "product" "0xc123"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; +notify 100 { + match "system" "USB"; + match "subsystem" "DEVICE"; + match "type" "ATTACH"; + match "vendor" "0x0cb5"; + match "product" "0xc547"; + action "/usr/sbin/rtlbtfw -d $cdev -f /usr/local/share/rtlbt-firmware"; +}; diff --git a/usr.sbin/bluetooth/sdpcontrol/Makefile b/usr.sbin/bluetooth/sdpcontrol/Makefile new file mode 100644 index 000000000000..7f1fe3f05b43 --- /dev/null +++ b/usr.sbin/bluetooth/sdpcontrol/Makefile @@ -0,0 +1,11 @@ +# $Id: Makefile,v 1.1 2003/09/08 02:27:27 max Exp $ + +PACKAGE= bluetooth +PROG= sdpcontrol +MAN= sdpcontrol.8 +SRCS= sdpcontrol.c search.c +WARNS?= 2 + +LIBADD= bluetooth sdp + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/sdpcontrol/Makefile.depend b/usr.sbin/bluetooth/sdpcontrol/Makefile.depend new file mode 100644 index 000000000000..ccc9ef8b7fa3 --- /dev/null +++ b/usr.sbin/bluetooth/sdpcontrol/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + lib/libsdp \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.8 b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.8 new file mode 100644 index 000000000000..1a7e4fe574fb --- /dev/null +++ b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.8 @@ -0,0 +1,117 @@ +.\" Copyright (c) 2003 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. +.\" +.\" $Id: sdpcontrol.8,v 1.1 2003/09/08 02:27:27 max Exp $ +.\" +.Dd February 7, 2015 +.Dt SDPCONTROL 8 +.Os +.Sh NAME +.Nm sdpcontrol +.Nd Bluetooth Service Discovery Protocol query utility +.Sh SYNOPSIS +.Nm +.Fl h +.Nm +.Fl a Ar address +.Ar command +.Op Ar parameters ... +.Nm +.Fl l +.Op Fl c Ar path +.Ar command +.Op Ar parameters ... +.Sh DESCRIPTION +The +.Nm +utility attempts to query the specified Service Discovery Protocol (SDP) server. +Remote SDP servers are identified by their address. +Connection to the local SDP server is made via the control socket. +The +.Nm +utility uses Service Search Attribute Requests and prints results to +standard output and error messages to standard error. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar address +Connect to the remote device with the specified address. +The address can be specified as BD_ADDR or a name. +If a name was specified, the +.Nm +utility attempts to resolve the name via +.Xr bt_gethostbyname 3 . +.It Fl c Ar path +Specify path to the control socket. +The default path is +.Pa /var/run/sdp . +.It Fl h +Display usage message and exit. +.It Fl l +Query the local SDP server via the control socket. +.It Ar command +One of the supported commands (see below). +The special command +.Cm help +can be used to obtain a list of all supported commands. +To get more information about a specific command, use +.Cm help Ar command . +.It Ar parameters +One or more optional space separated command parameters. +.El +.Sh COMMANDS +The currently supported node commands in +.Nm +are: +.Pp +.Bl -tag -width "Browse" -offset indent -compact +.It Cm Browse +.It Cm Search +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr bluetooth 3 , +.Xr sdp 3 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +.Sh CAVEATS +The +.Nm +utility only implements client side functionality. +.Pp +The +.Nm +utility only requests the following attributes from the SDP server: +.Pp +.Bl -enum -offset indent -compact +.It +Service Record Handle +.It +Service Class ID List +.It +Protocol Descriptor List +.It +Bluetooth Profile Descriptor List +.El diff --git a/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.c b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.c new file mode 100644 index 000000000000..890e6e3cfdba --- /dev/null +++ b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.c @@ -0,0 +1,221 @@ +/*- + * sdpcontrol.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2003 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. + * + * $Id: sdpcontrol.c,v 1.1 2003/09/08 02:27:27 max Exp $ + */ + +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <err.h> +#include <errno.h> +#include <sdp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "sdpcontrol.h" + +/* Prototypes */ +static int do_sdp_command (bdaddr_p, char const *, int, + int, char **); +static struct sdp_command * find_sdp_command (char const *, + struct sdp_command *); +static void print_sdp_command (struct sdp_command *); +static void usage (void); + +/* Main */ +int +main(int argc, char *argv[]) +{ + char const *control = SDP_LOCAL_PATH; + int n, local; + bdaddr_t bdaddr; + + memset(&bdaddr, 0, sizeof(bdaddr)); + local = 0; + + /* Process command line arguments */ + while ((n = getopt(argc, argv, "a:c:lh")) != -1) { + switch (n) { + case 'a': /* bdaddr */ + if (!bt_aton(optarg, &bdaddr)) { + struct hostent *he = NULL; + + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(1, "%s: %s", optarg, hstrerror(h_errno)); + + memcpy(&bdaddr, he->h_addr, sizeof(bdaddr)); + } + break; + + case 'c': /* control socket */ + control = optarg; + break; + + case 'l': /* local sdpd */ + local = 1; + break; + + case 'h': + default: + usage(); + /* NOT REACHED */ + } + } + + argc -= optind; + argv += optind; + + if (*argv == NULL) + usage(); + + return (do_sdp_command(&bdaddr, control, local, argc, argv)); +} + +/* Execute commands */ +static int +do_sdp_command(bdaddr_p bdaddr, char const *control, int local, + int argc, char **argv) +{ + char *cmd = argv[0]; + struct sdp_command *c = NULL; + void *xs = NULL; + int e, help; + + help = 0; + if (strcasecmp(cmd, "help") == 0) { + argc --; + argv ++; + + if (argc <= 0) { + fprintf(stdout, "Supported commands:\n"); + print_sdp_command(sdp_commands); + fprintf(stdout, "\nFor more information use " \ + "'help command'\n"); + + return (OK); + } + + help = 1; + cmd = argv[0]; + } + + c = find_sdp_command(cmd, sdp_commands); + if (c == NULL) { + fprintf(stdout, "Unknown command: \"%s\"\n", cmd); + return (ERROR); + } + + if (!help) { + if (!local) { + if (memcmp(bdaddr, NG_HCI_BDADDR_ANY, sizeof(*bdaddr)) == 0) + usage(); + + xs = sdp_open(NG_HCI_BDADDR_ANY, bdaddr); + } else + xs = sdp_open_local(control); + + if (xs == NULL) + errx(1, "Could not create SDP session object"); + if (sdp_error(xs) == 0) + e = (c->handler)(xs, -- argc, ++ argv); + else + e = ERROR; + } else + e = USAGE; + + switch (e) { + case OK: + case FAILED: + break; + + case ERROR: + fprintf(stdout, "Could not execute command \"%s\". %s\n", + cmd, strerror(sdp_error(xs))); + break; + + case USAGE: + fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description); + break; + + default: assert(0); break; + } + + sdp_close(xs); + + return (e); +} /* do_sdp_command */ + +/* Try to find command in specified category */ +static struct sdp_command * +find_sdp_command(char const *command, struct sdp_command *category) +{ + struct sdp_command *c = NULL; + + for (c = category; c->command != NULL; c++) { + char *c_end = strchr(c->command, ' '); + + if (c_end != NULL) { + int len = c_end - c->command; + + if (strncasecmp(command, c->command, len) == 0) + return (c); + } else if (strcasecmp(command, c->command) == 0) + return (c); + } + + return (NULL); +} /* find_sdp_command */ + +/* Print commands in specified category */ +static void +print_sdp_command(struct sdp_command *category) +{ + struct sdp_command *c = NULL; + + for (c = category; c->command != NULL; c++) + fprintf(stdout, "\t%s\n", c->command); +} /* print_sdp_command */ + +/* Usage */ +static void +usage(void) +{ + fprintf(stderr, +"Usage: sdpcontrol options command\n" \ +"Where options are:\n" +" -a address address to connect to\n" \ +" -c path path to the control socket (default is %s)\n" \ +" -h display usage and quit\n" \ +" -l connect to the local SDP server via control socket\n" \ +" command one of the supported commands\n", SDP_LOCAL_PATH); + exit(255); +} /* usage */ + diff --git a/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.h b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.h new file mode 100644 index 000000000000..818005ee34ce --- /dev/null +++ b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.h @@ -0,0 +1,50 @@ +/*- + * sdpcontrol.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2003 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. + * + * $Id: sdpcontrol.h,v 1.1 2003/09/08 02:27:27 max Exp $ + */ + +#ifndef __SDPCONTROL_H__ +#define __SDPCONTROL_H__ + +#define OK 0 /* everything was OK */ +#define ERROR 1 /* could not execute command */ +#define FAILED 2 /* error was reported */ +#define USAGE 3 /* invalid parameters */ + +struct sdp_command { + char const *command; + char const *description; + int (*handler)(void *, int, char **); +}; + +extern struct sdp_command sdp_commands[]; + +#endif /* __SDPCONTROL_H__ */ + diff --git a/usr.sbin/bluetooth/sdpcontrol/search.c b/usr.sbin/bluetooth/sdpcontrol/search.c new file mode 100644 index 000000000000..2a4b2468c7f7 --- /dev/null +++ b/usr.sbin/bluetooth/sdpcontrol/search.c @@ -0,0 +1,750 @@ +/*- + * search.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2001-2003 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. + * + * $Id: search.c,v 1.2 2003/09/08 17:35:15 max Exp $ + */ + +#include <sys/param.h> +#include <netinet/in.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <ctype.h> +#include <sdp.h> +#include <stdio.h> +#include <stdlib.h> +#include "sdpcontrol.h" + +/* List of the attributes we are looking for */ +static uint32_t attrs[] = +{ + SDP_ATTR_RANGE( SDP_ATTR_SERVICE_RECORD_HANDLE, + SDP_ATTR_SERVICE_RECORD_HANDLE), + SDP_ATTR_RANGE( SDP_ATTR_SERVICE_CLASS_ID_LIST, + SDP_ATTR_SERVICE_CLASS_ID_LIST), + SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST), + SDP_ATTR_RANGE( SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST) +}; +#define attrs_len nitems(attrs) + +/* Buffer for the attributes */ +#define NRECS 25 /* request this much records from the SDP server */ +#define BSIZE 256 /* one attribute buffer size */ +static uint8_t buffer[NRECS * attrs_len][BSIZE]; + +/* SDP attributes */ +static sdp_attr_t values[NRECS * attrs_len]; +#define values_len nitems(values) + +/* + * Print Service Class ID List + * + * The ServiceClassIDList attribute consists of a data element sequence in + * which each data element is a UUID representing the service classes that + * a given service record conforms to. The UUIDs are listed in order from + * the most specific class to the most general class. The ServiceClassIDList + * must contain at least one service class UUID. + */ + +static void +print_service_class_id_list(uint8_t const *start, uint8_t const *end) +{ + uint32_t type, len, value; + + if (end - start < 2) { + fprintf(stderr, "Invalid Service Class ID List. " \ + "Too short, len=%zd\n", end - start); + return; + } + + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + fprintf(stderr, "Invalid Service Class ID List. " \ + "Not a sequence, type=%#x\n", type); + return; + /* NOT REACHED */ + } + + if (len > (end - start)) { + fprintf(stderr, "Invalid Service Class ID List. " \ + "Too long len=%d\n", len); + return; + } + + while (start < end) { + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_UUID16: + SDP_GET16(value, start); + fprintf(stdout, "\t%s (%#4.4x)\n", + sdp_uuid2desc(value), value); + break; + + case SDP_DATA_UUID32: + SDP_GET32(value, start); + fprintf(stdout, "\t%#8.8x\n", value); + break; + + case SDP_DATA_UUID128: { + int128_t uuid; + + SDP_GET_UUID128(&uuid, start); + fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n", + ntohl(*(uint32_t *)&uuid.b[0]), + ntohs(*(uint16_t *)&uuid.b[4]), + ntohs(*(uint16_t *)&uuid.b[6]), + ntohs(*(uint16_t *)&uuid.b[8]), + ntohs(*(uint16_t *)&uuid.b[10]), + ntohl(*(uint32_t *)&uuid.b[12])); + } break; + + default: + fprintf(stderr, "Invalid Service Class ID List. " \ + "Not a UUID, type=%#x\n", type); + return; + /* NOT REACHED */ + } + } +} /* print_service_class_id_list */ + +/* + * Print Protocol Descriptor List + * + * If the ProtocolDescriptorList describes a single stack, it takes the form + * of a data element sequence in which each element of the sequence is a + * protocol descriptor. Each protocol descriptor is, in turn, a data element + * sequence whose first element is a UUID identifying the protocol and whose + * successive elements are protocol-specific parameters. The protocol + * descriptors are listed in order from the lowest layer protocol to the + * highest layer protocol used to gain access to the service. 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 as described above. + */ + +static void +print_protocol_descriptor(uint8_t const *start, uint8_t const *end) +{ + union { + uint8_t uint8; + uint16_t uint16; + uint32_t uint32; + uint64_t uint64; + int128_t int128; + } value; + uint32_t type, len, param; + + /* Get Protocol UUID */ + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_UUID16: + SDP_GET16(value.uint16, start); + fprintf(stdout, "\t%s (%#4.4x)\n", sdp_uuid2desc(value.uint16), + value.uint16); + break; + + case SDP_DATA_UUID32: + SDP_GET32(value.uint32, start); + fprintf(stdout, "\t%#8.8x\n", value.uint32); + break; + + case SDP_DATA_UUID128: + SDP_GET_UUID128(&value.int128, start); + fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n", + ntohl(*(uint32_t *)&value.int128.b[0]), + ntohs(*(uint16_t *)&value.int128.b[4]), + ntohs(*(uint16_t *)&value.int128.b[6]), + ntohs(*(uint16_t *)&value.int128.b[8]), + ntohs(*(uint16_t *)&value.int128.b[10]), + ntohl(*(uint32_t *)&value.int128.b[12])); + break; + + default: + fprintf(stderr, "Invalid Protocol Descriptor. " \ + "Not a UUID, type=%#x\n", type); + return; + /* NOT REACHED */ + } + + /* Protocol specific parameters */ + for (param = 1; start < end; param ++) { + fprintf(stdout, "\t\tProtocol specific parameter #%d: ", param); + + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_NIL: + fprintf(stdout, "nil\n"); + break; + + case SDP_DATA_UINT8: + case SDP_DATA_INT8: + case SDP_DATA_BOOL: + SDP_GET8(value.uint8, start); + fprintf(stdout, "u/int8/bool %u\n", value.uint8); + break; + + case SDP_DATA_UINT16: + case SDP_DATA_INT16: + case SDP_DATA_UUID16: + SDP_GET16(value.uint16, start); + fprintf(stdout, "u/int/uuid16 %u\n", value.uint16); + break; + + case SDP_DATA_UINT32: + case SDP_DATA_INT32: + case SDP_DATA_UUID32: + SDP_GET32(value.uint32, start); + fprintf(stdout, "u/int/uuid32 %u\n", value.uint32); + break; + + case SDP_DATA_UINT64: + case SDP_DATA_INT64: + SDP_GET64(value.uint64, start); + fprintf(stdout, "u/int64 %ju\n", value.uint64); + break; + + case SDP_DATA_UINT128: + case SDP_DATA_INT128: + SDP_GET128(&value.int128, start); + fprintf(stdout, "u/int128 %#8.8x%8.8x%8.8x%8.8x\n", + *(uint32_t *)&value.int128.b[0], + *(uint32_t *)&value.int128.b[4], + *(uint32_t *)&value.int128.b[8], + *(uint32_t *)&value.int128.b[12]); + break; + + case SDP_DATA_UUID128: + SDP_GET_UUID128(&value.int128, start); + fprintf(stdout, "uuid128 %#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n", + ntohl(*(uint32_t *)&value.int128.b[0]), + ntohs(*(uint16_t *)&value.int128.b[4]), + ntohs(*(uint16_t *)&value.int128.b[6]), + ntohs(*(uint16_t *)&value.int128.b[8]), + ntohs(*(uint16_t *)&value.int128.b[10]), + ntohl(*(uint32_t *)&value.int128.b[12])); + break; + + case SDP_DATA_STR8: + case SDP_DATA_URL8: + SDP_GET8(len, start); + for (; start < end && len > 0; start ++, len --) + fprintf(stdout, "%c", *start); + fprintf(stdout, "\n"); + break; + + case SDP_DATA_STR16: + case SDP_DATA_URL16: + SDP_GET16(len, start); + for (; start < end && len > 0; start ++, len --) + fprintf(stdout, "%c", *start); + fprintf(stdout, "\n"); + break; + + case SDP_DATA_STR32: + case SDP_DATA_URL32: + SDP_GET32(len, start); + for (; start < end && len > 0; start ++, len --) + fprintf(stdout, "%c", *start); + fprintf(stdout, "\n"); + break; + + case SDP_DATA_SEQ8: + case SDP_DATA_ALT8: + SDP_GET8(len, start); + for (; start < end && len > 0; start ++, len --) + fprintf(stdout, "%#2.2x ", *start); + fprintf(stdout, "\n"); + break; + + case SDP_DATA_SEQ16: + case SDP_DATA_ALT16: + SDP_GET16(len, start); + for (; start < end && len > 0; start ++, len --) + fprintf(stdout, "%#2.2x ", *start); + fprintf(stdout, "\n"); + break; + + case SDP_DATA_SEQ32: + case SDP_DATA_ALT32: + SDP_GET32(len, start); + for (; start < end && len > 0; start ++, len --) + fprintf(stdout, "%#2.2x ", *start); + fprintf(stdout, "\n"); + break; + + default: + fprintf(stderr, "Invalid Protocol Descriptor. " \ + "Unknown data type: %#02x\n", type); + return; + /* NOT REACHED */ + } + } +} /* print_protocol_descriptor */ + +static void +print_protocol_descriptor_list(uint8_t const *start, uint8_t const *end) +{ + uint32_t type, len; + + if (end - start < 2) { + fprintf(stderr, "Invalid Protocol Descriptor List. " \ + "Too short, len=%zd\n", end - start); + return; + } + + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + fprintf(stderr, "Invalid Protocol Descriptor List. " \ + "Not a sequence, type=%#x\n", type); + return; + /* NOT REACHED */ + } + + if (len > (end - start)) { + fprintf(stderr, "Invalid Protocol Descriptor List. " \ + "Too long, len=%d\n", len); + return; + } + + while (start < end) { + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + fprintf(stderr, "Invalid Protocol Descriptor List. " \ + "Not a sequence, type=%#x\n", type); + return; + /* NOT REACHED */ + } + + if (len > (end - start)) { + fprintf(stderr, "Invalid Protocol Descriptor List. " \ + "Too long, len=%d\n", len); + return; + } + + print_protocol_descriptor(start, start + len); + start += len; + } +} /* print_protocol_descriptor_list */ + +/* + * Print Bluetooth Profile Descriptor List + * + * The BluetoothProfileDescriptorList attribute consists of a data element + * sequence in which each element is a profile descriptor that contains + * information about a Bluetooth profile to which the service represented by + * this service record conforms. Each profile descriptor is a data element + * sequence whose first element is the UUID assigned to the profile and whose + * second element is a 16-bit profile version number. Each version of a profile + * is assigned a 16-bit unsigned integer profile version number, which consists + * of two 8-bit fields. The higher-order 8 bits contain the major version + * number field and the lower-order 8 bits contain the minor version number + * field. + */ + +static void +print_bluetooth_profile_descriptor_list(uint8_t const *start, uint8_t const *end) +{ + uint32_t type, len, value; + + if (end - start < 2) { + fprintf(stderr, "Invalid Bluetooth Profile Descriptor List. " \ + "Too short, len=%zd\n", end - start); + return; + } + + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + fprintf(stderr, "Invalid Bluetooth Profile Descriptor List. " \ + "Not a sequence, type=%#x\n", type); + return; + /* NOT REACHED */ + } + + if (len > (end - start)) { + fprintf(stderr, "Invalid Bluetooth Profile Descriptor List. " \ + "Too long, len=%d\n", len); + return; + } + + while (start < end) { + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(len, start); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(len, start); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(len, start); + break; + + default: + fprintf(stderr, "Invalid Bluetooth Profile " \ + "Descriptor List. " \ + "Not a sequence, type=%#x\n", type); + return; + /* NOT REACHED */ + } + + if (len > (end - start)) { + fprintf(stderr, "Invalid Bluetooth Profile " \ + "Descriptor List. " \ + "Too long, len=%d\n", len); + return; + } + + /* Get UUID */ + SDP_GET8(type, start); + switch (type) { + case SDP_DATA_UUID16: + SDP_GET16(value, start); + fprintf(stdout, "\t%s (%#4.4x) ", + sdp_uuid2desc(value), value); + break; + + case SDP_DATA_UUID32: + SDP_GET32(value, start); + fprintf(stdout, "\t%#8.8x ", value); + break; + + case SDP_DATA_UUID128: { + int128_t uuid; + + SDP_GET_UUID128(&uuid, start); + fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x ", + ntohl(*(uint32_t *)&uuid.b[0]), + ntohs(*(uint16_t *)&uuid.b[4]), + ntohs(*(uint16_t *)&uuid.b[6]), + ntohs(*(uint16_t *)&uuid.b[8]), + ntohs(*(uint16_t *)&uuid.b[10]), + ntohl(*(uint32_t *)&uuid.b[12])); + } break; + + default: + fprintf(stderr, "Invalid Bluetooth Profile " \ + "Descriptor List. " \ + "Not a UUID, type=%#x\n", type); + return; + /* NOT REACHED */ + } + + /* Get version */ + SDP_GET8(type, start); + if (type != SDP_DATA_UINT16) { + fprintf(stderr, "Invalid Bluetooth Profile " \ + "Descriptor List. " \ + "Invalid version type=%#x\n", type); + return; + } + + SDP_GET16(value, start); + fprintf(stdout, "ver. %d.%d\n", + (value >> 8) & 0xff, value & 0xff); + } +} /* print_bluetooth_profile_descriptor_list */ + +/* Perform SDP search command */ +static int +do_sdp_search(void *xs, int argc, char **argv) +{ + char *ep = NULL; + int32_t n, type, value; + uint16_t service; + + /* Parse command line arguments */ + switch (argc) { + case 1: + n = strtoul(argv[0], &ep, 16); + if (*ep != 0) { + switch (tolower(argv[0][0])) { + case 'c': /* CIP/CTP */ + switch (tolower(argv[0][1])) { + case 'i': + service = SDP_SERVICE_CLASS_COMMON_ISDN_ACCESS; + break; + + case 't': + service = SDP_SERVICE_CLASS_CORDLESS_TELEPHONY; + break; + + default: + return (USAGE); + /* NOT REACHED */ + } + break; + + case 'd': /* DialUp Networking */ + service = SDP_SERVICE_CLASS_DIALUP_NETWORKING; + break; + + case 'f': /* Fax/OBEX File Transfer */ + switch (tolower(argv[0][1])) { + case 'a': + service = SDP_SERVICE_CLASS_FAX; + break; + + case 't': + service = SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER; + break; + + default: + return (USAGE); + /* NOT REACHED */ + } + break; + + case 'g': /* GN */ + service = SDP_SERVICE_CLASS_GN; + break; + + case 'h': /* Headset/HID */ + switch (tolower(argv[0][1])) { + case 'i': + service = SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE; + break; + + case 's': + service = SDP_SERVICE_CLASS_HEADSET; + break; + + default: + return (USAGE); + /* NOT REACHED */ + } + break; + + case 'l': /* LAN Access Using PPP */ + service = SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP; + break; + + case 'n': /* NAP */ + service = SDP_SERVICE_CLASS_NAP; + break; + + case 'o': /* OBEX Object Push */ + service = SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH; + break; + + case 's': /* Serial Port */ + service = SDP_SERVICE_CLASS_SERIAL_PORT; + break; + + default: + return (USAGE); + /* NOT REACHED */ + } + } else + service = (uint16_t) n; + break; + + default: + return (USAGE); + } + + /* Initialize attribute values array */ + for (n = 0; n < values_len; n ++) { + values[n].flags = SDP_ATTR_INVALID; + values[n].attr = 0; + values[n].vlen = BSIZE; + values[n].value = buffer[n]; + } + + /* Do SDP Service Search Attribute Request */ + n = sdp_search(xs, 1, &service, attrs_len, attrs, values_len, values); + if (n != 0) + return (ERROR); + + /* Print attributes values */ + for (n = 0; n < values_len; n ++) { + if (values[n].flags != SDP_ATTR_OK) + break; + + switch (values[n].attr) { + case SDP_ATTR_SERVICE_RECORD_HANDLE: + fprintf(stdout, "\n"); + if (values[n].vlen == 5) { + SDP_GET8(type, values[n].value); + if (type == SDP_DATA_UINT32) { + SDP_GET32(value, values[n].value); + fprintf(stdout, "Record Handle: " \ + "%#8.8x\n", value); + } else + fprintf(stderr, "Invalid type=%#x " \ + "Record Handle " \ + "attribute!\n", type); + } else + fprintf(stderr, "Invalid size=%d for Record " \ + "Handle attribute\n", + values[n].vlen); + break; + + case SDP_ATTR_SERVICE_CLASS_ID_LIST: + fprintf(stdout, "Service Class ID List:\n"); + print_service_class_id_list(values[n].value, + values[n].value + values[n].vlen); + break; + + case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST: + fprintf(stdout, "Protocol Descriptor List:\n"); + print_protocol_descriptor_list(values[n].value, + values[n].value + values[n].vlen); + break; + + case SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST: + fprintf(stdout, "Bluetooth Profile Descriptor List:\n"); + print_bluetooth_profile_descriptor_list(values[n].value, + values[n].value + values[n].vlen); + break; + + default: + fprintf(stderr, "Unexpected attribute ID=%#4.4x\n", + values[n].attr); + break; + } + } + + return (OK); +} /* do_sdp_search */ + +/* Perform SDP browse command */ +static int +do_sdp_browse(void *xs, int argc, char **argv) +{ +#undef _STR +#undef STR +#define _STR(x) #x +#define STR(x) _STR(x) + + static char const * const av[] = { + STR(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP), + NULL + }; + + switch (argc) { + case 0: + argc = 1; + argv = (char **) av; + /* FALL THROUGH */ + case 1: + return (do_sdp_search(xs, argc, argv)); + } + + return (USAGE); +} /* do_sdp_browse */ + +/* List of SDP commands */ +struct sdp_command sdp_commands[] = { +{ +"Browse [<Group>]", +"Browse for services. The <Group> parameter is a 16-bit UUID of the group\n" \ +"to browse. If omitted <Group> is set to Public Browse Group.\n\n" \ +"\t<Group> - xxxx; 16-bit UUID of the group to browse\n", +do_sdp_browse +}, +{ +"Search <Service>", +"Search for the <Service>. The <Service> parameter is a 16-bit UUID of the\n" \ +"service to search for. For some services it is possible to use service name\n"\ +"instead of service UUID\n\n" \ +"\t<Service> - xxxx; 16-bit UUID of the service to search for\n\n" \ +"\tKnown service names\n" \ +"\t===================\n" \ +"\tCIP - Common ISDN Access\n" \ +"\tCTP - Cordless Telephony\n" \ +"\tDUN - DialUp Networking\n" \ +"\tFAX - Fax\n" \ +"\tFTRN - OBEX File Transfer\n" \ +"\tGN - GN\n" \ +"\tHID - Human Interface Device\n" \ +"\tHSET - Headset\n" \ +"\tLAN - LAN Access Using PPP\n" \ +"\tNAP - Network Access Point\n" \ +"\tOPUSH - OBEX Object Push\n" \ +"\tSP - Serial Port\n", +do_sdp_search +}, +{ NULL, NULL, NULL } +}; + diff --git a/usr.sbin/bluetooth/sdpd/Makefile b/usr.sbin/bluetooth/sdpd/Makefile new file mode 100644 index 000000000000..aabb16d4c3ca --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/Makefile @@ -0,0 +1,14 @@ +# $Id: Makefile,v 1.1 2004/01/20 21:27:55 max Exp $ + +PACKAGE= bluetooth +PROG= sdpd +MAN= sdpd.8 +SRCS= audio_sink.c audio_source.c \ + bgd.c dun.c ftrn.c gn.c irmc.c irmc_command.c lan.c log.c \ + main.c nap.c opush.c panu.c profile.c provider.c sar.c scr.c \ + sd.c server.c sp.c srr.c ssar.c ssr.c sur.c uuid.c + +CFLAGS+= -I${.CURDIR} +WARNS?= 2 + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/sdpd/Makefile.depend b/usr.sbin/bluetooth/sdpd/Makefile.depend new file mode 100644 index 000000000000..de7bb33fd2e5 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/Makefile.depend @@ -0,0 +1,18 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/arpa \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + lib/libsdp \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/bluetooth/sdpd/audio_sink.c b/usr.sbin/bluetooth/sdpd/audio_sink.c new file mode 100644 index 000000000000..3b631376d02a --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/audio_sink.c @@ -0,0 +1,186 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Hans Petter Selasky <hselasky@freebsd.org> + * 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/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +audio_sink_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + static const uint16_t service_classes[] = { + SDP_SERVICE_CLASS_AUDIO_SINK, + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *)service_classes, + sizeof(service_classes))); +} + +static int32_t +audio_sink_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_audio_sink_profile_p audio_sink = (sdp_audio_sink_profile_p) provider->data; + + if (buf + 18 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(16, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(6, buf); + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(audio_sink->psm, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(6, buf); + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_AVDTP, buf); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(audio_sink->protover, buf); + + return (18); +} + +static int32_t +audio_sink_profile_create_browse_group_list( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + + if (buf + 5 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP, buf); + + return (5); +} + +static int32_t +audio_sink_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + static const uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_ADVANCED_AUDIO_DISTRIBUTION, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *)profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +audio_sink_profile_create_service_name( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + static const char service_name[] = "Audio SNK"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *)service_name, strlen(service_name))); +} + +static int32_t +audio_sink_create_supported_features( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_audio_sink_profile_p audio_sink = (sdp_audio_sink_profile_p) provider->data; + + if (buf + 3 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(audio_sink->features, buf); + + return (3); +} + +static int32_t +audio_sink_profile_valid(uint8_t const *data, uint32_t datalen) +{ + + if (datalen < sizeof(struct sdp_audio_sink_profile)) + return (0); + return (1); +} + +static const attr_t audio_sink_profile_attrs[] = { + {SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle}, + {SDP_ATTR_SERVICE_CLASS_ID_LIST, + audio_sink_profile_create_service_class_id_list}, + {SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + audio_sink_profile_create_protocol_descriptor_list}, + {SDP_ATTR_BROWSE_GROUP_LIST, + audio_sink_profile_create_browse_group_list}, + {SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list}, + {SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + audio_sink_profile_create_bluetooth_profile_descriptor_list}, + {SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + audio_sink_profile_create_service_name}, + {SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_PROVIDER_NAME_OFFSET, + common_profile_create_service_provider_name}, + {SDP_ATTR_SUPPORTED_FEATURES, + audio_sink_create_supported_features}, + {} /* end entry */ +}; + +profile_t audio_sink_profile_descriptor = { + SDP_SERVICE_CLASS_AUDIO_SINK, + sizeof(sdp_audio_sink_profile_t), + audio_sink_profile_valid, + (attr_t const *const)&audio_sink_profile_attrs +}; diff --git a/usr.sbin/bluetooth/sdpd/audio_source.c b/usr.sbin/bluetooth/sdpd/audio_source.c new file mode 100644 index 000000000000..be827527732d --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/audio_source.c @@ -0,0 +1,186 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Hans Petter Selasky <hselasky@freebsd.org> + * 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/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +audio_source_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + static const uint16_t service_classes[] = { + SDP_SERVICE_CLASS_AUDIO_SOURCE, + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *)service_classes, + sizeof(service_classes))); +} + +static int32_t +audio_source_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_audio_source_profile_p audio_source = (sdp_audio_source_profile_p) provider->data; + + if (buf + 18 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(16, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(6, buf); + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(audio_source->psm, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(6, buf); + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_AVDTP, buf); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(audio_source->protover, buf); + + return (18); +} + +static int32_t +audio_source_profile_create_browse_group_list( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + + if (buf + 5 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP, buf); + + return (5); +} + +static int32_t +audio_source_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + static const uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_ADVANCED_AUDIO_DISTRIBUTION, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *)profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +audio_source_profile_create_service_name( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + static const char service_name[] = "Audio SRC"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *)service_name, strlen(service_name))); +} + +static int32_t +audio_source_create_supported_features( + uint8_t *buf, uint8_t const *const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_audio_source_profile_p audio_source = (sdp_audio_source_profile_p) provider->data; + + if (buf + 3 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(audio_source->features, buf); + + return (3); +} + +static int32_t +audio_source_profile_valid(uint8_t const *data, uint32_t datalen) +{ + + if (datalen < sizeof(struct sdp_audio_source_profile)) + return (0); + return (1); +} + +static const attr_t audio_source_profile_attrs[] = { + {SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle}, + {SDP_ATTR_SERVICE_CLASS_ID_LIST, + audio_source_profile_create_service_class_id_list}, + {SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + audio_source_profile_create_protocol_descriptor_list}, + {SDP_ATTR_BROWSE_GROUP_LIST, + audio_source_profile_create_browse_group_list}, + {SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list}, + {SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + audio_source_profile_create_bluetooth_profile_descriptor_list}, + {SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + audio_source_profile_create_service_name}, + {SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_PROVIDER_NAME_OFFSET, + common_profile_create_service_provider_name}, + {SDP_ATTR_SUPPORTED_FEATURES, + audio_source_create_supported_features}, + {} /* end entry */ +}; + +profile_t audio_source_profile_descriptor = { + SDP_SERVICE_CLASS_AUDIO_SOURCE, + sizeof(sdp_audio_source_profile_t), + audio_source_profile_valid, + (attr_t const *const)&audio_source_profile_attrs +}; diff --git a/usr.sbin/bluetooth/sdpd/bgd.c b/usr.sbin/bluetooth/sdpd/bgd.c new file mode 100644 index 000000000000..628389efc8e1 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/bgd.c @@ -0,0 +1,103 @@ +/*- + * bgd.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: bgd.c,v 1.4 2004/01/13 01:54:39 max Exp $ + */ +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" + +static int32_t +bgd_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_BROWSE_GROUP_DESCRIPTOR + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +bgd_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "Public Browse Group Root"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +bgd_profile_create_group_id( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (buf + 3 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP, buf); + + return (3); +} + +static attr_t bgd_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + bgd_profile_create_service_class_id_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + bgd_profile_create_service_name }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET, + bgd_profile_create_service_name }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_PROVIDER_NAME_OFFSET, + common_profile_create_service_provider_name }, + { SDP_ATTR_GROUP_ID, + bgd_profile_create_group_id }, + { 0, NULL } /* end entry */ +}; + +profile_t bgd_profile_descriptor = { + SDP_SERVICE_CLASS_BROWSE_GROUP_DESCRIPTOR, + 0, + (profile_data_valid_p) NULL, + (attr_t const * const) &bgd_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/dun.c b/usr.sbin/bluetooth/sdpd/dun.c new file mode 100644 index 000000000000..3f4159926275 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/dun.c @@ -0,0 +1,138 @@ +/*- + * dun.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: dun.c,v 1.5 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +dun_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_DIALUP_NETWORKING + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +dun_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_DIALUP_NETWORKING, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +dun_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "DialUp networking"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +dun_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_dun_profile_p dun = (sdp_dun_profile_p) provider->data; + + return (rfcomm_profile_create_protocol_descriptor_list( + buf, eob, + (uint8_t const *) &dun->server_channel, 1)); +} + +static int32_t +dun_profile_create_audio_feedback_support( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_dun_profile_p dun = (sdp_dun_profile_p) provider->data; + + if (buf + 2 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_BOOL, buf); + SDP_PUT8(dun->audio_feedback_support, buf); + + return (2); +} + +static attr_t dun_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + dun_profile_create_service_class_id_list }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + dun_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + dun_profile_create_service_name }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + dun_profile_create_protocol_descriptor_list }, + { SDP_ATTR_AUDIO_FEEDBACK_SUPPORT, + dun_profile_create_audio_feedback_support }, + { 0, NULL } /* end entry */ +}; + +profile_t dun_profile_descriptor = { + SDP_SERVICE_CLASS_DIALUP_NETWORKING, + sizeof(sdp_dun_profile_t), + common_profile_server_channel_valid, + (attr_t const * const) &dun_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/ftrn.c b/usr.sbin/bluetooth/sdpd/ftrn.c new file mode 100644 index 000000000000..32b4912f25ed --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/ftrn.c @@ -0,0 +1,119 @@ +/*- + * ftrn.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: ftrn.c,v 1.5 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +ftrn_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +ftrn_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +ftrn_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "OBEX File Transfer"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +ftrn_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_ftrn_profile_p ftrn = (sdp_ftrn_profile_p) provider->data; + + return (obex_profile_create_protocol_descriptor_list( + buf, eob, + (uint8_t const *) &ftrn->server_channel, 1)); +} + +static attr_t ftrn_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + ftrn_profile_create_service_class_id_list }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + ftrn_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + ftrn_profile_create_service_name }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + ftrn_profile_create_protocol_descriptor_list }, + { 0, NULL } /* end entry */ +}; + +profile_t ftrn_profile_descriptor = { + SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER, + sizeof(sdp_ftrn_profile_t), + common_profile_server_channel_valid, + (attr_t const * const) &ftrn_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/gn.c b/usr.sbin/bluetooth/sdpd/gn.c new file mode 100644 index 000000000000..17879c4fb193 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/gn.c @@ -0,0 +1,174 @@ +/* + * gn.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 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. + * + * $Id: gn.c,v 1.1 2008/03/11 00:02:42 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +gn_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_GN + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +gn_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_GN, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +gn_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "Group Ad-hoc Network"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +gn_profile_create_service_description( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_descr[] = + "Personal Group Ad-hoc Network Service"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_descr, + strlen(service_descr))); +} + +static int32_t +gn_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_gn_profile_p gn = (sdp_gn_profile_p) provider->data; + + return (bnep_profile_create_protocol_descriptor_list( + buf, eob, (uint8_t const *) &gn->psm, + sizeof(gn->psm))); +} + +static int32_t +gn_profile_create_security_description( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_gn_profile_p gn = (sdp_gn_profile_p) provider->data; + + return (bnep_profile_create_security_description(buf, eob, + (uint8_t const *) &gn->security_description, + sizeof(gn->security_description))); +} + +static int32_t +gn_profile_create_service_availability( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_gn_profile_p gn = (sdp_gn_profile_p) provider->data; + + return (common_profile_create_service_availability(buf, eob, + &gn->load_factor, 1)); +} + +static int32_t +gn_profile_data_valid(uint8_t const *data, uint32_t datalen) +{ + sdp_gn_profile_p gn = (sdp_gn_profile_p) data; + + return ((gn->psm == 0)? 0 : 1); +} + +static attr_t gn_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + gn_profile_create_service_class_id_list }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + gn_profile_create_protocol_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_SERVICE_AVAILABILITY, + gn_profile_create_service_availability }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + gn_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + gn_profile_create_service_name }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET, + gn_profile_create_service_description }, + { SDP_ATTR_SECURITY_DESCRIPTION, + gn_profile_create_security_description }, + { 0, NULL } /* end entry */ +}; + +profile_t gn_profile_descriptor = { + SDP_SERVICE_CLASS_GN, + sizeof(sdp_gn_profile_t), + gn_profile_data_valid, + (attr_t const * const) &gn_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/irmc.c b/usr.sbin/bluetooth/sdpd/irmc.c new file mode 100644 index 000000000000..90155e065492 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/irmc.c @@ -0,0 +1,135 @@ +/*- + * irmc.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: irmc.c,v 1.6 2004/01/13 19:31:54 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +irmc_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_IR_MC_SYNC + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +irmc_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_IR_MC_SYNC, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +irmc_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "IrMC Synchronization"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +irmc_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_irmc_profile_p irmc = (sdp_irmc_profile_p) provider->data; + + return (obex_profile_create_protocol_descriptor_list( + buf, eob, + (uint8_t const *) &irmc->server_channel, 1)); +} + +static int32_t +irmc_profile_create_supported_formats_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_irmc_profile_p irmc = (sdp_irmc_profile_p) provider->data; + + return (obex_profile_create_supported_formats_list( + buf, eob, + (uint8_t const *) irmc->supported_formats, + irmc->supported_formats_size)); +} + +static attr_t irmc_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + irmc_profile_create_service_class_id_list }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + irmc_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + irmc_profile_create_service_name }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + irmc_profile_create_protocol_descriptor_list }, + { SDP_ATTR_SUPPORTED_FORMATS_LIST, + irmc_profile_create_supported_formats_list }, + { 0, NULL } /* end entry */ +}; + +profile_t irmc_profile_descriptor = { + SDP_SERVICE_CLASS_IR_MC_SYNC, + sizeof(sdp_irmc_profile_t), + obex_profile_data_valid, + (attr_t const * const) &irmc_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/irmc_command.c b/usr.sbin/bluetooth/sdpd/irmc_command.c new file mode 100644 index 000000000000..e8d0723fa8a4 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/irmc_command.c @@ -0,0 +1,119 @@ +/*- + * irmc_command_command_command.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: irmc_command.c,v 1.5 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +irmc_command_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_IR_MC_SYNC_COMMAND + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +irmc_command_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_IR_MC_SYNC_COMMAND, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +irmc_command_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "Sync Command Service"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +irmc_command_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_irmc_command_profile_p irmc_command = (sdp_irmc_command_profile_p) provider->data; + + return (obex_profile_create_protocol_descriptor_list( + buf, eob, + (uint8_t const *) &irmc_command->server_channel, 1)); +} + +static attr_t irmc_command_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + irmc_command_profile_create_service_class_id_list }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + irmc_command_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + irmc_command_profile_create_service_name }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + irmc_command_profile_create_protocol_descriptor_list }, + { 0, NULL } /* end entry */ +}; + +profile_t irmc_command_profile_descriptor = { + SDP_SERVICE_CLASS_IR_MC_SYNC_COMMAND, + sizeof(sdp_irmc_command_profile_t), + common_profile_server_channel_valid, + (attr_t const * const) &irmc_command_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/lan.c b/usr.sbin/bluetooth/sdpd/lan.c new file mode 100644 index 000000000000..66a71754ef48 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/lan.c @@ -0,0 +1,174 @@ +/*- + * lan.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: lan.c,v 1.5 2004/01/13 01:54:39 max Exp $ + */ + +#include <arpa/inet.h> +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <stdio.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +lan_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +lan_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +lan_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "LAN Access using PPP"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +lan_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_lan_profile_p lan = (sdp_lan_profile_p) provider->data; + + return (rfcomm_profile_create_protocol_descriptor_list( + buf, eob, + (uint8_t const *) &lan->server_channel, 1)); +} + +static int32_t +lan_profile_create_service_availability( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_lan_profile_p lan = (sdp_lan_profile_p) provider->data; + + return (common_profile_create_service_availability(buf, eob, + &lan->load_factor, 1)); +} + +static int32_t +lan_profile_create_ip_subnet( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_lan_profile_p lan = (sdp_lan_profile_p) provider->data; + char net[32]; + int32_t len; + + len = snprintf(net, sizeof(net), "%s/%d", + inet_ntoa(* (struct in_addr *) &lan->ip_subnet), + lan->ip_subnet_radius); + + if (len < 0 || buf + 2 + len > eob) + return (-1); + + SDP_PUT8(SDP_DATA_STR8, buf); + SDP_PUT8(len, buf); + memcpy(buf, net, len); + + return (2 + len); +} + +static int32_t +lan_profile_data_valid(uint8_t const *data, uint32_t datalen) +{ + sdp_lan_profile_p lan = (sdp_lan_profile_p) data; + + if (lan->server_channel < 1 || + lan->server_channel > 30 || + lan->ip_subnet_radius > 32) + return (0); + + return (1); +} + +static attr_t lan_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + lan_profile_create_service_class_id_list }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + lan_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + lan_profile_create_service_name }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + lan_profile_create_protocol_descriptor_list }, + { SDP_ATTR_SERVICE_AVAILABILITY, + lan_profile_create_service_availability }, + { SDP_ATTR_IP_SUBNET, + lan_profile_create_ip_subnet }, + { 0, NULL } /* end entry */ +}; + +profile_t lan_profile_descriptor = { + SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP, + sizeof(sdp_lan_profile_t), + lan_profile_data_valid, + (attr_t const * const) &lan_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/log.c b/usr.sbin/bluetooth/sdpd/log.c new file mode 100644 index 000000000000..f7d4fa350c65 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/log.c @@ -0,0 +1,128 @@ +/*- + * log.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: log.c,v 1.1 2004/01/07 23:15:00 max Exp $ + */ + +#include <sys/types.h> +#include <stdarg.h> +#include <syslog.h> + +void +log_open(char const *prog, int32_t log2stderr) +{ + openlog(prog, LOG_PID|LOG_NDELAY|(log2stderr? LOG_PERROR:0), LOG_USER); +} + +void +log_close(void) +{ + closelog(); +} + +void +log_emerg(char const *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_EMERG, message, ap); + va_end(ap); +} + +void +log_alert(char const *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_ALERT, message, ap); + va_end(ap); +} + +void +log_crit(char const *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_CRIT, message, ap); + va_end(ap); +} + +void +log_err(char const *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_ERR, message, ap); + va_end(ap); +} + +void +log_warning(char const *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_WARNING, message, ap); + va_end(ap); +} + +void +log_notice(char const *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_NOTICE, message, ap); + va_end(ap); +} + +void +log_info(char const *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_INFO, message, ap); + va_end(ap); +} + +void +log_debug(char const *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_DEBUG, message, ap); + va_end(ap); +} + diff --git a/usr.sbin/bluetooth/sdpd/log.h b/usr.sbin/bluetooth/sdpd/log.h new file mode 100644 index 000000000000..e00ed4d8c838 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/log.h @@ -0,0 +1,48 @@ +/*- + * log.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: log.h,v 1.1 2004/01/07 23:15:00 max Exp $ + */ + +#ifndef _LOG_H_ +#define _LOG_H_ + +void log_open (char const *prog, int32_t log2stderr); +void log_close (void); +void log_emerg (char const *message, ...); +void log_alert (char const *message, ...); +void log_crit (char const *message, ...); +void log_err (char const *message, ...); +void log_warning (char const *message, ...); +void log_notice (char const *message, ...); +void log_info (char const *message, ...); +void log_debug (char const *message, ...); + +#endif /* ndef _LOG_H_ */ + diff --git a/usr.sbin/bluetooth/sdpd/main.c b/usr.sbin/bluetooth/sdpd/main.c new file mode 100644 index 000000000000..cc21b314f486 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/main.c @@ -0,0 +1,237 @@ +/*- + * main.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: main.c,v 1.8 2004/01/13 19:31:54 max Exp $ + */ + +#include <sys/select.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <sdp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "log.h" +#include "server.h" + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/queue.h> +#include "profile.h" +#include "provider.h" + +#define SDPD "sdpd" + +static int32_t drop_root (char const *user, char const *group); +static void sighandler (int32_t s); +static void usage (void); + +static int32_t done; + +/* + * Bluetooth Service Discovery Procotol (SDP) daemon + */ + +int +main(int argc, char *argv[]) +{ + server_t server; + char const *control = SDP_LOCAL_PATH; + char const *user = "nobody", *group = "nobody"; + int32_t detach = 1, opt; + struct sigaction sa; + + while ((opt = getopt(argc, argv, "c:dg:hu:")) != -1) { + switch (opt) { + case 'c': /* control */ + control = optarg; + break; + + case 'd': /* do not detach */ + detach = 0; + break; + + case 'g': /* group */ + group = optarg; + break; + + case 'u': /* user */ + user = optarg; + break; + + case 'h': + default: + usage(); + /* NOT REACHED */ + } + } + + log_open(SDPD, !detach); + + /* Become daemon if required */ + if (detach && daemon(0, 0) < 0) { + log_crit("Could not become daemon. %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* Set signal handlers */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sighandler; + + if (sigaction(SIGTERM, &sa, NULL) < 0 || + sigaction(SIGHUP, &sa, NULL) < 0 || + sigaction(SIGINT, &sa, NULL) < 0) { + log_crit("Could not install signal handlers. %s (%d)", + strerror(errno), errno); + exit(1); + } + + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + log_crit("Could not install signal handlers. %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* Initialize server */ + if (server_init(&server, control) < 0) + exit(1); + + if ((user != NULL || group != NULL) && drop_root(user, group) < 0) + exit(1); + + for (done = 0; !done; ) { + if (server_do(&server) != 0) + done ++; + } + + server_shutdown(&server); + log_close(); + + return (0); +} + +/* + * Drop root + */ + +static int32_t +drop_root(char const *user, char const *group) +{ + int uid, gid; + char *ep; + + if ((uid = getuid()) != 0) { + log_notice("Cannot set uid/gid. Not a superuser"); + return (0); /* dont do anything unless root */ + } + + gid = getgid(); + + if (user != NULL) { + uid = strtol(user, &ep, 10); + if (*ep != '\0') { + struct passwd *pwd = getpwnam(user); + + if (pwd == NULL) { + log_err("Could not find passwd entry for " \ + "user %s", user); + return (-1); + } + + uid = pwd->pw_uid; + } + } + + if (group != NULL) { + gid = strtol(group, &ep, 10); + if (*ep != '\0') { + struct group *grp = getgrnam(group); + + if (grp == NULL) { + log_err("Could not find group entry for " \ + "group %s", group); + return (-1); + } + + gid = grp->gr_gid; + } + } + + if (setgid(gid) < 0) { + log_err("Could not setgid(%s). %s (%d)", + group, strerror(errno), errno); + return (-1); + } + + if (setuid(uid) < 0) { + log_err("Could not setuid(%s). %s (%d)", + user, strerror(errno), errno); + return (-1); + } + + return (0); +} + +/* + * Signal handler + */ + +static void +sighandler(int32_t s) +{ + log_notice("Got signal %d. Total number of signals received %d", + s, ++ done); +} + +/* + * Display usage information and quit + */ + +static void +usage(void) +{ + fprintf(stderr, +"Usage: %s [options]\n" \ +"Where options are:\n" \ +" -c specify control socket name (default %s)\n" \ +" -d do not detach (run in foreground)\n" \ +" -g grp specify group\n" \ +" -h display usage and exit\n" \ +" -u usr specify user\n", + SDPD, SDP_LOCAL_PATH); + exit(255); +} + diff --git a/usr.sbin/bluetooth/sdpd/nap.c b/usr.sbin/bluetooth/sdpd/nap.c new file mode 100644 index 000000000000..229600b9c950 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/nap.c @@ -0,0 +1,211 @@ +/* + * nap.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 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. + * + * $Id: nap.c,v 1.1 2008/03/11 00:02:42 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +nap_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_NAP + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +nap_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_NAP, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +nap_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "Network Access Point"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +nap_profile_create_service_description( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_descr[] = "Personal Ad-hoc Network Service"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_descr, + strlen(service_descr))); +} + +static int32_t +nap_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data; + + return (bnep_profile_create_protocol_descriptor_list( + buf, eob, (uint8_t const *) &nap->psm, + sizeof(nap->psm))); +} + +static int32_t +nap_profile_create_security_description( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data; + + return (bnep_profile_create_security_description(buf, eob, + (uint8_t const *) &nap->security_description, + sizeof(nap->security_description))); +} + +static int32_t +nap_profile_create_net_access_type( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data; + + if (buf + 3 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(nap->net_access_type, buf); + + return (3); +} + +static int32_t +nap_profile_create_max_net_access_rate( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data; + + if (buf + 3 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(nap->max_net_access_rate, buf); + + return (3); +} + +static int32_t +nap_profile_create_service_availability( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data; + + return (common_profile_create_service_availability(buf, eob, + &nap->load_factor, 1)); +} + +static int32_t +nap_profile_data_valid(uint8_t const *data, uint32_t datalen) +{ + sdp_nap_profile_p nap = (sdp_nap_profile_p) data; + + return ((nap->psm == 0)? 0 : 1); +} + +static attr_t nap_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + nap_profile_create_service_class_id_list }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + nap_profile_create_protocol_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_SERVICE_AVAILABILITY, + nap_profile_create_service_availability }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + nap_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + nap_profile_create_service_name }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET, + nap_profile_create_service_description }, + { SDP_ATTR_SECURITY_DESCRIPTION, + nap_profile_create_security_description }, + { SDP_ATTR_NET_ACCESS_TYPE, + nap_profile_create_net_access_type }, + { SDP_ATTR_MAX_NET_ACCESS_RATE, + nap_profile_create_max_net_access_rate }, + { 0, NULL } /* end entry */ +}; + +profile_t nap_profile_descriptor = { + SDP_SERVICE_CLASS_NAP, + sizeof(sdp_nap_profile_t), + nap_profile_data_valid, + (attr_t const * const) &nap_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/opush.c b/usr.sbin/bluetooth/sdpd/opush.c new file mode 100644 index 000000000000..6f2464191292 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/opush.c @@ -0,0 +1,135 @@ +/*- + * opush.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: opush.c,v 1.6 2004/01/13 19:31:54 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +opush_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +opush_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +opush_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "OBEX Object Push"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +opush_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_opush_profile_p opush = (sdp_opush_profile_p) provider->data; + + return (obex_profile_create_protocol_descriptor_list( + buf, eob, + (uint8_t const *) &opush->server_channel, 1)); +} + +static int32_t +opush_profile_create_supported_formats_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_opush_profile_p opush = (sdp_opush_profile_p) provider->data; + + return (obex_profile_create_supported_formats_list( + buf, eob, + (uint8_t const *) opush->supported_formats, + opush->supported_formats_size)); +} + +static attr_t opush_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + opush_profile_create_service_class_id_list }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + opush_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + opush_profile_create_service_name }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + opush_profile_create_protocol_descriptor_list }, + { SDP_ATTR_SUPPORTED_FORMATS_LIST, + opush_profile_create_supported_formats_list }, + { 0, NULL } /* end entry */ +}; + +profile_t opush_profile_descriptor = { + SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH, + sizeof(sdp_opush_profile_t), + obex_profile_data_valid, + (attr_t const * const) &opush_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/panu.c b/usr.sbin/bluetooth/sdpd/panu.c new file mode 100644 index 000000000000..bba1dbd45d1d --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/panu.c @@ -0,0 +1,174 @@ +/* + * panu.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 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. + * + * $Id: panu.c,v 1.1 2008/03/11 00:02:42 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +panu_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_PANU + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +panu_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_PANU, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +panu_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "Personal Ad-hoc User Service"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +panu_profile_create_service_description( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_descr[] = + "Personal Ad-hoc User Service"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_descr, + strlen(service_descr))); +} + +static int32_t +panu_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_panu_profile_p panu = (sdp_panu_profile_p) provider->data; + + return (bnep_profile_create_protocol_descriptor_list( + buf, eob, (uint8_t const *) &panu->psm, + sizeof(panu->psm))); +} + +static int32_t +panu_profile_data_valid(uint8_t const *data, uint32_t datalen) +{ + sdp_panu_profile_p panu = (sdp_panu_profile_p) data; + + return ((panu->psm == 0)? 0 : 1); +} + +static int32_t +panu_profile_create_service_availability( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_panu_profile_p panu = (sdp_panu_profile_p) provider->data; + + return (common_profile_create_service_availability( buf, eob, + &panu->load_factor, 1)); +} + +static int32_t +panu_profile_create_security_description( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_panu_profile_p panu = (sdp_panu_profile_p) provider->data; + + return (bnep_profile_create_security_description(buf, eob, + (uint8_t const *) &panu->security_description, + sizeof(panu->security_description))); +} + +static attr_t panu_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + panu_profile_create_service_class_id_list }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + panu_profile_create_protocol_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_SERVICE_AVAILABILITY, + panu_profile_create_service_availability }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + panu_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + panu_profile_create_service_name }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET, + panu_profile_create_service_description }, + { SDP_ATTR_SECURITY_DESCRIPTION, + panu_profile_create_security_description }, + { 0, NULL } /* end entry */ +}; + +profile_t panu_profile_descriptor = { + SDP_SERVICE_CLASS_PANU, + sizeof(sdp_panu_profile_t), + panu_profile_data_valid, + (attr_t const * const) &panu_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/profile.c b/usr.sbin/bluetooth/sdpd/profile.c new file mode 100644 index 000000000000..e6d81f8ef4b1 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/profile.c @@ -0,0 +1,504 @@ +/* + * profile.c + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: profile.c,v 1.6 2004/01/13 19:31:54 max Exp $ + */ + +#include <sys/param.h> +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +/* + * Lookup profile descriptor + */ + +profile_p +profile_get_descriptor(uint16_t uuid) +{ + extern profile_t audio_sink_profile_descriptor; + extern profile_t audio_source_profile_descriptor; + extern profile_t dun_profile_descriptor; + extern profile_t ftrn_profile_descriptor; + extern profile_t irmc_profile_descriptor; + extern profile_t irmc_command_profile_descriptor; + extern profile_t lan_profile_descriptor; + extern profile_t opush_profile_descriptor; + extern profile_t sp_profile_descriptor; + extern profile_t nap_profile_descriptor; + extern profile_t gn_profile_descriptor; + extern profile_t panu_profile_descriptor; + + static const profile_p profiles[] = { + &audio_sink_profile_descriptor, + &audio_source_profile_descriptor, + &dun_profile_descriptor, + &ftrn_profile_descriptor, + &irmc_profile_descriptor, + &irmc_command_profile_descriptor, + &lan_profile_descriptor, + &opush_profile_descriptor, + &sp_profile_descriptor, + &nap_profile_descriptor, + &gn_profile_descriptor, + &panu_profile_descriptor + }; + + int32_t i; + + for (i = 0; i < nitems(profiles); i++) + if (profiles[i]->uuid == uuid) + return (profiles[i]); + + return (NULL); +} + +/* + * Look attribute in the profile descripror + */ + +profile_attr_create_p +profile_get_attr(const profile_p profile, uint16_t attr) +{ + attr_p ad = (attr_p) profile->attrs; + + for (; ad->create != NULL; ad ++) + if (ad->attr == attr) + return (ad->create); + + return (NULL); +} + +/* + * uint32 value32 - 5 bytes + */ + +int32_t +common_profile_create_service_record_handle( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (buf + 5 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UINT32, buf); + SDP_PUT32(((provider_p) data)->handle, buf); + + return (5); +} + +/* + * seq8 len8 - 2 bytes + * uuid16 value16 - 3 bytes + * [ uuid16 value ] + */ + +int32_t +common_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + int32_t len = 3 * (datalen >>= 1); + + if (len <= 0 || len > 0xff || buf + 2 + len > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(len, buf); + + for (; datalen > 0; datalen --) { + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(*((uint16_t const *)data), buf); + data += sizeof(uint16_t); + } + + return (2 + len); +} + +/* + * seq8 len8 - 2 bytes + * seq 8 len8 - 2 bytes + * uuid16 value16 - 3 bytes + * uint16 value16 - 3 bytes + * [ seq 8 len8 + * uuid16 value16 + * uint16 value16 ] + */ + +int32_t +common_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + int32_t len = 8 * (datalen >>= 2); + + if (len <= 0 || len > 0xff || buf + 2 + len > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(len, buf); + + for (; datalen > 0; datalen --) { + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(6, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(*((uint16_t const *)data), buf); + data += sizeof(uint16_t); + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(*((uint16_t const *)data), buf); + data += sizeof(uint16_t); + } + + return (2 + len); +} + +/* + * seq8 len8 - 2 bytes + * uint16 value16 - 3 bytes + * uint16 value16 - 3 bytes + * uint16 value16 - 3 bytes + */ + +int32_t +common_profile_create_language_base_attribute_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (buf + 11 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(9, buf); + + /* + * Language code per ISO 639:1988. Use "en". + */ + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(((0x65 << 8) | 0x6e), buf); + + /* + * Encoding. Recommended is UTF-8. ISO639 UTF-8 MIBenum is 106 + * (http://www.iana.org/assignments/character-sets) + */ + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(106, buf); + + /* + * Offset (Primary Language Base is 0x100) + */ + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID, buf); + + return (11); +} + +/* + * Common provider name is "FreeBSD" + */ + +int32_t +common_profile_create_service_provider_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + char provider_name[] = "FreeBSD"; + + return (common_profile_create_string8(buf, eob, + (uint8_t const *) provider_name, + strlen(provider_name))); +} + +/* + * str8 len8 string + */ + +int32_t +common_profile_create_string8( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (datalen == 0 || datalen > 0xff || buf + 2 + datalen > eob) + return (-1); + + SDP_PUT8(SDP_DATA_STR8, buf); + SDP_PUT8(datalen, buf); + memcpy(buf, data, datalen); + + return (2 + datalen); +} + +/* + * Service Availability + */ + +int32_t +common_profile_create_service_availability( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (datalen != 1 || buf + 2 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UINT8, buf); + SDP_PUT8(data[0], buf); + + return (2); +} + +/* + * seq8 len8 - 2 bytes + * seq8 len8 - 2 bytes + * uuid16 value16 - 3 bytes + * seq8 len8 - 2 bytes + * uuid16 value16 - 3 bytes + * uint8 value8 - 2 bytes + */ + +int32_t +rfcomm_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (datalen != 1 || buf + 14 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(12, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(5, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_RFCOMM, buf); + SDP_PUT8(SDP_DATA_UINT8, buf); + SDP_PUT8(*data, buf); + + return (14); +} + +/* + * seq8 len8 - 2 bytes + * seq8 len8 - 2 bytes + * uuid16 value16 - 3 bytes + * seq8 len8 - 2 bytes + * uuid16 value16 - 3 bytes + * uint8 value8 - 2 bytes + * seq8 len8 - 2 bytes + * uuid16 value16 - 3 bytes + */ + +int32_t +obex_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (datalen != 1 || buf + 19 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(17, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(5, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_RFCOMM, buf); + SDP_PUT8(SDP_DATA_UINT8, buf); + SDP_PUT8(*data, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_OBEX, buf); + + return (19); +} + +/* + * seq8 len8 + * uint8 value8 - bytes + * [ uint8 value 8 ] + */ + +int32_t +obex_profile_create_supported_formats_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + int32_t len = 2 * datalen; + + if (len <= 0 || len > 0xff || buf + 2 + len > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(len, buf); + + for (; datalen > 0; datalen --) { + SDP_PUT8(SDP_DATA_UINT8, buf); + SDP_PUT8(*data++, buf); + } + + return (2 + len); +} + +/* + * do not check anything + */ + +int32_t +common_profile_always_valid(uint8_t const *data, uint32_t datalen) +{ + return (1); +} + +/* + * verify server channel number (the first byte in the data) + */ + +int32_t +common_profile_server_channel_valid(uint8_t const *data, uint32_t datalen) +{ + if (data[0] < 1 || data[0] > 30) + return (0); + + return (1); +} + +/* + * verify server channel number and supported_formats_size + * sdp_opush_profile and sdp_irmc_profile + */ + +int32_t +obex_profile_data_valid(uint8_t const *data, uint32_t datalen) +{ + sdp_opush_profile_p opush = (sdp_opush_profile_p) data; + + if (opush->server_channel < 1 || + opush->server_channel > 30 || + opush->supported_formats_size == 0 || + opush->supported_formats_size > sizeof(opush->supported_formats)) + return (0); + + return (1); +} + +/* + * BNEP protocol descriptor + */ + +int32_t +bnep_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + /* supported protocol types */ + uint16_t ptype[] = { + 0x0800, /* IPv4 */ + 0x0806, /* ARP */ +#ifdef INET6 + 0x86dd, /* IPv6 */ +#endif + }; + + uint16_t i, psm, version = 0x0100, + nptypes = nitems(ptype), + nptypes_size = nptypes * 3; + + if (datalen != 2 || 18 + nptypes_size > 255 || + buf + 20 + nptypes_size > eob) + return (-1); + + memcpy(&psm, data, sizeof(psm)); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(18 + nptypes_size, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(6, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf); + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(psm, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(8 + nptypes_size, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_BNEP, buf); + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(version, buf); + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(nptypes_size, buf); + for (i = 0; i < nptypes; i ++) { + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(ptype[i], buf); + } + + return (20 + nptypes_size); +} + +/* + * BNEP security description + */ + +int32_t +bnep_profile_create_security_description( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + uint16_t security_descr; + + if (datalen != 2 || buf + 3 > eob) + return (-1); + + memcpy(&security_descr, data, sizeof(security_descr)); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(security_descr, buf); + + return (3); +} + diff --git a/usr.sbin/bluetooth/sdpd/profile.h b/usr.sbin/bluetooth/sdpd/profile.h new file mode 100644 index 000000000000..7184c0fd742e --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/profile.h @@ -0,0 +1,97 @@ +/* + * profile.h + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: profile.h,v 1.6 2004/01/13 19:31:54 max Exp $ + */ + +#ifndef _PROFILE_H_ +#define _PROFILE_H_ + +/* + * Attribute descriptor + */ + +typedef int32_t (profile_attr_create_t)( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen); +typedef profile_attr_create_t * profile_attr_create_p; + +typedef int32_t (profile_data_valid_t)( + uint8_t const *data, uint32_t datalen); +typedef profile_data_valid_t * profile_data_valid_p; + +struct attr +{ + uint16_t attr; /* attribute id */ + profile_attr_create_p create; /* create attr value */ +}; + +typedef struct attr attr_t; +typedef struct attr * attr_p; + +/* + * Profile descriptor + */ + + +struct profile +{ + uint16_t uuid; /* profile uuid */ + uint16_t dsize; /* profile data size */ + profile_data_valid_p valid; /* profile data validator */ + attr_t const * const attrs; /* supported attributes */ +}; + +typedef struct profile profile_t; +typedef struct profile *profile_p; + +profile_p profile_get_descriptor(uint16_t uuid); +profile_attr_create_p profile_get_attr(const profile_p profile, uint16_t attr); + +profile_attr_create_t common_profile_create_service_record_handle; +profile_attr_create_t common_profile_create_service_class_id_list; +profile_attr_create_t common_profile_create_bluetooth_profile_descriptor_list; +profile_attr_create_t common_profile_create_language_base_attribute_id_list; +profile_attr_create_t common_profile_create_service_provider_name; +profile_attr_create_t common_profile_create_string8; +profile_attr_create_t common_profile_create_service_availability; +profile_attr_create_t rfcomm_profile_create_protocol_descriptor_list; +profile_attr_create_t obex_profile_create_protocol_descriptor_list; +profile_attr_create_t obex_profile_create_supported_formats_list; +profile_attr_create_t bnep_profile_create_protocol_descriptor_list; +profile_attr_create_t bnep_profile_create_security_description; + +profile_data_valid_t common_profile_always_valid; +profile_data_valid_t common_profile_server_channel_valid; +profile_data_valid_t obex_profile_data_valid; + +#endif /* ndef _PROFILE_H_ */ + diff --git a/usr.sbin/bluetooth/sdpd/provider.c b/usr.sbin/bluetooth/sdpd/provider.c new file mode 100644 index 000000000000..7ac800a85006 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/provider.c @@ -0,0 +1,198 @@ +/*- + * provider.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: provider.c,v 1.5 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <string.h> +#include <stdlib.h> +#include "profile.h" +#include "provider.h" + +static TAILQ_HEAD(, provider) providers = TAILQ_HEAD_INITIALIZER(providers); +static uint32_t change_state = 0; +static uint32_t handle = 0; + +/* + * Register Service Discovery provider. + * Should not be called more the once. + */ + +int32_t +provider_register_sd(int32_t fd) +{ + extern profile_t sd_profile_descriptor; + extern profile_t bgd_profile_descriptor; + + provider_p sd = calloc(1, sizeof(*sd)); + provider_p bgd = calloc(1, sizeof(*bgd)); + + if (sd == NULL || bgd == NULL) { + if (sd != NULL) + free(sd); + + if (bgd != NULL) + free(bgd); + + return (-1); + } + + sd->profile = &sd_profile_descriptor; + bgd->handle = 0; + sd->fd = fd; + TAILQ_INSERT_HEAD(&providers, sd, provider_next); + + bgd->profile = &bgd_profile_descriptor; + bgd->handle = 1; + sd->fd = fd; + TAILQ_INSERT_AFTER(&providers, sd, bgd, provider_next); + + change_state ++; + + return (0); +} + +/* + * Register new provider for a given profile, bdaddr and session. + */ + +provider_p +provider_register(profile_p const profile, bdaddr_p const bdaddr, int32_t fd, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = calloc(1, sizeof(*provider)); + + if (provider != NULL) { + provider->data = malloc(datalen); + if (provider->data != NULL) { + provider->profile = profile; + memcpy(provider->data, data, datalen); + + /* + * Record handles 0x0 and 0x1 are reserved + * for SDP itself + */ + + if (++ handle <= 1) + handle = 2; + + provider->handle = handle; + + memcpy(&provider->bdaddr, bdaddr, + sizeof(provider->bdaddr)); + provider->fd = fd; + + TAILQ_INSERT_TAIL(&providers, provider, provider_next); + change_state ++; + } else { + free(provider); + provider = NULL; + } + } + + return (provider); +} + +/* + * Unregister provider + */ + +void +provider_unregister(provider_p provider) +{ + TAILQ_REMOVE(&providers, provider, provider_next); + if (provider->data != NULL) + free(provider->data); + free(provider); + change_state ++; +} + +/* + * Update provider data + */ + +int32_t +provider_update(provider_p provider, uint8_t const *data, uint32_t datalen) +{ + uint8_t *new_data = (uint8_t *) realloc(provider->data, datalen); + + if (new_data == NULL) + return (-1); + + memcpy(new_data, data, datalen); + provider->data = new_data; + + return (0); +} + +/* + * Get a provider for given record handle + */ + +provider_p +provider_by_handle(uint32_t handle) +{ + provider_p provider = NULL; + + TAILQ_FOREACH(provider, &providers, provider_next) + if (provider->handle == handle) + break; + + return (provider); +} + +/* + * Cursor access + */ + +provider_p +provider_get_first(void) +{ + return (TAILQ_FIRST(&providers)); +} + +provider_p +provider_get_next(provider_p provider) +{ + return (TAILQ_NEXT(provider, provider_next)); +} + +/* + * Return change state + */ + +uint32_t +provider_get_change_state(void) +{ + return (change_state); +} + diff --git a/usr.sbin/bluetooth/sdpd/provider.h b/usr.sbin/bluetooth/sdpd/provider.h new file mode 100644 index 000000000000..b4f3bb0a46d0 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/provider.h @@ -0,0 +1,76 @@ +/*- + * provider.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: provider.h,v 1.6 2004/01/13 01:54:39 max Exp $ + */ + +#ifndef _PROVIDER_H_ +#define _PROVIDER_H_ + +/* + * Provider of service + */ + +struct profile; + +struct provider +{ + struct profile *profile; /* profile */ + void *data; /* profile data */ + uint32_t handle; /* record handle */ + bdaddr_t bdaddr; /* provider's BDADDR */ + int32_t fd; /* session descriptor */ + TAILQ_ENTRY(provider) provider_next; /* all providers */ +}; + +typedef struct provider provider_t; +typedef struct provider * provider_p; + +#define provider_match_bdaddr(p, b) \ + (memcmp(b, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0 || \ + memcmp(&(p)->bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0 || \ + memcmp(&(p)->bdaddr, b, sizeof(bdaddr_t)) == 0) + +int32_t provider_register_sd (int32_t fd); +provider_p provider_register (profile_p const profile, + bdaddr_p const bdaddr, + int32_t fd, + uint8_t const *data, + uint32_t datalen); + +void provider_unregister (provider_p provider); +int32_t provider_update (provider_p provider, + uint8_t const *data, + uint32_t datalen); +provider_p provider_by_handle (uint32_t handle); +provider_p provider_get_first (void); +provider_p provider_get_next (provider_p provider); +uint32_t provider_get_change_state (void); + +#endif /* ndef _PROVIDER_H_ */ diff --git a/usr.sbin/bluetooth/sdpd/sar.c b/usr.sbin/bluetooth/sdpd/sar.c new file mode 100644 index 000000000000..68193b567fd3 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/sar.c @@ -0,0 +1,320 @@ +/*- + * sar.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: sar.c,v 1.2 2004/01/08 23:46:51 max Exp $ + */ + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <sdp.h> +#include <stdio.h> /* for NULL */ +#include "profile.h" +#include "provider.h" +#include "server.h" + +/* + * Prepare SDP attr/value pair. Check if profile implements the attribute + * and if so call the attribute value function. + * + * uint16 value16 - 3 bytes (attribute) + * value - N bytes (value) + */ + +static int32_t +server_prepare_attr_value_pair( + provider_p const provider, uint16_t attr, + uint8_t *buf, uint8_t const * const eob) +{ + profile_attr_create_p cf = profile_get_attr(provider->profile, attr); + int32_t len; + + if (cf == NULL) + return (0); /* no attribute */ + + if (buf + 3 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(attr, buf); + + len = cf(buf, eob, (uint8_t const *) provider, sizeof(*provider)); + if (len < 0) + return (-1); + + return (3 + len); +} + +/* + * seq16 value16 - 3 bytes + * attr value - 3+ bytes + * [ attr value ] + */ + +int32_t +server_prepare_attr_list(provider_p const provider, + uint8_t const *req, uint8_t const * const req_end, + uint8_t *rsp, uint8_t const * const rsp_end) +{ + uint8_t *ptr = rsp + 3; + int32_t type, hi, lo, len; + + if (ptr > rsp_end) + return (-1); + + while (req < req_end) { + SDP_GET8(type, req); + + switch (type) { + case SDP_DATA_UINT16: + if (req + 2 > req_end) + return (-1); + + SDP_GET16(lo, req); + hi = lo; + break; + + case SDP_DATA_UINT32: + if (req + 4 > req_end) + return (-1); + + SDP_GET16(lo, req); + SDP_GET16(hi, req); + break; + + default: + return (-1); + /* NOT REACHED */ + } + + for (; lo <= hi; lo ++) { + len = server_prepare_attr_value_pair(provider, lo, ptr, rsp_end); + if (len < 0) + return (-1); + + ptr += len; + } + } + + len = ptr - rsp; /* we put this much bytes in rsp */ + + /* Fix SEQ16 header for the rsp */ + SDP_PUT8(SDP_DATA_SEQ16, rsp); + SDP_PUT16(len - 3, rsp); + + return (len); +} + +/* + * Prepare SDP Service Attribute Response + */ + +int32_t +server_prepare_service_attribute_response(server_p srv, int32_t fd) +{ + uint8_t const *req = srv->req + sizeof(sdp_pdu_t); + uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len; + uint8_t *rsp = srv->fdidx[fd].rsp; + uint8_t const *rsp_end = rsp + NG_L2CAP_MTU_MAXIMUM; + + uint8_t *ptr = NULL; + provider_t *provider = NULL; + uint32_t handle; + int32_t type, rsp_limit, aidlen, cslen, cs; + + /* + * Minimal Service Attribute Request request + * + * value32 - 4 bytes ServiceRecordHandle + * value16 - 2 bytes MaximumAttributeByteCount + * seq8 len8 - 2 bytes + * uint16 value16 - 3 bytes AttributeIDList + * value8 - 1 byte ContinuationState + */ + + if (req_end - req < 12) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get ServiceRecordHandle and MaximumAttributeByteCount */ + SDP_GET32(handle, req); + SDP_GET16(rsp_limit, req); + if (rsp_limit <= 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get size of AttributeIDList */ + aidlen = 0; + SDP_GET8(type, req); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(aidlen, req); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(aidlen, req); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(aidlen, req); + break; + } + if (aidlen <= 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + ptr = (uint8_t *) req + aidlen; + + /* Get ContinuationState */ + if (ptr + 1 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET8(cslen, ptr); + if (cslen != 0) { + if (cslen != 2 || req_end - ptr != 2) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET16(cs, ptr); + } else + cs = 0; + + /* Process the request. First, check continuation state */ + if (srv->fdidx[fd].rsp_cs != cs) + return (SDP_ERROR_CODE_INVALID_CONTINUATION_STATE); + if (srv->fdidx[fd].rsp_size > 0) + return (0); + + /* Lookup record handle */ + if ((provider = provider_by_handle(handle)) == NULL) + return (SDP_ERROR_CODE_INVALID_SERVICE_RECORD_HANDLE); + + /* + * Service Attribute Response format + * + * value16 - 2 bytes AttributeListByteCount (not incl.) + * seq8 len16 - 3 bytes + * attr value - 3+ bytes AttributeList + * [ attr value ] + */ + + cs = server_prepare_attr_list(provider, req, req+aidlen, rsp, rsp_end); + if (cs < 0) + return (SDP_ERROR_CODE_INSUFFICIENT_RESOURCES); + + /* Set reply size (not counting PDU header and continuation state) */ + srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t) - 2; + if (srv->fdidx[fd].rsp_limit > rsp_limit) + srv->fdidx[fd].rsp_limit = rsp_limit; + + srv->fdidx[fd].rsp_size = cs; + srv->fdidx[fd].rsp_cs = 0; + + return (0); +} + +/* + * Send SDP Service [Search] Attribute Response + */ + +int32_t +server_send_service_attribute_response(server_p srv, int32_t fd) +{ + uint8_t *rsp = srv->fdidx[fd].rsp + srv->fdidx[fd].rsp_cs; + uint8_t *rsp_end = srv->fdidx[fd].rsp + srv->fdidx[fd].rsp_size; + + struct iovec iov[4]; + sdp_pdu_t pdu; + uint16_t bcount; + uint8_t cs[3]; + int32_t size; + + /* First update continuation state (assume we will send all data) */ + size = rsp_end - rsp; + srv->fdidx[fd].rsp_cs += size; + + if (size + 1 > srv->fdidx[fd].rsp_limit) { + /* + * We need to split out response. Add 3 more bytes for the + * continuation state and move rsp_end and rsp_cs backwards. + */ + + while ((rsp_end - rsp) + 3 > srv->fdidx[fd].rsp_limit) { + rsp_end --; + srv->fdidx[fd].rsp_cs --; + } + + cs[0] = 2; + cs[1] = srv->fdidx[fd].rsp_cs >> 8; + cs[2] = srv->fdidx[fd].rsp_cs & 0xff; + } else + cs[0] = 0; + + assert(rsp_end >= rsp); + + bcount = rsp_end - rsp; + + if (((sdp_pdu_p)(srv->req))->pid == SDP_PDU_SERVICE_ATTRIBUTE_REQUEST) + pdu.pid = SDP_PDU_SERVICE_ATTRIBUTE_RESPONSE; + else + pdu.pid = SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_RESPONSE; + + pdu.tid = ((sdp_pdu_p)(srv->req))->tid; + pdu.len = htons(sizeof(bcount) + bcount + 1 + cs[0]); + + bcount = htons(bcount); + + iov[0].iov_base = &pdu; + iov[0].iov_len = sizeof(pdu); + + iov[1].iov_base = &bcount; + iov[1].iov_len = sizeof(bcount); + + iov[2].iov_base = rsp; + iov[2].iov_len = rsp_end - rsp; + + iov[3].iov_base = cs; + iov[3].iov_len = 1 + cs[0]; + + do { + size = writev(fd, (struct iovec const *) &iov, nitems(iov)); + } while (size < 0 && errno == EINTR); + + /* Check if we have sent (or failed to sent) last response chunk */ + if (srv->fdidx[fd].rsp_cs == srv->fdidx[fd].rsp_size) { + srv->fdidx[fd].rsp_cs = 0; + srv->fdidx[fd].rsp_size = 0; + srv->fdidx[fd].rsp_limit = 0; + } + + return ((size < 0)? errno : 0); +} + diff --git a/usr.sbin/bluetooth/sdpd/scr.c b/usr.sbin/bluetooth/sdpd/scr.c new file mode 100644 index 000000000000..a5bced947f8e --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/scr.c @@ -0,0 +1,94 @@ +/*- + * scr.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: scr.c,v 1.1 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" +#include "server.h" + +/* + * Prepare Service Change response + */ + +int32_t +server_prepare_service_change_response(server_p srv, int32_t fd) +{ + uint8_t const *req = srv->req + sizeof(sdp_pdu_t); + uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len; + uint8_t *rsp = srv->fdidx[fd].rsp; + + provider_t *provider = NULL; + uint32_t handle; + + /* + * Minimal Service Change Request + * + * value32 - handle 4 bytes + */ + + if (!srv->fdidx[fd].control || + !srv->fdidx[fd].priv || req_end - req < 4) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get handle */ + SDP_GET32(handle, req); + + /* Lookup provider */ + provider = provider_by_handle(handle); + if (provider == NULL || provider->fd != fd) + return (SDP_ERROR_CODE_INVALID_SERVICE_RECORD_HANDLE); + + /* Validate user data */ + if (req_end - req < provider->profile->dsize || + provider->profile->valid == NULL || + (provider->profile->valid)(req, req_end - req) == 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Update provider */ + if (provider_update(provider, req, req_end - req) < 0) + return (SDP_ERROR_CODE_INSUFFICIENT_RESOURCES); + + SDP_PUT16(0, rsp); + + /* Set reply size */ + srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t); + srv->fdidx[fd].rsp_size = rsp - srv->fdidx[fd].rsp; + srv->fdidx[fd].rsp_cs = 0; + + return (0); +} + diff --git a/usr.sbin/bluetooth/sdpd/sd.c b/usr.sbin/bluetooth/sdpd/sd.c new file mode 100644 index 000000000000..cd4f7fa3364b --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/sd.c @@ -0,0 +1,230 @@ +/*- + * sd.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: sd.c,v 1.4 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +sd_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_SERVICE_DISCOVERY_SERVER + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +sd_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_SERVICE_DISCOVERY_SERVER, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +sd_profile_create_service_id( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (buf + 3 > eob) + return (-1); + + /* + * The ServiceID is a UUID that universally and uniquely identifies + * the service instance described by the service record. This service + * attribute is particularly useful if the same service is described + * by service records in more than one SDP server + */ + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_SDP, buf); /* XXX ??? */ + + return (3); +} + +static int32_t +sd_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "Bluetooth service discovery"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +sd_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (buf + 12 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(10, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_UUID_PROTOCOL_SDP, buf); + + return (12); +} + +static int32_t +sd_profile_create_browse_group_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (buf + 5 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + + /* + * The top-level browse group ID, called PublicBrowseRoot and + * representing the root of the browsing hierarchy, has the value + * 00001002-0000-1000-8000-00805F9B34FB (UUID16: 0x1002) from the + * Bluetooth Assigned Numbers document + */ + + SDP_PUT8(SDP_DATA_UUID16, buf); + SDP_PUT16(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP, buf); + + return (5); +} + +static int32_t +sd_profile_create_version_number_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + if (buf + 5 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_SEQ8, buf); + SDP_PUT8(3, buf); + + /* + * The VersionNumberList is a data element sequence in which each + * element of the sequence is a version number supported by the SDP + * server. A version number is a 16-bit unsigned integer consisting + * of two fields. The higher-order 8 bits contain the major version + * number field and the low-order 8 bits contain the minor version + * number field. The initial version of SDP has a major version of + * 1 and a minor version of 0 + */ + + SDP_PUT8(SDP_DATA_UINT16, buf); + SDP_PUT16(0x0100, buf); + + return (5); +} + +static int32_t +sd_profile_create_service_database_state( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + uint32_t change_state = provider_get_change_state(); + + if (buf + 5 > eob) + return (-1); + + SDP_PUT8(SDP_DATA_UINT32, buf); + SDP_PUT32(change_state, buf); + + return (5); +} + +static attr_t sd_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + sd_profile_create_service_class_id_list }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + sd_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_SERVICE_ID, + sd_profile_create_service_id }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + sd_profile_create_service_name }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET, + sd_profile_create_service_name }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_PROVIDER_NAME_OFFSET, + common_profile_create_service_provider_name }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + sd_profile_create_protocol_descriptor_list }, + { SDP_ATTR_BROWSE_GROUP_LIST, + sd_profile_create_browse_group_list }, + { SDP_ATTR_VERSION_NUMBER_LIST, + sd_profile_create_version_number_list }, + { SDP_ATTR_SERVICE_DATABASE_STATE, + sd_profile_create_service_database_state }, + { 0, NULL } /* end entry */ +}; + +profile_t sd_profile_descriptor = { + SDP_SERVICE_CLASS_SERVICE_DISCOVERY_SERVER, + 0, + (profile_data_valid_p) NULL, + (attr_t const * const) &sd_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/sdpd.8 b/usr.sbin/bluetooth/sdpd/sdpd.8 new file mode 100644 index 000000000000..b5915f729e46 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/sdpd.8 @@ -0,0 +1,139 @@ +.\" 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. +.\" +.\" $Id: sdpd.8,v 1.1 2004/01/13 19:31:54 max Exp $ +.\" +.Dd January 13, 2004 +.Dt SDPD 8 +.Os +.Sh NAME +.Nm sdpd +.Nd Bluetooth Service Discovery Protocol daemon +.Sh SYNOPSIS +.Nm +.Op Fl dh +.Op Fl c Ar path +.Op Fl g Ar group +.Op Fl u Ar user +.Sh DESCRIPTION +The +.Nm +daemon keeps track of the Bluetooth services registered on the host +and responds to Service Discovery inquiries from the remote Bluetooth devices. +.Pp +In order to use any service remote Bluetooth device need to send Service +Search and Service Attribute or Service Search Attribute request over +Bluetooth L2CAP connection on SDP PSM (0x0001). +The +.Nm +daemon will try to find matching Service Record in its Service Database +and will send appropriate response back. +The remote device then will process the response, extract all required +information and will make a separate connection in order to use the service. +.Pp +Bluetooth applications, running on the host, register services with +the local +.Nm +daemon. +Operation like service registration, service removal and service change are +performed over the control socket. +It is possible to query entire content of the +.Nm +Service Database with +.Xr sdpcontrol 8 +by issuing +.Cm browse +command on the control socket. +.Pp +The command line options are as follows: +.Bl -tag -width indent +.It Fl d +Do not detach from the controlling terminal. +.It Fl c Ar path +Specify path to the control socket. +The default path is +.Pa /var/run/sdp . +.It Fl g Ar group +Specifies the group the +.Nm +should run as after it initializes. +The value specified may be either a group name or a numeric group ID. +This only works if +.Nm +was started as root. +The default group name is +.Dq Li nobody . +.It Fl h +Display usage message and exit. +.It Fl u Ar user +Specifies the user the +.Nm +should run as after it initializes. +The value specified may be either a user name or a numeric user ID. +This only works if +.Nm +was started as root. +The default user name is +.Dq Li nobody . +.El +.Sh FILES +.Bl -tag -width ".Pa /var/run/sdp" -compact +.It Pa /var/run/sdp +.El +.Sh SEE ALSO +.Xr sdp 3 , +.Xr sdpcontrol 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +.Sh CAVEATS +The +.Nm +daemon +will listen for incoming L2CAP connections on a wildcard BD_ADDR. +.Pp +In case of multiple Bluetooth devices connected to the same host it is +possible to specify which services should be +.Dq bound +to which Bluetooth device. +Such assignment should be done at service registration time. +.Pp +Requests to register, remove or change service can only be made via the +control socket. +The +.Nm +daemon will check peer's credentials and will only accept the request if +the application has the same effective user ID as the +.Dq Li root +user ID. +.Pp +The +.Nm +daemon does not check for duplicated Service Records. +It only performs minimal checking on the service data sent in the Service +Register request. +It is assumed that application must obtain all required resources such +as RFCOMM channels etc., before registering the service. +.Sh BUGS +Most likely. +Please report if found. diff --git a/usr.sbin/bluetooth/sdpd/server.c b/usr.sbin/bluetooth/sdpd/server.c new file mode 100644 index 000000000000..05a4cb5f0236 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/server.c @@ -0,0 +1,589 @@ +/*- + * server.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: server.c,v 1.6 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/param.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/queue.h> +#include <sys/ucred.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <pwd.h> +#include <sdp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "log.h" +#include "profile.h" +#include "provider.h" +#include "server.h" + +static void server_accept_client (server_p srv, int32_t fd); +static int32_t server_process_request (server_p srv, int32_t fd); +static int32_t server_send_error_response (server_p srv, int32_t fd, + uint16_t error); +static void server_close_fd (server_p srv, int32_t fd); + +/* + * Initialize server + */ + +int32_t +server_init(server_p srv, char const *control) +{ + struct sockaddr_un un; + struct sockaddr_l2cap l2; + int32_t unsock, l2sock; + socklen_t size; + uint16_t imtu; + + assert(srv != NULL); + assert(control != NULL); + + memset(srv, 0, sizeof(*srv)); + + /* Open control socket */ + if (unlink(control) < 0 && errno != ENOENT) { + log_crit("Could not unlink(%s). %s (%d)", + control, strerror(errno), errno); + return (-1); + } + + unsock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (unsock < 0) { + log_crit("Could not create control socket. %s (%d)", + strerror(errno), errno); + return (-1); + } + + memset(&un, 0, sizeof(un)); + un.sun_len = sizeof(un); + un.sun_family = AF_LOCAL; + strlcpy(un.sun_path, control, sizeof(un.sun_path)); + + if (bind(unsock, (struct sockaddr *) &un, sizeof(un)) < 0) { + log_crit("Could not bind control socket. %s (%d)", + strerror(errno), errno); + close(unsock); + return (-1); + } + + if (chmod(control, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) < 0) { + log_crit("Could not change permissions on control socket. " \ + "%s (%d)", strerror(errno), errno); + close(unsock); + return (-1); + } + + if (listen(unsock, 10) < 0) { + log_crit("Could not listen on control socket. %s (%d)", + strerror(errno), errno); + close(unsock); + return (-1); + } + + /* Open L2CAP socket */ + l2sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (l2sock < 0) { + log_crit("Could not create L2CAP socket. %s (%d)", + strerror(errno), errno); + close(unsock); + return (-1); + } + + size = sizeof(imtu); + if (getsockopt(l2sock, SOL_L2CAP, SO_L2CAP_IMTU, &imtu, &size) < 0) { + log_crit("Could not get L2CAP IMTU. %s (%d)", + strerror(errno), errno); + close(unsock); + close(l2sock); + return (-1); + } + + memset(&l2, 0, sizeof(l2)); + l2.l2cap_len = sizeof(l2); + l2.l2cap_family = AF_BLUETOOTH; + memcpy(&l2.l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(l2.l2cap_bdaddr)); + l2.l2cap_psm = htole16(NG_L2CAP_PSM_SDP); + + if (bind(l2sock, (struct sockaddr *) &l2, sizeof(l2)) < 0) { + log_crit("Could not bind L2CAP socket. %s (%d)", + strerror(errno), errno); + close(unsock); + close(l2sock); + return (-1); + } + + if (listen(l2sock, 10) < 0) { + log_crit("Could not listen on L2CAP socket. %s (%d)", + strerror(errno), errno); + close(unsock); + close(l2sock); + return (-1); + } + + /* Allocate incoming buffer */ + srv->imtu = (imtu > SDP_LOCAL_MTU)? imtu : SDP_LOCAL_MTU; + srv->req = (uint8_t *) calloc(srv->imtu, sizeof(srv->req[0])); + if (srv->req == NULL) { + log_crit("Could not allocate request buffer"); + close(unsock); + close(l2sock); + return (-1); + } + + /* Allocate memory for descriptor index */ + srv->fdidx = (fd_idx_p) calloc(FD_SETSIZE, sizeof(srv->fdidx[0])); + if (srv->fdidx == NULL) { + log_crit("Could not allocate fd index"); + free(srv->req); + close(unsock); + close(l2sock); + return (-1); + } + + /* Register Service Discovery profile (attach it to control socket) */ + if (provider_register_sd(unsock) < 0) { + log_crit("Could not register Service Discovery profile"); + free(srv->fdidx); + free(srv->req); + close(unsock); + close(l2sock); + return (-1); + } + + /* + * If we got here then everything is fine. Add both control sockets + * to the index. + */ + + FD_ZERO(&srv->fdset); + srv->maxfd = (unsock > l2sock)? unsock : l2sock; + + FD_SET(unsock, &srv->fdset); + srv->fdidx[unsock].valid = 1; + srv->fdidx[unsock].server = 1; + srv->fdidx[unsock].control = 1; + srv->fdidx[unsock].priv = 0; + srv->fdidx[unsock].rsp_cs = 0; + srv->fdidx[unsock].rsp_size = 0; + srv->fdidx[unsock].rsp_limit = 0; + srv->fdidx[unsock].omtu = SDP_LOCAL_MTU; + srv->fdidx[unsock].rsp = NULL; + + FD_SET(l2sock, &srv->fdset); + srv->fdidx[l2sock].valid = 1; + srv->fdidx[l2sock].server = 1; + srv->fdidx[l2sock].control = 0; + srv->fdidx[l2sock].priv = 0; + srv->fdidx[l2sock].rsp_cs = 0; + srv->fdidx[l2sock].rsp_size = 0; + srv->fdidx[l2sock].rsp_limit = 0; + srv->fdidx[l2sock].omtu = 0; /* unknown */ + srv->fdidx[l2sock].rsp = NULL; + + return (0); +} + +/* + * Shutdown server + */ + +void +server_shutdown(server_p srv) +{ + int fd; + + assert(srv != NULL); + + for (fd = 0; fd < srv->maxfd + 1; fd ++) + if (srv->fdidx[fd].valid) + server_close_fd(srv, fd); + + free(srv->req); + free(srv->fdidx); + + memset(srv, 0, sizeof(*srv)); +} + +/* + * Do one server iteration + */ + +int32_t +server_do(server_p srv) +{ + fd_set fdset; + int32_t n, fd; + + assert(srv != NULL); + + /* Copy cached version of the fd set and call select */ + memcpy(&fdset, &srv->fdset, sizeof(fdset)); + n = select(srv->maxfd + 1, &fdset, NULL, NULL, NULL); + if (n < 0) { + if (errno == EINTR) + return (0); + + log_err("Could not select(%d, %p). %s (%d)", + srv->maxfd + 1, &fdset, strerror(errno), errno); + + return (-1); + } + + /* Process descriptors */ + for (fd = 0; fd < srv->maxfd + 1 && n > 0; fd ++) { + if (!FD_ISSET(fd, &fdset)) + continue; + + assert(srv->fdidx[fd].valid); + n --; + + if (srv->fdidx[fd].server) + server_accept_client(srv, fd); + else if (server_process_request(srv, fd) != 0) + server_close_fd(srv, fd); + } + + return (0); + +} + +/* + * Accept new client connection and register it with index + */ + +static void +server_accept_client(server_p srv, int32_t fd) +{ + uint8_t *rsp = NULL; + int32_t cfd, priv; + uint16_t omtu; + socklen_t size; + + do { + cfd = accept(fd, NULL, NULL); + } while (cfd < 0 && errno == EINTR); + + if (cfd < 0) { + log_err("Could not accept connection on %s socket. %s (%d)", + srv->fdidx[fd].control? "control" : "L2CAP", + strerror(errno), errno); + return; + } + + assert(!FD_ISSET(cfd, &srv->fdset)); + assert(!srv->fdidx[cfd].valid); + + priv = 0; + + if (!srv->fdidx[fd].control) { + /* Get local BD_ADDR */ + size = sizeof(srv->req_sa); + if (getsockname(cfd,(struct sockaddr*)&srv->req_sa,&size) < 0) { + log_err("Could not get local BD_ADDR. %s (%d)", + strerror(errno), errno); + close(cfd); + return; + } + + /* Get outgoing MTU */ + size = sizeof(omtu); + if (getsockopt(cfd,SOL_L2CAP,SO_L2CAP_OMTU,&omtu,&size) < 0) { + log_err("Could not get L2CAP OMTU. %s (%d)", + strerror(errno), errno); + close(cfd); + return; + } + + /* + * The maximum size of the L2CAP packet is 65536 bytes. + * The minimum L2CAP MTU is 43 bytes. That means we need + * 65536 / 43 = ~1524 chunks to transfer maximum packet + * size with minimum MTU. The "rsp_cs" field in fd_idx_t + * is 11 bits wide, which gives us up to 2048 chunks. + */ + + if (omtu < NG_L2CAP_MTU_MINIMUM) { + log_err("L2CAP OMTU is too small (%d bytes)", omtu); + close(cfd); + return; + } + } else { + uid_t uid; + gid_t gid; + struct passwd *pw; + + /* Get peer's credentials */ + if (getpeereid(cfd, &uid, &gid) < 0) { + log_err("Could not get peer's credentials. %s (%d)", + strerror(errno), errno); + close(cfd); + return; + } + + /* Check credentials */ + pw = getpwuid(uid); + if (pw != NULL) + priv = (strcmp(pw->pw_name, "root") == 0); + else + log_warning("Could not verify credentials for uid %d", + uid); + + memcpy(&srv->req_sa.l2cap_bdaddr, NG_HCI_BDADDR_ANY, + sizeof(srv->req_sa.l2cap_bdaddr)); + + omtu = srv->fdidx[fd].omtu; + } + + /* + * Allocate buffer. This is an overkill, but we can not know how + * big our reply is going to be. + */ + + rsp = (uint8_t *) calloc(NG_L2CAP_MTU_MAXIMUM, sizeof(rsp[0])); + if (rsp == NULL) { + log_crit("Could not allocate response buffer"); + close(cfd); + return; + } + + /* Add client descriptor to the index */ + FD_SET(cfd, &srv->fdset); + if (srv->maxfd < cfd) + srv->maxfd = cfd; + srv->fdidx[cfd].valid = 1; + srv->fdidx[cfd].server = 0; + srv->fdidx[cfd].control = srv->fdidx[fd].control; + srv->fdidx[cfd].priv = priv; + srv->fdidx[cfd].rsp_cs = 0; + srv->fdidx[cfd].rsp_size = 0; + srv->fdidx[cfd].rsp_limit = 0; + srv->fdidx[cfd].omtu = omtu; + srv->fdidx[cfd].rsp = rsp; +} + +/* + * Process request from the client + */ + +static int32_t +server_process_request(server_p srv, int32_t fd) +{ + sdp_pdu_p pdu = (sdp_pdu_p) srv->req; + int32_t len, error; + + assert(srv->imtu > 0); + assert(srv->req != NULL); + assert(FD_ISSET(fd, &srv->fdset)); + assert(srv->fdidx[fd].valid); + assert(!srv->fdidx[fd].server); + assert(srv->fdidx[fd].rsp != NULL); + assert(srv->fdidx[fd].omtu >= NG_L2CAP_MTU_MINIMUM); + + do { + len = read(fd, srv->req, srv->imtu); + } while (len < 0 && errno == EINTR); + + if (len < 0) { + log_err("Could not receive SDP request from %s socket. %s (%d)", + srv->fdidx[fd].control? "control" : "L2CAP", + strerror(errno), errno); + return (-1); + } + if (len == 0) { + log_info("Client on %s socket has disconnected", + srv->fdidx[fd].control? "control" : "L2CAP"); + return (-1); + } + + if (len >= sizeof(*pdu) && + sizeof(*pdu) + (pdu->len = ntohs(pdu->len)) == len) { + switch (pdu->pid) { + case SDP_PDU_SERVICE_SEARCH_REQUEST: + error = server_prepare_service_search_response(srv, fd); + break; + + case SDP_PDU_SERVICE_ATTRIBUTE_REQUEST: + error = server_prepare_service_attribute_response(srv, fd); + break; + + case SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_REQUEST: + error = server_prepare_service_search_attribute_response(srv, fd); + break; + + case SDP_PDU_SERVICE_REGISTER_REQUEST: + error = server_prepare_service_register_response(srv, fd); + break; + + case SDP_PDU_SERVICE_UNREGISTER_REQUEST: + error = server_prepare_service_unregister_response(srv, fd); + break; + + case SDP_PDU_SERVICE_CHANGE_REQUEST: + error = server_prepare_service_change_response(srv, fd); + break; + + default: + error = SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; + break; + } + } else + error = SDP_ERROR_CODE_INVALID_PDU_SIZE; + + if (error == 0) { + switch (pdu->pid) { + case SDP_PDU_SERVICE_SEARCH_REQUEST: + error = server_send_service_search_response(srv, fd); + break; + + case SDP_PDU_SERVICE_ATTRIBUTE_REQUEST: + error = server_send_service_attribute_response(srv, fd); + break; + + case SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_REQUEST: + error = server_send_service_search_attribute_response(srv, fd); + break; + + case SDP_PDU_SERVICE_REGISTER_REQUEST: + error = server_send_service_register_response(srv, fd); + break; + + case SDP_PDU_SERVICE_UNREGISTER_REQUEST: + error = server_send_service_unregister_response(srv, fd); + break; + + case SDP_PDU_SERVICE_CHANGE_REQUEST: + error = server_send_service_change_response(srv, fd); + break; + + default: + error = SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX; + break; + } + + if (error != 0) + log_err("Could not send SDP response to %s socket, " \ + "pdu->pid=%d, pdu->tid=%d, error=%d", + srv->fdidx[fd].control? "control" : "L2CAP", + pdu->pid, ntohs(pdu->tid), error); + } else { + log_err("Could not process SDP request from %s socket, " \ + "pdu->pid=%d, pdu->tid=%d, pdu->len=%d, len=%d, " \ + "error=%d", + srv->fdidx[fd].control? "control" : "L2CAP", + pdu->pid, ntohs(pdu->tid), pdu->len, len, error); + + error = server_send_error_response(srv, fd, error); + if (error != 0) + log_err("Could not send SDP error response to %s " \ + "socket, pdu->pid=%d, pdu->tid=%d, error=%d", + srv->fdidx[fd].control? "control" : "L2CAP", + pdu->pid, ntohs(pdu->tid), error); + } + + /* On error forget response (if any) */ + if (error != 0) { + srv->fdidx[fd].rsp_cs = 0; + srv->fdidx[fd].rsp_size = 0; + srv->fdidx[fd].rsp_limit = 0; + } + + return (error); +} + +/* + * Send SDP_Error_Response PDU + */ + +static int32_t +server_send_error_response(server_p srv, int32_t fd, uint16_t error) +{ + int32_t size; + + struct { + sdp_pdu_t pdu; + uint16_t error; + } __attribute__ ((packed)) rsp; + + /* Prepare and send SDP error response */ + rsp.pdu.pid = SDP_PDU_ERROR_RESPONSE; + rsp.pdu.tid = ((sdp_pdu_p)(srv->req))->tid; + rsp.pdu.len = htons(sizeof(rsp.error)); + rsp.error = htons(error); + + do { + size = write(fd, &rsp, sizeof(rsp)); + } while (size < 0 && errno == EINTR); + + return ((size < 0)? errno : 0); +} + +/* + * Close descriptor and remove it from index + */ + +static void +server_close_fd(server_p srv, int32_t fd) +{ + provider_p provider = NULL, provider_next = NULL; + + assert(FD_ISSET(fd, &srv->fdset)); + assert(srv->fdidx[fd].valid); + + close(fd); + + FD_CLR(fd, &srv->fdset); + if (fd == srv->maxfd) + srv->maxfd --; + + if (srv->fdidx[fd].rsp != NULL) + free(srv->fdidx[fd].rsp); + + memset(&srv->fdidx[fd], 0, sizeof(srv->fdidx[fd])); + + for (provider = provider_get_first(); + provider != NULL; + provider = provider_next) { + provider_next = provider_get_next(provider); + + if (provider->fd == fd) + provider_unregister(provider); + } +} + diff --git a/usr.sbin/bluetooth/sdpd/server.h b/usr.sbin/bluetooth/sdpd/server.h new file mode 100644 index 000000000000..9a1cb86eb3da --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/server.h @@ -0,0 +1,103 @@ +/*- + * server.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: server.h,v 1.5 2004/01/13 01:54:39 max Exp $ + */ + +#ifndef _SERVER_H_ +#define _SERVER_H_ + +/* + * File descriptor index entry + */ + +struct fd_idx +{ + unsigned valid : 1; /* descriptor is valid */ + unsigned server : 1; /* descriptor is listening */ + unsigned control : 1; /* descriptor is a control socket */ + unsigned priv : 1; /* descriptor is privileged */ + unsigned reserved : 1; + unsigned rsp_cs : 11; /* response continuation state */ + uint16_t rsp_size; /* response size */ + uint16_t rsp_limit; /* response limit */ + uint16_t omtu; /* outgoing MTU */ + uint8_t *rsp; /* outgoing buffer */ +}; + +typedef struct fd_idx fd_idx_t; +typedef struct fd_idx * fd_idx_p; + +/* + * SDP server + */ + +struct server +{ + uint32_t imtu; /* incoming MTU */ + uint8_t *req; /* incoming buffer */ + int32_t maxfd; /* max. descriptor is the set */ + fd_set fdset; /* current descriptor set */ + fd_idx_p fdidx; /* descriptor index */ + struct sockaddr_l2cap req_sa; /* local address */ +}; + +typedef struct server server_t; +typedef struct server * server_p; + +/* + * External API + */ + +int32_t server_init(server_p srv, const char *control); +void server_shutdown(server_p srv); +int32_t server_do(server_p srv); + +int32_t server_prepare_service_search_response(server_p srv, int32_t fd); +int32_t server_send_service_search_response(server_p srv, int32_t fd); + +int32_t server_prepare_service_attribute_response(server_p srv, int32_t fd); +int32_t server_send_service_attribute_response(server_p srv, int32_t fd); + +int32_t server_prepare_service_search_attribute_response(server_p srv, int32_t fd); +#define server_send_service_search_attribute_response \ + server_send_service_attribute_response + +int32_t server_prepare_service_register_response(server_p srv, int32_t fd); +int32_t server_send_service_register_response(server_p srv, int32_t fd); + +int32_t server_prepare_service_unregister_response(server_p srv, int32_t fd); +#define server_send_service_unregister_response \ + server_send_service_register_response + +int32_t server_prepare_service_change_response(server_p srv, int32_t fd); +#define server_send_service_change_response \ + server_send_service_register_response + +#endif /* ndef _SERVER_H_ */ diff --git a/usr.sbin/bluetooth/sdpd/sp.c b/usr.sbin/bluetooth/sdpd/sp.c new file mode 100644 index 000000000000..3c79dd93965d --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/sp.c @@ -0,0 +1,119 @@ +/*- + * sp.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: sp.c,v 1.5 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" + +static int32_t +sp_profile_create_service_class_id_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t service_classes[] = { + SDP_SERVICE_CLASS_SERIAL_PORT + }; + + return (common_profile_create_service_class_id_list( + buf, eob, + (uint8_t const *) service_classes, + sizeof(service_classes))); +} + +static int32_t +sp_profile_create_bluetooth_profile_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static uint16_t profile_descriptor_list[] = { + SDP_SERVICE_CLASS_SERIAL_PORT, + 0x0100 + }; + + return (common_profile_create_bluetooth_profile_descriptor_list( + buf, eob, + (uint8_t const *) profile_descriptor_list, + sizeof(profile_descriptor_list))); +} + +static int32_t +sp_profile_create_service_name( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + static char service_name[] = "Serial Port"; + + return (common_profile_create_string8( + buf, eob, + (uint8_t const *) service_name, strlen(service_name))); +} + +static int32_t +sp_profile_create_protocol_descriptor_list( + uint8_t *buf, uint8_t const * const eob, + uint8_t const *data, uint32_t datalen) +{ + provider_p provider = (provider_p) data; + sdp_sp_profile_p sp = (sdp_sp_profile_p) provider->data; + + return (rfcomm_profile_create_protocol_descriptor_list( + buf, eob, + (uint8_t const *) &sp->server_channel, 1)); +} + +static attr_t sp_profile_attrs[] = { + { SDP_ATTR_SERVICE_RECORD_HANDLE, + common_profile_create_service_record_handle }, + { SDP_ATTR_SERVICE_CLASS_ID_LIST, + sp_profile_create_service_class_id_list }, + { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, + sp_profile_create_bluetooth_profile_descriptor_list }, + { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST, + common_profile_create_language_base_attribute_id_list }, + { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET, + sp_profile_create_service_name }, + { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST, + sp_profile_create_protocol_descriptor_list }, + { 0, NULL } /* end entry */ +}; + +profile_t sp_profile_descriptor = { + SDP_SERVICE_CLASS_SERIAL_PORT, + sizeof(sdp_sp_profile_t), + common_profile_server_channel_valid, + (attr_t const * const) &sp_profile_attrs +}; + diff --git a/usr.sbin/bluetooth/sdpd/srr.c b/usr.sbin/bluetooth/sdpd/srr.c new file mode 100644 index 000000000000..b16eb25c9ea4 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/srr.c @@ -0,0 +1,142 @@ +/*- + * srr.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: srr.c,v 1.1 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" +#include "server.h" + +/* + * Prepare Service Register response + */ + +int32_t +server_prepare_service_register_response(server_p srv, int32_t fd) +{ + uint8_t const *req = srv->req + sizeof(sdp_pdu_t); + uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len; + uint8_t *rsp = srv->fdidx[fd].rsp; + + profile_t *profile = NULL; + provider_t *provider = NULL; + bdaddr_t *bdaddr = NULL; + int32_t uuid; + + /* + * Minimal Service Register Request + * + * value16 - uuid 2 bytes + * bdaddr - BD_ADDR 6 bytes + */ + + if (!srv->fdidx[fd].control || + !srv->fdidx[fd].priv || req_end - req < 8) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get ServiceClass UUID */ + SDP_GET16(uuid, req); + + /* Get BD_ADDR */ + bdaddr = (bdaddr_p) req; + req += sizeof(*bdaddr); + + /* Lookup profile descriptror */ + profile = profile_get_descriptor(uuid); + if (profile == NULL) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Validate user data */ + if (req_end - req < profile->dsize || + profile->valid == NULL || + (profile->valid)(req, req_end - req) == 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Register provider */ + provider = provider_register(profile, bdaddr, fd, req, req_end - req); + if (provider == NULL) + return (SDP_ERROR_CODE_INSUFFICIENT_RESOURCES); + + SDP_PUT16(0, rsp); + SDP_PUT32(provider->handle, rsp); + + /* Set reply size */ + srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t); + srv->fdidx[fd].rsp_size = rsp - srv->fdidx[fd].rsp; + srv->fdidx[fd].rsp_cs = 0; + + return (0); +} + +/* + * Send Service Register Response + */ + +int32_t +server_send_service_register_response(server_p srv, int32_t fd) +{ + struct iovec iov[2]; + sdp_pdu_t pdu; + int32_t size; + + assert(srv->fdidx[fd].rsp_size < srv->fdidx[fd].rsp_limit); + + pdu.pid = SDP_PDU_ERROR_RESPONSE; + pdu.tid = ((sdp_pdu_p)(srv->req))->tid; + pdu.len = htons(srv->fdidx[fd].rsp_size); + + iov[0].iov_base = &pdu; + iov[0].iov_len = sizeof(pdu); + + iov[1].iov_base = srv->fdidx[fd].rsp; + iov[1].iov_len = srv->fdidx[fd].rsp_size; + + do { + size = writev(fd, (struct iovec const *) &iov, nitems(iov)); + } while (size < 0 && errno == EINTR); + + srv->fdidx[fd].rsp_cs = 0; + srv->fdidx[fd].rsp_size = 0; + srv->fdidx[fd].rsp_limit = 0; + + return ((size < 0)? errno : 0); +} + diff --git a/usr.sbin/bluetooth/sdpd/ssar.c b/usr.sbin/bluetooth/sdpd/ssar.c new file mode 100644 index 000000000000..fad498c2b34a --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/ssar.c @@ -0,0 +1,380 @@ +/*- + * ssar.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: ssar.c,v 1.4 2004/01/12 22:54:31 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" +#include "server.h" +#include "uuid-private.h" + +/* from sar.c */ +int32_t server_prepare_attr_list(provider_p const provider, + uint8_t const *req, uint8_t const * const req_end, + uint8_t *rsp, uint8_t const * const rsp_end); + +/* + * Scan an attribute for matching UUID. + */ +static int +server_search_uuid_sub(uint8_t *buf, uint8_t const * const eob, const uint128_t *uuid) +{ + int128_t duuid; + uint32_t value; + uint8_t type; + + while (buf < eob) { + + SDP_GET8(type, buf); + + switch (type) { + case SDP_DATA_UUID16: + if (buf + 2 > eob) + continue; + SDP_GET16(value, buf); + + memcpy(&duuid, &uuid_base, sizeof(duuid)); + duuid.b[2] = value >> 8 & 0xff; + duuid.b[3] = value & 0xff; + + if (memcmp(&duuid, uuid, sizeof(duuid)) == 0) + return (0); + break; + case SDP_DATA_UUID32: + if (buf + 4 > eob) + continue; + SDP_GET32(value, buf); + memcpy(&duuid, &uuid_base, sizeof(duuid)); + duuid.b[0] = value >> 24 & 0xff; + duuid.b[1] = value >> 16 & 0xff; + duuid.b[2] = value >> 8 & 0xff; + duuid.b[3] = value & 0xff; + + if (memcmp(&duuid, uuid, sizeof(duuid)) == 0) + return (0); + break; + case SDP_DATA_UUID128: + if (buf + 16 > eob) + continue; + SDP_GET_UUID128(&duuid, buf); + + if (memcmp(&duuid, uuid, sizeof(duuid)) == 0) + return (0); + break; + case SDP_DATA_UINT8: + case SDP_DATA_INT8: + case SDP_DATA_SEQ8: + buf++; + break; + case SDP_DATA_UINT16: + case SDP_DATA_INT16: + case SDP_DATA_SEQ16: + buf += 2; + break; + case SDP_DATA_UINT32: + case SDP_DATA_INT32: + case SDP_DATA_SEQ32: + buf += 4; + break; + case SDP_DATA_UINT64: + case SDP_DATA_INT64: + buf += 8; + break; + case SDP_DATA_UINT128: + case SDP_DATA_INT128: + buf += 16; + break; + case SDP_DATA_STR8: + if (buf + 1 > eob) + continue; + SDP_GET8(value, buf); + buf += value; + break; + case SDP_DATA_STR16: + if (buf + 2 > eob) + continue; + SDP_GET16(value, buf); + if (value > (eob - buf)) + return (1); + buf += value; + break; + case SDP_DATA_STR32: + if (buf + 4 > eob) + continue; + SDP_GET32(value, buf); + if (value > (eob - buf)) + return (1); + buf += value; + break; + case SDP_DATA_BOOL: + buf += 1; + break; + default: + return (1); + } + } + return (1); +} + +/* + * Search a provider for matching UUID in its attributes. + */ +static int +server_search_uuid(provider_p const provider, const uint128_t *uuid) +{ + uint8_t buffer[256]; + const attr_t *attr; + int len; + + for (attr = provider->profile->attrs; attr->create != NULL; attr++) { + + len = attr->create(buffer, buffer + sizeof(buffer), + (const uint8_t *)provider->profile, sizeof(*provider->profile)); + if (len < 0) + continue; + if (server_search_uuid_sub(buffer, buffer + len, uuid) == 0) + return (0); + } + return (1); +} + +/* + * Prepare SDP Service Search Attribute Response + */ + +int32_t +server_prepare_service_search_attribute_response(server_p srv, int32_t fd) +{ + uint8_t const *req = srv->req + sizeof(sdp_pdu_t); + uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len; + uint8_t *rsp = srv->fdidx[fd].rsp; + uint8_t const *rsp_end = rsp + NG_L2CAP_MTU_MAXIMUM; + + uint8_t const *sspptr = NULL, *aidptr = NULL; + uint8_t *ptr = NULL; + + provider_t *provider = NULL; + int32_t type, rsp_limit, ssplen, aidlen, cslen, cs; + uint128_t uuid, puuid; + + /* + * Minimal Service Search Attribute Request request + * + * seq8 len8 - 2 bytes + * uuid16 value16 - 3 bytes ServiceSearchPattern + * value16 - 2 bytes MaximumAttributeByteCount + * seq8 len8 - 2 bytes + * uint16 value16 - 3 bytes AttributeIDList + * value8 - 1 byte ContinuationState + */ + + if (req_end - req < 13) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get size of ServiceSearchPattern */ + ssplen = 0; + SDP_GET8(type, req); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(ssplen, req); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(ssplen, req); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(ssplen, req); + break; + } + if (ssplen <= 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + sspptr = req; + req += ssplen; + + /* Get MaximumAttributeByteCount */ + if (req + 2 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET16(rsp_limit, req); + if (rsp_limit <= 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get size of AttributeIDList */ + if (req + 1 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + aidlen = 0; + SDP_GET8(type, req); + switch (type) { + case SDP_DATA_SEQ8: + if (req + 1 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET8(aidlen, req); + break; + + case SDP_DATA_SEQ16: + if (req + 2 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET16(aidlen, req); + break; + + case SDP_DATA_SEQ32: + if (req + 4 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET32(aidlen, req); + break; + } + if (aidlen <= 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + aidptr = req; + req += aidlen; + + /* Get ContinuationState */ + if (req + 1 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET8(cslen, req); + if (cslen != 0) { + if (cslen != 2 || req_end - req != 2) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET16(cs, req); + } else + cs = 0; + + /* Process the request. First, check continuation state */ + if (srv->fdidx[fd].rsp_cs != cs) + return (SDP_ERROR_CODE_INVALID_CONTINUATION_STATE); + if (srv->fdidx[fd].rsp_size > 0) + return (0); + + /* + * Service Search Attribute Response format + * + * value16 - 2 bytes AttributeListByteCount (not incl.) + * seq8 len16 - 3 bytes + * attr list - 3+ bytes AttributeLists + * [ attr list ] + */ + + ptr = rsp + 3; + + while (ssplen > 0) { + SDP_GET8(type, sspptr); + ssplen --; + + switch (type) { + case SDP_DATA_UUID16: + if (ssplen < 2) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + memcpy(&uuid, &uuid_base, sizeof(uuid)); + uuid.b[2] = *sspptr ++; + uuid.b[3] = *sspptr ++; + ssplen -= 2; + break; + + case SDP_DATA_UUID32: + if (ssplen < 4) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + memcpy(&uuid, &uuid_base, sizeof(uuid)); + uuid.b[0] = *sspptr ++; + uuid.b[1] = *sspptr ++; + uuid.b[2] = *sspptr ++; + uuid.b[3] = *sspptr ++; + ssplen -= 4; + break; + + case SDP_DATA_UUID128: + if (ssplen < 16) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + memcpy(uuid.b, sspptr, 16); + sspptr += 16; + ssplen -= 16; + break; + + default: + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + /* NOT REACHED */ + } + + for (provider = provider_get_first(); + provider != NULL; + provider = provider_get_next(provider)) { + if (!provider_match_bdaddr(provider, &srv->req_sa.l2cap_bdaddr)) + continue; + + memcpy(&puuid, &uuid_base, sizeof(puuid)); + puuid.b[2] = provider->profile->uuid >> 8; + puuid.b[3] = provider->profile->uuid; + + if (memcmp(&uuid, &puuid, sizeof(uuid)) != 0 && + memcmp(&uuid, &uuid_public_browse_group, sizeof(uuid)) != 0 && + server_search_uuid(provider, &uuid) != 0) + continue; + + cs = server_prepare_attr_list(provider, + aidptr, aidptr + aidlen, ptr, rsp_end); + if (cs < 0) + return (SDP_ERROR_CODE_INSUFFICIENT_RESOURCES); + + ptr += cs; + } + } + + /* Set reply size (not counting PDU header and continuation state) */ + srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t) - 2; + if (srv->fdidx[fd].rsp_limit > rsp_limit) + srv->fdidx[fd].rsp_limit = rsp_limit; + + srv->fdidx[fd].rsp_size = ptr - rsp; + srv->fdidx[fd].rsp_cs = 0; + + /* Fix AttributeLists sequence header */ + ptr = rsp; + SDP_PUT8(SDP_DATA_SEQ16, ptr); + SDP_PUT16(srv->fdidx[fd].rsp_size - 3, ptr); + + return (0); +} + diff --git a/usr.sbin/bluetooth/sdpd/ssr.c b/usr.sbin/bluetooth/sdpd/ssr.c new file mode 100644 index 000000000000..1a29dde97ee5 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/ssr.c @@ -0,0 +1,285 @@ +/*- + * ssr.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: ssr.c,v 1.5 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" +#include "server.h" +#include "uuid-private.h" + +/* + * Prepare SDP Service Search Response + */ + +int32_t +server_prepare_service_search_response(server_p srv, int32_t fd) +{ + uint8_t const *req = srv->req + sizeof(sdp_pdu_t); + uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len; + uint8_t *rsp = srv->fdidx[fd].rsp; + uint8_t const *rsp_end = rsp + NG_L2CAP_MTU_MAXIMUM; + + uint8_t *ptr = NULL; + provider_t *provider = NULL; + int32_t type, ssplen, rsp_limit, rcount, cslen, cs; + uint128_t uuid, puuid; + + /* + * Minimal SDP Service Search Request + * + * seq8 len8 - 2 bytes + * uuid16 value16 - 3 bytes ServiceSearchPattern + * value16 - 2 bytes MaximumServiceRecordCount + * value8 - 1 byte ContinuationState + */ + + if (req_end - req < 8) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get size of ServiceSearchPattern */ + ssplen = 0; + SDP_GET8(type, req); + switch (type) { + case SDP_DATA_SEQ8: + SDP_GET8(ssplen, req); + break; + + case SDP_DATA_SEQ16: + SDP_GET16(ssplen, req); + break; + + case SDP_DATA_SEQ32: + SDP_GET32(ssplen, req); + break; + } + if (ssplen <= 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + ptr = (uint8_t *) req + ssplen; + + /* Get MaximumServiceRecordCount */ + if (ptr + 2 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET16(rsp_limit, ptr); + if (rsp_limit <= 0) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get ContinuationState */ + if (ptr + 1 > req_end) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET8(cslen, ptr); + if (cslen != 0) { + if (cslen != 2 || req_end - ptr != 2) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + SDP_GET16(cs, ptr); + } else + cs = 0; + + /* Process the request. First, check continuation state */ + if (srv->fdidx[fd].rsp_cs != cs) + return (SDP_ERROR_CODE_INVALID_CONTINUATION_STATE); + if (srv->fdidx[fd].rsp_size > 0) + return (0); + + /* + * Service Search Response format + * + * value16 - 2 bytes TotalServiceRecordCount (not incl.) + * value16 - 2 bytes CurrentServiceRecordCount (not incl.) + * value32 - 4 bytes handle + * [ value32 ] + * + * Calculate how many record handles we can fit + * in our reply buffer and adjust rlimit. + */ + + ptr = rsp; + rcount = (rsp_end - ptr) / 4; + if (rcount < rsp_limit) + rsp_limit = rcount; + + /* Look for the record handles */ + for (rcount = 0; ssplen > 0 && rcount < rsp_limit; ) { + SDP_GET8(type, req); + ssplen --; + + switch (type) { + case SDP_DATA_UUID16: + if (ssplen < 2) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + memcpy(&uuid, &uuid_base, sizeof(uuid)); + uuid.b[2] = *req ++; + uuid.b[3] = *req ++; + ssplen -= 2; + break; + + case SDP_DATA_UUID32: + if (ssplen < 4) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + memcpy(&uuid, &uuid_base, sizeof(uuid)); + uuid.b[0] = *req ++; + uuid.b[1] = *req ++; + uuid.b[2] = *req ++; + uuid.b[3] = *req ++; + ssplen -= 4; + break; + + case SDP_DATA_UUID128: + if (ssplen < 16) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + memcpy(uuid.b, req, 16); + req += 16; + ssplen -= 16; + break; + + default: + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + /* NOT REACHED */ + } + + for (provider = provider_get_first(); + provider != NULL && rcount < rsp_limit; + provider = provider_get_next(provider)) { + if (!provider_match_bdaddr(provider, &srv->req_sa.l2cap_bdaddr)) + continue; + + memcpy(&puuid, &uuid_base, sizeof(puuid)); + puuid.b[2] = provider->profile->uuid >> 8; + puuid.b[3] = provider->profile->uuid; + + if (memcmp(&uuid, &puuid, sizeof(uuid)) == 0 || + memcmp(&uuid, &uuid_public_browse_group, sizeof(uuid)) == 0) { + SDP_PUT32(provider->handle, ptr); + rcount ++; + } + } + } + + /* Set reply size (not counting PDU header and continuation state) */ + srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t) - 4; + srv->fdidx[fd].rsp_size = ptr - rsp; + srv->fdidx[fd].rsp_cs = 0; + + return (0); +} + +/* + * Send SDP Service Search Response + */ + +int32_t +server_send_service_search_response(server_p srv, int32_t fd) +{ + uint8_t *rsp = srv->fdidx[fd].rsp + srv->fdidx[fd].rsp_cs; + uint8_t *rsp_end = srv->fdidx[fd].rsp + srv->fdidx[fd].rsp_size; + + struct iovec iov[4]; + sdp_pdu_t pdu; + uint16_t rcounts[2]; + uint8_t cs[3]; + int32_t size; + + /* First update continuation state (assume we will send all data) */ + size = rsp_end - rsp; + srv->fdidx[fd].rsp_cs += size; + + if (size + 1 > srv->fdidx[fd].rsp_limit) { + /* + * We need to split out response. Add 3 more bytes for the + * continuation state and move rsp_end and rsp_cs backwards. + */ + + while ((rsp_end - rsp) + 3 > srv->fdidx[fd].rsp_limit) { + rsp_end -= 4; + srv->fdidx[fd].rsp_cs -= 4; + } + + cs[0] = 2; + cs[1] = srv->fdidx[fd].rsp_cs >> 8; + cs[2] = srv->fdidx[fd].rsp_cs & 0xff; + } else + cs[0] = 0; + + assert(rsp_end >= rsp); + + rcounts[0] = srv->fdidx[fd].rsp_size / 4; /* TotalServiceRecordCount */ + rcounts[1] = (rsp_end - rsp) / 4; /* CurrentServiceRecordCount */ + + pdu.pid = SDP_PDU_SERVICE_SEARCH_RESPONSE; + pdu.tid = ((sdp_pdu_p)(srv->req))->tid; + pdu.len = htons(sizeof(rcounts) + rcounts[1] * 4 + 1 + cs[0]); + + rcounts[0] = htons(rcounts[0]); + rcounts[1] = htons(rcounts[1]); + + iov[0].iov_base = &pdu; + iov[0].iov_len = sizeof(pdu); + + iov[1].iov_base = rcounts; + iov[1].iov_len = sizeof(rcounts); + + iov[2].iov_base = rsp; + iov[2].iov_len = rsp_end - rsp; + + iov[3].iov_base = cs; + iov[3].iov_len = 1 + cs[0]; + + do { + size = writev(fd, (struct iovec const *) &iov, nitems(iov)); + } while (size < 0 && errno == EINTR); + + /* Check if we have sent (or failed to sent) last response chunk */ + if (srv->fdidx[fd].rsp_cs == srv->fdidx[fd].rsp_size) { + srv->fdidx[fd].rsp_cs = 0; + srv->fdidx[fd].rsp_size = 0; + srv->fdidx[fd].rsp_limit = 0; + } + + return ((size < 0)? errno : 0); +} + diff --git a/usr.sbin/bluetooth/sdpd/sur.c b/usr.sbin/bluetooth/sdpd/sur.c new file mode 100644 index 000000000000..43581be0bbf1 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/sur.c @@ -0,0 +1,85 @@ +/*- + * sur.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + * + * $Id: sur.c,v 1.1 2004/01/13 01:54:39 max Exp $ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <sdp.h> +#include <string.h> +#include "profile.h" +#include "provider.h" +#include "server.h" + +/* + * Prepare Service Unregister response + */ + +int32_t +server_prepare_service_unregister_response(server_p srv, int32_t fd) +{ + uint8_t const *req = srv->req + sizeof(sdp_pdu_t); + uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len; + uint8_t *rsp = srv->fdidx[fd].rsp; + + provider_t *provider = NULL; + uint32_t handle; + + /* + * Minimal Service Unregister Request + * + * value32 - uuid 4 bytes + */ + + if (!srv->fdidx[fd].control || + !srv->fdidx[fd].priv || req_end - req < 4) + return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX); + + /* Get handle */ + SDP_GET32(handle, req); + + /* Lookup provider */ + provider = provider_by_handle(handle); + if (provider == NULL || provider->fd != fd) + return (SDP_ERROR_CODE_INVALID_SERVICE_RECORD_HANDLE); + + provider_unregister(provider); + SDP_PUT16(0, rsp); + + /* Set reply size */ + srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t); + srv->fdidx[fd].rsp_size = rsp - srv->fdidx[fd].rsp; + srv->fdidx[fd].rsp_cs = 0; + + return (0); +} + diff --git a/usr.sbin/bluetooth/sdpd/uuid-private.h b/usr.sbin/bluetooth/sdpd/uuid-private.h new file mode 100644 index 000000000000..8a3f9bb1c62b --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/uuid-private.h @@ -0,0 +1,40 @@ +/*- + * uuid-private.h + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2005 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. + * + * $Id: uuid-private.h,v 1.1 2004/12/09 18:20:26 max Exp $ + */ + +#ifndef _UUID_PRIVATE_H_ +#define _UUID_PRIVATE_H_ + +extern uint128_t uuid_base; +extern uint128_t uuid_public_browse_group; + +#endif /* ndef _UUID_PRIVATE_H_ */ + diff --git a/usr.sbin/bluetooth/sdpd/uuid.c b/usr.sbin/bluetooth/sdpd/uuid.c new file mode 100644 index 000000000000..90a6d5b17322 --- /dev/null +++ b/usr.sbin/bluetooth/sdpd/uuid.c @@ -0,0 +1,57 @@ +/*- + * uuid.c + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2005 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. + * + * $Id: uuid.c,v 1.1 2004/12/09 18:20:26 max Exp $ + */ +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <sdp.h> +#include <uuid.h> +#include "uuid-private.h" + +uint128_t uuid_base = { + .b = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + 0x10, 0x00, + 0x80, 0x00, + 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb + } +}; + +uint128_t uuid_public_browse_group = { + .b = { + 0x00, 0x00, 0x10, 0x02, + 0x00, 0x00, + 0x10, 0x00, + 0x80, 0x00, + 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb + } +}; + |
