blob: 44bf4d2019ea30ddd6dd85df03825a35be61ae22 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2011 Intel Corporation. All rights reserved.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <getopt.h>
#include <stdbool.h>
#include <wordexp.h>
#include <ctype.h>
#include "lib/bluetooth.h"
#include "lib/hci.h"
#include "lib/hci_lib.h"
#include "lib/sdp.h"
#include "lib/sdp_lib.h"
#include "lib/uuid.h"
#include "src/uuid-helper.h"
#include "lib/mgmt.h"
#include "src/shared/mainloop.h"
#include "src/shared/io.h"
#include "src/shared/util.h"
#include "src/shared/mgmt.h"
#include "src/shared/shell.h"
#include "client/mgmt.h"
#define SCAN_TYPE_BREDR (1 << BDADDR_BREDR)
#define SCAN_TYPE_LE ((1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM))
#define SCAN_TYPE_DUAL (SCAN_TYPE_BREDR | SCAN_TYPE_LE)
static struct mgmt *mgmt = NULL;
static uint16_t mgmt_index = MGMT_INDEX_NONE;
static bool discovery = false;
static bool resolve_names = true;
static struct {
uint16_t index;
uint16_t req;
struct mgmt_addr_info addr;
} prompt = {
.index = MGMT_INDEX_NONE,
};
static int pending_index = 0;
#ifndef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif
#define PROMPT_ON COLOR_BLUE "[mgmt]" COLOR_OFF "# "
static void update_prompt(uint16_t index)
{
char str[32];
if (index == MGMT_INDEX_NONE)
snprintf(str, sizeof(str), "%s# ",
COLOR_BLUE "[mgmt]" COLOR_OFF);
else
snprintf(str, sizeof(str),
COLOR_BLUE "[hci%u]" COLOR_OFF "# ", index);
bt_shell_set_prompt(str);
}
void mgmt_set_index(const char *arg)
{
if (!arg || !strcmp(arg, "none") || !strcmp(arg, "any") ||
!strcmp(arg, "all"))
mgmt_index = MGMT_INDEX_NONE;
else if (strlen(arg) > 3 && !strncasecmp(arg, "hci", 3))
mgmt_index = atoi(&arg[3]);
else
mgmt_index = atoi(arg);
update_prompt(mgmt_index);
}
static bool parse_setting(int argc, char **argv, uint8_t *val)
{
if (strcasecmp(argv[1], "on") == 0 || strcasecmp(argv[1], "yes") == 0)
*val = 1;
else if (strcasecmp(argv[1], "off") == 0)
*val = 0;
else
*val = atoi(argv[1]);
return true;
}
#define print(fmt, arg...) do { \
bt_shell_printf(fmt "\n", ## arg); \
} while (0)
#define error(fmt, arg...) do { \
bt_shell_printf(COLOR_RED fmt "\n" COLOR_OFF, ## arg); \
} while (0)
static size_t hex2bin(const char *hexstr, uint8_t *buf, size_t buflen)
{
size_t i, len;
len = MIN((strlen(hexstr) / 2), buflen);
memset(buf, 0, len);
for (i = 0; i < len; i++)
sscanf(hexstr + (i * 2), "%02hhX", &buf[i]);
return len;
}
static size_t bin2hex(const uint8_t *buf, size_t buflen, char *str,
size_t strlen)
{
size_t i;
for (i = 0; i < buflen && i < (strlen / 2); i++)
sprintf(str + (i * 2), "%02x", buf[i]);
return i;
}
static void print_eir(const uint8_t *eir, uint16_t eir_len)
{
uint16_t parsed = 0;
char str[33];
while (parsed < eir_len - 1) {
uint8_t field_len = eir[0];
if (field_len == 0)
break;
parsed += field_len + 1;
if (parsed > eir_len)
break;
switch (eir[1]) {
case 0x01:
print("Flags: 0x%02x", eir[2]);
break;
case 0x0d:
print("Class of Device: 0x%02x%02x%02x",
eir[4], eir[3], eir[2]);
break;
case 0x0e:
bin2hex(eir + 2, 16, str, sizeof(str));
print("SSP Hash C-192: %s", str);
break;
case 0x0f:
bin2hex(eir + 2, 16, str, sizeof(str));
print("SSP Rand R-192: %s", str);
break;
case 0x1b:
ba2str((bdaddr_t *) (eir + 2), str);
print("LE Device Address: %s (%s)", str,
eir[8] ? "random" : "public");
break;
case 0x1c:
print("LE Role: 0x%02x", eir[2]);
break;
case 0x1d:
bin2hex(eir + 2, 16, str, sizeof(str));
print("SSP Hash C-256: %s", str);
break;
case 0x1e:
bin2hex(eir + 2, 16, str, sizeof(str));
print("SSP Rand R-256: %s", str);
break;
case 0x22:
bin2hex(eir + 2, 16, str, sizeof(str));
print("LE SC Confirmation Value: %s", str);
break;
case 0x23:
bin2hex(eir + 2, 16, str, sizeof(str));
print("LE SC Random Value: %s", str);
break;
default:
print("Type %u: %u byte%s", eir[1], field_len - 1,
(field_len - 1) == 1 ? "" : "s");
break;
}
eir += field_len + 1;
}
}
static bool load_identity(const char *path, struct mgmt_irk_info *irk)
{
char *addr, *key;
unsigned int type;
int n;
FILE *fp;
fp = fopen(path, "r");
if (!fp) {
error("Failed to open identity file: %s", strerror(errno));
return false;
}
n = fscanf(fp, "%m[0-9a-f:] (type %u) %m[0-9a-f]", &addr, &type, &key);
fclose(fp);
if (n != 3)
return false;
str2ba(addr, &irk->addr.bdaddr);
hex2bin(key, irk->val, sizeof(irk->val));
free(addr);
free(key);
switch (type) {
case 0:
irk->addr.type = BDADDR_LE_PUBLIC;
break;
case 1:
irk->addr.type = BDADDR_LE_RANDOM;
break;
default:
error("Invalid address type %u", type);
return false;
}
return true;
}
static void controller_error(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_ev_controller_error *ev = param;
if (len < sizeof(*ev)) {
error("Too short (%u bytes) controller error event", len);
return;
}
print("hci%u error 0x%02x", index, ev->error_code);
}
static void index_added(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
print("hci%u added", index);
}
static void index_removed(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
print("hci%u removed", index);
}
static void unconf_index_added(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
print("hci%u added (unconfigured)", index);
}
static void unconf_index_removed(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
print("hci%u removed (unconfigured)", index);
}
static void ext_index_added(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_ev_ext_index_added *ev = param;
print("hci%u added (type %u bus %u)", index, ev->type, ev->bus);
}
static void ext_index_removed(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_ev_ext_index_removed *ev = param;
print("hci%u removed (type %u bus %u)", index, ev->type, ev->bus);
}
static const char *options_str[] = {
"external",
"public-address",
};
static const char *options2str(uint32_t options)
{
static char str[256];
unsigned i;
int off;
off = 0;
str[0] = '\0';
for (i = 0; i < NELEM(options_str); i++) {
if ((options & (1 << i)) != 0)
off += snprintf(str + off, sizeof(str) - off, "%s ",
options_str[i]);
}
return str;
}
static void new_config_options(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const uint32_t *ev = param;
if (len < sizeof(*ev)) {
error("Too short new_config_options event (%u)", len);
return;
}
print("hci%u new_config_options: %s", index, options2str(get_le32(ev)));
}
static const char *settings_str[] = {
"powered",
"connectable",
"fast-connectable",
"discoverable",
"bondable",
"link-security",
"ssp",
"br/edr",
"hs",
"le",
"advertising",
"secure-conn",
"debug-keys",
"privacy",
"configuration",
"static-addr",
"phy-configuration",
"wide-band-speech",
"cis-central",
"cis-peripheral",
"iso-broadcaster",
"sync-receiver"
};
static const char *settings2str(uint32_t settings)
{
static char str[256];
unsigned i;
int off;
off = 0;
str[0] = '\0';
for (i = 0; i < NELEM(settings_str); i++) {
if ((settings & (1 << i)) != 0)
off += snprintf(str + off, sizeof(str) - off, "%s ",
settings_str[i]);
}
return str;
}
static void new_settings(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const uint32_t *ev = param;
if (len < sizeof(*ev)) {
error("Too short new_settings event (%u)", len);
return;
}
print("hci%u new_settings: %s", index, settings2str(get_le32(ev)));
}
static void discovering(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_discovering *ev = param;
if (len < sizeof(*ev)) {
error("Too short (%u bytes) discovering event", len);
return;
}
print("hci%u type %u discovering %s", index, ev->type,
ev->discovering ? "on" : "off");
if (ev->discovering == 0 && discovery)
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void new_link_key(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_new_link_key *ev = param;
char addr[18];
if (len != sizeof(*ev)) {
error("Invalid new_link_key length (%u bytes)", len);
return;
}
ba2str(&ev->key.addr.bdaddr, addr);
print("hci%u new_link_key %s type 0x%02x pin_len %d store_hint %u",
index, addr, ev->key.type, ev->key.pin_len, ev->store_hint);
}
static const char *typestr(uint8_t type)
{
static const char *str[] = { "BR/EDR", "LE Public", "LE Random" };
if (type <= BDADDR_LE_RANDOM)
return str[type];
return "(unknown)";
}
static void connected(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_device_connected *ev = param;
uint16_t eir_len;
char addr[18];
if (len < sizeof(*ev)) {
error("Invalid connected event length (%u bytes)", len);
return;
}
eir_len = get_le16(&ev->eir_len);
if (len != sizeof(*ev) + eir_len) {
error("Invalid connected event length (%u != eir_len %u)",
len, eir_len);
return;
}
ba2str(&ev->addr.bdaddr, addr);
print("hci%u %s type %s connected eir_len %u", index, addr,
typestr(ev->addr.type), eir_len);
}
static void disconnected(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_device_disconnected *ev = param;
char addr[18];
uint8_t reason;
if (len < sizeof(struct mgmt_addr_info)) {
error("Invalid disconnected event length (%u bytes)", len);
return;
}
if (len < sizeof(*ev))
reason = MGMT_DEV_DISCONN_UNKNOWN;
else
reason = ev->reason;
ba2str(&ev->addr.bdaddr, addr);
print("hci%u %s type %s disconnected with reason %u",
index, addr, typestr(ev->addr.type), reason);
}
static void conn_failed(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_connect_failed *ev = param;
char addr[18];
if (len != sizeof(*ev)) {
error("Invalid connect_failed event length (%u bytes)", len);
return;
}
ba2str(&ev->addr.bdaddr, addr);
print("hci%u %s type %s connect failed (status 0x%02x, %s)",
index, addr, typestr(ev->addr.type), ev->status,
mgmt_errstr(ev->status));
}
static void auth_failed(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_auth_failed *ev = param;
char addr[18];
if (len != sizeof(*ev)) {
error("Invalid auth_failed event length (%u bytes)", len);
return;
}
ba2str(&ev->addr.bdaddr, addr);
print("hci%u %s auth failed with status 0x%02x (%s)",
index, addr, ev->status, mgmt_errstr(ev->status));
}
static void class_of_dev_changed(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_ev_class_of_dev_changed *ev = param;
if (len != sizeof(*ev)) {
error("Invalid class_of_dev_changed length (%u bytes)", len);
return;
}
print("hci%u class of device changed: 0x%02x%02x%02x", index,
ev->dev_class[2], ev->dev_class[1], ev->dev_class[0]);
}
static void local_name_changed(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_local_name_changed *ev = param;
if (len != sizeof(*ev)) {
error("Invalid local_name_changed length (%u bytes)", len);
return;
}
print("hci%u name changed: %s", index, ev->name);
}
static void confirm_name_rsp(uint8_t status, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_rp_confirm_name *rp = param;
char addr[18];
if (len == 0 && status != 0) {
error("confirm_name failed with status 0x%02x (%s)", status,
mgmt_errstr(status));
return;
}
if (len != sizeof(*rp)) {
error("confirm_name rsp length %u instead of %zu",
len, sizeof(*rp));
return;
}
ba2str(&rp->addr.bdaddr, addr);
if (status != 0)
error("confirm_name for %s failed: 0x%02x (%s)",
addr, status, mgmt_errstr(status));
else
print("confirm_name succeeded for %s", addr);
}
static char *eir_get_name(const uint8_t *eir, uint16_t eir_len)
{
uint8_t parsed = 0;
if (eir_len < 2)
return NULL;
while (parsed < eir_len - 1) {
uint8_t field_len = eir[0];
if (field_len == 0)
break;
parsed += field_len + 1;
if (parsed > eir_len)
break;
/* Check for short of complete name */
if (eir[1] == 0x09 || eir[1] == 0x08)
return strndup((char *) &eir[2], field_len - 1);
eir += field_len + 1;
}
return NULL;
}
static unsigned int eir_get_flags(const uint8_t *eir, uint16_t eir_len)
{
uint8_t parsed = 0;
if (eir_len < 2)
return 0;
while (parsed < eir_len - 1) {
uint8_t field_len = eir[0];
if (field_len == 0)
break;
parsed += field_len + 1;
if (parsed > eir_len)
break;
/* Check for flags */
if (eir[1] == 0x01)
return eir[2];
eir += field_len + 1;
}
return 0;
}
static void device_found(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_device_found *ev = param;
struct mgmt *mgmt = user_data;
uint16_t eir_len;
uint32_t flags;
if (len < sizeof(*ev)) {
error("Too short device_found length (%u bytes)", len);
return;
}
flags = btohl(ev->flags);
eir_len = get_le16(&ev->eir_len);
if (len != sizeof(*ev) + eir_len) {
error("dev_found: expected %zu bytes, got %u bytes",
sizeof(*ev) + eir_len, len);
return;
}
if (discovery) {
char addr[18], *name;
ba2str(&ev->addr.bdaddr, addr);
print("hci%u dev_found: %s type %s rssi %d "
"flags 0x%04x ", index, addr,
typestr(ev->addr.type), ev->rssi, flags);
if (ev->addr.type != BDADDR_BREDR)
print("AD flags 0x%02x ",
eir_get_flags(ev->eir, eir_len));
name = eir_get_name(ev->eir, eir_len);
if (name)
print("name %s", name);
else
print("eir_len %u", eir_len);
free(name);
}
if (discovery && (flags & MGMT_DEV_FOUND_CONFIRM_NAME)) {
struct mgmt_cp_confirm_name cp;
memset(&cp, 0, sizeof(cp));
memcpy(&cp.addr, &ev->addr, sizeof(cp.addr));
if (resolve_names)
cp.name_known = 0;
else
cp.name_known = 1;
mgmt_reply(mgmt, MGMT_OP_CONFIRM_NAME, index, sizeof(cp), &cp,
confirm_name_rsp, NULL, NULL);
}
}
static void pin_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("PIN Code reply failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("PIN Reply successful");
}
static int mgmt_pin_reply(uint16_t index, const struct mgmt_addr_info *addr,
const char *pin, size_t len)
{
struct mgmt_cp_pin_code_reply cp;
memset(&cp, 0, sizeof(cp));
memcpy(&cp.addr, addr, sizeof(cp.addr));
cp.pin_len = len;
memcpy(cp.pin_code, pin, len);
return mgmt_reply(mgmt, MGMT_OP_PIN_CODE_REPLY, index,
sizeof(cp), &cp, pin_rsp, NULL, NULL);
}
static void pin_neg_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("PIN Neg reply failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("PIN Negative Reply successful");
}
static int mgmt_pin_neg_reply(uint16_t index, const struct mgmt_addr_info *addr)
{
struct mgmt_cp_pin_code_neg_reply cp;
memset(&cp, 0, sizeof(cp));
memcpy(&cp.addr, addr, sizeof(cp.addr));
return mgmt_reply(mgmt, MGMT_OP_PIN_CODE_NEG_REPLY, index,
sizeof(cp), &cp, pin_neg_rsp, NULL, NULL);
}
static void confirm_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("User Confirm reply failed. status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("User Confirm Reply successful");
}
static int mgmt_confirm_reply(uint16_t index, const struct mgmt_addr_info *addr)
{
struct mgmt_cp_user_confirm_reply cp;
memset(&cp, 0, sizeof(cp));
memcpy(&cp.addr, addr, sizeof(*addr));
return mgmt_reply(mgmt, MGMT_OP_USER_CONFIRM_REPLY, index,
sizeof(cp), &cp, confirm_rsp, NULL, NULL);
}
static void confirm_neg_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("Confirm Neg reply failed. status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("User Confirm Negative Reply successful");
}
static int mgmt_confirm_neg_reply(uint16_t index,
const struct mgmt_addr_info *addr)
{
struct mgmt_cp_user_confirm_reply cp;
memset(&cp, 0, sizeof(cp));
memcpy(&cp.addr, addr, sizeof(*addr));
return mgmt_reply(mgmt, MGMT_OP_USER_CONFIRM_NEG_REPLY, index,
sizeof(cp), &cp, confirm_neg_rsp, NULL, NULL);
}
static void passkey_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("User Passkey reply failed. status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("User Passkey Reply successful");
}
static int mgmt_passkey_reply(uint16_t index, const struct mgmt_addr_info *addr,
uint32_t passkey)
{
struct mgmt_cp_user_passkey_reply cp;
memset(&cp, 0, sizeof(cp));
memcpy(&cp.addr, addr, sizeof(*addr));
put_le32(passkey, &cp.passkey);
return mgmt_reply(mgmt, MGMT_OP_USER_PASSKEY_REPLY, index,
sizeof(cp), &cp, passkey_rsp, NULL, NULL);
}
static void passkey_neg_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("Passkey Neg reply failed. status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("User Passkey Negative Reply successful");
}
static int mgmt_passkey_neg_reply(uint16_t index,
const struct mgmt_addr_info *addr)
{
struct mgmt_cp_user_passkey_reply cp;
memset(&cp, 0, sizeof(cp));
memcpy(&cp.addr, addr, sizeof(*addr));
return mgmt_reply(mgmt, MGMT_OP_USER_PASSKEY_NEG_REPLY, index,
sizeof(cp), &cp, passkey_neg_rsp, NULL, NULL);
}
static void prompt_input(const char *input, void *user_data)
{
size_t len;
len = strlen(input);
switch (prompt.req) {
case MGMT_EV_PIN_CODE_REQUEST:
if (len)
mgmt_pin_reply(prompt.index, &prompt.addr, input, len);
else
mgmt_pin_neg_reply(prompt.index, &prompt.addr);
break;
case MGMT_EV_USER_PASSKEY_REQUEST:
if (strlen(input) > 0)
mgmt_passkey_reply(prompt.index, &prompt.addr,
atoi(input));
else
mgmt_passkey_neg_reply(prompt.index,
&prompt.addr);
break;
case MGMT_EV_USER_CONFIRM_REQUEST:
if (len) {
if (input[0] == 'y' || input[0] == 'Y')
mgmt_confirm_reply(prompt.index, &prompt.addr);
else
mgmt_confirm_neg_reply(prompt.index,
&prompt.addr);
} else {
mgmt_confirm_neg_reply(prompt.index, &prompt.addr);
bt_shell_set_prompt(PROMPT_ON);
}
break;
}
}
static void ask(uint16_t index, uint16_t req, const struct mgmt_addr_info *addr,
const char *fmt, ...)
{
char msg[256];
va_list ap;
int off;
prompt.index = index;
prompt.req = req;
memcpy(&prompt.addr, addr, sizeof(*addr));
va_start(ap, fmt);
off = vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
snprintf(msg + off, sizeof(msg) - off, " %s ",
COLOR_BOLDGRAY ">>" COLOR_OFF);
bt_shell_prompt_input("", msg, prompt_input, NULL);
}
static void request_pin(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_pin_code_request *ev = param;
char addr[18];
if (len != sizeof(*ev)) {
error("Invalid pin_code request length (%u bytes)", len);
return;
}
ba2str(&ev->addr.bdaddr, addr);
print("hci%u %s request PIN", index, addr);
ask(index, MGMT_EV_PIN_CODE_REQUEST, &ev->addr,
"PIN Request (press enter to reject)");
}
static void user_confirm(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_user_confirm_request *ev = param;
uint32_t val;
char addr[18];
if (len != sizeof(*ev)) {
error("Invalid user_confirm request length (%u)", len);
return;
}
ba2str(&ev->addr.bdaddr, addr);
val = get_le32(&ev->value);
print("hci%u %s User Confirm %06u hint %u", index, addr,
val, ev->confirm_hint);
if (ev->confirm_hint)
ask(index, MGMT_EV_USER_CONFIRM_REQUEST, &ev->addr,
"Accept pairing with %s (yes/no)", addr);
else
ask(index, MGMT_EV_USER_CONFIRM_REQUEST, &ev->addr,
"Confirm value %06u for %s (yes/no)", val, addr);
}
static void request_passkey(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_user_passkey_request *ev = param;
char addr[18];
if (len != sizeof(*ev)) {
error("Invalid passkey request length (%u bytes)", len);
return;
}
ba2str(&ev->addr.bdaddr, addr);
print("hci%u %s request passkey", index, addr);
ask(index, MGMT_EV_USER_PASSKEY_REQUEST, &ev->addr,
"Passkey Request (press enter to reject)");
}
static void passkey_notify(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_passkey_notify *ev = param;
char addr[18];
if (len != sizeof(*ev)) {
error("Invalid passkey request length (%u bytes)", len);
return;
}
ba2str(&ev->addr.bdaddr, addr);
print("hci%u %s request passkey", index, addr);
print("Passkey Notify: %06u (entered %u)", get_le32(&ev->passkey),
ev->entered);
}
static void local_oob_data_updated(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_ev_local_oob_data_updated *ev = param;
uint16_t eir_len;
if (len < sizeof(*ev)) {
error("Too small (%u bytes) local_oob_updated event", len);
return;
}
eir_len = le16_to_cpu(ev->eir_len);
if (len != sizeof(*ev) + eir_len) {
error("local_oob_updated: expected %zu bytes, got %u bytes",
sizeof(*ev) + eir_len, len);
return;
}
print("hci%u oob data updated: type %u len %u", index,
ev->type, eir_len);
}
static void advertising_added(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_ev_advertising_added *ev = param;
if (len < sizeof(*ev)) {
error("Too small (%u bytes) advertising_added event", len);
return;
}
print("hci%u advertising_added: instance %u", index, ev->instance);
}
static void advertising_removed(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_ev_advertising_removed *ev = param;
if (len < sizeof(*ev)) {
error("Too small (%u bytes) advertising_removed event", len);
return;
}
print("hci%u advertising_removed: instance %u", index, ev->instance);
}
static void flags_changed(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_device_flags_changed *ev = param;
char addr[18];
if (len < sizeof(*ev)) {
error("Too small (%u bytes) %s event", len, __func__);
return;
}
ba2str(&ev->addr.bdaddr, addr);
print("hci%u device_flags_changed: %s (%s)", index, addr,
typestr(ev->addr.type));
print(" supp: 0x%08x curr: 0x%08x",
ev->supported_flags, ev->current_flags);
}
static void advmon_added(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_adv_monitor_added *ev = param;
if (len < sizeof(*ev)) {
error("Too small (%u bytes) %s event", len, __func__);
return;
}
print("hci%u %s: handle %u", index, __func__,
le16_to_cpu(ev->monitor_handle));
}
static void advmon_removed(uint16_t index, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_ev_adv_monitor_removed *ev = param;
if (len < sizeof(*ev)) {
error("Too small (%u bytes) %s event", len, __func__);
return;
}
print("hci%u %s: handle %u", index, __func__,
le16_to_cpu(ev->monitor_handle));
}
static void version_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_version *rp = param;
if (status != 0) {
error("Reading mgmt version failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small version reply (%u bytes)", len);
goto done;
}
print("MGMT Version %u, revision %u", rp->version,
get_le16(&rp->revision));
done:
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_revision(int argc, char **argv)
{
if (mgmt_send(mgmt, MGMT_OP_READ_VERSION, MGMT_INDEX_NONE,
0, NULL, version_rsp, NULL, NULL) == 0) {
error("Unable to send read_version cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void commands_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_commands *rp = param;
uint16_t num_commands, num_events;
size_t expected_len;
int i;
if (status != 0) {
error("Read Supported Commands failed: status 0x%02x (%s)",
status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small commands reply (%u bytes)", len);
goto done;
}
num_commands = get_le16(&rp->num_commands);
num_events = get_le16(&rp->num_events);
expected_len = sizeof(*rp) + num_commands * sizeof(uint16_t) +
num_events * sizeof(uint16_t);
if (len < expected_len) {
error("Too small commands reply (%u != %zu)",
len, expected_len);
goto done;
}
print("%u commands:", num_commands);
for (i = 0; i < num_commands; i++) {
uint16_t op = get_le16(rp->opcodes + i);
print("\t%s (0x%04x)", mgmt_opstr(op), op);
}
print("%u events:", num_events);
for (i = 0; i < num_events; i++) {
uint16_t ev = get_le16(rp->opcodes + num_commands + i);
print("\t%s (0x%04x)", mgmt_evstr(ev), ev);
}
done:
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_commands(int argc,
char **argv)
{
if (mgmt_send(mgmt, MGMT_OP_READ_COMMANDS, MGMT_INDEX_NONE,
0, NULL, commands_rsp, NULL, NULL) == 0) {
error("Unable to send read_commands cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void config_info_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_config_info *rp = param;
uint16_t index = PTR_TO_UINT(user_data);
uint32_t supported_options, missing_options;
if (status != 0) {
error("Reading hci%u config failed with status 0x%02x (%s)",
index, status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small info reply (%u bytes)", len);
goto done;
}
print("hci%u:\tUnconfigured controller", index);
print("\tmanufacturer %u", le16_to_cpu(rp->manufacturer));
supported_options = le32_to_cpu(rp->supported_options);
print("\tsupported options: %s", options2str(supported_options));
missing_options = le32_to_cpu(rp->missing_options);
print("\tmissing options: %s", options2str(missing_options));
done:
pending_index--;
if (pending_index > 0)
return;
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void unconf_index_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_unconf_index_list *rp = param;
uint16_t count;
unsigned int i;
if (status != 0) {
error("Reading index list failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (len < sizeof(*rp)) {
error("Too small index list reply (%u bytes)", len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
count = le16_to_cpu(rp->num_controllers);
if (len < sizeof(*rp) + count * sizeof(uint16_t)) {
error("Index count (%u) doesn't match reply length (%u)",
count, len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("Unconfigured index list with %u item%s",
count, count != 1 ? "s" : "");
for (i = 0; i < count; i++) {
uint16_t index = le16_to_cpu(rp->index[i]);
if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO, index, 0, NULL,
config_info_rsp, UINT_TO_PTR(index), NULL)) {
error("Unable to send read_config_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
pending_index++;
}
if (!count)
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_config(int argc, char **argv)
{
if (mgmt_index == MGMT_INDEX_NONE) {
if (!mgmt_send(mgmt, MGMT_OP_READ_UNCONF_INDEX_LIST,
MGMT_INDEX_NONE, 0, NULL,
unconf_index_rsp, mgmt, NULL)) {
error("Unable to send unconf_index_list cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
return;
}
if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO, mgmt_index, 0, NULL,
config_info_rsp, UINT_TO_PTR(index), NULL)) {
error("Unable to send read_config_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void config_options_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_config_info *rp = param;
uint16_t index = PTR_TO_UINT(user_data);
uint32_t supported_options, missing_options;
if (status != 0) {
error("Reading hci%u config failed with status 0x%02x (%s)",
index, status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small info reply (%u bytes)", len);
goto done;
}
print("hci%u:\tConfiguration options", index);
supported_options = le32_to_cpu(rp->supported_options);
print("\tsupported options: %s", options2str(supported_options));
missing_options = le32_to_cpu(rp->missing_options);
print("\tmissing options: %s", options2str(missing_options));
done:
pending_index--;
if (pending_index > 0)
return;
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void info_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_info *rp = param;
uint16_t index = PTR_TO_UINT(user_data);
uint32_t supported_settings, current_settings;
char addr[18];
if (status != 0) {
error("Reading hci%u info failed with status 0x%02x (%s)",
index, status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small info reply (%u bytes)", len);
goto done;
}
print("hci%u:\tPrimary controller", index);
ba2str(&rp->bdaddr, addr);
print("\taddr %s version %u manufacturer %u class 0x%02x%02x%02x",
addr, rp->version, le16_to_cpu(rp->manufacturer),
rp->dev_class[2], rp->dev_class[1], rp->dev_class[0]);
supported_settings = le32_to_cpu(rp->supported_settings);
print("\tsupported settings: %s", settings2str(supported_settings));
current_settings = le32_to_cpu(rp->current_settings);
print("\tcurrent settings: %s", settings2str(current_settings));
print("\tname %s", rp->name);
print("\tshort name %s", rp->short_name);
if (supported_settings & MGMT_SETTING_CONFIGURATION) {
if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO,
index, 0, NULL, config_options_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read_config cmd");
goto done;
}
return;
}
done:
pending_index--;
if (pending_index > 0)
return;
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void ext_info_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_ext_info *rp = param;
uint16_t index = PTR_TO_UINT(user_data);
uint32_t supported_settings, current_settings;
char addr[18];
if (status != 0) {
error("Reading hci%u info failed with status 0x%02x (%s)",
index, status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small info reply (%u bytes)", len);
goto done;
}
print("hci%u:\tPrimary controller", index);
ba2str(&rp->bdaddr, addr);
print("\taddr %s version %u manufacturer %u",
addr, rp->version, le16_to_cpu(rp->manufacturer));
supported_settings = le32_to_cpu(rp->supported_settings);
print("\tsupported settings: %s", settings2str(supported_settings));
current_settings = le32_to_cpu(rp->current_settings);
print("\tcurrent settings: %s", settings2str(current_settings));
if (supported_settings & MGMT_SETTING_CONFIGURATION) {
if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO,
index, 0, NULL, config_options_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read_config cmd");
goto done;
}
return;
}
done:
pending_index--;
if (pending_index > 0)
return;
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void index_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_index_list *rp = param;
struct mgmt *mgmt = user_data;
uint16_t count;
unsigned int i;
if (status != 0) {
error("Reading index list failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (len < sizeof(*rp)) {
error("Too small index list reply (%u bytes)", len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
count = le16_to_cpu(rp->num_controllers);
if (len < sizeof(*rp) + count * sizeof(uint16_t)) {
error("Index count (%u) doesn't match reply length (%u)",
count, len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("Index list with %u item%s", count, count != 1 ? "s" : "");
for (i = 0; i < count; i++) {
uint16_t index = le16_to_cpu(rp->index[i]);
if (!mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL,
info_rsp, UINT_TO_PTR(index), NULL)) {
error("Unable to send read_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
pending_index++;
}
if (!count)
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_info(int argc, char **argv)
{
if (mgmt_index == MGMT_INDEX_NONE) {
if (!mgmt_send(mgmt, MGMT_OP_READ_INDEX_LIST,
MGMT_INDEX_NONE, 0, NULL,
index_rsp, mgmt, NULL)) {
error("Unable to send index_list cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
return;
}
if (!mgmt_send(mgmt, MGMT_OP_READ_INFO, mgmt_index, 0, NULL, info_rsp,
UINT_TO_PTR(mgmt_index), NULL)) {
error("Unable to send read_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void ext_index_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_ext_index_list *rp = param;
uint16_t count;
unsigned int i;
if (status != 0) {
error("Reading ext index list failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (len < sizeof(*rp)) {
error("Too small ext index list reply (%u bytes)", len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
count = get_le16(&rp->num_controllers);
if (len < sizeof(*rp) + count * (sizeof(uint16_t) + sizeof(uint8_t))) {
error("Index count (%u) doesn't match reply length (%u)",
count, len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("Extended index list with %u item%s",
count, count != 1 ? "s" : "");
for (i = 0; i < count; i++) {
uint16_t index = le16_to_cpu(rp->entry[i].index);
const char *busstr = hci_bustostr(rp->entry[i].bus);
switch (rp->entry[i].type) {
case 0x00:
print("Primary controller (hci%u,%s)", index, busstr);
if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INFO,
index, 0, NULL, ext_info_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read_ext_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
pending_index++;
break;
case 0x01:
print("Unconfigured controller (hci%u,%s)",
index, busstr);
if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO,
index, 0, NULL, config_info_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read_config cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
pending_index++;
break;
case 0x02:
print("AMP controller (hci%u,%s)", index, busstr);
break;
default:
print("Type %u controller (hci%u,%s)",
rp->entry[i].type, index, busstr);
break;
}
}
print("");
if (!count)
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_extinfo(int argc, char **argv)
{
if (mgmt_index == MGMT_INDEX_NONE) {
if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INDEX_LIST,
MGMT_INDEX_NONE, 0, NULL,
ext_index_rsp, mgmt, NULL)) {
error("Unable to send ext_index_list cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
return;
}
if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INFO, mgmt_index, 0, NULL,
ext_info_rsp,
UINT_TO_PTR(mgmt_index), NULL)) {
error("Unable to send ext_read_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void print_cap(const uint8_t *cap, uint16_t cap_len)
{
uint16_t parsed = 0;
while (parsed < cap_len - 1) {
uint8_t field_len = cap[0];
if (field_len == 0)
break;
parsed += field_len + 1;
if (parsed > cap_len)
break;
switch (cap[1]) {
case 0x01:
print("\tFlags: 0x%02x", cap[2]);
break;
case 0x02:
print("\tMax Key Size (BR/EDR): %u", cap[2]);
break;
case 0x03:
print("\tMax Key Size (LE): %u", cap[2]);
break;
default:
print("\tType %u: %u byte%s", cap[1], field_len - 1,
(field_len - 1) == 1 ? "" : "s");
break;
}
cap += field_len + 1;
}
}
static void sec_info_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_controller_cap *rp = param;
uint16_t index = PTR_TO_UINT(user_data);
if (status != 0) {
error("Reading hci%u security failed with status 0x%02x (%s)",
index, status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small info reply (%u bytes)", len);
goto done;
}
print("Primary controller (hci%u)", index);
print("\tInfo length: %u", le16_to_cpu(rp->cap_len));
print_cap(rp->cap, le16_to_cpu(rp->cap_len));
done:
pending_index--;
if (pending_index > 0)
return;
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void sec_index_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_ext_index_list *rp = param;
uint16_t count;
unsigned int i;
if (status != 0) {
error("Reading ext index list failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (len < sizeof(*rp)) {
error("Too small ext index list reply (%u bytes)", len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
count = get_le16(&rp->num_controllers);
if (len < sizeof(*rp) + count * (sizeof(uint16_t) + sizeof(uint8_t))) {
error("Index count (%u) doesn't match reply length (%u)",
count, len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
for (i = 0; i < count; i++) {
uint16_t index = le16_to_cpu(rp->entry[i].index);
if (rp->entry[i].type != 0x00)
continue;
if (!mgmt_send(mgmt, MGMT_OP_READ_CONTROLLER_CAP,
index, 0, NULL, sec_info_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read_security_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
pending_index++;
}
if (!count)
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_secinfo(int argc, char **argv)
{
if (mgmt_index == MGMT_INDEX_NONE) {
if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INDEX_LIST,
MGMT_INDEX_NONE, 0, NULL,
sec_index_rsp, mgmt, NULL)) {
error("Unable to send ext_index_list cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
return;
}
if (!mgmt_send(mgmt, MGMT_OP_READ_CONTROLLER_CAP, mgmt_index, 0, NULL,
sec_info_rsp,
UINT_TO_PTR(mgmt_index), NULL)) {
error("Unable to send read_security_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void exp_info_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_exp_features_info *rp = param;
uint16_t index = PTR_TO_UINT(user_data);
if (status != 0) {
error("Reading hci%u exp features failed with status 0x%02x (%s)",
index, status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small info reply (%u bytes)", len);
goto done;
}
if (index == MGMT_INDEX_NONE)
print("Global");
else
print("Primary controller (hci%u)", index);
print("\tNumber of experimental features: %u",
le16_to_cpu(rp->feature_count));
done:
pending_index--;
if (pending_index > 0)
return;
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void exp_index_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_read_ext_index_list *rp = param;
uint16_t count;
unsigned int i;
if (status != 0) {
error("Reading ext index list failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (len < sizeof(*rp)) {
error("Too small ext index list reply (%u bytes)", len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
count = get_le16(&rp->num_controllers);
if (len < sizeof(*rp) + count * (sizeof(uint16_t) + sizeof(uint8_t))) {
error("Index count (%u) doesn't match reply length (%u)",
count, len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
for (i = 0; i < count; i++) {
uint16_t index = le16_to_cpu(rp->entry[i].index);
if (rp->entry[i].type != 0x00)
continue;
if (!mgmt_send(mgmt, MGMT_OP_READ_EXP_FEATURES_INFO,
index, 0, NULL, exp_info_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read_exp_features_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
pending_index++;
}
}
static void cmd_expinfo(int argc, char **argv)
{
if (mgmt_index == MGMT_INDEX_NONE) {
if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INDEX_LIST,
MGMT_INDEX_NONE, 0, NULL,
exp_index_rsp, mgmt, NULL)) {
error("Unable to send ext_index_list cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (!mgmt_send(mgmt, MGMT_OP_READ_EXP_FEATURES_INFO,
MGMT_INDEX_NONE, 0, NULL,
exp_info_rsp,
UINT_TO_PTR(MGMT_INDEX_NONE), NULL)) {
error("Unable to send read_exp_features_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
pending_index++;
return;
}
if (!mgmt_send(mgmt, MGMT_OP_READ_EXP_FEATURES_INFO, mgmt_index,
0, NULL, exp_info_rsp,
UINT_TO_PTR(mgmt_index), NULL)) {
error("Unable to send read_exp_features_info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void exp_debug_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0)
error("Set debug feature failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
else
print("Debug feature successfully set");
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_exp_debug(int argc, char **argv)
{
/* d4992530-b9ec-469f-ab01-6c481c47da1c */
static const uint8_t uuid[16] = {
0x1c, 0xda, 0x47, 0x1c, 0x48, 0x6c, 0x01, 0xab,
0x9f, 0x46, 0xec, 0xb9, 0x30, 0x25, 0x99, 0xd4,
};
struct mgmt_cp_set_exp_feature cp;
uint8_t val;
if (parse_setting(argc, argv, &val) == false)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
memset(&cp, 0, sizeof(cp));
memcpy(cp.uuid, uuid, 16);
cp.action = val;
if (mgmt_send(mgmt, MGMT_OP_SET_EXP_FEATURE, mgmt_index,
sizeof(cp), &cp, exp_debug_rsp, NULL, NULL) == 0) {
error("Unable to send debug feature cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void exp_privacy_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0)
error("Set LL privacy feature failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
else
print("LL privacy feature successfully set");
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_exp_privacy(int argc, char **argv)
{
/* 15c0a148-c273-11ea-b3de-0242ac130004 */
static const uint8_t uuid[16] = {
0x04, 0x00, 0x13, 0xac, 0x42, 0x02, 0xde, 0xb3,
0xea, 0x11, 0x73, 0xc2, 0x48, 0xa1, 0xc0, 0x15,
};
struct mgmt_cp_set_exp_feature cp;
uint8_t val;
if (parse_setting(argc, argv, &val) == false)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
memset(&cp, 0, sizeof(cp));
memcpy(cp.uuid, uuid, 16);
cp.action = val;
if (mgmt_send(mgmt, MGMT_OP_SET_EXP_FEATURE, mgmt_index,
sizeof(cp), &cp, exp_privacy_rsp, NULL, NULL) == 0) {
error("Unable to send LL privacy feature cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void exp_quality_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0)
error("Set Quality Report feature failed: 0x%02x (%s)",
status, mgmt_errstr(status));
else
print("Quality Report feature successfully set");
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_exp_quality(int argc, char **argv)
{
/* 330859bc-7506-492d-9370-9a6f0614037f */
static const uint8_t uuid[16] = {
0x7f, 0x03, 0x14, 0x06, 0x6f, 0x9a, 0x70, 0x93,
0x2d, 0x49, 0x06, 0x75, 0xbc, 0x59, 0x08, 0x33,
};
struct mgmt_cp_set_exp_feature cp;
uint8_t val;
if (mgmt_index == MGMT_INDEX_NONE) {
error("BQR feature requires a valid controller index");
return;
}
if (parse_setting(argc, argv, &val) == false)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (val != 0 && val != 1) {
error("Invalid value %u", val);
return;
}
memset(&cp, 0, sizeof(cp));
memcpy(cp.uuid, uuid, 16);
cp.action = val;
if (mgmt_send(mgmt, MGMT_OP_SET_EXP_FEATURE, mgmt_index,
sizeof(cp), &cp, exp_quality_rsp, NULL, NULL) == 0) {
error("Unable to send quality report feature cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void print_mgmt_tlv(void *data, void *user_data)
{
const struct mgmt_tlv *entry = data;
char buf[256];
bin2hex(entry->value, entry->length, buf, sizeof(buf));
print("Type: 0x%04x\tLength: %02hhu\tValue: %s", entry->type,
entry->length, buf);
}
static void read_sysconfig_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
struct mgmt_tlv_list *tlv_list;
if (status != 0) {
error("Read system configuration failed with status "
"0x%02x (%s)", status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
tlv_list = mgmt_tlv_list_load_from_buf(param, len);
if (!tlv_list) {
error("Unable to parse response of read system configuration");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
mgmt_tlv_list_foreach(tlv_list, print_mgmt_tlv, NULL);
mgmt_tlv_list_free(tlv_list);
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_read_sysconfig(int argc, char **argv)
{
uint16_t index;
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
if (!mgmt_send(mgmt, MGMT_OP_READ_DEF_SYSTEM_CONFIG, index,
0, NULL, read_sysconfig_rsp, NULL, NULL)) {
error("Unable to send read system configuration cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static bool parse_mgmt_tlv(const char *input, uint16_t *type, uint8_t *length,
uint8_t *value)
{
int i, value_starting_pos;
if (sscanf(input, "%4hx:%1hhu:%n", type, length,
&value_starting_pos) < 2) {
return false;
}
input += value_starting_pos;
if (*length * 2 != strlen(input))
return false;
for (i = 0; i < *length; i++) {
if (sscanf(input + i * 2, "%2hhx", &value[i]) < 1)
return false;
}
return true;
}
static void set_sysconfig_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != MGMT_STATUS_SUCCESS) {
error("Could not set default system configuration with status "
"0x%02x (%s)", status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("Set default system configuration success");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static bool set_sysconfig(int argc, char **argv)
{
struct mgmt_tlv_list *tlv_list = NULL;
int i;
uint16_t index, type;
uint8_t length;
uint8_t value[256] = {};
bool success = false;
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
tlv_list = mgmt_tlv_list_new();
if (!tlv_list) {
error("tlv_list failed to init");
goto failed;
}
for (i = 0; i < argc; i++) {
if (!parse_mgmt_tlv(argv[i], &type, &length, value)) {
error("failed to parse");
goto failed;
}
if (!mgmt_tlv_add(tlv_list, type, length, value)) {
error("failed to add");
goto failed;
}
}
if (!mgmt_send_tlv(mgmt, MGMT_OP_SET_DEF_SYSTEM_CONFIG, index,
tlv_list, set_sysconfig_rsp, NULL, NULL)) {
error("Failed to send \"Set Default System Configuration\""
" command");
goto failed;
}
success = true;
failed:
if (tlv_list)
mgmt_tlv_list_free(tlv_list);
return success;
}
static void set_sysconfig_usage(void)
{
bt_shell_usage();
print("Parameters:\n\t-v <type:length:value>...\n"
"e.g.:\n\tset-sysconfig -v 001a:2:1234 001f:1:00");
}
static void cmd_set_sysconfig(int argc, char **argv)
{
bool success = false;
if (strcasecmp(argv[1], "-v") == 0 && argc > 2) {
argc -= 2;
argv += 2;
success = set_sysconfig(argc, argv);
}
if (!success) {
set_sysconfig_usage();
bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void auto_power_enable_rsp(uint8_t status, uint16_t len,
const void *param, void *user_data)
{
uint16_t index = PTR_TO_UINT(user_data);
print("Successfully enabled controller with index %u", index);
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void auto_power_info_rsp(uint8_t status, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_rp_read_info *rp = param;
uint16_t index = PTR_TO_UINT(user_data);
uint32_t supported_settings, current_settings, missing_settings;
uint8_t val = 0x01;
if (status) {
error("Reading info failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
supported_settings = le32_to_cpu(rp->supported_settings);
current_settings = le32_to_cpu(rp->current_settings);
missing_settings = current_settings ^ supported_settings;
if (missing_settings & MGMT_SETTING_BREDR)
mgmt_send(mgmt, MGMT_OP_SET_BREDR, index, sizeof(val), &val,
NULL, NULL, NULL);
if (missing_settings & MGMT_SETTING_SSP)
mgmt_send(mgmt, MGMT_OP_SET_SSP, index, sizeof(val), &val,
NULL, NULL, NULL);
if (missing_settings & MGMT_SETTING_LE)
mgmt_send(mgmt, MGMT_OP_SET_LE, index, sizeof(val), &val,
NULL, NULL, NULL);
if (missing_settings & MGMT_SETTING_SECURE_CONN)
mgmt_send(mgmt, MGMT_OP_SET_SECURE_CONN, index,
sizeof(val), &val,
NULL, NULL, NULL);
if (missing_settings & MGMT_SETTING_BONDABLE)
mgmt_send(mgmt, MGMT_OP_SET_BONDABLE, index, sizeof(val), &val,
NULL, NULL, NULL);
if (current_settings & MGMT_SETTING_POWERED)
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
if (!mgmt_send(mgmt, MGMT_OP_SET_POWERED, index, sizeof(val), &val,
auto_power_enable_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send set powerd cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void auto_power_index_evt(uint16_t index, uint16_t len,
const void *param, void *user_data)
{
uint16_t index_filter = PTR_TO_UINT(user_data);
if (index != index_filter)
return;
print("New controller with index %u", index);
if (!mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL,
auto_power_info_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void auto_power_index_rsp(uint8_t status, uint16_t len,
const void *param, void *user_data)
{
const struct mgmt_rp_read_index_list *rp = param;
uint16_t index = PTR_TO_UINT(user_data);
uint16_t i, count;
bool found = false;
if (status) {
error("Reading index list failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
count = le16_to_cpu(rp->num_controllers);
for (i = 0; i < count; i++) {
if (le16_to_cpu(rp->index[i]) == index)
found = true;
}
if (!found) {
print("Waiting for index %u to appear", index);
mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, index,
auto_power_index_evt,
UINT_TO_PTR(index), NULL);
return;
}
print("Found controller with index %u", index);
if (!mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL,
auto_power_info_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read info cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void cmd_auto_power(int argc, char **argv)
{
int index;
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
if (!mgmt_send(mgmt, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, NULL,
auto_power_index_rsp,
UINT_TO_PTR(index), NULL)) {
error("Unable to send read index list cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void get_flags_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_get_device_flags *rp = param;
if (status != 0) {
error("Get device flags failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("Supported Flags: 0x%08x", rp->supported_flags);
print("Current Flags: 0x%08x", rp->current_flags);
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static const struct option get_flags_options[] = {
{ "help", 0, 0, 'h' },
{ "type", 1, 0, 't' },
{ 0, 0, 0, 0 }
};
static void cmd_get_flags(int argc, char **argv)
{
struct mgmt_cp_get_device_flags cp;
uint8_t type = BDADDR_BREDR;
char addr[18];
int opt;
uint16_t index;
while ((opt = getopt_long(argc, argv, "+t:h", get_flags_options,
NULL)) != -1) {
switch (opt) {
case 't':
type = strtol(optarg, NULL, 0);
break;
case 'h':
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
default:
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
optind = 0;
if (argc < 1) {
bt_shell_usage();
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
memset(&cp, 0, sizeof(cp));
str2ba(argv[0], &cp.addr.bdaddr);
cp.addr.type = type;
ba2str(&cp.addr.bdaddr, addr);
print("Get device flag of %s (%s)", addr, typestr(cp.addr.type));
if (mgmt_send(mgmt, MGMT_OP_GET_DEVICE_FLAGS, index, sizeof(cp), &cp,
get_flags_rsp, NULL, NULL) == 0) {
error("Unable to send Get Device Flags command");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void set_flags_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("Set device flags failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static const struct option set_flags_options[] = {
{ "help", 0, 0, 'h' },
{ "type", 1, 0, 't' },
{ "flags", 1, 0, 'f' },
{ 0, 0, 0, 0 }
};
static void cmd_set_flags(int argc, char **argv)
{
struct mgmt_cp_set_device_flags cp;
uint8_t type = BDADDR_BREDR;
uint32_t flags = 0;
char addr[18];
int opt;
uint16_t index;
while ((opt = getopt_long(argc, argv, "+f:t:h", set_flags_options,
NULL)) != -1) {
switch (opt) {
case 'f':
flags = strtol(optarg, NULL, 0);
break;
case 't':
type = strtol(optarg, NULL, 0);
break;
case 'h':
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
default:
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
optind = 0;
if (argc < 1) {
bt_shell_usage();
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
memset(&cp, 0, sizeof(cp));
str2ba(argv[0], &cp.addr.bdaddr);
cp.addr.type = type;
cp.current_flags = flags;
ba2str(&cp.addr.bdaddr, addr);
print("Set device flag of %s (%s)", addr, typestr(cp.addr.type));
if (mgmt_send(mgmt, MGMT_OP_SET_DEVICE_FLAGS, index, sizeof(cp), &cp,
set_flags_rsp, NULL, NULL) == 0) {
error("Unable to send Set Device Flags command");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
/* Wrapper to get the index and opcode to the response callback */
struct command_data {
uint16_t id;
uint16_t op;
void (*callback) (uint16_t id, uint16_t op, uint8_t status,
uint16_t len, const void *param);
};
static void cmd_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
struct command_data *data = user_data;
data->callback(data->op, data->id, status, len, param);
}
static unsigned int send_cmd(struct mgmt *mgmt, uint16_t op, uint16_t id,
uint16_t len, const void *param,
void (*cb)(uint16_t id, uint16_t op,
uint8_t status, uint16_t len,
const void *param))
{
struct command_data *data;
unsigned int send_id;
data = new0(struct command_data, 1);
if (!data)
return 0;
data->id = id;
data->op = op;
data->callback = cb;
send_id = mgmt_send(mgmt, op, id, len, param, cmd_rsp, data, free);
if (send_id == 0)
free(data);
return send_id;
}
static void setting_rsp(uint16_t op, uint16_t id, uint8_t status, uint16_t len,
const void *param)
{
const uint32_t *rp = param;
if (status != 0) {
error("%s for hci%u failed with status 0x%02x (%s)",
mgmt_opstr(op), id, status, mgmt_errstr(status));
goto done;
}
if (len < sizeof(*rp)) {
error("Too small %s response (%u bytes)",
mgmt_opstr(op), len);
goto done;
}
print("hci%u %s complete, settings: %s", id, mgmt_opstr(op),
settings2str(get_le32(rp)));
done:
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_setting(uint16_t op, int argc, char **argv)
{
int index;
uint8_t val;
if (parse_setting(argc, argv, &val) == false)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
if (send_cmd(mgmt, op, index, sizeof(val), &val, setting_rsp) == 0) {
error("Unable to send %s cmd", mgmt_opstr(op));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void cmd_power(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_POWERED, argc, argv);
}
static void cmd_discov(int argc, char **argv)
{
struct mgmt_cp_set_discoverable cp;
uint16_t index;
memset(&cp, 0, sizeof(cp));
if (strcasecmp(argv[1], "on") == 0 || strcasecmp(argv[1], "yes") == 0)
cp.val = 1;
else if (strcasecmp(argv[1], "off") == 0)
cp.val = 0;
else if (strcasecmp(argv[1], "limited") == 0)
cp.val = 2;
else
cp.val = atoi(argv[1]);
if (argc > 2)
cp.timeout = htobs(atoi(argv[2]));
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
if (send_cmd(mgmt, MGMT_OP_SET_DISCOVERABLE, index, sizeof(cp), &cp,
setting_rsp) == 0) {
error("Unable to send set_discoverable cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void cmd_connectable(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_CONNECTABLE, argc, argv);
}
static void cmd_fast_conn(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_FAST_CONNECTABLE, argc, argv);
}
static void cmd_bondable(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_BONDABLE, argc, argv);
}
static void cmd_linksec(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_LINK_SECURITY, argc, argv);
}
static void cmd_ssp(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_SSP, argc, argv);
}
static void cmd_sc(int argc, char **argv)
{
uint8_t val;
uint16_t index;
if (strcasecmp(argv[1], "on") == 0 || strcasecmp(argv[1], "yes") == 0)
val = 1;
else if (strcasecmp(argv[1], "off") == 0)
val = 0;
else if (strcasecmp(argv[1], "only") == 0)
val = 2;
else
val = atoi(argv[1]);
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
if (send_cmd(mgmt, MGMT_OP_SET_SECURE_CONN, index,
sizeof(val), &val, setting_rsp) == 0) {
error("Unable to send set_secure_conn cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void cmd_hs(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_HS, argc, argv);
}
static void cmd_le(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_LE, argc, argv);
}
static void cmd_advertising(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_ADVERTISING, argc, argv);
}
static void cmd_bredr(int argc, char **argv)
{
cmd_setting(MGMT_OP_SET_BREDR, argc, argv);
}
static void cmd_privacy(int argc, char **argv)
{
struct mgmt_cp_set_privacy cp;
uint16_t index;
if (parse_setting(argc, argv, &cp.privacy) == false)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
if (argc > 2) {
if (hex2bin(argv[2], cp.irk,
sizeof(cp.irk)) != sizeof(cp.irk)) {
error("Invalid key format");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
} else {
int fd;
fd = open("/dev/urandom", O_RDONLY);
if (fd < 0) {
error("open(/dev/urandom): %s", strerror(errno));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (read(fd, cp.irk, sizeof(cp.irk)) != sizeof(cp.irk)) {
error("Reading from urandom failed");
close(fd);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
close(fd);
}
if (send_cmd(mgmt, MGMT_OP_SET_PRIVACY, index, sizeof(cp), &cp,
setting_rsp) == 0) {
error("Unable to send Set Privacy command");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void exp_offload_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0)
error("Set offload codec failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
else
print("Offload codec feature successfully set");
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_exp_offload_codecs(int argc, char **argv)
{
/* a6695ace-ee7f-4fb9-881a-5fac66c629af */
static const uint8_t uuid[16] = {
0xaf, 0x29, 0xc6, 0x66, 0xac, 0x5f, 0x1a, 0x88,
0xb9, 0x4f, 0x7f, 0xee, 0xce, 0x5a, 0x69, 0xa6,
};
struct mgmt_cp_set_exp_feature cp;
uint8_t val;
uint16_t index;
if (parse_setting(argc, argv, &val) == false)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
memset(&cp, 0, sizeof(cp));
memcpy(cp.uuid, uuid, 16);
cp.action = val;
if (mgmt_send(mgmt, MGMT_OP_SET_EXP_FEATURE, index,
sizeof(cp), &cp, exp_offload_rsp, NULL, NULL) == 0) {
error("Unable to send offload codecs feature cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void class_rsp(uint16_t op, uint16_t id, uint8_t status, uint16_t len,
const void *param)
{
const struct mgmt_ev_class_of_dev_changed *rp = param;
if (len == 0 && status != 0) {
error("%s failed, status 0x%02x (%s)",
mgmt_opstr(op), status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (len != sizeof(*rp)) {
error("Unexpected %s len %u", mgmt_opstr(op), len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("%s succeeded. Class 0x%02x%02x%02x", mgmt_opstr(op),
rp->dev_class[2], rp->dev_class[1], rp->dev_class[0]);
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_class(int argc, char **argv)
{
uint8_t class[2];
uint16_t index;
class[0] = atoi(argv[1]);
class[1] = atoi(argv[2]);
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
if (send_cmd(mgmt, MGMT_OP_SET_DEV_CLASS, index, sizeof(class), class,
class_rsp) == 0) {
error("Unable to send set_dev_class cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void disconnect_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_disconnect *rp = param;
char addr[18];
if (len == 0 && status != 0) {
error("Disconnect failed with status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (len != sizeof(*rp)) {
error("Invalid disconnect response length (%u)", len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
ba2str(&rp->addr.bdaddr, addr);
if (status == 0)
print("%s disconnected", addr);
else
error("Disconnecting %s failed with status 0x%02x (%s)",
addr, status, mgmt_errstr(status));
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static const struct option disconnect_options[] = {
{ "help", 0, 0, 'h' },
{ "type", 1, 0, 't' },
{ 0, 0, 0, 0 }
};
static void cmd_disconnect(int argc, char **argv)
{
struct mgmt_cp_disconnect cp;
uint8_t type = BDADDR_BREDR;
int opt;
uint16_t index;
while ((opt = getopt_long(argc, argv, "+t:h", disconnect_options,
NULL)) != -1) {
switch (opt) {
case 't':
type = strtol(optarg, NULL, 0);
break;
case 'h':
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
default:
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
argv += optind;
optind = 0;
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
memset(&cp, 0, sizeof(cp));
str2ba(argv[0], &cp.addr.bdaddr);
cp.addr.type = type;
if (mgmt_send(mgmt, MGMT_OP_DISCONNECT, index, sizeof(cp), &cp,
disconnect_rsp, NULL, NULL) == 0) {
error("Unable to send disconnect cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void con_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
const struct mgmt_rp_get_connections *rp = param;
uint16_t count, i;
if (len < sizeof(*rp)) {
error("Too small (%u bytes) get_connections rsp", len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
count = get_le16(&rp->conn_count);
if (len != sizeof(*rp) + count * sizeof(struct mgmt_addr_info)) {
error("Invalid get_connections length (count=%u, len=%u)",
count, len);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
for (i = 0; i < count; i++) {
char addr[18];
ba2str(&rp->addr[i].bdaddr, addr);
print("%s type %s", addr, typestr(rp->addr[i].type));
}
bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_con(int argc, char **argv)
{
uint16_t index;
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
if (mgmt_send(mgmt, MGMT_OP_GET_CONNECTIONS, index, 0, NULL,
con_rsp, NULL, NULL) == 0) {
error("Unable to send get_connections cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void find_service_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("Start Service Discovery failed: status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("Service discovery started");
discovery = true;
}
static const struct option find_service_options[] = {
{ "help", no_argument, 0, 'h' },
{ "le-only", no_argument, 0, 'l' },
{ "bredr-only", no_argument, 0, 'b' },
{ "uuid", required_argument, 0, 'u' },
{ "rssi", required_argument, 0, 'r' },
{ 0, 0, 0, 0 }
};
#define MAX_UUIDS 4
static void cmd_find_service(int argc, char **argv)
{
struct mgmt_cp_start_service_discovery *cp;
uint8_t buf[sizeof(*cp) + 16 * MAX_UUIDS];
bt_uuid_t uuid;
uint8_t type = SCAN_TYPE_DUAL;
int8_t rssi;
uint16_t count;
int opt;
uint16_t index;
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
rssi = 127;
count = 0;
while ((opt = getopt_long(argc, argv, "+lbu:r:h",
find_service_options, NULL)) != -1) {
switch (opt) {
case 'l':
type &= ~SCAN_TYPE_BREDR;
type |= SCAN_TYPE_LE;
break;
case 'b':
type |= SCAN_TYPE_BREDR;
type &= ~SCAN_TYPE_LE;
break;
case 'u':
if (count == MAX_UUIDS) {
print("Max %u UUIDs supported", MAX_UUIDS);
optind = 0;
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (bt_string_to_uuid(&uuid, optarg) < 0) {
print("Invalid UUID: %s", optarg);
optind = 0;
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
cp = (void *) buf;
bt_uuid_to_le(&uuid, cp->uuids[count++]);
break;
case 'r':
rssi = atoi(optarg);
break;
case 'h':
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
default:
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
optind = 0;
cp = (void *) buf;
cp->type = type;
cp->rssi = rssi;
cp->uuid_count = cpu_to_le16(count);
if (mgmt_send(mgmt, MGMT_OP_START_SERVICE_DISCOVERY, index,
sizeof(*cp) + count * 16, cp,
find_service_rsp, NULL, NULL) == 0) {
error("Unable to send start_service_discovery cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void find_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("Unable to start discovery. status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
print("Discovery started");
discovery = true;
}
static const struct option find_options[] = {
{ "help", 0, 0, 'h' },
{ "le-only", 1, 0, 'l' },
{ "bredr-only", 1, 0, 'b' },
{ "limited", 1, 0, 'L' },
{ 0, 0, 0, 0 }
};
static void cmd_find(int argc, char **argv)
{
struct mgmt_cp_start_discovery cp;
uint8_t op = MGMT_OP_START_DISCOVERY;
uint8_t type = SCAN_TYPE_DUAL;
int opt;
uint16_t index;
index = mgmt_index;
if (index == MGMT_INDEX_NONE)
index = 0;
while ((opt = getopt_long(argc, argv, "+lbLh", find_options,
NULL)) != -1) {
switch (opt) {
case 'l':
type &= ~SCAN_TYPE_BREDR;
type |= SCAN_TYPE_LE;
break;
case 'b':
type |= SCAN_TYPE_BREDR;
type &= ~SCAN_TYPE_LE;
break;
case 'L':
op = MGMT_OP_START_LIMITED_DISCOVERY;
break;
case 'h':
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
default:
bt_shell_usage();
optind = 0;
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
optind = 0;
memset(&cp, 0, sizeof(cp));
cp.type = type;
if (mgmt_send(mgmt, op, index, sizeof(cp), &cp, find_rsp,
NULL, NULL) == 0) {
error("Unable to send start_discovery cmd");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
static void stop_find_rsp(uint8_t status, uint16_t len, const void *param,
void *user_data)
{
if (status != 0) {
error("Stop Discovery failed: status 0x%02x (%s)",
status, mgmt_errstr(status));
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
print("Discovery stopped");
discovery = false;
bt_shell_noninteractive_quit(EXIT_SUCCESS);