| /* |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2011 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #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 <readline/readline.h> |
| #include <readline/history.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/hci.h" |
| #include "lib/hci_lib.h" |
| #include "lib/sdp.h" |
| #include "lib/sdp_lib.h" |
| |
| #include "src/uuid-helper.h" |
| #include "lib/mgmt.h" |
| |
| #include "client/display.h" |
| #include "src/shared/mainloop.h" |
| #include "src/shared/io.h" |
| #include "src/shared/util.h" |
| #include "src/shared/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 bool interactive = false; |
| |
| static char *saved_prompt = NULL; |
| static int saved_point = 0; |
| |
| 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); |
| |
| if (saved_prompt) { |
| free(saved_prompt); |
| saved_prompt = strdup(str); |
| return; |
| } |
| |
| rl_set_prompt(str); |
| } |
| |
| static void noninteractive_quit(int status) |
| { |
| if (interactive) |
| return; |
| |
| if (status == EXIT_SUCCESS) |
| mainloop_exit_success(); |
| else |
| mainloop_exit_failure(); |
| } |
| |
| #define print(fmt, arg...) do { \ |
| if (interactive) \ |
| rl_printf(fmt "\n", ## arg); \ |
| else \ |
| printf(fmt "\n", ## arg); \ |
| } while (0) |
| |
| #define error(fmt, arg...) do { \ |
| if (interactive) \ |
| rl_printf(COLOR_RED fmt "\n" COLOR_OFF, ## arg); \ |
| else \ |
| fprintf(stderr, fmt "\n", ## 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(uint16_t index, struct mgmt_irk_info *irk) |
| { |
| char identity_path[PATH_MAX]; |
| char *addr, *key; |
| unsigned int type; |
| int n; |
| FILE *fp; |
| |
| snprintf(identity_path, sizeof(identity_path), |
| "/sys/kernel/debug/bluetooth/hci%u/identity", index); |
| |
| fp = fopen(identity_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", |
| }; |
| |
| 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 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 release_prompt(void) |
| { |
| if (!interactive) |
| return; |
| |
| memset(&prompt, 0, sizeof(prompt)); |
| prompt.index = MGMT_INDEX_NONE; |
| |
| if (!saved_prompt) |
| return; |
| |
| /* This will cause rl_expand_prompt to re-run over the last prompt, |
| * but our prompt doesn't expand anyway. |
| */ |
| rl_set_prompt(saved_prompt); |
| rl_replace_line("", 0); |
| rl_point = saved_point; |
| rl_redisplay(); |
| |
| free(saved_prompt); |
| saved_prompt = NULL; |
| } |
| |
| 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 (!memcmp(&ev->addr, &prompt.addr, sizeof(ev->addr))) |
| release_prompt(); |
| |
| 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; |
| } |
| |
| if (!memcmp(&ev->addr, &prompt.addr, sizeof(ev->addr))) |
| release_prompt(); |
| |
| 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| print("PIN Reply successful"); |
| } |
| |
| static int mgmt_pin_reply(struct mgmt *mgmt, 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| print("PIN Negative Reply successful"); |
| } |
| |
| static int mgmt_pin_neg_reply(struct mgmt *mgmt, 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| print("User Confirm Reply successful"); |
| } |
| |
| static int mgmt_confirm_reply(struct mgmt *mgmt, 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| print("User Confirm Negative Reply successful"); |
| } |
| |
| static int mgmt_confirm_neg_reply(struct mgmt *mgmt, 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| print("User Passkey Reply successful"); |
| } |
| |
| static int mgmt_passkey_reply(struct mgmt *mgmt, 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| print("User Passkey Negative Reply successful"); |
| } |
| |
| static int mgmt_passkey_neg_reply(struct mgmt *mgmt, 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 bool prompt_input(const char *input) |
| { |
| size_t len; |
| |
| if (!prompt.req) |
| return false; |
| |
| len = strlen(input); |
| |
| switch (prompt.req) { |
| case MGMT_EV_PIN_CODE_REQUEST: |
| if (len) |
| mgmt_pin_reply(mgmt, prompt.index, &prompt.addr, |
| input, len); |
| else |
| mgmt_pin_neg_reply(mgmt, prompt.index, &prompt.addr); |
| break; |
| case MGMT_EV_USER_PASSKEY_REQUEST: |
| if (strlen(input) > 0) |
| mgmt_passkey_reply(mgmt, prompt.index, &prompt.addr, |
| atoi(input)); |
| else |
| mgmt_passkey_neg_reply(mgmt, prompt.index, |
| &prompt.addr); |
| break; |
| case MGMT_EV_USER_CONFIRM_REQUEST: |
| if (input[0] == 'y' || input[0] == 'Y') |
| mgmt_confirm_reply(mgmt, prompt.index, &prompt.addr); |
| else |
| mgmt_confirm_neg_reply(mgmt, prompt.index, |
| &prompt.addr); |
| break; |
| } |
| |
| release_prompt(); |
| |
| return true; |
| } |
| |
| static void interactive_prompt(const char *msg) |
| { |
| if (saved_prompt) |
| return; |
| |
| saved_prompt = strdup(rl_prompt); |
| if (!saved_prompt) |
| return; |
| |
| saved_point = rl_point; |
| |
| rl_set_prompt(""); |
| rl_redisplay(); |
| |
| rl_set_prompt(msg); |
| |
| rl_replace_line("", 0); |
| rl_redisplay(); |
| } |
| |
| static size_t get_input(char *buf, size_t buf_len) |
| { |
| size_t len; |
| |
| if (!fgets(buf, buf_len, stdin)) |
| return 0; |
| |
| len = strlen(buf); |
| |
| /* Remove trailing white-space */ |
| while (len && isspace(buf[len - 1])) |
| buf[--len] = '\0'; |
| |
| return len; |
| } |
| |
| static void ask(uint16_t index, uint16_t req, const struct mgmt_addr_info *addr, |
| const char *fmt, ...) |
| { |
| char msg[256], buf[18]; |
| 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); |
| |
| if (interactive) { |
| interactive_prompt(msg); |
| va_end(ap); |
| return; |
| } |
| |
| printf("%s", msg); |
| fflush(stdout); |
| |
| memset(buf, 0, sizeof(buf)); |
| get_input(buf, sizeof(buf)); |
| prompt_input(buf); |
| } |
| |
| 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 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: |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_version(struct mgmt *mgmt, uint16_t index, 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 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; |
| const uint16_t *opcode; |
| 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; |
| } |
| |
| opcode = rp->opcodes; |
| |
| print("%u commands:", num_commands); |
| for (i = 0; i < num_commands; i++) { |
| uint16_t op = get_le16(opcode++); |
| 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(opcode++); |
| print("\t%s (0x%04x)", mgmt_evstr(ev), ev); |
| } |
| |
| done: |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_commands(struct mgmt *mgmt, uint16_t index, 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 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; |
| |
| 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len < sizeof(*rp)) { |
| error("Too small index list reply (%u bytes)", len); |
| return 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 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| pending_index++; |
| } |
| |
| if (!count) |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_config(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| if (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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| return; |
| } |
| |
| 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 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; |
| |
| 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; |
| |
| 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len < sizeof(*rp)) { |
| error("Too small index list reply (%u bytes)", len); |
| return 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 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| pending_index++; |
| } |
| |
| if (!count) |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_info(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| if (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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| return; |
| } |
| |
| 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 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, index_filter = PTR_TO_UINT(user_data); |
| unsigned int i; |
| |
| if (status != 0) { |
| error("Reading ext index list failed with status 0x%02x (%s)", |
| status, mgmt_errstr(status)); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len < sizeof(*rp)) { |
| error("Too small ext index list reply (%u bytes)", len); |
| return 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 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); |
| char *busstr = hci_bustostr(rp->entry[i].bus); |
| |
| if (index_filter != MGMT_INDEX_NONE && index_filter != index) |
| continue; |
| |
| switch (rp->entry[i].type) { |
| case 0x00: |
| print("Primary controller (hci%u,%s)", index, busstr); |
| 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 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 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) |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_extinfo(struct mgmt *mgmt, uint16_t index, |
| int argc, char **argv) |
| { |
| if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INDEX_LIST, |
| MGMT_INDEX_NONE, 0, NULL, |
| ext_index_rsp, UINT_TO_PTR(index), NULL)) { |
| error("Unable to send ext_index_list cmd"); |
| return 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); |
| |
| 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 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 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 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 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 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void cmd_auto_power(struct mgmt *mgmt, uint16_t index, |
| int argc, char **argv) |
| { |
| 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 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: |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_setting(struct mgmt *mgmt, uint16_t index, uint16_t op, |
| int argc, char **argv) |
| { |
| uint8_t val; |
| |
| if (argc < 2) { |
| print("Specify \"on\" or \"off\""); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| 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]); |
| |
| 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void cmd_power(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_POWERED, argc, argv); |
| } |
| |
| static void cmd_discov(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| struct mgmt_cp_set_discoverable cp; |
| |
| if (argc < 2) { |
| print("Usage: %s <yes/no/limited> [timeout]", argv[0]); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| 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])); |
| |
| 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void cmd_connectable(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_CONNECTABLE, argc, argv); |
| } |
| |
| static void cmd_fast_conn(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_FAST_CONNECTABLE, argc, argv); |
| } |
| |
| static void cmd_bondable(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_BONDABLE, argc, argv); |
| } |
| |
| static void cmd_linksec(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_LINK_SECURITY, argc, argv); |
| } |
| |
| static void cmd_ssp(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_SSP, argc, argv); |
| } |
| |
| static void cmd_sc(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| uint8_t val; |
| |
| if (argc < 2) { |
| print("Specify \"on\" or \"off\" or \"only\""); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| 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]); |
| |
| 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void cmd_hs(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_HS, argc, argv); |
| } |
| |
| static void cmd_le(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_LE, argc, argv); |
| } |
| |
| static void cmd_advertising(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_ADVERTISING, argc, argv); |
| } |
| |
| static void cmd_bredr(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| cmd_setting(mgmt, index, MGMT_OP_SET_BREDR, argc, argv); |
| } |
| |
| static void cmd_privacy(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| struct mgmt_cp_set_privacy cp; |
| |
| if (argc < 2) { |
| print("Specify \"on\" or \"off\""); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (strcasecmp(argv[1], "on") == 0 || strcasecmp(argv[1], "yes") == 0) |
| cp.privacy = 0x01; |
| else if (strcasecmp(argv[1], "off") == 0) |
| cp.privacy = 0x00; |
| else |
| cp.privacy = atoi(argv[1]); |
| |
| 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| } else { |
| int fd; |
| |
| fd = open("/dev/urandom", O_RDONLY); |
| if (fd < 0) { |
| error("open(/dev/urandom): %s", strerror(errno)); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (read(fd, cp.irk, sizeof(cp.irk)) != sizeof(cp.irk)) { |
| error("Reading from urandom failed"); |
| close(fd); |
| return 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 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len != sizeof(*rp)) { |
| error("Unexpected %s len %u", mgmt_opstr(op), len); |
| return 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]); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_class(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| uint8_t class[2]; |
| |
| if (argc < 3) { |
| print("Usage: %s <major> <minor>", argv[0]); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| class[0] = atoi(argv[1]); |
| class[1] = atoi(argv[2]); |
| |
| 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 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len != sizeof(*rp)) { |
| error("Invalid disconnect response length (%u)", len); |
| return 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)); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void disconnect_usage(void) |
| { |
| print("Usage: disconnect [-t type] <remote address>"); |
| } |
| |
| static struct option disconnect_options[] = { |
| { "help", 0, 0, 'h' }, |
| { "type", 1, 0, 't' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| static void cmd_disconnect(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| struct mgmt_cp_disconnect cp; |
| uint8_t type = BDADDR_BREDR; |
| int opt; |
| |
| while ((opt = getopt_long(argc, argv, "+t:h", disconnect_options, |
| NULL)) != -1) { |
| switch (opt) { |
| case 't': |
| type = strtol(optarg, NULL, 0); |
| break; |
| case 'h': |
| disconnect_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| disconnect_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc < 1) { |
| disconnect_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| 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 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 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 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)); |
| } |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_con(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| 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 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| print("Service discovery started"); |
| discovery = true; |
| } |
| |
| static void find_service_usage(void) |
| { |
| print("Usage: find-service [-u UUID] [-r RSSI_Threshold] [-l|-b]"); |
| } |
| |
| static 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 } |
| }; |
| |
| static void uuid_to_uuid128(uuid_t *uuid128, const uuid_t *uuid) |
| { |
| if (uuid->type == SDP_UUID16) |
| sdp_uuid16_to_uuid128(uuid128, uuid); |
| else if (uuid->type == SDP_UUID32) |
| sdp_uuid32_to_uuid128(uuid128, uuid); |
| else |
| memcpy(uuid128, uuid, sizeof(*uuid)); |
| } |
| |
| #define MAX_UUIDS 4 |
| |
| static void cmd_find_service(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| struct mgmt_cp_start_service_discovery *cp; |
| uint8_t buf[sizeof(*cp) + 16 * MAX_UUIDS]; |
| uuid_t uuid; |
| uint128_t uint128; |
| uuid_t uuid128; |
| uint8_t type = SCAN_TYPE_DUAL; |
| int8_t rssi; |
| uint16_t count; |
| int opt; |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| rssi = 127; |
| count = 0; |
| |
| if (argc == 1) { |
| find_service_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| while ((opt = getopt_long(argc, argv, "+lbu:r:p: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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (bt_string2uuid(&uuid, optarg) < 0) { |
| print("Invalid UUID: %s", optarg); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| cp = (void *) buf; |
| uuid_to_uuid128(&uuid128, &uuid); |
| ntoh128((uint128_t *) uuid128.value.uuid128.data, |
| &uint128); |
| htob128(&uint128, (uint128_t *) cp->uuids[count++]); |
| break; |
| case 'r': |
| rssi = atoi(optarg); |
| break; |
| case 'h': |
| find_service_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| find_service_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc > 0) { |
| find_service_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| 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 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| print("Discovery started"); |
| discovery = true; |
| } |
| |
| static void find_usage(void) |
| { |
| print("Usage: find [-l|-b]>"); |
| } |
| |
| static 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(struct mgmt *mgmt, uint16_t index, 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; |
| |
| 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': |
| find_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| find_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| 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 noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void stop_find_rsp(uint8_t status, uint16_t len, const void *param, |
| void *user_data) |
| { |
| if (status != 0) { |
| fprintf(stderr, |
| "Stop Discovery failed: status 0x%02x (%s)\n", |
| status, mgmt_errstr(status)); |
| return noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| printf("Discovery stopped\n"); |
| discovery = false; |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void stop_find_usage(void) |
| { |
| printf("Usage: btmgmt stop-find [-l|-b]>\n"); |
| } |
| |
| static struct option stop_find_options[] = { |
| { "help", 0, 0, 'h' }, |
| { "le-only", 1, 0, 'l' }, |
| { "bredr-only", 1, 0, 'b' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| static void cmd_stop_find(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| struct mgmt_cp_stop_discovery cp; |
| uint8_t type = SCAN_TYPE_DUAL; |
| int opt; |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| while ((opt = getopt_long(argc, argv, "+lbh", stop_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 'h': |
| default: |
| stop_find_usage(); |
| optind = 0; |
| exit(EXIT_SUCCESS); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| cp.type = type; |
| |
| if (mgmt_send(mgmt, MGMT_OP_STOP_DISCOVERY, index, sizeof(cp), &cp, |
| stop_find_rsp, NULL, NULL) == 0) { |
| fprintf(stderr, "Unable to send stop_discovery cmd\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| static void name_rsp(uint8_t status, uint16_t len, const void *param, |
| void *user_data) |
| { |
| if (status != 0) |
| error("Unable to set local name with status 0x%02x (%s)", |
| status, mgmt_errstr(status)); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_name(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| struct mgmt_cp_set_local_name cp; |
| |
| if (argc < 2) { |
| print("Usage: %s <name> [shortname]", argv[0]); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| strncpy((char *) cp.name, argv[1], HCI_MAX_NAME_LENGTH); |
| if (argc > 2) |
| strncpy((char *) cp.short_name, argv[2], |
| MGMT_MAX_SHORT_NAME_LENGTH); |
| |
| if (mgmt_send(mgmt, MGMT_OP_SET_LOCAL_NAME, index, sizeof(cp), &cp, |
| name_rsp, NULL, NULL) == 0) { |
| error("Unable to send set_name cmd"); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void pair_rsp(uint8_t status, uint16_t len, const void *param, |
| void *user_data) |
| { |
| const struct mgmt_rp_pair_device *rp = param; |
| char addr[18]; |
| |
| if (len == 0 && status != 0) { |
| error("Pairing failed with status 0x%02x (%s)", |
| status, mgmt_errstr(status)); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len != sizeof(*rp)) { |
| error("Unexpected pair_rsp len %u", len); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (!memcmp(&rp->addr, &prompt.addr, sizeof(rp->addr))) |
| release_prompt(); |
| |
| ba2str(&rp->addr.bdaddr, addr); |
| |
| if (status) |
| error("Pairing with %s (%s) failed. status 0x%02x (%s)", |
| addr, typestr(rp->addr.type), status, |
| mgmt_errstr(status)); |
| else |
| print("Paired with %s (%s)", addr, typestr(rp->addr.type)); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void pair_usage(void) |
| { |
| print("Usage: pair [-c cap] [-t type] <remote address>"); |
| } |
| |
| static struct option pair_options[] = { |
| { "help", 0, 0, 'h' }, |
| { "capability", 1, 0, 'c' }, |
| { "type", 1, 0, 't' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| static void cmd_pair(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| struct mgmt_cp_pair_device cp; |
| uint8_t cap = 0x01; |
| uint8_t type = BDADDR_BREDR; |
| char addr[18]; |
| int opt; |
| |
| while ((opt = getopt_long(argc, argv, "+c:t:h", pair_options, |
| NULL)) != -1) { |
| switch (opt) { |
| case 'c': |
| cap = strtol(optarg, NULL, 0); |
| break; |
| case 't': |
| type = strtol(optarg, NULL, 0); |
| break; |
| case 'h': |
| pair_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| pair_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc < 1) { |
| pair_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| str2ba(argv[0], &cp.addr.bdaddr); |
| cp.addr.type = type; |
| cp.io_cap = cap; |
| |
| ba2str(&cp.addr.bdaddr, addr); |
| print("Pairing with %s (%s)", addr, typestr(cp.addr.type)); |
| |
| if (mgmt_send(mgmt, MGMT_OP_PAIR_DEVICE, index, sizeof(cp), &cp, |
| pair_rsp, NULL, NULL) == 0) { |
| error("Unable to send pair_device cmd"); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void cancel_pair_rsp(uint8_t status, uint16_t len, const void *param, |
| void *user_data) |
| { |
| const struct mgmt_addr_info *rp = param; |
| char addr[18]; |
| |
| if (len == 0 && status != 0) { |
| error("Cancel Pairing failed with 0x%02x (%s)", |
| status, mgmt_errstr(status)); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len != sizeof(*rp)) { |
| error("Unexpected cancel_pair_rsp len %u", len); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| ba2str(&rp->bdaddr, addr); |
| |
| if (status) |
| error("Cancel Pairing with %s (%s) failed. 0x%02x (%s)", |
| addr, typestr(rp->type), status, |
| mgmt_errstr(status)); |
| else |
| print("Pairing Cancelled with %s", addr); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cancel_pair_usage(void) |
| { |
| print("Usage: cancelpair [-t type] <remote address>"); |
| } |
| |
| static struct option cancel_pair_options[] = { |
| { "help", 0, 0, 'h' }, |
| { "type", 1, 0, 't' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| static void cmd_cancel_pair(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| struct mgmt_addr_info cp; |
| uint8_t type = BDADDR_BREDR; |
| int opt; |
| |
| while ((opt = getopt_long(argc, argv, "+t:h", cancel_pair_options, |
| NULL)) != -1) { |
| switch (opt) { |
| case 't': |
| type = strtol(optarg, NULL, 0); |
| break; |
| case 'h': |
| cancel_pair_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| cancel_pair_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc < 1) { |
| cancel_pair_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| str2ba(argv[0], &cp.bdaddr); |
| cp.type = type; |
| |
| if (mgmt_reply(mgmt, MGMT_OP_CANCEL_PAIR_DEVICE, index, sizeof(cp), &cp, |
| cancel_pair_rsp, NULL, NULL) == 0) { |
| error("Unable to send cancel_pair_device cmd"); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void unpair_rsp(uint8_t status, uint16_t len, const void *param, |
| void *user_data) |
| { |
| const struct mgmt_rp_unpair_device *rp = param; |
| char addr[18]; |
| |
| if (len == 0 && status != 0) { |
| error("Unpair device failed. status 0x%02x (%s)", |
| status, mgmt_errstr(status)); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len != sizeof(*rp)) { |
| error("Unexpected unpair_device_rsp len %u", len); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| ba2str(&rp->addr.bdaddr, addr); |
| |
| if (status) |
| error("Unpairing %s failed. status 0x%02x (%s)", |
| addr, status, mgmt_errstr(status)); |
| else |
| print("%s unpaired", addr); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void unpair_usage(void) |
| { |
| print("Usage: unpair [-t type] <remote address>"); |
| } |
| |
| static struct option unpair_options[] = { |
| { "help", 0, 0, 'h' }, |
| { "type", 1, 0, 't' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| static void cmd_unpair(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| struct mgmt_cp_unpair_device cp; |
| uint8_t type = BDADDR_BREDR; |
| int opt; |
| |
| while ((opt = getopt_long(argc, argv, "+t:h", unpair_options, |
| NULL)) != -1) { |
| switch (opt) { |
| case 't': |
| type = strtol(optarg, NULL, 0); |
| break; |
| case 'h': |
| unpair_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| unpair_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc < 1) { |
| unpair_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| str2ba(argv[0], &cp.addr.bdaddr); |
| cp.addr.type = type; |
| cp.disconnect = 1; |
| |
| if (mgmt_send(mgmt, MGMT_OP_UNPAIR_DEVICE, index, sizeof(cp), &cp, |
| unpair_rsp, NULL, NULL) == 0) { |
| error("Unable to send unpair_device cmd"); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void keys_rsp(uint8_t status, uint16_t len, const void *param, |
| void *user_data) |
| { |
| if (status != 0) |
| error("Load keys failed with status 0x%02x (%s)", |
| status, mgmt_errstr(status)); |
| else |
| print("Keys successfully loaded"); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_keys(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| struct mgmt_cp_load_link_keys cp; |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| |
| if (mgmt_send(mgmt, MGMT_OP_LOAD_LINK_KEYS, index, sizeof(cp), &cp, |
| keys_rsp, NULL, NULL) == 0) { |
| error("Unable to send load_keys cmd"); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void ltks_rsp(uint8_t status, uint16_t len, const void *param, |
| void *user_data) |
| { |
| if (status != 0) |
| error("Load keys failed with status 0x%02x (%s)", |
| status, mgmt_errstr(status)); |
| else |
| print("Long term keys successfully loaded"); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_ltks(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| struct mgmt_cp_load_long_term_keys cp; |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| |
| if (mgmt_send(mgmt, MGMT_OP_LOAD_LONG_TERM_KEYS, index, sizeof(cp), &cp, |
| ltks_rsp, NULL, NULL) == 0) { |
| error("Unable to send load_ltks cmd"); |
| return noninteractive_quit(EXIT_SUCCESS); |
| } |
| } |
| |
| static void irks_rsp(uint8_t status, uint16_t len, const void *param, |
| void *user_data) |
| { |
| if (status != 0) |
| error("Load IRKs failed with status 0x%02x (%s)", |
| status, mgmt_errstr(status)); |
| else |
| print("Identity Resolving Keys successfully loaded"); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void irks_usage(void) |
| { |
| print("Usage: irks [--local]"); |
| } |
| |
| static struct option irks_options[] = { |
| { "help", 0, 0, 'h' }, |
| { "local", 1, 0, 'l' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| #define MAX_IRKS 4 |
| |
| static void cmd_irks(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| struct mgmt_cp_load_irks *cp; |
| uint8_t buf[sizeof(*cp) + 23 * MAX_IRKS]; |
| uint16_t count, local_index; |
| int opt; |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| cp = (void *) buf; |
| count = 0; |
| |
| while ((opt = getopt_long(argc, argv, "+l:h", |
| irks_options, NULL)) != -1) { |
| switch (opt) { |
| case 'l': |
| if (count >= MAX_IRKS) { |
| error("Number of IRKs exceeded"); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| if (strlen(optarg) > 3 && |
| strncasecmp(optarg, "hci", 3) == 0) |
| local_index = atoi(optarg + 3); |
| else |
| local_index = atoi(optarg); |
| if (!load_identity(local_index, &cp->irks[count])) { |
| error("Unable to load identity"); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| count++; |
| break; |
| case 'h': |
| irks_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| irks_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc > 0) { |
| irks_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| cp->irk_count = cpu_to_le16(count); |
| |
| if (mgmt_send(mgmt, MGMT_OP_LOAD_IRKS, index, |
| sizeof(*cp) + count * 23, cp, |
| irks_rsp, NULL, NULL) == 0) { |
| error("Unable to send load_irks cmd"); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void block_rsp(uint16_t op, uint16_t id, uint8_t status, uint16_t len, |
| const void *param) |
| { |
| const struct mgmt_addr_info *rp = param; |
| char addr[18]; |
| |
| if (len == 0 && status != 0) { |
| error("%s failed, status 0x%02x (%s)", |
| mgmt_opstr(op), status, mgmt_errstr(status)); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (len != sizeof(*rp)) { |
| error("Unexpected %s len %u", mgmt_opstr(op), len); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| ba2str(&rp->bdaddr, addr); |
| |
| if (status) |
| error("%s %s (%s) failed. status 0x%02x (%s)", |
| mgmt_opstr(op), addr, typestr(rp->type), |
| status, mgmt_errstr(status)); |
| else |
| print("%s %s succeeded", mgmt_opstr(op), addr); |
| |
| noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void block_usage(void) |
| { |
| print("Usage: block [-t type] <remote address>"); |
| } |
| |
| static struct option block_options[] = { |
| { "help", 0, 0, 'h' }, |
| { "type", 1, 0, 't' }, |
| { 0, 0, 0, 0 } |
| }; |
| |
| static void cmd_block(struct mgmt *mgmt, uint16_t index, int argc, char **argv) |
| { |
| struct mgmt_cp_block_device cp; |
| uint8_t type = BDADDR_BREDR; |
| int opt; |
| |
| while ((opt = getopt_long(argc, argv, "+t:h", block_options, |
| NULL)) != -1) { |
| switch (opt) { |
| case 't': |
| type = strtol(optarg, NULL, 0); |
| break; |
| case 'h': |
| block_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| block_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc < 1) { |
| block_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| str2ba(argv[0], &cp.addr.bdaddr); |
| cp.addr.type = type; |
| |
| if (send_cmd(mgmt, MGMT_OP_BLOCK_DEVICE, index, sizeof(cp), &cp, |
| block_rsp) == 0) { |
| error("Unable to send block_device cmd"); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| static void unblock_usage(void) |
| { |
| print("Usage: unblock [-t type] <remote address>"); |
| } |
| |
| static void cmd_unblock(struct mgmt *mgmt, uint16_t index, int argc, |
| char **argv) |
| { |
| struct mgmt_cp_unblock_device cp; |
| uint8_t type = BDADDR_BREDR; |
| int opt; |
| |
| while ((opt = getopt_long(argc, argv, "+t:h", block_options, |
| NULL)) != -1) { |
| switch (opt) { |
| case 't': |
| type = strtol(optarg, NULL, 0); |
| break; |
| case 'h': |
| unblock_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_SUCCESS); |
| default: |
| unblock_usage(); |
| optind = 0; |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| } |
| |
| argc -= optind; |
| argv += optind; |
| optind = 0; |
| |
| if (argc < 1) { |
| unblock_usage(); |
| return noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| if (index == MGMT_INDEX_NONE) |
| index = 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| str2ba(argv[0], &cp.addr.bdaddr); |
| cp.addr.type = type; |
| |
|