diff options
Diffstat (limited to 'btctl/btctl.c')
-rw-r--r-- | btctl/btctl.c | 2308 |
1 files changed, 2308 insertions, 0 deletions
diff --git a/btctl/btctl.c b/btctl/btctl.c new file mode 100644 index 0000000..d47d3f7 --- /dev/null +++ b/btctl/btctl.c @@ -0,0 +1,2308 @@ +/* + * Android Bluetooth Control tool + * + * Copyright (C) 2013 João Paulo Rechi Vita + * + * 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 + * + */ + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <hardware/bluetooth.h> +#include <hardware/bt_gatt.h> +#include <hardware/bt_gatt_client.h> +#include <hardware/hardware.h> + +#include "util.h" +#include "rl_helper.h" + +#define VERSION "0.4" + +#define MAX_LINE_SIZE 64 +#define MAX_SVCS_SIZE 128 +#define MAX_CHARS_SIZE 8 +#define MAX_CONNECTIONS 10 +#define PENDING_CONN_ID 0 +#define INVALID_CONN_ID -1 + +/* AD types */ +#define AD_FLAGS 0x01 +#define AD_UUID16_SOME 0x02 +#define AD_UUID16_ALL 0x03 +#define AD_UUID128_SOME 0x06 +#define AD_UUID128_ALL 0x07 +#define AD_NAME_SHORT 0x08 +#define AD_NAME_COMPLETE 0x09 +#define AD_TX_POWER 0x0a +#define AD_SLAVE_CONN_INT 0x12 +#define AD_SOLICIT_UUID16 0x14 +#define AD_SOLICIT_UUID128 0x15 +#define AD_SERVICE_DATA 0x16 +#define AD_PUBLIC_ADDRESS 0x17 +#define AD_RANDOM_ADDRESS 0x18 +#define AD_GAP_APPEARANCE 0x19 +#define AD_ADV_INTERVAL 0x1a +#define AD_MANUFACTURER_DATA 0xff + +typedef enum { + NORMAL_PSTATE, + SSP_CONSENT_PSTATE, + SSP_ENTRY_PSTATE +} prompt_state_t; + +typedef struct char_info { + btgatt_char_id_t char_id; + bt_uuid_t *descrs; + uint8_t descr_count; +} char_info_t; + +typedef struct service_info { + btgatt_srvc_id_t svc_id; + char_info_t *chars_buf; + uint8_t chars_buf_size; + uint8_t char_count; +} service_info_t; + +typedef struct connection { + bt_bdaddr_t remote_addr; + int conn_id; + + /* When searching for services, we receive at search_result_cb a pointer + * for btgatt_srvc_id_t. But its value is replaced each time. So one option + * is to store these values and show a simpler ID to user. + * + * This static list limits the number of services that we can store, but it + * is simpler than using linked list. + */ + service_info_t svcs[MAX_SVCS_SIZE]; + int svcs_size; +} connection_t; + +/* Data that have to be acessable by the callbacks */ +struct userdata { + const bt_interface_t *btiface; + uint8_t btiface_initialized; + const btgatt_interface_t *gattiface; + uint8_t gattiface_initialized; + uint8_t quit; + bt_state_t adapter_state; /* The adapter is always OFF in the beginning */ + bt_discovery_state_t discovery_state; + uint8_t scan_state; + bool client_registered; + int client_if; + + prompt_state_t prompt_state; + bt_bdaddr_t r_bd_addr; /* remote address when pairing */ + + connection_t conns[MAX_CONNECTIONS]; +} u; + +/* Arbitrary UUID used to identify this application with the GATT library. The + * Android JAVA framework + * (frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java, + * frameworks/base/core/java/android/bluetooth/BluetoothGatt.java and + * frameworks/base/core/java/android/bluetooth/BluetoothGattServer.java) uses + * the method randomUUID() + */ +static bt_uuid_t app_uuid = { + .uu = { 0x1b, 0x1c, 0xb9, 0x2e, 0x0d, 0x2e, 0x4c, 0x45, \ + 0xbb, 0xb9, 0xf4, 0x1b, 0x46, 0x39, 0x23, 0x36 } +}; + +/* Struct that defines a user command */ +typedef struct cmd { + const char *name; + const char *description; + void (*handler)(char *args); +} cmd_t; + + +void change_prompt_state(prompt_state_t new_state) { + static char prompt_line[64] = {0}; + char addr_str[BT_ADDRESS_STR_LEN]; + + switch (new_state) { + case NORMAL_PSTATE: + strcpy(prompt_line, "> "); + break; + case SSP_CONSENT_PSTATE: + sprintf(prompt_line, "Pair with %s (Y/N)? ", + ba2str(u.r_bd_addr.address, addr_str)); + break; + case SSP_ENTRY_PSTATE: + sprintf(prompt_line, "Entry PIN code of dev %s: ", + ba2str(u.r_bd_addr.address, addr_str)); + break; + } + rl_set_prompt(prompt_line); + u.prompt_state = new_state; +} + +static connection_t *get_connection(int conn_id) +{ + int i; + + if (conn_id <= INVALID_CONN_ID) + return NULL; + + for (i = 0; i < MAX_CONNECTIONS; i++) + if (u.conns[i].conn_id == conn_id) + return &u.conns[i]; + + return NULL; +} + +/* clear any cache list of connected device */ +static void clear_list_cache(int conn_id) { + connection_t *conn; + uint8_t i; + + conn = get_connection(conn_id); + if (conn == NULL) + return; + + for (i = 0; i < conn->svcs_size; i++) + conn->svcs[i].char_count = 0; + conn->svcs_size = 0; +} + +static int find_svc(connection_t *conn, btgatt_srvc_id_t *svc) { + uint8_t i; + + for (i = 0; i < conn->svcs_size; i++) + if (conn->svcs[i].svc_id.is_primary == svc->is_primary && + conn->svcs[i].svc_id.id.inst_id == svc->id.inst_id && + !memcmp(&conn->svcs[i].svc_id.id.uuid, &svc->id.uuid, + sizeof(bt_uuid_t))) + return i; + return -1; +} + +static int find_char(service_info_t *svc_info, btgatt_char_id_t *ch) { + uint8_t i; + + for (i = 0; i < svc_info->char_count; i++) { + btgatt_char_id_t *char_id = &svc_info->chars_buf[i].char_id; + + if (char_id->inst_id == ch->inst_id && + !memcmp(&char_id->uuid, &ch->uuid, sizeof(bt_uuid_t))) + return i; + } + + return -1; +} + +/* Clean blanks until a non-blank is found */ +static void line_skip_blanks(char **line) { + while (**line == ' ') + (*line)++; +} + +/* Parses a sub-string out of a string */ +static void line_get_str(char **line, char *str) { + line_skip_blanks(line); + + while (**line != 0 && **line != ' ') { + *str = **line; + (*line)++; + str++; + } + + *str = 0; +} + +static void cmd_quit(char *args) { + u.quit = 1; +} + +/* Called every time the adapter state changes */ +static void adapter_state_change_cb(bt_state_t state) { + + u.adapter_state = state; + rl_printf("\nAdapter state changed: %i\n", state); + + if (state == BT_STATE_ON) { + /* Register as a GATT client with the stack + * + * This has to be done here because it is the first available point we're + * sure the GATT interface is initialized and ready to be used, since + * there is callback for gattiface->init(). + */ + bt_status_t status = u.gattiface->client->register_client(&app_uuid); + if (status != BT_STATUS_SUCCESS) + rl_printf("Failed to register as a GATT client, status: %d\n", + status); + } +} + +/* Enables the Bluetooth adapter */ +static void cmd_enable(char *args) { + int status; + + if (u.adapter_state == BT_STATE_ON) { + rl_printf("Bluetooth is already enabled\n"); + return; + } + + if (u.gattiface == NULL) { + rl_printf("Unable to enable Bluetooth Adapter: GATT interface not " + "available\n"); + return; + } + + status = u.btiface->enable(); + if (status != BT_STATUS_SUCCESS) + rl_printf("Failed to enable Bluetooth\n"); +} + +/* Disables the Bluetooth adapter */ +static void cmd_disable(char *args) { + bt_status_t result; + int status; + + if (u.adapter_state == BT_STATE_OFF) { + rl_printf("Bluetooth is already disabled\n"); + return; + } + + result = u.gattiface->client->unregister_client(u.client_if); + if (result != BT_STATUS_SUCCESS) + rl_printf("Failed to unregister client, error: %u\n", result); + + status = u.btiface->disable(); + if (status != BT_STATUS_SUCCESS) + rl_printf("Failed to disable Bluetooth\n"); +} + +static void adapter_properties_cb(bt_status_t status, int num_properties, + bt_property_t *properties) { + char addr_str[BT_ADDRESS_STR_LEN]; + int i; + + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to get adapter properties, error: %i\n", status); + return; + } + + rl_printf("\nAdapter properties\n"); + + while (num_properties--) { + bt_property_t prop = properties[num_properties]; + + switch (prop.type) { + case BT_PROPERTY_BDNAME: + rl_printf(" Name: %s\n", (const char *) prop.val); + break; + + case BT_PROPERTY_BDADDR: + rl_printf(" Address: %s\n", ba2str((uint8_t *) prop.val, + addr_str)); + break; + + case BT_PROPERTY_CLASS_OF_DEVICE: + rl_printf(" Class of Device: 0x%x\n", + ((uint32_t *) prop.val)[0]); + break; + + case BT_PROPERTY_TYPE_OF_DEVICE: + switch (((bt_device_type_t *) prop.val)[0]) { + case BT_DEVICE_DEVTYPE_BREDR: + rl_printf(" Device Type: BR/EDR only\n"); + break; + case BT_DEVICE_DEVTYPE_BLE: + rl_printf(" Device Type: LE only\n"); + break; + case BT_DEVICE_DEVTYPE_DUAL: + rl_printf(" Device Type: DUAL MODE\n"); + break; + } + break; + + case BT_PROPERTY_ADAPTER_BONDED_DEVICES: + i = prop.len / sizeof(bt_bdaddr_t); + rl_printf(" Bonded devices: %u\n", i); + while (i-- > 0) { + uint8_t *addr = ((bt_bdaddr_t *) prop.val)[i].address; + rl_printf(" Address: %s\n", ba2str(addr, addr_str)); + } + break; + + default: + /* Other properties not handled */ + break; + } + } +} + +static void device_found_cb(int num_properties, bt_property_t *properties) { + char addr_str[BT_ADDRESS_STR_LEN]; + + rl_printf("\nDevice found\n"); + + while (num_properties--) { + bt_property_t prop = properties[num_properties]; + + switch (prop.type) { + case BT_PROPERTY_BDNAME: + rl_printf(" name: %s\n", (const char *) prop.val); + break; + + case BT_PROPERTY_BDADDR: + rl_printf(" addr: %s\n", ba2str((uint8_t *) prop.val, + addr_str)); + break; + + case BT_PROPERTY_CLASS_OF_DEVICE: + rl_printf(" class: 0x%x\n", ((uint32_t *) prop.val)[0]); + break; + + case BT_PROPERTY_TYPE_OF_DEVICE: + switch ( ((bt_device_type_t *) prop.val)[0] ) { + case BT_DEVICE_DEVTYPE_BREDR: + rl_printf(" type: BR/EDR only\n"); + break; + case BT_DEVICE_DEVTYPE_BLE: + rl_printf(" type: LE only\n"); + break; + case BT_DEVICE_DEVTYPE_DUAL: + rl_printf(" type: DUAL MODE\n"); + break; + } + break; + + case BT_PROPERTY_REMOTE_FRIENDLY_NAME: + rl_printf(" alias: %s\n", (const char *) prop.val); + break; + + case BT_PROPERTY_REMOTE_RSSI: + rl_printf(" rssi: %i\n", ((uint8_t *) prop.val)[0]); + break; + + case BT_PROPERTY_REMOTE_VERSION_INFO: + rl_printf(" version info:\n"); + rl_printf(" version: %d\n", + ((bt_remote_version_t *) prop.val)->version); + rl_printf(" subversion: %d\n", + ((bt_remote_version_t *) prop.val)->sub_ver); + rl_printf(" manufacturer: %d\n", + ((bt_remote_version_t *) prop.val)->manufacturer); + break; + + default: + rl_printf(" Unknown property type:%i len:%i val:%p\n", + prop.type, prop.len, prop.val); + break; + } + } +} + +static void discovery_state_changed_cb(bt_discovery_state_t state) { + u.discovery_state = state; + rl_printf("\nDiscovery state changed: %i\n", state); +} + +static void cmd_discovery(char *args) { + bt_status_t status; + char arg[MAX_LINE_SIZE]; + + line_get_str(&args, arg); + + if (arg[0] == 0 || strcmp(arg, "help") == 0) { + rl_printf("discovery -- Controls discovery of nearby devices\n"); + rl_printf("Arguments:\n"); + rl_printf("start starts a new discovery session\n"); + rl_printf("stop interrupts an ongoing discovery session\n"); + + } else if (strcmp(arg, "start") == 0) { + + if (u.adapter_state != BT_STATE_ON) { + rl_printf("Unable to start discovery: Adapter is down\n"); + return; + } + + if (u.discovery_state == BT_DISCOVERY_STARTED) { + rl_printf("Discovery is already running\n"); + return; + } + + status = u.btiface->start_discovery(); + if (status != BT_STATUS_SUCCESS) + rl_printf("Failed to start discovery\n"); + + } else if (strcmp(arg, "stop") == 0) { + + if (u.discovery_state == BT_DISCOVERY_STOPPED) { + rl_printf("Unable to stop discovery: Discovery is not running\n"); + return; + } + + status = u.btiface->cancel_discovery(); + if (status != BT_STATUS_SUCCESS) + rl_printf("Failed to stop discovery\n"); + + } else + rl_printf("Invalid argument \"%s\"\n", arg); +} + +static void parse_ad_data(uint8_t *data, uint8_t length) { + uint8_t i = 0; + uint8_t ad_type = data[i++]; + + switch (ad_type) { + uint8_t j; + + case AD_FLAGS: { + uint8_t mask = data[i]; + static const struct { + uint8_t bit; + const char *str; + } eir_flags_table[] = { + {0, "LE Limited Discoverable Mode"}, + {1, "LE General Discoverable Mode"}, + {2, "BR/EDR Not Supported"}, + {3, "Simultaneous LE and BR/EDR (Controller)"}, + {4, "Simultaneous LE and BR/EDR (Host)"}, + {0xFF, NULL} + }; + + rl_printf(" Flags\n"); + + for (j = 0; eir_flags_table[j].str; j++) { + if (data[i] & (1 << eir_flags_table[j].bit)) { + rl_printf(" %s\n", eir_flags_table[j].str); + mask &= ~(1 << eir_flags_table[j].bit); + } + } + + if (mask) + rl_printf(" Unknown flags (0x%02X)\n", mask); + + break; + } + case AD_UUID16_ALL: + case AD_UUID16_SOME: + case AD_SOLICIT_UUID16: { + uint8_t count = (length - 1) / sizeof(uint16_t); + const char *msg = NULL; + + switch (ad_type) { + case AD_UUID16_ALL: + msg = " Complete list of 16-bit Service UUIDs: "; + break; + case AD_UUID16_SOME: + msg = " Incomplete list of 16-bit Service UUIDs: "; + break; + case AD_SOLICIT_UUID16: + msg = " List of 16-bit Service Solicitation UUIDs: "; + break; + } + + rl_printf("%s%u entr%s\n", msg, count, count == 1 ? "y" : "ies"); + + for (j = 0; j < count; j++) + rl_printf(" 0x%02X%02X\n", data[i+j*sizeof(uint16_t)+1], + data[i+j*sizeof(uint16_t)]); + + break; + } + case AD_UUID128_ALL: + case AD_UUID128_SOME: + case AD_SOLICIT_UUID128: { + uint8_t count = (length - 1) / 16; + const char *msg = NULL; + + switch (ad_type) { + case AD_UUID128_ALL: + msg = " Complete list of 128-bit Service UUIDs: "; + break; + case AD_UUID128_SOME: + msg = " Incomplete list of 128-bit Service UUIDs: "; + break; + case AD_SOLICIT_UUID128: + msg = " List of 128-bit Service Solicitation UUIDs: "; + break; + } + + rl_printf("%s%u entr%s\n", msg, count, count == 1 ? "y" : "ies"); + + for (j = 0; j < count; j++) + rl_printf(" %02X %02X %02X %02X %02X %02X %02X %02X %02X " + "%02X %02X %02X %02X %02X %02X %02X\n", + data[i+j*16+15], data[i+j*16+14], data[i+j*16+13], + data[i+j*16+12], data[i+j*16+11], data[i+j*16+10], + data[i+j*16+9], data[i+j*16+8], data[i+j*16+7], + data[i+j*16+6], data[i+j*16+5], data[i+j*16+4], + data[i+j*16+3], data[i+j*16+2], data[i+j*16+1], + data[i+j*16]); + + break; + } + case AD_NAME_SHORT: + case AD_NAME_COMPLETE: { + char name[length]; + + memset(name, 0, sizeof(name)); + memcpy(name, &data[i], length-1); + + if (ad_type == AD_NAME_SHORT) + rl_printf(" Shortened Local Name\n"); + else + rl_printf(" Complete Local Name\n"); + + rl_printf(" %s\n", name); + + break; + } + case AD_TX_POWER: + rl_printf(" TX Power Level\n"); + rl_printf(" %d\n", (int8_t) data[i]); + break; + case AD_SLAVE_CONN_INT: { + uint16_t min, max; + + rl_printf(" Slave Connection Interval\n"); + + min = data[i] + (data[i+1] << 4); + if (min >= 0x0006 && min <= 0x0c80) + rl_printf(" Minimum = %.2f\n", (float) min * 1.25); + + max = data[i+2] + (data[i+3] << 4); + if (max >= 0x0006 && max <= 0x0c80) + rl_printf(" Maximum = %.2f\n", (float) max * 1.25); + + break; + } + case AD_SERVICE_DATA: + rl_printf(" Service Data\n"); + break; + case AD_PUBLIC_ADDRESS: + case AD_RANDOM_ADDRESS: + if (ad_type == AD_PUBLIC_ADDRESS) + rl_printf(" Public Target Address\n"); + else + rl_printf(" Random Target Address\n"); + + rl_printf(" %02X:%02X:%02X:%02X:%02X:%02X\n", data[i+5], + data[i+4], data[i+3], data[i+2], data[i+1], data[i]); + break; + case AD_GAP_APPEARANCE: + rl_printf(" Appearance\n"); + rl_printf(" 0x%02X%02X\n", data[i+1], data[i]); + break; + case AD_ADV_INTERVAL: { + uint16_t adv_interval; + + rl_printf(" Advertising Interval\n"); + + adv_interval = data[i] + (data[i+1] << 4); + rl_printf(" %.2f\n", (float) adv_interval * 0.625); + + break; + } + case AD_MANUFACTURER_DATA: + rl_printf(" Manufacturer-specific data\n"); + rl_printf(" Company ID: 0x%02X%02X\n", data[i+1], data[i]); + rl_printf(" Data:"); + for (j = i+2; j < i+length; j++) + rl_printf(" %02X", data[j]); + rl_printf("\n"); + break; + default: + rl_printf(" Invalid data type 0x%02X\n", ad_type); + break; + } +} + +static void scan_result_cb(bt_bdaddr_t *bda, int rssi, uint8_t *adv_data) { + char addr_str[BT_ADDRESS_STR_LEN]; + uint8_t i = 0; + + rl_printf("\nBLE device found\n"); + rl_printf(" Address: %s\n", ba2str(bda->address, addr_str)); + rl_printf(" RSSI: %d\n", rssi); + + rl_printf(" Advertising Data:\n"); + while (i < 31 && adv_data[i] != 0) { + uint8_t length, ad_type, j; + + length = adv_data[i++]; + parse_ad_data(&adv_data[i], length); + + i += length; + } +} + +static void cmd_scan(char *args) { + bt_status_t status; + char arg[MAX_LINE_SIZE]; + + if (u.gattiface == NULL) { + rl_printf("Unable to start/stop BLE scan: GATT interface not " + "available\n"); + return; + } + + line_get_str(&args, arg); + + if (arg[0] == 0 || strcmp(arg, "help") == 0) { + rl_printf("scan -- Controls BLE scan of nearby devices\n"); + rl_printf("Arguments:\n"); + rl_printf("start starts a new scan session\n"); + rl_printf("stop interrupts an ongoing scan session\n"); + + } else if (strcmp(arg, "start") == 0) { + + if (u.adapter_state != BT_STATE_ON) { + rl_printf("Unable to start discovery: Adapter is down\n"); + return; + } + + if (u.scan_state == 1) { + rl_printf("Scan is already running\n"); + return; + } + + status = u.gattiface->client->scan(u.client_if, 1); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to start discovery\n"); + return; + } + + u.scan_state = 1; + + } else if (strcmp(arg, "stop") == 0) { + + if (u.scan_state == 0) { + rl_printf("Unable to stop scan: Scan is not running\n"); + return; + } + + status = u.gattiface->client->scan(u.client_if, 0); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to stop scan\n"); + return; + } + + u.scan_state = 0; + + } else + rl_printf("Invalid argument \"%s\"\n", arg); +} + +static void connect_cb(int conn_id, int status, int client_if, + bt_bdaddr_t *bda) { + char addr_str[BT_ADDRESS_STR_LEN]; + connection_t *conn; + + /* Get the space reserved on buffer (conn_id is zero) */ + conn = get_connection(PENDING_CONN_ID); + if (conn == NULL) { + rl_printf("No space reserved on buffer\n"); + return; + } + + if (status != 0) { + rl_printf("Failed to connect to device %s, status: %i\n", + ba2str(bda->address, addr_str), status); + conn->conn_id = INVALID_CONN_ID; + return; + } + + rl_printf("Connected to device %s, conn_id: %d, client_if: %d\n", + ba2str(bda->address, addr_str), conn_id, client_if); + + conn->conn_id = conn_id; +} + +static void disconnect_cb(int conn_id, int status, int client_if, + bt_bdaddr_t *bda) { + char addr_str[BT_ADDRESS_STR_LEN]; + connection_t *conn; + + rl_printf("Disconnected from device %s, conn_id: %d, client_if: %d, " + "status: %d\n", ba2str(bda->address, addr_str), conn_id, + client_if, status); + + conn = get_connection(conn_id); + if (conn != NULL) { + conn->conn_id = INVALID_CONN_ID; + clear_list_cache(conn_id); + } +} + +static void cmd_disconnect(char *args) { + bt_status_t status; + connection_t *conn; + int id; + + if (sscanf(args, " %i ", &id) != 1) { + rl_printf("Usage: disconnect <connection ID>\n"); + return; + } + + conn = get_connection(id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + status = u.gattiface->client->disconnect(u.client_if, &conn->remote_addr, + conn->conn_id); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to disconnect, status: %d\n", status); + return; + } + + if (id == PENDING_CONN_ID) { + char addr_str[BT_ADDRESS_STR_LEN]; + + conn->conn_id = INVALID_CONN_ID; + rl_printf("Cancel pending connection: %s\n", + ba2str(conn->remote_addr.address, addr_str)); + clear_list_cache(id); + } +} + +void do_ssp_reply(const bt_bdaddr_t *bd_addr, bt_ssp_variant_t variant, + uint8_t accept, uint32_t passkey) { + bt_status_t status = u.btiface->ssp_reply(bd_addr, variant, accept, + passkey); + + if (status != BT_STATUS_SUCCESS) { + rl_printf("SSP Reply error: %u\n", status); + return; + } +} + +void pin_request_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *bd_name, + uint32_t cod) { + + /* ask user which PIN code is showed at remote device */ + memcpy(&u.r_bd_addr, remote_bd_addr, sizeof(u.r_bd_addr)); + change_prompt_state(SSP_ENTRY_PSTATE); +} + +void ssp_request_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *bd_name, + uint32_t cod, bt_ssp_variant_t pairing_variant, + uint32_t pass_key) { + + if (pairing_variant == BT_SSP_VARIANT_CONSENT) { + /* we need to ask to user if he wants to bond */ + memcpy(&u.r_bd_addr, remote_bd_addr, sizeof(u.r_bd_addr)); + change_prompt_state(SSP_CONSENT_PSTATE); + } else { + char addr_str[BT_ADDRESS_STR_LEN]; + const char *action = "Enter"; + + if (pairing_variant == BT_SSP_VARIANT_PASSKEY_CONFIRMATION) { + action = "Confirm"; + do_ssp_reply(remote_bd_addr, pairing_variant, true, pass_key); + } + + rl_printf("Remote addr: %s\n", + ba2str(remote_bd_addr->address, addr_str)); + rl_printf("%s passkey on peer device: %d\n", action, pass_key); + } +} + +static void cmd_connect(char *args) { + bt_status_t status; + connection_t *conn = NULL; + char arg[MAX_LINE_SIZE]; + int ret, i; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE connect: GATT interface not available\n"); + return; + } + + if (u.adapter_state != BT_STATE_ON) { + rl_printf("Unable to connect: Adapter is down\n"); + return; + } + + if (u.client_registered == false) { + rl_printf("Unable to connect: We're not registered as GATT client\n"); + return; + } + + /* Check if there is a pending connect (conn_id is zero) */ + conn = get_connection(PENDING_CONN_ID); + if (conn != NULL) { + rl_printf("Unable to connect: previous connecting on going\n"); + return; + } + + /* Check if there is space available and save the index */ + for (i = 0; i < MAX_CONNECTIONS; i++) + if (u.conns[i].conn_id == INVALID_CONN_ID) { + conn = &u.conns[i]; + break; + } + + /* No space available on buffer */ + if (conn == NULL) { + rl_printf("Unable to connect: maximum number of connections " + "exceeded\n"); + return; + } + + line_get_str(&args, arg); + + ret = str2ba(arg, &conn->remote_addr); + if (ret != 0) { + rl_printf("Unable to connect: Invalid bluetooth address: %s\n", arg); + return; + } + + rl_printf("Connecting to: %s\n", arg); + + status = u.gattiface->client->connect(u.client_if, &conn->remote_addr, + true); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to connect, status: %d\n", status); + return; + } + + /* Lock the space on buffer */ + conn->conn_id = PENDING_CONN_ID; +} + +static void bond_state_changed_cb(bt_status_t status, bt_bdaddr_t *bda, + bt_bond_state_t state) { + char addr_str[BT_ADDRESS_STR_LEN]; + char state_str[32] = {0}; + + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to change bond state, status: %d\n", status); + return; + } + + switch (state) { + case BT_BOND_STATE_NONE: + strcpy(state_str, "BT_BOND_STATE_NONE"); + change_prompt_state(NORMAL_PSTATE); /* no bonding process running */ + break; + + case BT_BOND_STATE_BONDING: + strcpy(state_str, "BT_BOND_STATE_BONDING"); + break; + + case BT_BOND_STATE_BONDED: + strcpy(state_str, "BT_BOND_STATE_BONDED"); + break; + + default: + sprintf(state_str, "Unknown (%d)", state); + break; + } + + rl_printf("Bond state changed for device %s: %s\n", + ba2str(bda->address, addr_str), state_str); +} + +static void cmd_pair(char *args) { + char arg[MAX_LINE_SIZE]; + unsigned arg_pos; + bt_bdaddr_t addr; + bt_status_t status; + int ret; + const char* valid_arguments[] = { + "create", + "cancel", + "remove", + NULL, + }; + + if (u.btiface == NULL) { + rl_printf("Unable to BLE pair: Bluetooth interface not available\n"); + return; + } + + if (u.adapter_state != BT_STATE_ON) { + rl_printf("Unable to pair: Adapter is down\n"); + return; + } + + line_get_str(&args, arg); + + if (arg[0] == 0 || strcmp(arg, "help") == 0) { + rl_printf("bond -- Controls BLE bond process\n"); + rl_printf("Arguments:\n"); + rl_printf("create <address> start bond process to address\n"); + rl_printf("cancel <address> cancel bond process to address\n"); + rl_printf("remove <address> remove bond to address\n"); + return; + } + + arg_pos = str_in_list(valid_arguments, arg); + if (str_in_list(valid_arguments, arg) < 0) { + rl_printf("Invalid argument \"%s\"\n", arg); + return; + } + + line_get_str(&args, arg); + ret = str2ba(arg, &addr); + if (ret != 0) { + rl_printf("Invalid bluetooth address: %s\n", arg); + return; + } + + switch (arg_pos) { + case 0: + status = u.btiface->create_bond(&addr); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to create bond, status: %d\n", status); + return; + } + break; + case 1: + status = u.btiface->cancel_bond(&addr); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to cancel bond, status: %d\n", status); + return; + } + break; + case 2: + status = u.btiface->remove_bond(&addr); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to remove bond, status: %d\n", status); + return; + } + break; + } +} + +/* called when search has finished */ +void search_complete_cb(int conn_id, int status) { + + rl_printf("Search complete, status: %u\n", status); +} + +/* called for each search result */ +void search_result_cb(int conn_id, btgatt_srvc_id_t *srvc_id) { + char uuid_str[UUID128_STR_LEN] = {0}; + connection_t *conn = get_connection(conn_id); + + if (conn->svcs_size < MAX_SVCS_SIZE) { + /* srvc_id value is replaced each time, so we need to copy it */ + memcpy(&conn->svcs[conn->svcs_size].svc_id, srvc_id, + sizeof(btgatt_srvc_id_t)); + conn->svcs_size++; + } + + rl_printf("ID:%i %s UUID: %s instance:%i\n", conn->svcs_size - 1, + srvc_id->is_primary ? "Primary" : "Secondary", + uuid2str(&srvc_id->id.uuid, uuid_str), srvc_id->id.inst_id); +} + +static void cmd_search_svc(char *args) { + char arg[MAX_LINE_SIZE]; + bt_status_t status; + connection_t *conn; + int conn_id; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE search-svc: GATT interface not avaiable\n"); + return; + } + + /* Get connection ID argument */ + line_get_str(&args, arg); + if (sscanf(arg, " %i ", &conn_id) != 1) { + rl_printf("Usage: search-svc <connection ID> [UUID]\n"); + return; + } + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } else if (conn_id == PENDING_CONN_ID) { + rl_printf("Connection is not active\n"); + return; + } + + clear_list_cache(conn_id); + + /* Get UUID argument (if exists) */ + line_get_str(&args, arg); + if (strlen(arg) > 0) { + bt_uuid_t uuid; + if (!str2uuid(arg, &uuid)) { + rl_printf("Invalid format of UUID: %s\n", arg); + return; + } + + status = u.gattiface->client->search_service(conn_id, &uuid); + } else + status = u.gattiface->client->search_service(conn_id, NULL); + + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to search services\n"); + return; + } +} + +void get_included_service_cb(int conn_id, int status, btgatt_srvc_id_t *srvc_id, + btgatt_srvc_id_t *incl_srvc_id) { + + if (status == 0) { + bt_status_t ret; + char uuid_str[UUID128_STR_LEN] = {0}; + + rl_printf("Included UUID: %s\n", + uuid2str(&incl_srvc_id->id.uuid, uuid_str)); + + /* this callback is called only one time, so to have next included + * service we need to call get_included_service again using incl_srvc_id + * as parameter + */ + ret = u.gattiface->client->get_included_service(conn_id, srvc_id, + incl_srvc_id); + if (ret != BT_STATUS_SUCCESS) { + rl_printf("Failed to list included services\n"); + return; + } + } else + rl_printf("Included finished, status: %i\n", status); +} + +static void cmd_included(char *args) { + char arg[MAX_LINE_SIZE]; + bt_status_t status; + connection_t *conn; + int conn_id, id; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE included: GATT interface not avaiable\n"); + return; + } + + if (sscanf(args, " %i %i", &conn_id, &id) != 2) { + rl_printf("Usage: included <connection ID> <service ID>\n"); + return; + } + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (id < 0 || id >= conn->svcs_size) { + rl_printf("Invalid ID: %s need to be between 0 and %i\n", arg, + conn->svcs_size - 1); + return; + } + + /* get first included service */ + status = u.gattiface->client->get_included_service(conn->conn_id, + &conn->svcs[id].svc_id, + NULL); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to list included services\n"); + return; + } +} + +void get_characteristic_cb(int conn_id, int status, btgatt_srvc_id_t *srvc_id, + btgatt_char_id_t *char_id, int char_prop) { + bt_status_t ret; + char uuid_str[UUID128_STR_LEN] = {0}; + int svc_id; + service_info_t *svc_info; + connection_t *conn; + + if (status != 0) { + if (status == 0x85) { /* it's not really an error, just finished */ + rl_printf("List characteristics finished\n"); + return; + } + + rl_printf("List characteristics finished, status: %i %s\n", status, + atterror2str(status)); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("%s: Invalid connection ID\n", __func__); + return; + } + + svc_id = find_svc(conn, srvc_id); + + if (svc_id < 0) { + rl_printf("Received invalid characteristic (service inexistent)\n"); + return; + } + svc_info = &conn->svcs[svc_id]; + + rl_printf("ID:%i UUID: %s instance:%i properties:0x%x\n", + svc_info->char_count, uuid2str(&char_id->uuid, uuid_str), + char_id->inst_id, char_prop); + + if (svc_info->char_count == svc_info->chars_buf_size) { + int i; + + svc_info->chars_buf_size += MAX_CHARS_SIZE; + svc_info->chars_buf = realloc(svc_info->chars_buf, sizeof(char_info_t) * + svc_info->chars_buf_size); + + for (i = svc_info->char_count; i < svc_info->chars_buf_size; i++) { + svc_info->chars_buf[i].descrs = NULL; + svc_info->chars_buf[i].descr_count = 0; + } + } + + /* copy characteristic data */ + memcpy(&svc_info->chars_buf[svc_info->char_count].char_id, char_id, + sizeof(btgatt_char_id_t)); + svc_info->chars_buf[svc_info->char_count].descr_count = 0; + + svc_info->char_count++; + + /* get next characteristic */ + ret = u.gattiface->client->get_characteristic(conn->conn_id, srvc_id, + char_id); + if (ret != BT_STATUS_SUCCESS) { + rl_printf("Failed to list characteristics\n"); + return; + } +} + +/* search all characteristics of specific service */ +static void cmd_chars(char *args) { + bt_status_t status; + connection_t *conn; + service_info_t *svc; + int id, conn_id; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE characteristics: GATT interface not " + "avaiable\n"); + return; + } + + if (sscanf(args, " %i %i ", &conn_id, &id) != 2) { + rl_printf("Usage: characteristics <connection ID> <serviceID>\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (id < 0 || id >= conn->svcs_size) { + rl_printf("Invalid ID: %i need to be between 0 and %i\n", id, + conn->svcs_size - 1); + return; + } + + svc = &conn->svcs[id]; + if (svc->chars_buf == NULL) { + int i; + + svc->chars_buf_size = MAX_CHARS_SIZE; + svc->chars_buf = malloc(sizeof(char_info_t) * svc->chars_buf_size); + + for (i = svc->char_count; i < svc->chars_buf_size; i++) { + svc->chars_buf[i].descrs = NULL; + svc->chars_buf[i].descr_count = 0; + } + } else if (svc->char_count > 0) + svc->char_count = 0; + + /* get first characteristic of service */ + status = u.gattiface->client->get_characteristic(conn->conn_id, + &svc->svc_id, + NULL); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to list characteristics\n"); + return; + } +} + +void read_characteristic_cb(int conn_id, int status, + btgatt_read_params_t *p_data) { + char uuid_str[UUID128_STR_LEN] = {0}; + char value_hexstr[BTGATT_MAX_ATTR_LEN * 3 + 1] = {0}; + int i; + + if (status != 0) { + rl_printf("Read characteristic error, status:%i %s\n", status, + atterror2str(status)); + return; + } + + for (i = 0; i < p_data->value.len; i++) + sprintf(&value_hexstr[i * 3], "%02hhx ", p_data->value.value[i]); + + rl_printf("Read Characteristic\n"); + rl_printf(" Service UUID: %s\n", uuid2str(&p_data->srvc_id.id.uuid, + uuid_str)); + rl_printf(" Characteristic UUID: %s\n", uuid2str(&p_data->char_id.uuid, + uuid_str)); + rl_printf(" value_type:%i status:%i value(hex): %s\n", p_data->value_type, + p_data->status, value_hexstr); +} + +static void cmd_read_char(char *args) { + bt_status_t status; + service_info_t *svc_info; + char_info_t *char_info; + connection_t *conn; + int svc_id, char_id, auth, conn_id; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE read-char: GATT interface not avaiable\n"); + return; + } + + if (sscanf(args, " %i %i %i %i ", &conn_id, &svc_id, &char_id, &auth) + != 4) { + rl_printf("Usage: read-char <connection ID> <serviceID> " + "<characteristicID> <auth>\n"); + rl_printf(" auth - enable authentication (1) or not (0)\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (svc_id < 0 || svc_id >= conn->svcs_size) { + rl_printf("Invalid serviceID: %i need to be between 0 and %i\n", svc_id, + conn->svcs_size - 1); + return; + } + + svc_info = &conn->svcs[svc_id]; + if (char_id < 0 || char_id >= svc_info->char_count) { + rl_printf("Invalid characteristicID, try to run characteristics " + "command.\n"); + return; + } + + char_info = &svc_info->chars_buf[char_id]; + status = u.gattiface->client->read_characteristic(conn->conn_id, + &svc_info->svc_id, + &char_info->char_id, + auth); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to read characteristic\n"); + return; + } +} + +void write_characteristic_cb(int conn_id, int status, + btgatt_write_params_t *p_data) { + char uuid_str[UUID128_STR_LEN] = {0}; + + if (status != 0) { + rl_printf("Write characteristic error, status:%i %s\n", status, + atterror2str(status)); + return; + } + + rl_printf("Write characteristic success\n"); + rl_printf(" Service UUID: %s\n", uuid2str(&p_data->srvc_id.id.uuid, + uuid_str)); + rl_printf(" Characteristic UUID: %s\n", uuid2str(&p_data->char_id.uuid, + uuid_str)); +} + +/* + * @param write_type 1 -> Write Command + * 2 -> Write Request + * 3 -> Prepare Write + */ +void write_char(int write_type, const char *cmd, char *args) { + bt_status_t status; + connection_t *conn; + service_info_t *svc_info; + char_info_t *char_info; + char *saveptr = NULL, *tok; + int params = 0; + int conn_id, svc_id, char_id, auth; + char new_value[BTGATT_MAX_ATTR_LEN]; + int new_value_len = 0; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE %s: GATT interface not avaiable\n", cmd); + return; + } + + tok = strtok_r(args, " ", &saveptr); + while (tok != NULL) { + switch (params) { + case 0: + if (sscanf(tok, " %i ", &conn_id) != 1) { + rl_printf("Invalid connection ID: %s\n", tok); + return; + } + break; + case 1: + if (sscanf(tok, " %i ", &svc_id) != 1) { + rl_printf("Invalid serviceID: %s\n", tok); + return; + } + break; + case 2: + if (sscanf(tok, " %i ", &char_id) != 1) { + rl_printf("Invalid characteristicID: %s\n", tok); + return; + } + break; + case 3: + if (sscanf(tok, " %i ", &auth) != 1) { + rl_printf("Invalid auth: %s\n", tok); + return; + } + break; + default: { + char *endptr = NULL; + unsigned long int v = strtoul(tok, &endptr, 16); + + if (endptr[0] != '\0' || v > 0xff) { + rl_printf("Invalid hex value: %s\n", tok); + return; + } + + if (new_value_len == sizeof(new_value)) { + rl_printf("Too many bytes to write in value!\n"); + return; + } + + new_value[new_value_len] = v; + new_value_len++; + break; + } + } + params++; + tok = strtok_r(NULL, " ", &saveptr); + } + + if (params < 5) { + rl_printf("Usage: %s <connection ID> <serviceID> <characteristicID> " + "<auth> [value ...]\n", cmd); + rl_printf(" auth - enable authentication (1) or not (0)\n"); + rl_printf(" value - a sequence of hex values (eg: DE AD BE EF)\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (svc_id < 0 || svc_id >= conn->svcs_size) { + rl_printf("Invalid serviceID: %i need to be between 0 and %i\n", svc_id, + conn->svcs_size - 1); + return; + } + + svc_info = &conn->svcs[svc_id]; + if (char_id < 0 || char_id >= svc_info->char_count) { + rl_printf("Invalid characteristicID, try to run characteristics " + "command.\n"); + return; + } + + rl_printf("Writing %i bytes\n", new_value_len); + char_info = &svc_info->chars_buf[char_id]; + status = u.gattiface->client->write_characteristic(conn_id, + &svc_info->svc_id, + &char_info->char_id, + write_type, + new_value_len, + auth, new_value); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to write characteristic\n"); + return; + } +} + +static void cmd_write_req_char(char *args) { + + write_char(2, "write-req-char", args); +} + +static void cmd_write_cmd_char(char *args) { + + write_char(1, "write-cmd-char", args); +} + +void get_descriptor_cb(int conn_id, int status, btgatt_srvc_id_t *srvc_id, + btgatt_char_id_t *char_id, bt_uuid_t *descr_id) { + bt_status_t ret; + char uuid_str[UUID128_STR_LEN] = {0}; + int svc_id, ch_id; + service_info_t *svc_info = NULL; + char_info_t *char_info = NULL; + connection_t *conn; + + if (status != 0) { + if (status == 0x85) { /* it's not really an error, just finished */ + rl_printf("List characteristics descriptors finished\n"); + return; + } + + rl_printf("List characteristic descriptors finished, status: %i %s\n", + status, atterror2str(status)); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("%s: Invalid connection ID\n", __func__); + return; + } + + svc_id = find_svc(conn, srvc_id); + if (svc_id < 0) { + rl_printf("Received invalid descriptor (service inexistent)\n"); + return; + } + svc_info = &conn->svcs[svc_id]; + + ch_id = find_char(svc_info, char_id); + if (ch_id < 0) { + rl_printf("Received invalid descriptor (characteristic inexistent)\n"); + return; + } + char_info = &svc_info->chars_buf[ch_id]; + + rl_printf("ID:%i UUID: %s\n", char_info->descr_count, + uuid2str(descr_id, uuid_str)); + + if (char_info->descr_count == 255) { + rl_printf("Max descriptors overflow error\n"); + return; + } + + char_info->descr_count++; + char_info->descrs = realloc(char_info->descrs, char_info->descr_count * + sizeof(char_info->descrs[0])); + + /* copy descriptor data */ + memcpy(&char_info->descrs[char_info->descr_count - 1], descr_id, + sizeof(*descr_id)); + + /* get next descriptor */ + ret = u.gattiface->client->get_descriptor(conn->conn_id, srvc_id, char_id, + descr_id); + if (ret != BT_STATUS_SUCCESS) { + rl_printf("Failed to list descriptors\n"); + return; + } +} + +static void cmd_char_desc(char *args) { + bt_status_t status; + service_info_t *svc_info; + char_info_t *char_info; + connection_t *conn; + int svc_id, char_id, conn_id; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE char-desc: GATT interface not avaiable\n"); + return; + } + + if (sscanf(args, " %i %i %i ", &conn_id, &svc_id, &char_id) != 3) { + rl_printf("Usage: char-desc <connection ID> <serviceID> " + "<characteristicID>\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (svc_id < 0 || svc_id >= conn->svcs_size) { + rl_printf("Invalid serviceID: %i need to be between 0 and %i\n", svc_id, + conn->svcs_size - 1); + return; + } + + svc_info = &conn->svcs[svc_id]; + if (char_id < 0 || char_id >= svc_info->char_count) { + rl_printf("Invalid characteristicID, try to run characteristics " + "command.\n"); + return; + } + + char_info = &svc_info->chars_buf[char_id]; + char_info->descr_count = 0; + /* get first descriptor */ + status = u.gattiface->client->get_descriptor(conn->conn_id, + &svc_info->svc_id, + &char_info->char_id, NULL); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to list characteristic descriptors\n"); + return; + } +} + +void write_descriptor_cb(int conn_id, int status, + btgatt_write_params_t *p_data) { + char uuid_str[UUID128_STR_LEN] = {0}; + + if (status != 0) { + rl_printf("Write descriptor error, status:%i %s\n", status, + atterror2str(status)); + return; + } + + rl_printf("Write descriptor success\n"); + rl_printf(" Service UUID: %s\n", uuid2str(&p_data->srvc_id.id.uuid, + uuid_str)); + rl_printf(" Characteristic UUID: %s\n", uuid2str(&p_data->char_id.uuid, + uuid_str)); + rl_printf(" Descriptor UUID: %s\n", uuid2str(&p_data->descr_id, + uuid_str)); +} + +static void cmd_write_desc(char *args) { + bt_status_t status; + connection_t *conn; + service_info_t *svc_info; + char_info_t *char_info; + bt_uuid_t *descr_uuid; + char *saveptr = NULL, *tok; + int params = 0; + int conn_id, svc_id, char_id, desc_id, auth; + char new_value[BTGATT_MAX_ATTR_LEN]; + int new_value_len = 0; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE write-desc: GATT interface not avaiable\n"); + return; + } + + tok = strtok_r(args, " ", &saveptr); + while (tok != NULL) { + switch (params) { + case 0: + if (sscanf(tok, " %i ", &conn_id) != 1) { + rl_printf("Invalid connection ID: %s\n", tok); + return; + } + break; + case 1: + if (sscanf(tok, " %i ", &svc_id) != 1) { + rl_printf("Invalid serviceID: %s\n", tok); + return; + } + break; + case 2: + if (sscanf(tok, " %i ", &char_id) != 1) { + rl_printf("Invalid characteristicID: %s\n", tok); + return; + } + break; + case 3: + if (sscanf(tok, " %i ", &desc_id) != 1) { + rl_printf("Invalid descriptorID: %s\n", tok); + return; + } + break; + case 4: + if (sscanf(tok, " %i ", &auth) != 1) { + rl_printf("Invalid auth: %s\n", tok); + return; + } + break; + default: { + char *endptr = NULL; + unsigned long int v = strtoul(tok, &endptr, 16); + + if (endptr[0] != '\0' || v > 0xff) { + rl_printf("Invalid hex value: %s\n", tok); + return; + } + + if (new_value_len == sizeof(new_value)) { + rl_printf("Too many bytes to write in value!\n"); + return; + } + + new_value[new_value_len] = v; + new_value_len++; + break; + } + } + params++; + tok = strtok_r(NULL, " ", &saveptr); + } + + if (params < 6) { + rl_printf("Usage: write-desc <connection ID> <service ID> " + "<characteristic ID> <descriptor ID> <auth> [value ...]\n"); + rl_printf(" auth - enable authentication (1) or not (0)\n"); + rl_printf(" value - a sequence of hex values (eg: DE AD BE EF)\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (svc_id < 0 || svc_id >= conn->svcs_size) { + rl_printf("Invalid serviceID: %i need to be between 0 and %i\n", svc_id, + conn->svcs_size - 1); + return; + } + + svc_info = &conn->svcs[svc_id]; + if (char_id < 0 || char_id >= svc_info->char_count) { + rl_printf("Invalid characteristicID, try to run characteristics " + "command.\n"); + return; + } + + char_info = &svc_info->chars_buf[char_id]; + if (desc_id < 0 || desc_id >= char_info->descr_count) { + rl_printf("Invalid descriptorID, try to run char-desc command.\n"); + return; + } + descr_uuid = &char_info->descrs[desc_id]; + + rl_printf("Writing %i bytes\n", new_value_len); + status = u.gattiface->client->write_descriptor(conn_id, &svc_info->svc_id, + &char_info->char_id, + descr_uuid, + 2 /* Write Request */, + new_value_len, auth, + new_value); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to write descriptor\n"); + return; + } +} + +void read_descriptor_cb(int conn_id, int status, btgatt_read_params_t *p_data) { + char uuid_str[UUID128_STR_LEN] = {0}; + char value_hexstr[BTGATT_MAX_ATTR_LEN * 3 + 1] = {0}; + int i; + + if (status != 0) { + rl_printf("Read descriptor error, status:%i %s\n", status, + atterror2str(status)); + return; + } + + for (i = 0; i < p_data->value.len; i++) + sprintf(&value_hexstr[i * 3], "%02hhx ", p_data->value.value[i]); + + rl_printf("Read Descriptor\n"); + rl_printf(" Service UUID: %s\n", uuid2str(&p_data->srvc_id.id.uuid, + uuid_str)); + rl_printf(" Characteristic UUID: %s\n", uuid2str(&p_data->char_id.uuid, + uuid_str)); + rl_printf(" Descriptor UUID: %s\n", uuid2str(&p_data->descr_id, + uuid_str)); + rl_printf(" value_type:%i status:%i value(hex): %s\n", p_data->value_type, + p_data->status, value_hexstr); +} + +static void cmd_read_desc(char *args) { + bt_status_t status; + service_info_t *svc_info; + char_info_t *char_info; + bt_uuid_t *descr_uuid; + connection_t *conn; + int svc_id, char_id, desc_id, auth, conn_id; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE read-desc: GATT interface not avaiable\n"); + return; + } + + if (sscanf(args, " %i %i %i %i %i ", &conn_id, &svc_id, &char_id, &desc_id, + &auth) != 5) { + rl_printf("Usage: read-desc <connection ID> <serviceID> " + "<characteristicID> <descriptorID> <auth>\n"); + rl_printf(" auth - enable authentication (1) or not (0)\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (svc_id < 0 || svc_id >= conn->svcs_size) { + rl_printf("Invalid serviceID: %i need to be between 0 and %i\n", svc_id, + conn->svcs_size - 1); + return; + } + + svc_info = &conn->svcs[svc_id]; + if (char_id < 0 || char_id >= svc_info->char_count) { + rl_printf("Invalid characteristicID, try to run characteristics " + "command.\n"); + return; + } + + char_info = &svc_info->chars_buf[char_id]; + if (desc_id < 0 || desc_id >= char_info->descr_count) { + rl_printf("Invalid descriptorID, try to run char-desc command.\n"); + return; + } + descr_uuid = &char_info->descrs[desc_id]; + + status = u.gattiface->client->read_descriptor(conn->conn_id, + &svc_info->svc_id, + &char_info->char_id, + descr_uuid, auth); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to read descriptor\n"); + return; + } +} + +void register_for_notification_cb(int conn_id, int registered, int status, + btgatt_srvc_id_t *srvc_id, + btgatt_char_id_t *char_id) { + char uuid_str[UUID128_STR_LEN] = {0}; + + if (status != 0) { + rl_printf("Un/register for characteristic notification status: %i %s\n", + status, atterror2str(status)); + return; + } + + rl_printf("Register for notification/indication: %s\n", registered ? + "registered" : "unregistered"); + rl_printf(" Service UUID: %s\n", uuid2str(&srvc_id->id.uuid, + uuid_str)); + rl_printf(" Characteristic UUID: %s\n", uuid2str(&char_id->uuid, + uuid_str)); +} + +void notify_cb(int conn_id, btgatt_notify_params_t *p_data) { + char uuid_str[UUID128_STR_LEN] = {0}; + char value_hexstr[BTGATT_MAX_ATTR_LEN * 3 + 1] = {0}; + char addr_str[BT_ADDRESS_STR_LEN]; + int i; + connection_t *conn; + + for (i = 0; i < p_data->len; i++) + sprintf(&value_hexstr[i * 3], "%02hhx ", p_data->value[i]); + + conn = get_connection(conn_id); + rl_printf("Notify Characteristic from address: %s connection ID: %i\n", + conn ? ba2str(conn->remote_addr.address, addr_str) : "Unknown", + conn_id); + rl_printf(" Service UUID: %s\n", uuid2str(&p_data->srvc_id.id.uuid, + uuid_str)); + rl_printf(" Characteristic UUID: %s\n", uuid2str(&p_data->char_id.uuid, + uuid_str)); + rl_printf(" is_notify:%i value(hex): %s\n", p_data->is_notify, + value_hexstr); +} + +static void cmd_reg_notification(char *args) { + bt_status_t status; + connection_t *conn; + service_info_t *svc_info; + char_info_t *char_info; + int conn_id, svc_id, char_id; + + if (u.gattiface == NULL) { + rl_printf("Unable to register notification/indication: GATT interface " + "not available\n"); + return; + } + + if (sscanf(args, " %i %i %i ", &conn_id, &svc_id, &char_id) != 3) { + rl_printf("Usage: reg-notif <connection ID> <service ID> " + "<characteristic ID>\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (svc_id < 0 || svc_id >= conn->svcs_size) { + rl_printf("Invalid serviceID: %i need to be between 0 and %i\n", svc_id, + conn->svcs_size - 1); + return; + } + + svc_info = &conn->svcs[svc_id]; + if (char_id < 0 || char_id >= svc_info->char_count) { + rl_printf("Invalid characteristicID, try to run characteristics " + "command\n"); + return; + } + + char_info = &svc_info->chars_buf[char_id]; + status = u.gattiface->client->register_for_notification(u.client_if, + &conn->remote_addr, + &svc_info->svc_id, + &char_info->char_id); + if (status != BT_STATUS_SUCCESS) + rl_printf("Failed to register for characteristic " + "notification/indication\n"); +} + +static void cmd_unreg_notification(char *args) { + bt_status_t status; + connection_t *conn; + service_info_t *svc_info; + char_info_t *char_info; + int conn_id, svc_id, char_id; + + if (u.gattiface == NULL) { + rl_printf("Unable to unregister notification/indication: GATT interface" + " not available\n"); + return; + } + + if (sscanf(args, " %i %i %i ", &conn_id, &svc_id, &char_id) != 3) { + rl_printf("Usage: unreg-notif <connection ID> <service ID> " + "<characteristic ID>\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } + + if (conn->svcs_size <= 0) { + rl_printf("Run search-svc first to get all services list\n"); + return; + } + + if (svc_id < 0 || svc_id >= conn->svcs_size) { + rl_printf("Invalid serviceID: %i need to be between 0 and %i\n", svc_id, + conn->svcs_size - 1); + return; + } + + svc_info = &conn->svcs[svc_id]; + if (char_id < 0 || char_id >= svc_info->char_count) { + rl_printf("Invalid characteristicID, try to run characteristics " + "command\n"); + return; + } + + char_info = &svc_info->chars_buf[char_id]; + status = u.gattiface->client->deregister_for_notification(u.client_if, + &conn->remote_addr, + &svc_info->svc_id, + &char_info->char_id); + if (status != BT_STATUS_SUCCESS) + rl_printf("Failed to unregister for characteristic " + "notification/indication\n"); +} + +void read_remote_rssi_cb(int client_if, bt_bdaddr_t *bda, int rssi, + int status) { + char addr_str[BT_ADDRESS_STR_LEN]; + + if (status != 0) { + rl_printf("Read RSSI error, status:%i %s\n", status, + atterror2str(status)); + return; + } + + rl_printf("Address: %s RSSI: %i\n", ba2str(bda->address, addr_str), rssi); +} + +static void cmd_rssi(char *args) { + bt_status_t status; + connection_t *conn; + int conn_id; + + if (u.gattiface == NULL) { + rl_printf("Unable to BLE RSSI: GATT interface not avaiable\n"); + return; + } + + if (sscanf(args, " %i ", &conn_id) != 1) { + rl_printf("Usage: rssi <connection ID>\n"); + return; + } + + conn = get_connection(conn_id); + if (conn == NULL) { + rl_printf("Invalid connection ID\n"); + return; + } else if (conn_id == PENDING_CONN_ID) { + rl_printf("Connection is not active\n"); + return; + } + + status = u.gattiface->client->read_remote_rssi(u.client_if, + &conn->remote_addr); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to request RSSI, status: %d\n", status); + return; + } +} + +static void cmd_conns(char *args) { + int i, c = 0; + + for (i = 0; i < MAX_CONNECTIONS; i++) { + connection_t *conn = &u.conns[i]; + char addr_str[BT_ADDRESS_STR_LEN]; + + if (conn->conn_id <= INVALID_CONN_ID) + continue; + + rl_printf("Connection ID: %i Address: %s%s\n", conn->conn_id, + ba2str(conn->remote_addr.address, addr_str), + conn->conn_id == PENDING_CONN_ID ? " (pending)" : ""); + c++; + } + + if (c == 0) + rl_printf("No connections active\n"); +} + +/* List of available user commands */ +static const cmd_t cmd_list[] = { + { "quit", " Exits", cmd_quit }, + { "enable", " Enables the Bluetooth adapter", cmd_enable }, + { "disable", " Disables the Bluetooth adapter", cmd_disable }, + { "discovery", " Controls discovery of nearby devices", cmd_discovery }, + { "scan", " Controls BLE scan of nearby devices", cmd_scan }, + { "connect", " Create a connection to a remote device", cmd_connect }, + { "pair", " Pair with remote device", cmd_pair }, + { "disconnect", " Disconnect from remote device", cmd_disconnect }, + { "search-svc", " Search services on remote device", cmd_search_svc }, + { "included", " List included services of a service", cmd_included }, + { "characteristics", "List characteristics of a service", cmd_chars }, + { "read-char", " Read a characteristic of a service", cmd_read_char }, + { "write-req-char", "Write a characteristic (Write Request)", + cmd_write_req_char }, + { "write-cmd-char", "Write a characteristic (No response)", + cmd_write_cmd_char }, + { "char-desc", " List descriptors from a characteristic", cmd_char_desc }, + { "write-desc", " Write on characteristic descriptor", cmd_write_desc }, + { "read-desc", " Read a characteristic descriptor", cmd_read_desc }, + { "reg-notif", " Register to receive characteristic " + "notification/indicaton", cmd_reg_notification }, + { "unreg-notif", " Unregister a previous request to receive " + "notification/indicaton", cmd_unreg_notification }, + { "rssi", " Request RSSI for connected device", cmd_rssi }, + { "connections", " Display active connections", cmd_conns }, + { NULL, NULL, NULL } +}; + +/* Parses a command and calls the respective handler */ +static void cmd_process(char *line) { + char cmd[MAX_LINE_SIZE]; + int i; + + if (line[0] == 0) + return; + + if (u.prompt_state == SSP_ENTRY_PSTATE) { + bt_status_t status = u.btiface->pin_reply(&u.r_bd_addr, true, + strlen(line), + (bt_pin_code_t *) line); + change_prompt_state(NORMAL_PSTATE); + return; + } + + line_get_str(&line, cmd); + + if (strcmp(cmd, "help") == 0) { + for (i = 0; cmd_list[i].name != NULL; i++) + rl_printf("%s %s\n", cmd_list[i].name, cmd_list[i].description); + return; + } + + for (i = 0; cmd_list[i].name != NULL; i++) + if (strcmp(cmd, cmd_list[i].name) == 0) { + cmd_list[i].handler(line); + return; + } + + rl_printf("%s: unknown command, use 'help' for a list of available " + "commands\n", cmd); +} + +static void register_client_cb(int status, int client_if, + bt_uuid_t *app_uuid) { + + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to register client, status: %d\n", status); + return; + } + + rl_printf("Registered!, client_if: %d\n", client_if); + + u.client_if = client_if; + u.client_registered = true; +} + +/* GATT client callbacks */ +static const btgatt_client_callbacks_t gattccbs = { + register_client_cb, /* Called after client is registered */ + scan_result_cb, /* called every time an advertising report is seen */ + connect_cb, /* called every time a connection attempt finishes */ + disconnect_cb, /* called every time a connection attempt finishes */ + search_complete_cb, /* search_complete_callback */ + search_result_cb, /* search_result_callback */ + get_characteristic_cb, /* get_characteristic_callback */ + get_descriptor_cb, /* get_descriptor_callback */ + get_included_service_cb, /* get_included_service_callback */ + register_for_notification_cb, /* register_for_notification_callback */ + notify_cb, /* notify_callback */ + read_characteristic_cb, /* read_characteristic_callback */ + write_characteristic_cb, /* write_characteristic_callback */ + read_descriptor_cb, /* read_descriptor_callback */ + write_descriptor_cb, /* write_descriptor_callback */ + NULL, /* execute_write_callback */ + read_remote_rssi_cb, /* read_remote_rssi_callback */ +}; + +/* GATT interface callbacks */ +static const btgatt_callbacks_t gattcbs = { + sizeof(btgatt_callbacks_t), + &gattccbs, + NULL /* btgatt_server_callbacks_t */ +}; + +/* This callback is used by the thread that handles Bluetooth interface (btif) + * to send events for its users. At the moment there are two events defined: + * + * ASSOCIATE_JVM: sinalizes the btif handler thread is ready; + * DISASSOCIATE_JVM: sinalizes the btif handler thread is about to exit. + * + * This events are used by the JNI to know when the btif handler thread should + * be associated or dessociated with the JVM + */ +static void thread_event_cb(bt_cb_thread_evt event) { + rl_printf("\nBluetooth interface %s\n", + event == ASSOCIATE_JVM ? "ready" : "finished"); + if (event == ASSOCIATE_JVM) { + u.btiface_initialized = 1; + + u.gattiface = u.btiface->get_profile_interface(BT_PROFILE_GATT_ID); + if (u.gattiface != NULL) { + bt_status_t status = u.gattiface->init(&gattcbs); + if (status != BT_STATUS_SUCCESS) { + rl_printf("Failed to initialize Bluetooth GATT interface, " + "status: %d\n", status); + u.gattiface = NULL; + } else + u.gattiface_initialized = 1; + } else + rl_printf("Failed to get Bluetooth GATT Interface\n"); + } else + u.btiface_initialized = 0; +} + +/* Bluetooth interface callbacks */ +static bt_callbacks_t btcbs = { + sizeof(bt_callbacks_t), + adapter_state_change_cb, /* Called every time the adapter state changes */ + adapter_properties_cb, /* adapter_properties_callback */ + NULL, /* remote_device_properties_callback */ + device_found_cb, /* Called for every device found */ + discovery_state_changed_cb, /* Called every time the discovery state changes */ + pin_request_cb, /* pin_request_callback */ + ssp_request_cb, /* ssp_request_callback */ + bond_state_changed_cb, /* bond_state_changed_callback */ + NULL, /* acl_state_changed_callback */ + thread_event_cb, /* Called when the JVM is associated / dissociated */ + NULL, /* dut_mode_recv_callback */ + NULL, /* le_test_mode_callback */ +}; + +/* Initialize the Bluetooth stack */ +static void bt_init() { + int status, i; + hw_module_t *module; + hw_device_t *hwdev; + bluetooth_device_t *btdev; + + u.btiface_initialized = 0; + u.quit = 0; + u.adapter_state = BT_STATE_OFF; /* The adapter is OFF in the beginning */ + + for (i = 0; i < MAX_CONNECTIONS; i++) + u.conns[i].conn_id = INVALID_CONN_ID; + + /* Get the Bluetooth module from libhardware */ + status = hw_get_module(BT_STACK_MODULE_ID, (hw_module_t const**) &module); + if (status < 0) { + errno = status; + err(1, "Failed to get the Bluetooth module"); + } + rl_printf("Bluetooth stack infomation:\n"); + rl_printf(" id = %s\n", module->id); + rl_printf(" name = %s\n", module->name); + rl_printf(" author = %s\n", module->author); + rl_printf(" HAL API version = %d\n", module->hal_api_version); + + /* Get the Bluetooth hardware device */ + status = module->methods->open(module, BT_STACK_MODULE_ID, &hwdev); + if (status < 0) { + errno = status; + err(2, "Failed to get the Bluetooth hardware device"); + } + rl_printf("Bluetooth device infomation:\n"); + rl_printf(" API version = %d\n", hwdev->version); + + /* Get the Bluetooth interface */ + btdev = (bluetooth_device_t *) hwdev; + u.btiface = btdev->get_bluetooth_interface(); + if (u.btiface == NULL) + err(3, "Failed to get the Bluetooth interface"); + + /* Init the Bluetooth interface, setting a callback for each operation */ + status = u.btiface->init(&btcbs); + if (status != BT_STATUS_SUCCESS && status != BT_STATUS_DONE) + err(4, "Failed to initialize the Bluetooth interface"); +} + +/* simple tab completer */ +const char *tab_completer_cb(char *line, int pos) { + int i = 0; + const cmd_t *p = &(cmd_list[0]); + + while (p->name) { + /* test only commands starting with it */ + if (strncmp(p->name, line, pos) == 0) + return p->name + pos; + + p = &cmd_list[++i]; + } + + return NULL; +} + +int main(int argc, char *argv[]) { + + /* check if I am root */ + if (getuid() != 0) { + printf("This software requires root access\n"); + return 1; + } + + rl_init(cmd_process); + change_prompt_state(NORMAL_PSTATE); + rl_set_tab_completer(tab_completer_cb); + + rl_printf("Android Bluetooth control tool version " VERSION "\n"); + + bt_init(); + + while (!u.quit) { + int c = getchar(); + + if (c == EOF) { + rl_printf("error reading input, exiting...\n"); + u.quit = true; + break; + } + + /* if we are in consent bonding process, we need only a char */ + if (u.prompt_state == SSP_CONSENT_PSTATE) { + c = toupper(c); + if (c == 'Y' || c == 'N') { + bt_status_t status; + printf("%c\n", c); /* user feedback */ + do_ssp_reply(&u.r_bd_addr, BT_SSP_VARIANT_CONSENT, + c == 'Y' ? true : false, 0); + } + change_prompt_state(NORMAL_PSTATE); + } else if (!rl_feed(c)) + break; /* user pressed ctrl-d */ + } + + /* Disable adapter on exit */ + if (u.adapter_state == BT_STATE_ON) + cmd_disable(NULL); + + /* Cleanup the Bluetooth interface */ + rl_printf("Processing Bluetooth interface cleanup\n"); + u.btiface->cleanup(); + while (u.btiface_initialized) + usleep(10000); + + rl_quit(); + return 0; +} |