root/usr.sbin/bluetooth/hccontrol/le.c
/*
 * 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,
  }
};