| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * BlueZ - Bluetooth protocol stack for Linux
- *
- * Copyright (C) 2014 Google Inc.
- *
- *
- */
- #ifdef HAVE_CONFIG_H
- #include <config.h>
- #endif
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <stdbool.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <getopt.h>
- #include <limits.h>
- #include <errno.h>
- #include "lib/bluetooth.h"
- #include "lib/hci.h"
- #include "lib/hci_lib.h"
- #include "lib/l2cap.h"
- #include "lib/uuid.h"
- #include "src/shared/mainloop.h"
- #include "src/shared/util.h"
- #include "src/shared/att.h"
- #include "src/shared/queue.h"
- #include "src/shared/gatt-db.h"
- #include "src/shared/gatt-client.h"
- #define ATT_CID 4
- #define PRLOG(...) \
- printf(__VA_ARGS__); print_prompt();
- #define COLOR_OFF "\x1B[0m"
- #define COLOR_RED "\x1B[0;91m"
- #define COLOR_GREEN "\x1B[0;92m"
- #define COLOR_YELLOW "\x1B[0;93m"
- #define COLOR_BLUE "\x1B[0;94m"
- #define COLOR_MAGENTA "\x1B[0;95m"
- #define COLOR_BOLDGRAY "\x1B[1;30m"
- #define COLOR_BOLDWHITE "\x1B[1;37m"
- static bool verbose = false;
- struct client {
- int fd;
- struct bt_att *att;
- struct gatt_db *db;
- struct bt_gatt_client *gatt;
- unsigned int reliable_session_id;
- };
- static void print_prompt(void)
- {
- printf(COLOR_BLUE "[GATT client]" COLOR_OFF "# ");
- fflush(stdout);
- }
- static const char *ecode_to_string(uint8_t ecode)
- {
- switch (ecode) {
- case BT_ATT_ERROR_INVALID_HANDLE:
- return "Invalid Handle";
- case BT_ATT_ERROR_READ_NOT_PERMITTED:
- return "Read Not Permitted";
- case BT_ATT_ERROR_WRITE_NOT_PERMITTED:
- return "Write Not Permitted";
- case BT_ATT_ERROR_INVALID_PDU:
- return "Invalid PDU";
- case BT_ATT_ERROR_AUTHENTICATION:
- return "Authentication Required";
- case BT_ATT_ERROR_REQUEST_NOT_SUPPORTED:
- return "Request Not Supported";
- case BT_ATT_ERROR_INVALID_OFFSET:
- return "Invalid Offset";
- case BT_ATT_ERROR_AUTHORIZATION:
- return "Authorization Required";
- case BT_ATT_ERROR_PREPARE_QUEUE_FULL:
- return "Prepare Write Queue Full";
- case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND:
- return "Attribute Not Found";
- case BT_ATT_ERROR_ATTRIBUTE_NOT_LONG:
- return "Attribute Not Long";
- case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE:
- return "Insuficient Encryption Key Size";
- case BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN:
- return "Invalid Attribute value len";
- case BT_ATT_ERROR_UNLIKELY:
- return "Unlikely Error";
- case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION:
- return "Insufficient Encryption";
- case BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE:
- return "Group type Not Supported";
- case BT_ATT_ERROR_INSUFFICIENT_RESOURCES:
- return "Insufficient Resources";
- case BT_ERROR_CCC_IMPROPERLY_CONFIGURED:
- return "CCC Improperly Configured";
- case BT_ERROR_ALREADY_IN_PROGRESS:
- return "Procedure Already in Progress";
- case BT_ERROR_OUT_OF_RANGE:
- return "Out of Range";
- default:
- return "Unknown error type";
- }
- }
- static void att_disconnect_cb(int err, void *user_data)
- {
- printf("Device disconnected: %s\n", strerror(err));
- mainloop_quit();
- }
- static void att_debug_cb(const char *str, void *user_data)
- {
- const char *prefix = user_data;
- PRLOG(COLOR_BOLDGRAY "%s" COLOR_BOLDWHITE "%s\n" COLOR_OFF, prefix, str);
- }
- static void gatt_debug_cb(const char *str, void *user_data)
- {
- const char *prefix = user_data;
- PRLOG(COLOR_GREEN "%s%s\n" COLOR_OFF, prefix, str);
- }
- static void ready_cb(bool success, uint8_t att_ecode, void *user_data);
- static void service_changed_cb(uint16_t start_handle, uint16_t end_handle,
- void *user_data);
- static void log_service_event(struct gatt_db_attribute *attr, const char *str)
- {
- char uuid_str[MAX_LEN_UUID_STR];
- bt_uuid_t uuid;
- uint16_t start, end;
- gatt_db_attribute_get_service_uuid(attr, &uuid);
- bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
- gatt_db_attribute_get_service_handles(attr, &start, &end);
- PRLOG("%s - UUID: %s start: 0x%04x end: 0x%04x\n", str, uuid_str,
- start, end);
- }
- static void service_added_cb(struct gatt_db_attribute *attr, void *user_data)
- {
- log_service_event(attr, "Service Added");
- }
- static void service_removed_cb(struct gatt_db_attribute *attr, void *user_data)
- {
- log_service_event(attr, "Service Removed");
- }
- static struct client *client_create(int fd, uint16_t mtu)
- {
- struct client *cli;
- cli = new0(struct client, 1);
- if (!cli) {
- fprintf(stderr, "Failed to allocate memory for client\n");
- return NULL;
- }
- cli->att = bt_att_new(fd, false);
- if (!cli->att) {
- fprintf(stderr, "Failed to initialze ATT transport layer\n");
- bt_att_unref(cli->att);
- free(cli);
- return NULL;
- }
- if (!bt_att_set_close_on_unref(cli->att, true)) {
- fprintf(stderr, "Failed to set up ATT transport layer\n");
- bt_att_unref(cli->att);
- free(cli);
- return NULL;
- }
- if (!bt_att_register_disconnect(cli->att, att_disconnect_cb, NULL,
- NULL)) {
- fprintf(stderr, "Failed to set ATT disconnect handler\n");
- bt_att_unref(cli->att);
- free(cli);
- return NULL;
- }
- cli->fd = fd;
- cli->db = gatt_db_new();
- if (!cli->db) {
- fprintf(stderr, "Failed to create GATT database\n");
- bt_att_unref(cli->att);
- free(cli);
- return NULL;
- }
- cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu, 0);
- if (!cli->gatt) {
- fprintf(stderr, "Failed to create GATT client\n");
- gatt_db_unref(cli->db);
- bt_att_unref(cli->att);
- free(cli);
- return NULL;
- }
- gatt_db_register(cli->db, service_added_cb, service_removed_cb,
- NULL, NULL);
- if (verbose) {
- bt_att_set_debug(cli->att, BT_ATT_DEBUG_VERBOSE, att_debug_cb,
- "att: ", NULL);
- bt_gatt_client_set_debug(cli->gatt, gatt_debug_cb, "gatt: ",
- NULL);
- }
- bt_gatt_client_ready_register(cli->gatt, ready_cb, cli, NULL);
- bt_gatt_client_set_service_changed(cli->gatt, service_changed_cb, cli,
- NULL);
- /* bt_gatt_client already holds a reference */
- gatt_db_unref(cli->db);
- return cli;
- }
- static void client_destroy(struct client *cli)
- {
- bt_gatt_client_unref(cli->gatt);
- bt_att_unref(cli->att);
- free(cli);
- }
- static void print_uuid(const bt_uuid_t *uuid)
- {
- char uuid_str[MAX_LEN_UUID_STR];
- bt_uuid_t uuid128;
- bt_uuid_to_uuid128(uuid, &uuid128);
- bt_uuid_to_string(&uuid128, uuid_str, sizeof(uuid_str));
- printf("%s\n", uuid_str);
- }
- static void print_incl(struct gatt_db_attribute *attr, void *user_data)
- {
- struct client *cli = user_data;
- uint16_t handle, start, end;
- struct gatt_db_attribute *service;
- bt_uuid_t uuid;
- if (!gatt_db_attribute_get_incl_data(attr, &handle, &start, &end))
- return;
- service = gatt_db_get_attribute(cli->db, start);
- if (!service)
- return;
- gatt_db_attribute_get_service_uuid(service, &uuid);
- printf("\t " COLOR_GREEN "include" COLOR_OFF " - handle: "
- "0x%04x, - start: 0x%04x, end: 0x%04x,"
- "uuid: ", handle, start, end);
- print_uuid(&uuid);
- }
- static void print_desc(struct gatt_db_attribute *attr, void *user_data)
- {
- printf("\t\t " COLOR_MAGENTA "descr" COLOR_OFF
- " - handle: 0x%04x, uuid: ",
- gatt_db_attribute_get_handle(attr));
- print_uuid(gatt_db_attribute_get_type(attr));
- }
- static void print_chrc(struct gatt_db_attribute *attr, void *user_data)
- {
- uint16_t handle, value_handle;
- uint8_t properties;
- uint16_t ext_prop;
- bt_uuid_t uuid;
- if (!gatt_db_attribute_get_char_data(attr, &handle,
- &value_handle,
- &properties,
- &ext_prop,
- &uuid))
- return;
- printf("\t " COLOR_YELLOW "charac" COLOR_OFF
- " - start: 0x%04x, value: 0x%04x, "
- "props: 0x%02x, ext_props: 0x%04x, uuid: ",
- handle, value_handle, properties, ext_prop);
- print_uuid(&uuid);
- gatt_db_service_foreach_desc(attr, print_desc, NULL);
- }
- static void print_service(struct gatt_db_attribute *attr, void *user_data)
- {
- struct client *cli = user_data;
- uint16_t start, end;
- bool primary;
- bt_uuid_t uuid;
- if (!gatt_db_attribute_get_service_data(attr, &start, &end, &primary,
- &uuid))
- return;
- printf(COLOR_RED "service" COLOR_OFF " - start: 0x%04x, "
- "end: 0x%04x, type: %s, uuid: ",
- start, end, primary ? "primary" : "secondary");
- print_uuid(&uuid);
- gatt_db_service_foreach_incl(attr, print_incl, cli);
- gatt_db_service_foreach_char(attr, print_chrc, NULL);
- printf("\n");
- }
- static void print_services(struct client *cli)
- {
- printf("\n");
- gatt_db_foreach_service(cli->db, NULL, print_service, cli);
- }
- static void print_services_by_uuid(struct client *cli, const bt_uuid_t *uuid)
- {
- printf("\n");
- gatt_db_foreach_service(cli->db, uuid, print_service, cli);
- }
- static void print_services_by_handle(struct client *cli, uint16_t handle)
- {
- printf("\n");
- /* TODO: Filter by handle */
- gatt_db_foreach_service(cli->db, NULL, print_service, cli);
- }
- static void ready_cb(bool success, uint8_t att_ecode, void *user_data)
- {
- struct client *cli = user_data;
- if (!success) {
- PRLOG("GATT discovery procedures failed - error code: 0x%02x\n",
- att_ecode);
- return;
- }
- PRLOG("GATT discovery procedures complete\n");
- print_services(cli);
- print_prompt();
- }
- static void service_changed_cb(uint16_t start_handle, uint16_t end_handle,
- void *user_data)
- {
- struct client *cli = user_data;
- printf("\nService Changed handled - start: 0x%04x end: 0x%04x\n",
- start_handle, end_handle);
- gatt_db_foreach_service_in_range(cli->db, NULL, print_service, cli,
- start_handle, end_handle);
- print_prompt();
- }
- static void services_usage(void)
- {
- printf("Usage: services [options]\nOptions:\n"
- "\t -u, --uuid <uuid>\tService UUID\n"
- "\t -a, --handle <handle>\tService start handle\n"
- "\t -h, --help\t\tShow help message\n"
- "e.g.:\n"
- "\tservices\n\tservices -u 0x180d\n\tservices -a 0x0009\n");
- }
- static bool parse_args(char *str, int expected_argc, char **argv, int *argc)
- {
- char **ap;
- for (ap = argv; (*ap = strsep(&str, " \t")) != NULL;) {
- if (**ap == '\0')
- continue;
- (*argc)++;
- ap++;
- if (*argc > expected_argc)
- return false;
- }
- return true;
- }
- static void cmd_services(struct client *cli, char *cmd_str)
- {
- char *argv[3];
- int argc = 0;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 2, argv, &argc)) {
- services_usage();
- return;
- }
- if (!argc) {
- print_services(cli);
- return;
- }
- if (argc != 2) {
- services_usage();
- return;
- }
- if (!strcmp(argv[0], "-u") || !strcmp(argv[0], "--uuid")) {
- bt_uuid_t tmp, uuid;
- if (bt_string_to_uuid(&tmp, argv[1]) < 0) {
- printf("Invalid UUID: %s\n", argv[1]);
- return;
- }
- bt_uuid_to_uuid128(&tmp, &uuid);
- print_services_by_uuid(cli, &uuid);
- } else if (!strcmp(argv[0], "-a") || !strcmp(argv[0], "--handle")) {
- uint16_t handle;
- char *endptr = NULL;
- handle = strtol(argv[1], &endptr, 0);
- if (!endptr || *endptr != '\0') {
- printf("Invalid start handle: %s\n", argv[1]);
- return;
- }
- print_services_by_handle(cli, handle);
- } else
- services_usage();
- }
- static void read_multiple_usage(void)
- {
- printf("Usage: read-multiple <handle_1> <handle_2> ...\n");
- }
- static void read_multiple_cb(bool success, uint8_t att_ecode,
- const uint8_t *value, uint16_t length,
- void *user_data)
- {
- int i;
- if (!success) {
- PRLOG("\nRead multiple request failed: 0x%02x\n", att_ecode);
- return;
- }
- printf("\nRead multiple value (%u bytes):", length);
- for (i = 0; i < length; i++)
- printf("%02x ", value[i]);
- PRLOG("\n");
- }
- static void cmd_read_multiple(struct client *cli, char *cmd_str)
- {
- int argc = 0;
- uint16_t *value;
- char *argv[512];
- int i;
- char *endptr = NULL;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, sizeof(argv), argv, &argc) || argc < 2) {
- read_multiple_usage();
- return;
- }
- value = malloc(sizeof(uint16_t) * argc);
- if (!value) {
- printf("Failed to construct value\n");
- return;
- }
- for (i = 0; i < argc; i++) {
- value[i] = strtol(argv[i], &endptr, 0);
- if (endptr == argv[i] || *endptr != '\0' || !value[i]) {
- printf("Invalid value byte: %s\n", argv[i]);
- free(value);
- return;
- }
- }
- if (!bt_gatt_client_read_multiple(cli->gatt, value, argc,
- read_multiple_cb, NULL, NULL))
- printf("Failed to initiate read multiple procedure\n");
- free(value);
- }
- static void read_value_usage(void)
- {
- printf("Usage: read-value <value_handle>\n");
- }
- static void read_cb(bool success, uint8_t att_ecode, const uint8_t *value,
- uint16_t length, void *user_data)
- {
- int i;
- if (!success) {
- PRLOG("\nRead request failed: %s (0x%02x)\n",
- ecode_to_string(att_ecode), att_ecode);
- return;
- }
- printf("\nRead value");
- if (length == 0) {
- PRLOG(": 0 bytes\n");
- return;
- }
- printf(" (%u bytes): ", length);
- for (i = 0; i < length; i++)
- printf("%02x ", value[i]);
- PRLOG("\n");
- }
- static void cmd_read_value(struct client *cli, char *cmd_str)
- {
- char *argv[2];
- int argc = 0;
- uint16_t handle;
- char *endptr = NULL;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) {
- read_value_usage();
- return;
- }
- handle = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0' || !handle) {
- printf("Invalid value handle: %s\n", argv[0]);
- return;
- }
- if (!bt_gatt_client_read_value(cli->gatt, handle, read_cb,
- NULL, NULL))
- printf("Failed to initiate read value procedure\n");
- }
- static void read_long_value_usage(void)
- {
- printf("Usage: read-long-value <value_handle> <offset>\n");
- }
- static void cmd_read_long_value(struct client *cli, char *cmd_str)
- {
- char *argv[3];
- int argc = 0;
- uint16_t handle;
- uint16_t offset;
- char *endptr = NULL;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 2, argv, &argc) || argc != 2) {
- read_long_value_usage();
- return;
- }
- handle = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0' || !handle) {
- printf("Invalid value handle: %s\n", argv[0]);
- return;
- }
- endptr = NULL;
- offset = strtol(argv[1], &endptr, 0);
- if (!endptr || *endptr != '\0') {
- printf("Invalid offset: %s\n", argv[1]);
- return;
- }
- if (!bt_gatt_client_read_long_value(cli->gatt, handle, offset, read_cb,
- NULL, NULL))
- printf("Failed to initiate read long value procedure\n");
- }
- static void write_value_usage(void)
- {
- printf("Usage: write-value [options] <value_handle> <value>\n"
- "Options:\n"
- "\t-w, --without-response\tWrite without response\n"
- "\t-s, --signed-write\tSigned write command\n"
- "e.g.:\n"
- "\twrite-value 0x0001 00 01 00\n");
- }
- static struct option write_value_options[] = {
- { "without-response", 0, 0, 'w' },
- { "signed-write", 0, 0, 's' },
- { }
- };
- static void write_cb(bool success, uint8_t att_ecode, void *user_data)
- {
- if (success) {
- PRLOG("\nWrite successful\n");
- } else {
- PRLOG("\nWrite failed: %s (0x%02x)\n",
- ecode_to_string(att_ecode), att_ecode);
- }
- }
- static void cmd_write_value(struct client *cli, char *cmd_str)
- {
- int opt, i, val;
- char *argvbuf[516];
- char **argv = argvbuf;
- int argc = 1;
- uint16_t handle;
- char *endptr = NULL;
- int length;
- uint8_t *value = NULL;
- bool without_response = false;
- bool signed_write = false;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 514, argv + 1, &argc)) {
- printf("Too many arguments\n");
- write_value_usage();
- return;
- }
- optind = 0;
- argv[0] = "write-value";
- while ((opt = getopt_long(argc, argv, "+ws", write_value_options,
- NULL)) != -1) {
- switch (opt) {
- case 'w':
- without_response = true;
- break;
- case 's':
- signed_write = true;
- break;
- default:
- write_value_usage();
- return;
- }
- }
- argc -= optind;
- argv += optind;
- if (argc < 1) {
- write_value_usage();
- return;
- }
- handle = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0' || !handle) {
- printf("Invalid handle: %s\n", argv[0]);
- return;
- }
- length = argc - 1;
- if (length > 0) {
- if (length > UINT16_MAX) {
- printf("Write value too long\n");
- return;
- }
- value = malloc(length);
- if (!value) {
- printf("Failed to construct write value\n");
- return;
- }
- for (i = 1; i < argc; i++) {
- val = strtol(argv[i], &endptr, 0);
- if (endptr == argv[i] || *endptr != '\0'
- || errno == ERANGE || val < 0 || val > 255) {
- printf("Invalid value byte: %s\n",
- argv[i]);
- goto done;
- }
- value[i-1] = val;
- }
- }
- if (without_response) {
- if (!bt_gatt_client_write_without_response(cli->gatt, handle,
- signed_write, value, length)) {
- printf("Failed to initiate write without response "
- "procedure\n");
- goto done;
- }
- printf("Write command sent\n");
- goto done;
- }
- if (!bt_gatt_client_write_value(cli->gatt, handle, value, length,
- write_cb,
- NULL, NULL))
- printf("Failed to initiate write procedure\n");
- done:
- free(value);
- }
- static void write_long_value_usage(void)
- {
- printf("Usage: write-long-value [options] <value_handle> <offset> "
- "<value>\n"
- "Options:\n"
- "\t-r, --reliable-write\tReliable write\n"
- "e.g.:\n"
- "\twrite-long-value 0x0001 0 00 01 00\n");
- }
- static struct option write_long_value_options[] = {
- { "reliable-write", 0, 0, 'r' },
- { }
- };
- static void write_long_cb(bool success, bool reliable_error, uint8_t att_ecode,
- void *user_data)
- {
- if (success) {
- PRLOG("Write successful\n");
- } else if (reliable_error) {
- PRLOG("Reliable write not verified\n");
- } else {
- PRLOG("\nWrite failed: %s (0x%02x)\n",
- ecode_to_string(att_ecode), att_ecode);
- }
- }
- static void cmd_write_long_value(struct client *cli, char *cmd_str)
- {
- int opt, i, val;
- char *argvbuf[516];
- char **argv = argvbuf;
- int argc = 1;
- uint16_t handle;
- uint16_t offset;
- char *endptr = NULL;
- int length;
- uint8_t *value = NULL;
- bool reliable_writes = false;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 514, argv + 1, &argc)) {
- printf("Too many arguments\n");
- write_value_usage();
- return;
- }
- optind = 0;
- argv[0] = "write-long-value";
- while ((opt = getopt_long(argc, argv, "+r", write_long_value_options,
- NULL)) != -1) {
- switch (opt) {
- case 'r':
- reliable_writes = true;
- break;
- default:
- write_long_value_usage();
- return;
- }
- }
- argc -= optind;
- argv += optind;
- if (argc < 2) {
- write_long_value_usage();
- return;
- }
- handle = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0' || !handle) {
- printf("Invalid handle: %s\n", argv[0]);
- return;
- }
- endptr = NULL;
- offset = strtol(argv[1], &endptr, 0);
- if (!endptr || *endptr != '\0' || errno == ERANGE) {
- printf("Invalid offset: %s\n", argv[1]);
- return;
- }
- length = argc - 2;
- if (length > 0) {
- if (length > UINT16_MAX) {
- printf("Write value too long\n");
- return;
- }
- value = malloc(length);
- if (!value) {
- printf("Failed to construct write value\n");
- return;
- }
- for (i = 2; i < argc; i++) {
- val = strtol(argv[i], &endptr, 0);
- if (endptr == argv[i] || *endptr != '\0'
- || errno == ERANGE || val < 0 || val > 255) {
- printf("Invalid value byte: %s\n",
- argv[i]);
- free(value);
- return;
- }
- value[i-2] = val;
- }
- }
- if (!bt_gatt_client_write_long_value(cli->gatt, reliable_writes, handle,
- offset, value, length,
- write_long_cb,
- NULL, NULL))
- printf("Failed to initiate long write procedure\n");
- free(value);
- }
- static void write_prepare_usage(void)
- {
- printf("Usage: write-prepare [options] <value_handle> <offset> "
- "<value>\n"
- "Options:\n"
- "\t-s, --session-id\tSession id\n"
- "e.g.:\n"
- "\twrite-prepare -s 1 0x0001 00 01 00\n");
- }
- static struct option write_prepare_options[] = {
- { "session-id", 1, 0, 's' },
- { }
- };
- static void cmd_write_prepare(struct client *cli, char *cmd_str)
- {
- int opt, i, val;
- char *argvbuf[516];
- char **argv = argvbuf;
- int argc = 0;
- unsigned int id = 0;
- uint16_t handle;
- uint16_t offset;
- char *endptr = NULL;
- unsigned int length;
- uint8_t *value = NULL;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 514, argv + 1, &argc)) {
- printf("Too many arguments\n");
- write_value_usage();
- return;
- }
- /* Add command name for getopt_long */
- argc++;
- argv[0] = "write-prepare";
- optind = 0;
- while ((opt = getopt_long(argc, argv , "s:", write_prepare_options,
- NULL)) != -1) {
- switch (opt) {
- case 's':
- if (!optarg) {
- write_prepare_usage();
- return;
- }
- id = atoi(optarg);
- break;
- default:
- write_prepare_usage();
- return;
- }
- }
- argc -= optind;
- argv += optind;
- if (argc < 3) {
- write_prepare_usage();
- return;
- }
- if (cli->reliable_session_id != id) {
- printf("Session id != Ongoing session id (%u!=%u)\n", id,
- cli->reliable_session_id);
- return;
- }
- handle = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0' || !handle) {
- printf("Invalid handle: %s\n", argv[0]);
- return;
- }
- endptr = NULL;
- offset = strtol(argv[1], &endptr, 0);
- if (!endptr || *endptr != '\0' || errno == ERANGE) {
- printf("Invalid offset: %s\n", argv[1]);
- return;
- }
- /*
- * First two arguments are handle and offset. What remains is the value
- * length
- */
- length = argc - 2;
- if (length == 0)
- goto done;
- if (length > UINT16_MAX) {
- printf("Write value too long\n");
- return;
- }
- value = malloc(length);
- if (!value) {
- printf("Failed to allocate memory for value\n");
- return;
- }
- for (i = 2; i < argc; i++) {
- val = strtol(argv[i], &endptr, 0);
- if (endptr == argv[i] || *endptr != '\0' || errno == ERANGE
- || val < 0 || val > 255) {
- printf("Invalid value byte: %s\n", argv[i]);
- free(value);
- return;
- }
- value[i-2] = val;
- }
- done:
- cli->reliable_session_id =
- bt_gatt_client_prepare_write(cli->gatt, id,
- handle, offset,
- value, length,
- write_long_cb, NULL,
- NULL);
- if (!cli->reliable_session_id)
- printf("Failed to proceed prepare write\n");
- else
- printf("Prepare write success.\n"
- "Session id: %d to be used on next write\n",
- cli->reliable_session_id);
- free(value);
- }
- static void write_execute_usage(void)
- {
- printf("Usage: write-execute <session_id> <execute>\n"
- "e.g.:\n"
- "\twrite-execute 1 0\n");
- }
- static void cmd_write_execute(struct client *cli, char *cmd_str)
- {
- char *argvbuf[516];
- char **argv = argvbuf;
- int argc = 0;
- char *endptr = NULL;
- unsigned int session_id;
- bool execute;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 514, argv, &argc)) {
- printf("Too many arguments\n");
- write_value_usage();
- return;
- }
- if (argc < 2) {
- write_execute_usage();
- return;
- }
- session_id = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0') {
- printf("Invalid session id: %s\n", argv[0]);
- return;
- }
- if (session_id != cli->reliable_session_id) {
- printf("Invalid session id: %u != %u\n", session_id,
- cli->reliable_session_id);
- return;
- }
- execute = !!strtol(argv[1], &endptr, 0);
- if (!endptr || *endptr != '\0') {
- printf("Invalid execute: %s\n", argv[1]);
- return;
- }
- if (execute) {
- if (!bt_gatt_client_write_execute(cli->gatt, session_id,
- write_cb, NULL, NULL))
- printf("Failed to proceed write execute\n");
- } else {
- bt_gatt_client_cancel(cli->gatt, session_id);
- }
- cli->reliable_session_id = 0;
- }
- static void register_notify_usage(void)
- {
- printf("Usage: register-notify <chrc value handle>\n");
- }
- static void notify_cb(uint16_t value_handle, const uint8_t *value,
- uint16_t length, void *user_data)
- {
- int i;
- printf("\n\tHandle Value Not/Ind: 0x%04x - ", value_handle);
- if (length == 0) {
- PRLOG("(0 bytes)\n");
- return;
- }
- printf("(%u bytes): ", length);
- for (i = 0; i < length; i++)
- printf("%02x ", value[i]);
- PRLOG("\n");
- }
- static void register_notify_cb(uint16_t att_ecode, void *user_data)
- {
- if (att_ecode) {
- PRLOG("Failed to register notify handler "
- "- error code: 0x%02x\n", att_ecode);
- return;
- }
- PRLOG("Registered notify handler!\n");
- }
- static void cmd_register_notify(struct client *cli, char *cmd_str)
- {
- char *argv[2];
- int argc = 0;
- uint16_t value_handle;
- unsigned int id;
- char *endptr = NULL;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) {
- register_notify_usage();
- return;
- }
- value_handle = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0' || !value_handle) {
- printf("Invalid value handle: %s\n", argv[0]);
- return;
- }
- id = bt_gatt_client_register_notify(cli->gatt, value_handle,
- register_notify_cb,
- notify_cb, NULL, NULL);
- if (!id) {
- printf("Failed to register notify handler\n");
- return;
- }
- printf("Registering notify handler with id: %u\n", id);
- }
- static void unregister_notify_usage(void)
- {
- printf("Usage: unregister-notify <notify id>\n");
- }
- static void cmd_unregister_notify(struct client *cli, char *cmd_str)
- {
- char *argv[2];
- int argc = 0;
- unsigned int id;
- char *endptr = NULL;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) {
- unregister_notify_usage();
- return;
- }
- id = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0' || !id) {
- printf("Invalid notify id: %s\n", argv[0]);
- return;
- }
- if (!bt_gatt_client_unregister_notify(cli->gatt, id)) {
- printf("Failed to unregister notify handler with id: %u\n", id);
- return;
- }
- printf("Unregistered notify handler with id: %u\n", id);
- }
- static void set_security_usage(void)
- {
- printf("Usage: set-security <level>\n"
- "level: 1-3\n"
- "e.g.:\n"
- "\tset-security 2\n");
- }
- static void cmd_set_security(struct client *cli, char *cmd_str)
- {
- char *argv[2];
- int argc = 0;
- char *endptr = NULL;
- int level;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- if (!parse_args(cmd_str, 1, argv, &argc)) {
- printf("Too many arguments\n");
- set_security_usage();
- return;
- }
- if (argc < 1) {
- set_security_usage();
- return;
- }
- level = strtol(argv[0], &endptr, 0);
- if (!endptr || *endptr != '\0' || level < 1 || level > 3) {
- printf("Invalid level: %s\n", argv[0]);
- return;
- }
- if (!bt_gatt_client_set_security(cli->gatt, level))
- printf("Could not set sec level\n");
- else
- printf("Setting security level %d success\n", level);
- }
- static void cmd_get_security(struct client *cli, char *cmd_str)
- {
- int level;
- if (!bt_gatt_client_is_ready(cli->gatt)) {
- printf("GATT client not initialized\n");
- return;
- }
- level = bt_gatt_client_get_security(cli->gatt);
- if (level < 0)
- printf("Could not set sec level\n");
- else
- printf("Security level: %u\n", level);
- }
- static bool convert_sign_key(char *optarg, uint8_t key[16])
- {
- int i;
- if (strlen(optarg) != 32) {
- printf("sign-key length is invalid\n");
- return false;
- }
- for (i = 0; i < 16; i++) {
- if (sscanf(optarg + (i * 2), "%2hhx", &key[i]) != 1)
- return false;
- }
- return true;
- }
- static void set_sign_key_usage(void)
- {
- printf("Usage: set-sign-key [options]\nOptions:\n"
- "\t -c, --sign-key <csrk>\tCSRK\n"
- "e.g.:\n"
- "\tset-sign-key -c D8515948451FEA320DC05A2E88308188\n");
- }
- static bool local_counter(uint32_t *sign_cnt, void *user_data)
- {
- static uint32_t cnt = 0;
- *sign_cnt = cnt++;
- return true;
- }
- static void cmd_set_sign_key(struct client *cli, char *cmd_str)
- {
- char *argv[3];
- int argc = 0;
- uint8_t key[16];
- memset(key, 0, 16);
- if (!parse_args(cmd_str, 2, argv, &argc)) {
- set_sign_key_usage();
- return;
- }
- if (argc != 2) {
- set_sign_key_usage();
- return;
- }
- if (!strcmp(argv[0], "-c") || !strcmp(argv[0], "--sign-key")) {
- if (convert_sign_key(argv[1], key))
- bt_att_set_local_key(cli->att, key, local_counter, cli);
- } else
- set_sign_key_usage();
- }
- static void cmd_help(struct client *cli, char *cmd_str);
- typedef void (*command_func_t)(struct client *cli, char *cmd_str);
- static struct {
- char *cmd;
- command_func_t func;
- char *doc;
- } command[] = {
- { "help", cmd_help, "\tDisplay help message" },
- { "services", cmd_services, "\tShow discovered services" },
- { "read-value", cmd_read_value,
- "\tRead a characteristic or descriptor value" },
- { "read-long-value", cmd_read_long_value,
- "\tRead a long characteristic or desctriptor value" },
- { "read-multiple", cmd_read_multiple, "\tRead Multiple" },
- { "write-value", cmd_write_value,
- "\tWrite a characteristic or descriptor value" },
- { "write-long-value", cmd_write_long_value,
- "Write long characteristic or descriptor value" },
- { "write-prepare", cmd_write_prepare,
- "\tWrite prepare characteristic or descriptor value" },
- { "write-execute", cmd_write_execute,
- "\tExecute already prepared write" },
- { "register-notify", cmd_register_notify,
- "\tSubscribe to not/ind from a characteristic" },
- { "unregister-notify", cmd_unregister_notify,
- "Unregister a not/ind session"},
- { "set-security", cmd_set_security,
- "\tSet security level on le connection"},
- { "get-security", cmd_get_security,
- "\tGet security level on le connection"},
- { "set-sign-key", cmd_set_sign_key,
- "\tSet signing key for signed write command"},
- { }
- };
- static void cmd_help(struct client *cli, char *cmd_str)
- {
- int i;
- printf("Commands:\n");
- for (i = 0; command[i].cmd; i++)
- printf("\t%-15s\t%s\n", command[i].cmd, command[i].doc);
- }
- static void prompt_read_cb(int fd, uint32_t events, void *user_data)
- {
- ssize_t read;
- size_t len = 0;
- char *line = NULL;
- char *cmd = NULL, *args;
- struct client *cli = user_data;
- int i;
- if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
- mainloop_quit();
- return;
- }
- if ((read = getline(&line, &len, stdin)) == -1)
- return;
- if (read <= 1) {
- cmd_help(cli, NULL);
- print_prompt();
- return;
- }
- line[read-1] = '\0';
- args = line;
- while ((cmd = strsep(&args, " \t")))
- if (*cmd != '\0')
- break;
- if (!cmd)
- goto failed;
- for (i = 0; command[i].cmd; i++) {
- if (strcmp(command[i].cmd, cmd) == 0)
- break;
- }
- if (command[i].cmd)
- command[i].func(cli, args);
- else
- fprintf(stderr, "Unknown command: %s\n", line);
- failed:
- print_prompt();
- free(line);
- }
- static void signal_cb(int signum, void *user_data)
- {
- switch (signum) {
- case SIGINT:
- case SIGTERM:
- mainloop_quit();
- break;
- default:
- break;
- }
- }
- static int l2cap_le_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type,
- int sec)
- {
- int sock;
- struct sockaddr_l2 srcaddr, dstaddr;
- struct bt_security btsec;
- if (verbose) {
- char srcaddr_str[18], dstaddr_str[18];
- ba2str(src, srcaddr_str);
- ba2str(dst, dstaddr_str);
- printf("btgatt-client: Opening L2CAP LE connection on ATT "
- "channel:\n\t src: %s\n\tdest: %s\n",
- srcaddr_str, dstaddr_str);
- }
- sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
- if (sock < 0) {
- perror("Failed to create L2CAP socket");
- return -1;
- }
- /* Set up source address */
- memset(&srcaddr, 0, sizeof(srcaddr));
- srcaddr.l2_family = AF_BLUETOOTH;
- srcaddr.l2_cid = htobs(ATT_CID);
- srcaddr.l2_bdaddr_type = 0;
- bacpy(&srcaddr.l2_bdaddr, src);
- if (bind(sock, (struct sockaddr *)&srcaddr, sizeof(srcaddr)) < 0) {
- perror("Failed to bind L2CAP socket");
- close(sock);
- return -1;
- }
- /* Set the security level */
- memset(&btsec, 0, sizeof(btsec));
- btsec.level = sec;
- if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &btsec,
- sizeof(btsec)) != 0) {
- fprintf(stderr, "Failed to set L2CAP security level\n");
- close(sock);
- return -1;
- }
- /* Set up destination address */
- memset(&dstaddr, 0, sizeof(dstaddr));
- dstaddr.l2_family = AF_BLUETOOTH;
- dstaddr.l2_cid = htobs(ATT_CID);
- dstaddr.l2_bdaddr_type = dst_type;
- bacpy(&dstaddr.l2_bdaddr, dst);
- printf("Connecting to device...");
- fflush(stdout);
- if (connect(sock, (struct sockaddr *) &dstaddr, sizeof(dstaddr)) < 0) {
- perror(" Failed to connect");
- close(sock);
- return -1;
- }
- printf(" Done\n");
- return sock;
- }
- static void usage(void)
- {
- printf("btgatt-client\n");
- printf("Usage:\n\tbtgatt-client [options]\n");
- printf("Options:\n"
- "\t-i, --index <id>\t\tSpecify adapter index, e.g. hci0\n"
- "\t-d, --dest <addr>\t\tSpecify the destination address\n"
- "\t-t, --type [random|public] \tSpecify the LE address type\n"
- "\t-m, --mtu <mtu> \t\tThe ATT MTU to use\n"
- "\t-s, --security-level <sec> \tSet security level (low|medium|"
- "high|fips)\n"
- "\t-v, --verbose\t\t\tEnable extra logging\n"
- "\t-h, --help\t\t\tDisplay help\n");
- }
- static struct option main_options[] = {
- { "index", 1, 0, 'i' },
- { "dest", 1, 0, 'd' },
- { "type", 1, 0, 't' },
- { "mtu", 1, 0, 'm' },
- { "security-level", 1, 0, 's' },
- { "verbose", 0, 0, 'v' },
- { "help", 0, 0, 'h' },
- { }
- };
- int main(int argc, char *argv[])
- {
- int opt;
- int sec = BT_SECURITY_LOW;
- uint16_t mtu = 0;
- uint8_t dst_type = BDADDR_LE_PUBLIC;
- bool dst_addr_given = false;
- bdaddr_t src_addr, dst_addr;
- int dev_id = -1;
- int fd;
- struct client *cli;
- while ((opt = getopt_long(argc, argv, "+hvs:m:t:d:i:",
- main_options, NULL)) != -1) {
- switch (opt) {
- case 'h':
- usage();
- return EXIT_SUCCESS;
- case 'v':
- verbose = true;
- break;
- case 's':
- if (strcmp(optarg, "low") == 0)
- sec = BT_SECURITY_LOW;
- else if (strcmp(optarg, "medium") == 0)
- sec = BT_SECURITY_MEDIUM;
- else if (strcmp(optarg, "high") == 0)
- sec = BT_SECURITY_HIGH;
- else if (strcmp(optarg, "fips") == 0)
- sec = BT_SECURITY_FIPS;
- else {
- fprintf(stderr, "Invalid security level\n");
- return EXIT_FAILURE;
- }
- break;
- case 'm': {
- int arg;
- arg = atoi(optarg);
- if (arg <= 0) {
- fprintf(stderr, "Invalid MTU: %d\n", arg);
- return EXIT_FAILURE;
- }
- if (arg > UINT16_MAX) {
- fprintf(stderr, "MTU too large: %d\n", arg);
- return EXIT_FAILURE;
- }
- mtu = (uint16_t)arg;
- break;
- }
- case 't':
- if (strcmp(optarg, "random") == 0)
- dst_type = BDADDR_LE_RANDOM;
- else if (strcmp(optarg, "public") == 0)
- dst_type = BDADDR_LE_PUBLIC;
- else {
- fprintf(stderr,
- "Allowed types: random, public\n");
- return EXIT_FAILURE;
- }
- break;
- case 'd':
- if (str2ba(optarg, &dst_addr) < 0) {
- fprintf(stderr, "Invalid remote address: %s\n",
- optarg);
- return EXIT_FAILURE;
- }
- dst_addr_given = true;
- break;
- case 'i':
- dev_id = hci_devid(optarg);
- if (dev_id < 0) {
- perror("Invalid adapter");
- return EXIT_FAILURE;
- }
- break;
- default:
- fprintf(stderr, "Invalid option: %c\n", opt);
- return EXIT_FAILURE;
- }
- }
- if (!argc) {
- usage();
- return EXIT_SUCCESS;
- }
- argc -= optind;
- argv += optind;
- optind = 0;
- if (argc) {
- usage();
- return EXIT_SUCCESS;
- }
- if (dev_id == -1)
- bacpy(&src_addr, BDADDR_ANY);
- else if (hci_devba(dev_id, &src_addr) < 0) {
- perror("Adapter not available");
- return EXIT_FAILURE;
- }
- if (!dst_addr_given) {
- fprintf(stderr, "Destination address required!\n");
- return EXIT_FAILURE;
- }
- mainloop_init();
- fd = l2cap_le_att_connect(&src_addr, &dst_addr, dst_type, sec);
- if (fd < 0)
- return EXIT_FAILURE;
- cli = client_create(fd, mtu);
- if (!cli) {
- close(fd);
- return EXIT_FAILURE;
- }
- if (mainloop_add_fd(fileno(stdin),
- EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR,
- prompt_read_cb, cli, NULL) < 0) {
- fprintf(stderr, "Failed to initialize console\n");
- return EXIT_FAILURE;
- }
- print_prompt();
- mainloop_run_with_signal(signal_cb, NULL);
- printf("\n\nShutting down...\n");
- client_destroy(cli);
- return EXIT_SUCCESS;
- }
|