aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/bluetooth/hccontrol/le.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/bluetooth/hccontrol/le.c')
-rw-r--r--usr.sbin/bluetooth/hccontrol/le.c1370
1 files changed, 1370 insertions, 0 deletions
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,
+ }
+};