aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/bluetooth')
-rw-r--r--usr.sbin/bluetooth/Makefile26
-rw-r--r--usr.sbin/bluetooth/Makefile.inc1
-rw-r--r--usr.sbin/bluetooth/ath3kfw/Makefile7
-rw-r--r--usr.sbin/bluetooth/ath3kfw/Makefile.depend16
-rw-r--r--usr.sbin/bluetooth/ath3kfw/ath3k_dbg.h39
-rw-r--r--usr.sbin/bluetooth/ath3kfw/ath3k_fw.c110
-rw-r--r--usr.sbin/bluetooth/ath3kfw/ath3k_fw.h54
-rw-r--r--usr.sbin/bluetooth/ath3kfw/ath3k_hw.c356
-rw-r--r--usr.sbin/bluetooth/ath3kfw/ath3k_hw.h64
-rw-r--r--usr.sbin/bluetooth/ath3kfw/ath3kfw.892
-rw-r--r--usr.sbin/bluetooth/ath3kfw/main.c392
-rw-r--r--usr.sbin/bluetooth/bcmfw/BCM-LEGAL.txt7
-rw-r--r--usr.sbin/bluetooth/bcmfw/Makefile8
-rw-r--r--usr.sbin/bluetooth/bcmfw/Makefile.depend16
-rw-r--r--usr.sbin/bluetooth/bcmfw/README21
-rw-r--r--usr.sbin/bluetooth/bcmfw/bcmfw.8105
-rw-r--r--usr.sbin/bluetooth/bcmfw/bcmfw.c309
-rw-r--r--usr.sbin/bluetooth/bluetooth-config/Makefile5
-rw-r--r--usr.sbin/bluetooth/bluetooth-config/Makefile.depend10
-rw-r--r--usr.sbin/bluetooth/bluetooth-config/bluetooth-config.8111
-rwxr-xr-xusr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh316
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/Makefile15
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/Makefile.depend19
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/bthidcontrol.8101
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/bthidcontrol.c217
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/bthidcontrol.h51
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/hid.c216
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/sdp.c503
-rw-r--r--usr.sbin/bluetooth/bthidd/Makefile16
-rw-r--r--usr.sbin/bluetooth/bthidd/Makefile.depend18
-rw-r--r--usr.sbin/bluetooth/bthidd/bthid_config.h79
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.8132
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.c276
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.conf.sample79
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.h102
-rw-r--r--usr.sbin/bluetooth/bthidd/btuinput.c616
-rw-r--r--usr.sbin/bluetooth/bthidd/btuinput.h42
-rw-r--r--usr.sbin/bluetooth/bthidd/client.c257
-rw-r--r--usr.sbin/bluetooth/bthidd/hid.c578
-rw-r--r--usr.sbin/bluetooth/bthidd/kbd.c614
-rw-r--r--usr.sbin/bluetooth/bthidd/kbd.h42
-rw-r--r--usr.sbin/bluetooth/bthidd/lexer.l132
-rw-r--r--usr.sbin/bluetooth/bthidd/parser.y565
-rw-r--r--usr.sbin/bluetooth/bthidd/server.c356
-rw-r--r--usr.sbin/bluetooth/bthidd/session.c249
-rw-r--r--usr.sbin/bluetooth/btpand/Makefile12
-rw-r--r--usr.sbin/bluetooth/btpand/Makefile.depend18
-rw-r--r--usr.sbin/bluetooth/btpand/bnep.c757
-rw-r--r--usr.sbin/bluetooth/btpand/bnep.h73
-rw-r--r--usr.sbin/bluetooth/btpand/btpand.8239
-rw-r--r--usr.sbin/bluetooth/btpand/btpand.c294
-rw-r--r--usr.sbin/bluetooth/btpand/btpand.h213
-rw-r--r--usr.sbin/bluetooth/btpand/channel.c336
-rw-r--r--usr.sbin/bluetooth/btpand/client.c229
-rw-r--r--usr.sbin/bluetooth/btpand/event.c311
-rw-r--r--usr.sbin/bluetooth/btpand/event.h148
-rw-r--r--usr.sbin/bluetooth/btpand/packet.c112
-rw-r--r--usr.sbin/bluetooth/btpand/sdp.c211
-rw-r--r--usr.sbin/bluetooth/btpand/sdp.h39
-rw-r--r--usr.sbin/bluetooth/btpand/server.c294
-rw-r--r--usr.sbin/bluetooth/btpand/tap.c169
-rw-r--r--usr.sbin/bluetooth/hccontrol/Makefile15
-rw-r--r--usr.sbin/bluetooth/hccontrol/Makefile.depend16
-rw-r--r--usr.sbin/bluetooth/hccontrol/adv_data.c248
-rw-r--r--usr.sbin/bluetooth/hccontrol/bluetooth.device.conf110
-rw-r--r--usr.sbin/bluetooth/hccontrol/hccontrol.8216
-rw-r--r--usr.sbin/bluetooth/hccontrol/hccontrol.c334
-rw-r--r--usr.sbin/bluetooth/hccontrol/hccontrol.h90
-rw-r--r--usr.sbin/bluetooth/hccontrol/host_controller_baseband.c1962
-rw-r--r--usr.sbin/bluetooth/hccontrol/info.c255
-rw-r--r--usr.sbin/bluetooth/hccontrol/le.c1370
-rw-r--r--usr.sbin/bluetooth/hccontrol/link_control.c962
-rw-r--r--usr.sbin/bluetooth/hccontrol/link_policy.c307
-rw-r--r--usr.sbin/bluetooth/hccontrol/node.c620
-rw-r--r--usr.sbin/bluetooth/hccontrol/send_recv.c185
-rw-r--r--usr.sbin/bluetooth/hccontrol/status.c246
-rw-r--r--usr.sbin/bluetooth/hccontrol/util.c3366
-rw-r--r--usr.sbin/bluetooth/hcsecd/Makefile15
-rw-r--r--usr.sbin/bluetooth/hcsecd/Makefile.depend17
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.8126
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.c448
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.conf55
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.conf.5130
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.h66
-rw-r--r--usr.sbin/bluetooth/hcsecd/lexer.l96
-rw-r--r--usr.sbin/bluetooth/hcsecd/parser.y436
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/Makefile11
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/Makefile.depend16
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_dbg.h45
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.c194
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_fw.h156
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.c777
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbt_hw.h119
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbtfw.8101
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf12
-rw-r--r--usr.sbin/bluetooth/iwmbtfw/main.c770
-rw-r--r--usr.sbin/bluetooth/l2control/Makefile11
-rw-r--r--usr.sbin/bluetooth/l2control/Makefile.depend16
-rw-r--r--usr.sbin/bluetooth/l2control/l2cap.c315
-rw-r--r--usr.sbin/bluetooth/l2control/l2control.896
-rw-r--r--usr.sbin/bluetooth/l2control/l2control.c222
-rw-r--r--usr.sbin/bluetooth/l2control/l2control.h50
-rw-r--r--usr.sbin/bluetooth/l2ping/Makefile10
-rw-r--r--usr.sbin/bluetooth/l2ping/Makefile.depend17
-rw-r--r--usr.sbin/bluetooth/l2ping/l2ping.8113
-rw-r--r--usr.sbin/bluetooth/l2ping/l2ping.c294
-rw-r--r--usr.sbin/bluetooth/rfcomm_pppd/Makefile13
-rw-r--r--usr.sbin/bluetooth/rfcomm_pppd/Makefile.depend17
-rw-r--r--usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.8353
-rw-r--r--usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.c472
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/Makefile9
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/main.c554
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_dbg.h46
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.c582
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_fw.h140
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.c270
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbt_hw.h117
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbtfw.899
-rw-r--r--usr.sbin/bluetooth/rtlbtfw/rtlbtfw.conf433
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/Makefile11
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/Makefile.depend17
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/sdpcontrol.8117
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/sdpcontrol.c221
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/sdpcontrol.h50
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/search.c750
-rw-r--r--usr.sbin/bluetooth/sdpd/Makefile14
-rw-r--r--usr.sbin/bluetooth/sdpd/Makefile.depend18
-rw-r--r--usr.sbin/bluetooth/sdpd/audio_sink.c186
-rw-r--r--usr.sbin/bluetooth/sdpd/audio_source.c186
-rw-r--r--usr.sbin/bluetooth/sdpd/bgd.c103
-rw-r--r--usr.sbin/bluetooth/sdpd/dun.c138
-rw-r--r--usr.sbin/bluetooth/sdpd/ftrn.c119
-rw-r--r--usr.sbin/bluetooth/sdpd/gn.c174
-rw-r--r--usr.sbin/bluetooth/sdpd/irmc.c135
-rw-r--r--usr.sbin/bluetooth/sdpd/irmc_command.c119
-rw-r--r--usr.sbin/bluetooth/sdpd/lan.c174
-rw-r--r--usr.sbin/bluetooth/sdpd/log.c128
-rw-r--r--usr.sbin/bluetooth/sdpd/log.h48
-rw-r--r--usr.sbin/bluetooth/sdpd/main.c237
-rw-r--r--usr.sbin/bluetooth/sdpd/nap.c211
-rw-r--r--usr.sbin/bluetooth/sdpd/opush.c135
-rw-r--r--usr.sbin/bluetooth/sdpd/panu.c174
-rw-r--r--usr.sbin/bluetooth/sdpd/profile.c504
-rw-r--r--usr.sbin/bluetooth/sdpd/profile.h97
-rw-r--r--usr.sbin/bluetooth/sdpd/provider.c198
-rw-r--r--usr.sbin/bluetooth/sdpd/provider.h76
-rw-r--r--usr.sbin/bluetooth/sdpd/sar.c320
-rw-r--r--usr.sbin/bluetooth/sdpd/scr.c94
-rw-r--r--usr.sbin/bluetooth/sdpd/sd.c230
-rw-r--r--usr.sbin/bluetooth/sdpd/sdpd.8139
-rw-r--r--usr.sbin/bluetooth/sdpd/server.c589
-rw-r--r--usr.sbin/bluetooth/sdpd/server.h103
-rw-r--r--usr.sbin/bluetooth/sdpd/sp.c119
-rw-r--r--usr.sbin/bluetooth/sdpd/srr.c142
-rw-r--r--usr.sbin/bluetooth/sdpd/ssar.c380
-rw-r--r--usr.sbin/bluetooth/sdpd/ssr.c285
-rw-r--r--usr.sbin/bluetooth/sdpd/sur.c85
-rw-r--r--usr.sbin/bluetooth/sdpd/uuid-private.h40
-rw-r--r--usr.sbin/bluetooth/sdpd/uuid.c57
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(&current);
+
+ /*
+ * 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(&current)) {
+ ev = TAILQ_FIRST(&current);
+ __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(&current, 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(&current, 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, &params);
+ if (r < 0) {
+ iwmbt_debug("iwmbt_get_boot_params() failed!");
+ return 1;
+ }
+ iwmbt_dump_boot_params(&params);
+
+ /* 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, &params, 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, &params, 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
+ }
+};
+