/* * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 [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 ", 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] "); } 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' }, { 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 type = SCAN_TYPE_DUAL; int opt; if (index == MGMT_INDEX_NONE) index = 0; while ((opt = getopt_long(argc, argv, "+lbh", 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': 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, MGMT_OP_START_DISCOVERY, 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 [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] "); } 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] "); } 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_send(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] "); } 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] "); } 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] "); } 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; if (send_cmd(mgmt, MGMT_OP_UNBLOCK_DEVICE, index, sizeof(cp), &cp, block_rsp) == 0) { error("Unable to send unblock_device cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void cmd_add_uuid(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_add_uuid cp; uint128_t uint128; uuid_t uuid, uuid128; if (argc < 3) { print("UUID and service hint needed"); return noninteractive_quit(EXIT_FAILURE); } if (index == MGMT_INDEX_NONE) index = 0; if (bt_string2uuid(&uuid, argv[1]) < 0) { print("Invalid UUID: %s", argv[1]); return noninteractive_quit(EXIT_FAILURE); } memset(&cp, 0, sizeof(cp)); uuid_to_uuid128(&uuid128, &uuid); ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); htob128(&uint128, (uint128_t *) cp.uuid); cp.svc_hint = atoi(argv[2]); if (send_cmd(mgmt, MGMT_OP_ADD_UUID, index, sizeof(cp), &cp, class_rsp) == 0) { error("Unable to send add_uuid cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void cmd_remove_uuid(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_remove_uuid cp; uint128_t uint128; uuid_t uuid, uuid128; if (argc < 2) { print("UUID needed"); return noninteractive_quit(EXIT_FAILURE); } if (index == MGMT_INDEX_NONE) index = 0; if (bt_string2uuid(&uuid, argv[1]) < 0) { print("Invalid UUID: %s", argv[1]); return noninteractive_quit(EXIT_FAILURE); } memset(&cp, 0, sizeof(cp)); uuid_to_uuid128(&uuid128, &uuid); ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); htob128(&uint128, (uint128_t *) cp.uuid); if (send_cmd(mgmt, MGMT_OP_REMOVE_UUID, index, sizeof(cp), &cp, class_rsp) == 0) { error("Unable to send remove_uuid cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void cmd_clr_uuids(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { char *uuid_any = "00000000-0000-0000-0000-000000000000"; char *rm_argv[] = { "rm-uuid", uuid_any, NULL }; cmd_remove_uuid(mgmt, index, 2, rm_argv); } static void local_oob_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_read_local_oob_data *rp = param; char str[33]; if (status != 0) { error("Read Local OOB Data failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return noninteractive_quit(EXIT_FAILURE); } if (len < sizeof(*rp)) { error("Too small (%u bytes) read_local_oob rsp", len); return noninteractive_quit(EXIT_FAILURE); } bin2hex(rp->hash192, 16, str, sizeof(str)); print("Hash C from P-192: %s", str); bin2hex(rp->rand192, 16, str, sizeof(str)); print("Randomizer R with P-192: %s", str); if (len < sizeof(*rp)) return noninteractive_quit(EXIT_SUCCESS); bin2hex(rp->hash256, 16, str, sizeof(str)); print("Hash C from P-256: %s", str); bin2hex(rp->rand256, 16, str, sizeof(str)); print("Randomizer R with P-256: %s", str); noninteractive_quit(EXIT_SUCCESS); } static void cmd_local_oob(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { if (index == MGMT_INDEX_NONE) index = 0; if (mgmt_send(mgmt, MGMT_OP_READ_LOCAL_OOB_DATA, index, 0, NULL, local_oob_rsp, NULL, NULL) == 0) { error("Unable to send read_local_oob cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void remote_oob_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_addr_info *rp = param; char addr[18]; if (status != 0) { error("Add Remote OOB Data failed: 0x%02x (%s)", status, mgmt_errstr(status)); return; } if (len < sizeof(*rp)) { error("Too small (%u bytes) add_remote_oob rsp", len); return; } ba2str(&rp->bdaddr, addr); print("Remote OOB data added for %s (%u)", addr, rp->type); } static void remote_oob_usage(void) { print("Usage: remote-oob [-t ] " "[-r ] [-h ] [-R ] [-H ] " ""); } static struct option remote_oob_opt[] = { { "help", 0, 0, 'h' }, { "type", 1, 0, 't' }, { 0, 0, 0, 0 } }; static void cmd_remote_oob(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_add_remote_oob_data cp; int opt; memset(&cp, 0, sizeof(cp)); cp.addr.type = BDADDR_BREDR; while ((opt = getopt_long(argc, argv, "+t:r:R:h:H:", remote_oob_opt, NULL)) != -1) { switch (opt) { case 't': cp.addr.type = strtol(optarg, NULL, 0); break; case 'r': hex2bin(optarg, cp.rand192, 16); break; case 'h': hex2bin(optarg, cp.hash192, 16); break; case 'R': hex2bin(optarg, cp.rand256, 16); break; case 'H': hex2bin(optarg, cp.hash256, 16); break; default: remote_oob_usage(); return noninteractive_quit(EXIT_FAILURE); } } argc -= optind; argv += optind; optind = 0; if (argc < 1) { remote_oob_usage(); return noninteractive_quit(EXIT_FAILURE); } if (index == MGMT_INDEX_NONE) index = 0; str2ba(argv[0], &cp.addr.bdaddr); print("Adding OOB data for %s (%s)", argv[0], typestr(cp.addr.type)); if (mgmt_send(mgmt, MGMT_OP_ADD_REMOTE_OOB_DATA, index, sizeof(cp), &cp, remote_oob_rsp, NULL, NULL) == 0) { error("Unable to send add_remote_oob cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void did_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { if (status != 0) error("Set Device ID failed with status 0x%02x (%s)", status, mgmt_errstr(status)); else print("Device ID successfully set"); noninteractive_quit(EXIT_SUCCESS); } static void did_usage(void) { print("Usage: did :::"); print(" possible source values: bluetooth, usb"); } static void cmd_did(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_set_device_id cp; uint16_t vendor, product, version , source; int result; if (argc < 2) { did_usage(); return noninteractive_quit(EXIT_FAILURE); } result = sscanf(argv[1], "bluetooth:%4hx:%4hx:%4hx", &vendor, &product, &version); if (result == 3) { source = 0x0001; goto done; } result = sscanf(argv[1], "usb:%4hx:%4hx:%4hx", &vendor, &product, &version); if (result == 3) { source = 0x0002; goto done; } did_usage(); return noninteractive_quit(EXIT_FAILURE); done: if (index == MGMT_INDEX_NONE) index = 0; cp.source = htobs(source); cp.vendor = htobs(vendor); cp.product = htobs(product); cp.version = htobs(version); if (mgmt_send(mgmt, MGMT_OP_SET_DEVICE_ID, index, sizeof(cp), &cp, did_rsp, NULL, NULL) == 0) { error("Unable to send set_device_id cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void static_addr_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { if (status != 0) error("Set static address failed with status 0x%02x (%s)", status, mgmt_errstr(status)); else print("Static address successfully set"); noninteractive_quit(EXIT_SUCCESS); } static void static_addr_usage(void) { print("Usage: static-addr
"); } static void cmd_static_addr(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_set_static_address cp; if (argc < 2) { static_addr_usage(); return noninteractive_quit(EXIT_FAILURE); } if (index == MGMT_INDEX_NONE) index = 0; str2ba(argv[1], &cp.bdaddr); if (mgmt_send(mgmt, MGMT_OP_SET_STATIC_ADDRESS, index, sizeof(cp), &cp, static_addr_rsp, NULL, NULL) == 0) { error("Unable to send set_static_address cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void options_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)); return noninteractive_quit(EXIT_FAILURE); } if (len < sizeof(*rp)) { error("Too small %s response (%u bytes)", mgmt_opstr(op), len); return noninteractive_quit(EXIT_FAILURE); } print("hci%u %s complete, options: %s", id, mgmt_opstr(op), options2str(get_le32(rp))); noninteractive_quit(EXIT_SUCCESS); } static void cmd_public_addr(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_set_public_address cp; if (argc < 2) { print("Usage: public-addr
"); return noninteractive_quit(EXIT_FAILURE); } if (index == MGMT_INDEX_NONE) index = 0; str2ba(argv[1], &cp.bdaddr); if (send_cmd(mgmt, MGMT_OP_SET_PUBLIC_ADDRESS, index, sizeof(cp), &cp, options_rsp) == 0) { error("Unable to send Set Public Address cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void cmd_ext_config(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_set_external_config 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.config = 0x01; else if (strcasecmp(argv[1], "off") == 0) cp.config = 0x00; else cp.config = atoi(argv[1]); if (index == MGMT_INDEX_NONE) index = 0; if (send_cmd(mgmt, MGMT_OP_SET_EXTERNAL_CONFIG, index, sizeof(cp), &cp, options_rsp) == 0) { error("Unable to send Set External Config cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void cmd_debug_keys(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { cmd_setting(mgmt, index, MGMT_OP_SET_DEBUG_KEYS, argc, argv); } static void conn_info_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_get_conn_info *rp = param; char addr[18]; if (len == 0 && status != 0) { error("Get Conn Info failed, status 0x%02x (%s)", status, mgmt_errstr(status)); return noninteractive_quit(EXIT_FAILURE); } if (len < sizeof(*rp)) { error("Unexpected Get Conn Info len %u", len); return noninteractive_quit(EXIT_FAILURE); } ba2str(&rp->addr.bdaddr, addr); if (status) { error("Get Conn Info for %s (%s) failed. status 0x%02x (%s)", addr, typestr(rp->addr.type), status, mgmt_errstr(status)); } else { print("Connection Information for %s (%s)", addr, typestr(rp->addr.type)); print("\tRSSI %d\tTX power %d\tmaximum TX power %d", rp->rssi, rp->tx_power, rp->max_tx_power); } noninteractive_quit(EXIT_SUCCESS); } static void conn_info_usage(void) { print("Usage: conn-info [-t type] "); } static struct option conn_info_options[] = { { "help", 0, 0, 'h' }, { "type", 1, 0, 't' }, { 0, 0, 0, 0 } }; static void cmd_conn_info(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_get_conn_info cp; uint8_t type = BDADDR_BREDR; int opt; while ((opt = getopt_long(argc, argv, "+t:h", conn_info_options, NULL)) != -1) { switch (opt) { case 't': type = strtol(optarg, NULL, 0); break; case 'h': conn_info_usage(); return noninteractive_quit(EXIT_SUCCESS); default: conn_info_usage(); return noninteractive_quit(EXIT_FAILURE); } } argc -= optind; argv += optind; optind = 0; if (argc < 1) { conn_info_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_GET_CONN_INFO, index, sizeof(cp), &cp, conn_info_rsp, NULL, NULL) == 0) { error("Unable to send get_conn_info cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void io_cap_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { if (status != 0) error("Could not set IO Capability with status 0x%02x (%s)", status, mgmt_errstr(status)); else print("IO Capabilities successfully set"); noninteractive_quit(EXIT_SUCCESS); } static void io_cap_usage(void) { print("Usage: io-cap "); } static void cmd_io_cap(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_set_io_capability cp; uint8_t cap; if (argc < 2) { io_cap_usage(); return noninteractive_quit(EXIT_FAILURE); } if (index == MGMT_INDEX_NONE) index = 0; cap = strtol(argv[1], NULL, 0); memset(&cp, 0, sizeof(cp)); cp.io_capability = cap; if (mgmt_send(mgmt, MGMT_OP_SET_IO_CAPABILITY, index, sizeof(cp), &cp, io_cap_rsp, NULL, NULL) == 0) { error("Unable to send set-io-cap cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void scan_params_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { if (status != 0) error("Set scan parameters failed with status 0x%02x (%s)", status, mgmt_errstr(status)); else print("Scan parameters successfully set"); noninteractive_quit(EXIT_SUCCESS); } static void scan_params_usage(void) { print("Usage: scan-params "); } static void cmd_scan_params(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_set_scan_params cp; if (argc < 3) { scan_params_usage(); return noninteractive_quit(EXIT_FAILURE); } if (index == MGMT_INDEX_NONE) index = 0; cp.interval = strtol(argv[1], NULL, 0); cp.window = strtol(argv[2], NULL, 0); if (mgmt_send(mgmt, MGMT_OP_SET_SCAN_PARAMS, index, sizeof(cp), &cp, scan_params_rsp, NULL, NULL) == 0) { error("Unable to send set_scan_params cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void clock_info_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_get_clock_info *rp = param; if (len < sizeof(*rp)) { error("Unexpected Get Clock Info len %u", len); return noninteractive_quit(EXIT_FAILURE); } if (status) { error("Get Clock Info failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return noninteractive_quit(EXIT_FAILURE); } print("Local Clock: %u", le32_to_cpu(rp->local_clock)); print("Piconet Clock: %u", le32_to_cpu(rp->piconet_clock)); print("Accurary: %u", le16_to_cpu(rp->accuracy)); noninteractive_quit(EXIT_SUCCESS); } static void cmd_clock_info(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_get_clock_info cp; if (index == MGMT_INDEX_NONE) index = 0; memset(&cp, 0, sizeof(cp)); if (argc > 1) str2ba(argv[1], &cp.addr.bdaddr); if (mgmt_send(mgmt, MGMT_OP_GET_CLOCK_INFO, index, sizeof(cp), &cp, clock_info_rsp, NULL, NULL) == 0) { error("Unable to send get_clock_info cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void add_device_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { if (status != 0) error("Add device failed with status 0x%02x (%s)", status, mgmt_errstr(status)); noninteractive_quit(EXIT_SUCCESS); } static void add_device_usage(void) { print("Usage: add-device [-a action] [-t type]
"); } static struct option add_device_options[] = { { "help", 0, 0, 'h' }, { "action", 1, 0, 'a' }, { "type", 1, 0, 't' }, { 0, 0, 0, 0 } }; static void cmd_add_device(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_add_device cp; uint8_t action = 0x00; uint8_t type = BDADDR_BREDR; char addr[18]; int opt; while ((opt = getopt_long(argc, argv, "+a:t:h", add_device_options, NULL)) != -1) { switch (opt) { case 'a': action = strtol(optarg, NULL, 0); break; case 't': type = strtol(optarg, NULL, 0); break; case 'h': add_device_usage(); return noninteractive_quit(EXIT_SUCCESS); default: add_device_usage(); return noninteractive_quit(EXIT_FAILURE); } } argc -= optind; argv += optind; optind = 0; if (argc < 1) { add_device_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.action = action; ba2str(&cp.addr.bdaddr, addr); print("Adding device with %s (%s)", addr, typestr(cp.addr.type)); if (mgmt_send(mgmt, MGMT_OP_ADD_DEVICE, index, sizeof(cp), &cp, add_device_rsp, NULL, NULL) == 0) { error("Unable to send add device command"); return noninteractive_quit(EXIT_FAILURE); } } static void remove_device_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { if (status != 0) error("Remove device failed with status 0x%02x (%s)", status, mgmt_errstr(status)); noninteractive_quit(EXIT_SUCCESS); } static void del_device_usage(void) { print("Usage: del-device [-t type]
"); } static struct option del_device_options[] = { { "help", 0, 0, 'h' }, { "type", 1, 0, 't' }, { 0, 0, 0, 0 } }; static void cmd_del_device(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_remove_device cp; uint8_t type = BDADDR_BREDR; char addr[18]; int opt; while ((opt = getopt_long(argc, argv, "+t:h", del_device_options, NULL)) != -1) { switch (opt) { case 't': type = strtol(optarg, NULL, 0); break; case 'h': del_device_usage(); return noninteractive_quit(EXIT_SUCCESS); default: del_device_usage(); return noninteractive_quit(EXIT_FAILURE); } } argc -= optind; argv += optind; optind = 0; if (argc < 1) { del_device_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; ba2str(&cp.addr.bdaddr, addr); print("Removing device with %s (%s)", addr, typestr(cp.addr.type)); if (mgmt_send(mgmt, MGMT_OP_REMOVE_DEVICE, index, sizeof(cp), &cp, remove_device_rsp, NULL, NULL) == 0) { error("Unable to send remove device command"); return noninteractive_quit(EXIT_FAILURE); } } static void cmd_clr_devices(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { char *bdaddr_any = "00:00:00:00:00:00"; char *rm_argv[] = { "del-device", bdaddr_any, NULL }; cmd_del_device(mgmt, index, 2, rm_argv); } static void local_oob_ext_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_read_local_oob_ext_data *rp = param; uint16_t eir_len; if (status != 0) { error("Read Local OOB Ext Data failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return noninteractive_quit(EXIT_FAILURE); } if (len < sizeof(*rp)) { error("Too small (%u bytes) read_local_oob_ext rsp", len); return noninteractive_quit(EXIT_FAILURE); } eir_len = le16_to_cpu(rp->eir_len); if (len != sizeof(*rp) + eir_len) { error("local_oob_ext: expected %zu bytes, got %u bytes", sizeof(*rp) + eir_len, len); return noninteractive_quit(EXIT_FAILURE); } print_eir(rp->eir, eir_len); noninteractive_quit(EXIT_SUCCESS); } static void cmd_bredr_oob(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_read_local_oob_ext_data cp; if (index == MGMT_INDEX_NONE) index = 0; cp.type = SCAN_TYPE_BREDR; if (!mgmt_send(mgmt, MGMT_OP_READ_LOCAL_OOB_EXT_DATA, index, sizeof(cp), &cp, local_oob_ext_rsp, NULL, NULL)) { error("Unable to send read_local_oob_ext cmd"); return noninteractive_quit(EXIT_FAILURE); } } static void cmd_le_oob(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_read_local_oob_ext_data cp; if (index == MGMT_INDEX_NONE) index = 0; cp.type = SCAN_TYPE_LE; if (!mgmt_send(mgmt, MGMT_OP_READ_LOCAL_OOB_EXT_DATA, index, sizeof(cp), &cp, local_oob_ext_rsp, NULL, NULL)) { error("Unable to send read_local_oob_ext cmd"); return noninteractive_quit(EXIT_FAILURE); } } static const char *adv_flags_str[] = { "connectable", "general-discoverable", "limited-discoverable", "managed-flags", "tx-power", "scan-rsp-appearance", "scan-rsp-local-name", }; static const char *adv_flags2str(uint32_t flags) { static char str[256]; unsigned i; int off; off = 0; str[0] = '\0'; for (i = 0; i < NELEM(adv_flags_str); i++) { if ((flags & (1 << i)) != 0) off += snprintf(str + off, sizeof(str) - off, "%s ", adv_flags_str[i]); } return str; } static void adv_features_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_read_adv_features *rp = param; uint32_t supported_flags; if (status != 0) { error("Reading adv features failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return noninteractive_quit(EXIT_FAILURE); } if (len < sizeof(*rp)) { error("Too small adv features reply (%u bytes)", len); return noninteractive_quit(EXIT_FAILURE); } if (len < sizeof(*rp) + rp->num_instances * sizeof(uint8_t)) { error("Instances count (%u) doesn't match reply length (%u)", rp->num_instances, len); return noninteractive_quit(EXIT_FAILURE); } supported_flags = le32_to_cpu(rp->supported_flags); print("Supported flags: %s", adv_flags2str(supported_flags)); print("Max advertising data len: %u", rp->max_adv_data_len); print("Max scan response data len: %u", rp->max_scan_rsp_len); print("Max instances: %u", rp->max_instances); print("Instances list with %u item%s", rp->num_instances, rp->num_instances != 1 ? "s" : ""); return noninteractive_quit(EXIT_SUCCESS); } static void cmd_advinfo(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { if (index == MGMT_INDEX_NONE) index = 0; if (!mgmt_send(mgmt, MGMT_OP_READ_ADV_FEATURES, index, 0, NULL, adv_features_rsp, NULL, NULL)) { error("Unable to send advertising features command"); return noninteractive_quit(EXIT_FAILURE); } } static void adv_size_info_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_get_adv_size_info *rp = param; uint32_t flags; if (status != 0) { error("Reading adv size info failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return noninteractive_quit(EXIT_FAILURE); } if (len < sizeof(*rp)) { error("Too small adv size info reply (%u bytes)", len); return noninteractive_quit(EXIT_FAILURE); } flags = le32_to_cpu(rp->flags); print("Instance: %u", rp->instance); print("Flags: %s", adv_flags2str(flags)); print("Max advertising data len: %u", rp->max_adv_data_len); print("Max scan response data len: %u", rp->max_scan_rsp_len); return noninteractive_quit(EXIT_SUCCESS); } static void advsize_usage(void) { print("Usage: advsize [options] \nOptions:\n" "\t -c, --connectable \"connectable\" flag\n" "\t -g, --general-discov \"general-discoverable\" flag\n" "\t -l, --limited-discov \"limited-discoverable\" flag\n" "\t -m, --managed-flags \"managed-flags\" flag\n" "\t -p, --tx-power \"tx-power\" flag"); } static struct option advsize_options[] = { { "help", 0, 0, 'h' }, { "connectable", 0, 0, 'c' }, { "general-discov", 0, 0, 'g' }, { "limited-discov", 0, 0, 'l' }, { "managed-flags", 0, 0, 'm' }, { "tx-power", 0, 0, 'p' }, { 0, 0, 0, 0} }; static void cmd_advsize(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_get_adv_size_info cp; uint8_t instance; uint32_t flags = 0; int opt; while ((opt = getopt_long(argc, argv, "+cglmph", advsize_options, NULL)) != -1) { switch (opt) { case 'c': flags |= MGMT_ADV_FLAG_CONNECTABLE; break; case 'g': flags |= MGMT_ADV_FLAG_DISCOV; break; case 'l': flags |= MGMT_ADV_FLAG_LIMITED_DISCOV; break; case 'm': flags |= MGMT_ADV_FLAG_MANAGED_FLAGS; break; case 'p': flags |= MGMT_ADV_FLAG_TX_POWER; break; default: advsize_usage(); return noninteractive_quit(EXIT_FAILURE); } } argc -= optind; argv += optind; optind = 0; if (argc != 1) { advsize_usage(); return noninteractive_quit(EXIT_FAILURE); } instance = strtol(argv[0], NULL, 0); if (index == MGMT_INDEX_NONE) index = 0; memset(&cp, 0, sizeof(cp)); cp.instance = instance; cp.flags = cpu_to_le32(flags); if (!mgmt_send(mgmt, MGMT_OP_GET_ADV_SIZE_INFO, index, sizeof(cp), &cp, adv_size_info_rsp, NULL, NULL)) { error("Unable to send advertising size info command"); return noninteractive_quit(EXIT_FAILURE); } } static void add_adv_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_add_advertising *rp = param; if (status != 0) { error("Add Advertising failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return noninteractive_quit(EXIT_FAILURE); } if (len != sizeof(*rp)) { error("Invalid Add Advertising response length (%u)", len); return noninteractive_quit(EXIT_FAILURE); } print("Instance added: %u", rp->instance); return noninteractive_quit(EXIT_SUCCESS); } static void add_adv_usage(void) { print("Usage: add-adv [options] \nOptions:\n" "\t -u, --uuid Service UUID\n" "\t -d, --adv-data Advertising Data bytes\n" "\t -s, --scan-rsp Scan Response Data bytes\n" "\t -t, --timeout Timeout in seconds\n" "\t -D, --duration Duration in seconds\n" "\t -c, --connectable \"connectable\" flag\n" "\t -g, --general-discov \"general-discoverable\" flag\n" "\t -l, --limited-discov \"limited-discoverable\" flag\n" "\t -m, --managed-flags \"managed-flags\" flag\n" "\t -p, --tx-power \"tx-power\" flag\n" "e.g.:\n" "\tadd-adv -u 180d -u 180f -d 080954657374204C45 1"); } static struct option add_adv_options[] = { { "help", 0, 0, 'h' }, { "uuid", 1, 0, 'u' }, { "adv-data", 1, 0, 'd' }, { "scan-rsp", 1, 0, 's' }, { "timeout", 1, 0, 't' }, { "duration", 1, 0, 'D' }, { "connectable", 0, 0, 'c' }, { "general-discov", 0, 0, 'g' }, { "limited-discov", 0, 0, 'l' }, { "managed-flags", 0, 0, 'm' }, { "tx-power", 0, 0, 'p' }, { 0, 0, 0, 0} }; static bool parse_bytes(char *optarg, uint8_t **bytes, size_t *len) { unsigned i; if (!optarg) { add_adv_usage(); return false; } *len = strlen(optarg); if (*len % 2) { error("Malformed data"); return false; } *len /= 2; if (*len > UINT8_MAX) { error("Data too long"); return false; } *bytes = malloc(*len); if (!*bytes) { error("Failed to allocate memory"); return false; } for (i = 0; i < *len; i++) { if (sscanf(optarg + (i * 2), "%2hhx", *bytes + i) != 1) { error("Invalid data"); free(*bytes); *bytes = NULL; return false; } } return true; } #define MAX_AD_UUID_BYTES 32 static void cmd_add_adv(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_add_advertising *cp = NULL; int opt; uint8_t *adv_data = NULL, *scan_rsp = NULL; size_t adv_len = 0, scan_rsp_len = 0; size_t cp_len; uint8_t uuids[MAX_AD_UUID_BYTES]; size_t uuid_bytes = 0; uint8_t uuid_type = 0; uint16_t timeout = 0, duration = 0; uint8_t instance; uuid_t uuid; bool success = false; bool quit = true; uint32_t flags = 0; while ((opt = getopt_long(argc, argv, "+u:d:s:t:D:cglmph", add_adv_options, NULL)) != -1) { switch (opt) { case 'u': if (bt_string2uuid(&uuid, optarg) < 0) { print("Invalid UUID: %s", optarg); goto done; } if (uuid_type && uuid_type != uuid.type) { print("UUID types must be consistent"); goto done; } if (uuid.type == SDP_UUID16) { if (uuid_bytes + 2 >= MAX_AD_UUID_BYTES) { print("Too many UUIDs"); goto done; } put_le16(uuid.value.uuid16, uuids + uuid_bytes); uuid_bytes += 2; } else if (uuid.type == SDP_UUID128) { if (uuid_bytes + 16 >= MAX_AD_UUID_BYTES) { print("Too many UUIDs"); goto done; } bswap_128(uuid.value.uuid128.data, uuids + uuid_bytes); uuid_bytes += 16; } else { printf("Unsupported UUID type"); goto done; } if (!uuid_type) uuid_type = uuid.type; break; case 'd': if (adv_len) { print("Only one adv-data option allowed"); goto done; } if (!parse_bytes(optarg, &adv_data, &adv_len)) goto done; break; case 's': if (scan_rsp_len) { print("Only one scan-rsp option allowed"); goto done; } if (!parse_bytes(optarg, &scan_rsp, &scan_rsp_len)) goto done; break; case 't': timeout = strtol(optarg, NULL, 0); break; case 'D': duration = strtol(optarg, NULL, 0); break; case 'c': flags |= MGMT_ADV_FLAG_CONNECTABLE; break; case 'g': flags |= MGMT_ADV_FLAG_DISCOV; break; case 'l': flags |= MGMT_ADV_FLAG_LIMITED_DISCOV; break; case 'm': flags |= MGMT_ADV_FLAG_MANAGED_FLAGS; break; case 'p': flags |= MGMT_ADV_FLAG_TX_POWER; break; case 'h': success = true; default: add_adv_usage(); goto done; } } argc -= optind; argv += optind; optind = 0; if (argc != 1) { add_adv_usage(); goto done; } if (uuid_bytes) uuid_bytes += 2; instance = strtol(argv[0], NULL, 0); if (index == MGMT_INDEX_NONE) index = 0; cp_len = sizeof(*cp) + uuid_bytes + adv_len + scan_rsp_len; cp = malloc0(cp_len); if (!cp) goto done; cp->instance = instance; put_le32(flags, &cp->flags); put_le16(timeout, &cp->timeout); put_le16(duration, &cp->duration); cp->adv_data_len = adv_len + uuid_bytes; cp->scan_rsp_len = scan_rsp_len; if (uuid_bytes) { cp->data[0] = uuid_bytes - 1; cp->data[1] = uuid_type == SDP_UUID16 ? 0x03 : 0x07; memcpy(cp->data + 2, uuids, uuid_bytes - 2); } memcpy(cp->data + uuid_bytes, adv_data, adv_len); memcpy(cp->data + uuid_bytes + adv_len, scan_rsp, scan_rsp_len); if (!mgmt_send(mgmt, MGMT_OP_ADD_ADVERTISING, index, cp_len, cp, add_adv_rsp, NULL, NULL)) { error("Unable to send \"Add Advertising\" command"); goto done; } quit = false; done: free(adv_data); free(scan_rsp); free(cp); if (quit) noninteractive_quit(success ? EXIT_SUCCESS : EXIT_FAILURE); } static void rm_adv_rsp(uint8_t status, uint16_t len, const void *param, void *user_data) { const struct mgmt_rp_remove_advertising *rp = param; if (status != 0) { error("Remove Advertising failed with status 0x%02x (%s)", status, mgmt_errstr(status)); return noninteractive_quit(EXIT_FAILURE); } if (len != sizeof(*rp)) { error("Invalid Remove Advertising response length (%u)", len); return noninteractive_quit(EXIT_FAILURE); } print("Instance removed: %u", rp->instance); return noninteractive_quit(EXIT_SUCCESS); } static void rm_adv_usage(void) { print("Usage: rm-adv "); } static void cmd_rm_adv(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { struct mgmt_cp_remove_advertising cp; uint8_t instance; if (argc != 2) { rm_adv_usage(); return noninteractive_quit(EXIT_FAILURE); } instance = strtol(argv[1], NULL, 0); if (index == MGMT_INDEX_NONE) index = 0; memset(&cp, 0, sizeof(cp)); cp.instance = instance; if (!mgmt_send(mgmt, MGMT_OP_REMOVE_ADVERTISING, index, sizeof(cp), &cp, rm_adv_rsp, NULL, NULL)) { error("Unable to send \"Remove Advertising\" command"); return noninteractive_quit(EXIT_FAILURE); } } static void cmd_clr_adv(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { char *all_instances = "0"; char *rm_argv[] = { "rm-adv", all_instances, NULL }; cmd_rm_adv(mgmt, index, 2, rm_argv); } struct cmd_info { char *cmd; void (*func)(struct mgmt *mgmt, uint16_t index, int argc, char **argv); char *doc; char * (*gen) (const char *text, int state); void (*disp) (char **matches, int num_matches, int max_length); }; static struct cmd_info all_cmd[] = { { "version", cmd_version, "Get the MGMT Version" }, { "commands", cmd_commands, "List supported commands" }, { "config", cmd_config, "Show configuration info" }, { "info", cmd_info, "Show controller info" }, { "extinfo", cmd_extinfo, "Show extended controller info" }, { "auto-power", cmd_auto_power, "Power all available features" }, { "power", cmd_power, "Toggle powered state" }, { "discov", cmd_discov, "Toggle discoverable state" }, { "connectable",cmd_connectable,"Toggle connectable state" }, { "fast-conn", cmd_fast_conn, "Toggle fast connectable state" }, { "bondable", cmd_bondable, "Toggle bondable state" }, { "pairable", cmd_bondable, "Toggle bondable state" }, { "linksec", cmd_linksec, "Toggle link level security" }, { "ssp", cmd_ssp, "Toggle SSP mode" }, { "sc", cmd_sc, "Toogle SC support" }, { "hs", cmd_hs, "Toggle HS support" }, { "le", cmd_le, "Toggle LE support" }, { "advertising",cmd_advertising,"Toggle LE advertising", }, { "bredr", cmd_bredr, "Toggle BR/EDR support", }, { "privacy", cmd_privacy, "Toggle privacy support" }, { "class", cmd_class, "Set device major/minor class" }, { "disconnect", cmd_disconnect, "Disconnect device" }, { "con", cmd_con, "List connections" }, { "find", cmd_find, "Discover nearby devices" }, { "find-service", cmd_find_service, "Discover nearby service" }, { "stop-find", cmd_stop_find, "Stop discovery" }, { "name", cmd_name, "Set local name" }, { "pair", cmd_pair, "Pair with a remote device" }, { "cancelpair", cmd_cancel_pair,"Cancel pairing" }, { "unpair", cmd_unpair, "Unpair device" }, { "keys", cmd_keys, "Load Link Keys" }, { "ltks", cmd_ltks, "Load Long Term Keys" }, { "irks", cmd_irks, "Load Identity Resolving Keys" }, { "block", cmd_block, "Block Device" }, { "unblock", cmd_unblock, "Unblock Device" }, { "add-uuid", cmd_add_uuid, "Add UUID" }, { "rm-uuid", cmd_remove_uuid,"Remove UUID" }, { "clr-uuids", cmd_clr_uuids, "Clear UUIDs" }, { "local-oob", cmd_local_oob, "Local OOB data" }, { "remote-oob", cmd_remote_oob, "Remote OOB data" }, { "did", cmd_did, "Set Device ID" }, { "static-addr",cmd_static_addr,"Set static address" }, { "public-addr",cmd_public_addr,"Set public address" }, { "ext-config", cmd_ext_config, "External configuration" }, { "debug-keys", cmd_debug_keys, "Toogle debug keys" }, { "conn-info", cmd_conn_info, "Get connection information" }, { "io-cap", cmd_io_cap, "Set IO Capability" }, { "scan-params",cmd_scan_params,"Set Scan Parameters" }, { "get-clock", cmd_clock_info, "Get Clock Information" }, { "add-device", cmd_add_device, "Add Device" }, { "del-device", cmd_del_device, "Remove Device" }, { "clr-devices",cmd_clr_devices,"Clear Devices" }, { "bredr-oob", cmd_bredr_oob, "Local OOB data (BR/EDR)" }, { "le-oob", cmd_le_oob, "Local OOB data (LE)" }, { "advinfo", cmd_advinfo, "Show advertising features" }, { "advsize", cmd_advsize, "Show advertising size info" }, { "add-adv", cmd_add_adv, "Add advertising instance" }, { "rm-adv", cmd_rm_adv, "Remove advertising instance" }, { "clr-adv", cmd_clr_adv, "Clear advertising instances" }, }; static void cmd_quit(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { mainloop_exit_success(); } static void register_mgmt_callbacks(struct mgmt *mgmt, uint16_t index) { mgmt_register(mgmt, MGMT_EV_CONTROLLER_ERROR, index, controller_error, NULL, NULL); mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, index, index_added, NULL, NULL); mgmt_register(mgmt, MGMT_EV_INDEX_REMOVED, index, index_removed, NULL, NULL); mgmt_register(mgmt, MGMT_EV_NEW_SETTINGS, index, new_settings, NULL, NULL); mgmt_register(mgmt, MGMT_EV_DISCOVERING, index, discovering, NULL, NULL); mgmt_register(mgmt, MGMT_EV_NEW_LINK_KEY, index, new_link_key, NULL, NULL); mgmt_register(mgmt, MGMT_EV_DEVICE_CONNECTED, index, connected, NULL, NULL); mgmt_register(mgmt, MGMT_EV_DEVICE_DISCONNECTED, index, disconnected, NULL, NULL); mgmt_register(mgmt, MGMT_EV_CONNECT_FAILED, index, conn_failed, NULL, NULL); mgmt_register(mgmt, MGMT_EV_AUTH_FAILED, index, auth_failed, NULL, NULL); mgmt_register(mgmt, MGMT_EV_CLASS_OF_DEV_CHANGED, index, class_of_dev_changed, NULL, NULL); mgmt_register(mgmt, MGMT_EV_LOCAL_NAME_CHANGED, index, local_name_changed, NULL, NULL); mgmt_register(mgmt, MGMT_EV_DEVICE_FOUND, index, device_found, mgmt, NULL); mgmt_register(mgmt, MGMT_EV_PIN_CODE_REQUEST, index, request_pin, mgmt, NULL); mgmt_register(mgmt, MGMT_EV_USER_CONFIRM_REQUEST, index, user_confirm, mgmt, NULL); mgmt_register(mgmt, MGMT_EV_USER_PASSKEY_REQUEST, index, request_passkey, mgmt, NULL); mgmt_register(mgmt, MGMT_EV_PASSKEY_NOTIFY, index, passkey_notify, mgmt, NULL); mgmt_register(mgmt, MGMT_EV_UNCONF_INDEX_ADDED, index, unconf_index_added, NULL, NULL); mgmt_register(mgmt, MGMT_EV_UNCONF_INDEX_REMOVED, index, unconf_index_removed, NULL, NULL); mgmt_register(mgmt, MGMT_EV_NEW_CONFIG_OPTIONS, index, new_config_options, NULL, NULL); mgmt_register(mgmt, MGMT_EV_EXT_INDEX_ADDED, index, ext_index_added, NULL, NULL); mgmt_register(mgmt, MGMT_EV_EXT_INDEX_REMOVED, index, ext_index_removed, NULL, NULL); mgmt_register(mgmt, MGMT_EV_LOCAL_OOB_DATA_UPDATED, index, local_oob_data_updated, NULL, NULL); mgmt_register(mgmt, MGMT_EV_ADVERTISING_ADDED, index, advertising_added, NULL, NULL); mgmt_register(mgmt, MGMT_EV_ADVERTISING_REMOVED, index, advertising_removed, NULL, NULL); } static void cmd_select(struct mgmt *mgmt, uint16_t index, int argc, char **argv) { if (argc != 2) { error("Usage: select "); return; } mgmt_cancel_all(mgmt); mgmt_unregister_all(mgmt); if (!strcmp(argv[1], "none") || !strcmp(argv[1], "any") || !strcmp(argv[1], "all")) mgmt_index = MGMT_INDEX_NONE; else if (!strncmp(argv[1], "hci", 3)) mgmt_index = atoi(&argv[1][3]); else mgmt_index = atoi(argv[1]); register_mgmt_callbacks(mgmt, mgmt_index); print("Selected index %u", mgmt_index); update_prompt(mgmt_index); } static struct cmd_info interactive_cmd[] = { { "select", cmd_select, "Select a different index" }, { "quit", cmd_quit, "Exit program" }, { "exit", cmd_quit, "Exit program" }, { "help", NULL, "List supported commands" }, }; static char *cmd_generator(const char *text, int state) { static size_t i, j, len; const char *cmd; if (!state) { i = 0; j = 0; len = strlen(text); } while (i < NELEM(all_cmd)) { cmd = all_cmd[i++].cmd; if (!strncmp(cmd, text, len)) return strdup(cmd); } while (j < NELEM(interactive_cmd)) { cmd = interactive_cmd[j++].cmd; if (!strncmp(cmd, text, len)) return strdup(cmd); } return NULL; } static char **cmd_completion(const char *text, int start, int end) { char **matches = NULL; if (start > 0) { unsigned int i; for (i = 0; i < NELEM(all_cmd); i++) { struct cmd_info *c = &all_cmd[i]; if (strncmp(c->cmd, rl_line_buffer, start - 1)) continue; if (!c->gen) continue; rl_completion_display_matches_hook = c->disp; matches = rl_completion_matches(text, c->gen); break; } } else { rl_completion_display_matches_hook = NULL; matches = rl_completion_matches(text, cmd_generator); } if (!matches) rl_attempted_completion_over = 1; return matches; } static struct cmd_info *find_cmd(const char *cmd, struct cmd_info table[], size_t cmd_count) { size_t i; for (i = 0; i < cmd_count; i++) { if (!strcmp(table[i].cmd, cmd)) return &table[i]; } return NULL; } static void rl_handler(char *input) { struct cmd_info *c; wordexp_t w; char *cmd, **argv; size_t argc, i; if (!input) { rl_insert_text("quit"); rl_redisplay(); rl_crlf(); mainloop_quit(); return; } if (!strlen(input)) goto done; if (prompt_input(input)) goto done; add_history(input); if (wordexp(input, &w, WRDE_NOCMD)) goto done; if (w.we_wordc == 0) goto free_we; cmd = w.we_wordv[0]; argv = w.we_wordv; argc = w.we_wordc; c = find_cmd(cmd, all_cmd, NELEM(all_cmd)); if (!c && interactive) c = find_cmd(cmd, interactive_cmd, NELEM(interactive_cmd)); if (c && c->func) { c->func(mgmt, mgmt_index, argc, argv); goto free_we; } if (strcmp(cmd, "help")) { print("Invalid command"); goto free_we; } print("Available commands:"); for (i = 0; i < NELEM(all_cmd); i++) { c = &all_cmd[i]; if (c->doc) print(" %s %-*s %s", c->cmd, (int)(25 - strlen(c->cmd)), "", c->doc ? : ""); } if (!interactive) goto free_we; for (i = 0; i < NELEM(interactive_cmd); i++) { c = &interactive_cmd[i]; if (c->doc) print(" %s %-*s %s", c->cmd, (int)(25 - strlen(c->cmd)), "", c->doc ? : ""); } free_we: wordfree(&w); done: free(input); } static void usage(void) { unsigned int i; printf("btmgmt ver %s\n", VERSION); printf("Usage:\n" "\tbtmgmt [options] [command parameters]\n"); printf("Options:\n" "\t--index \tSpecify adapter index\n" "\t--verbose\tEnable extra logging\n" "\t--help\tDisplay help\n"); printf("Commands:\n"); for (i = 0; i < NELEM(all_cmd); i++) printf("\t%-15s\t%s\n", all_cmd[i].cmd, all_cmd[i].doc); printf("\n" "For more information on the usage of each command use:\n" "\tbtmgmt --help\n" ); } static struct option main_options[] = { { "index", 1, 0, 'i' }, { "verbose", 0, 0, 'v' }, { "help", 0, 0, 'h' }, { 0, 0, 0, 0 } }; static bool prompt_read(struct io *io, void *user_data) { rl_callback_read_char(); return true; } static struct io *setup_stdin(void) { struct io *io; io = io_new(STDIN_FILENO); if (!io) return io; io_set_read_handler(io, prompt_read, NULL, NULL); return io; } int main(int argc, char *argv[]) { struct io *input; uint16_t index = MGMT_INDEX_NONE; int status, opt; while ((opt = getopt_long(argc, argv, "+hi:", main_options, NULL)) != -1) { switch (opt) { case 'i': if (strlen(optarg) > 3 && strncasecmp(optarg, "hci", 3) == 0) index = atoi(optarg + 3); else index = atoi(optarg); break; case 'h': default: usage(); return 0; } } argc -= optind; argv += optind; optind = 0; mainloop_init(); mgmt = mgmt_new_default(); if (!mgmt) { fprintf(stderr, "Unable to open mgmt_socket\n"); return EXIT_FAILURE; } if (argc > 0) { struct cmd_info *c; c = find_cmd(argv[0], all_cmd, NELEM(all_cmd)); if (!c) { fprintf(stderr, "Unknown command: %s\n", argv[0]); mgmt_unref(mgmt); return EXIT_FAILURE; } c->func(mgmt, index, argc, argv); } register_mgmt_callbacks(mgmt, index); /* Interactive mode */ if (!argc) input = setup_stdin(); else input = NULL; if (input) { interactive = true; rl_attempted_completion_function = cmd_completion; rl_erase_empty_line = 1; rl_callback_handler_install(NULL, rl_handler); update_prompt(index); rl_redisplay(); } mgmt_index = index; status = mainloop_run(); if (input) { io_destroy(input); rl_message(""); rl_callback_handler_remove(); } mgmt_cancel_all(mgmt); mgmt_unregister_all(mgmt); mgmt_unref(mgmt); return status; }