/*
* 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 $
* $FreeBSD$
*/
#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));
}
}
return;
}
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)));
}
return;
} /* 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
},
};