| // 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); |