| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254 |
- // 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 <time.h>
- #include <stdlib.h>
- #include <getopt.h>
- #include <unistd.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/timeout.h"
- #include "src/shared/gatt-db.h"
- #include "src/shared/gatt-server.h"
- #define UUID_GAP 0x1800
- #define UUID_GATT 0x1801
- #define UUID_HEART_RATE 0x180d
- #define UUID_HEART_RATE_MSRMT 0x2a37
- #define UUID_HEART_RATE_BODY 0x2a38
- #define UUID_HEART_RATE_CTRL 0x2a39
- #define ATT_CID 4
- #define PRLOG(...) \
- do { \
- printf(__VA_ARGS__); \
- print_prompt(); \
- } while (0)
- #ifndef MIN
- #define MIN(a, b) ((a) < (b) ? (a) : (b))
- #endif
- #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 const char test_device_name[] = "Very Long Test Device Name For Testing "
- "ATT Protocol Operations On GATT Server";
- static bool verbose = false;
- struct server {
- int fd;
- struct bt_att *att;
- struct gatt_db *db;
- struct bt_gatt_server *gatt;
- uint8_t *device_name;
- size_t name_len;
- uint16_t gatt_svc_chngd_handle;
- bool svc_chngd_enabled;
- uint16_t hr_handle;
- uint16_t hr_msrmt_handle;
- uint16_t hr_energy_expended;
- bool hr_visible;
- bool hr_msrmt_enabled;
- int hr_ee_count;
- unsigned int hr_timeout_id;
- };
- static void print_prompt(void)
- {
- printf(COLOR_BLUE "[GATT server]" COLOR_OFF "# ");
- fflush(stdout);
- }
- 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 gap_device_name_read_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- struct server *server = user_data;
- uint8_t error = 0;
- size_t len = 0;
- const uint8_t *value = NULL;
- PRLOG("GAP Device Name Read called\n");
- len = server->name_len;
- if (offset > len) {
- error = BT_ATT_ERROR_INVALID_OFFSET;
- goto done;
- }
- len -= offset;
- value = len ? &server->device_name[offset] : NULL;
- done:
- gatt_db_attribute_read_result(attrib, id, error, value, len);
- }
- static void gap_device_name_write_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- const uint8_t *value, size_t len,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- struct server *server = user_data;
- uint8_t error = 0;
- PRLOG("GAP Device Name Write called\n");
- /* If the value is being completely truncated, clean up and return */
- if (!(offset + len)) {
- free(server->device_name);
- server->device_name = NULL;
- server->name_len = 0;
- goto done;
- }
- /* Implement this as a variable length attribute value. */
- if (offset > server->name_len) {
- error = BT_ATT_ERROR_INVALID_OFFSET;
- goto done;
- }
- if (offset + len != server->name_len) {
- uint8_t *name;
- name = realloc(server->device_name, offset + len);
- if (!name) {
- error = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
- goto done;
- }
- server->device_name = name;
- server->name_len = offset + len;
- }
- if (value)
- memcpy(server->device_name + offset, value, len);
- done:
- gatt_db_attribute_write_result(attrib, id, error);
- }
- static void gap_device_name_ext_prop_read_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- uint8_t value[2];
- PRLOG("Device Name Extended Properties Read called\n");
- value[0] = BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE;
- value[1] = 0;
- gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value));
- }
- static void gatt_service_changed_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- PRLOG("Service Changed Read called\n");
- gatt_db_attribute_read_result(attrib, id, 0, NULL, 0);
- }
- static void gatt_svc_chngd_ccc_read_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- struct server *server = user_data;
- uint8_t value[2];
- PRLOG("Service Changed CCC Read called\n");
- value[0] = server->svc_chngd_enabled ? 0x02 : 0x00;
- value[1] = 0x00;
- gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value));
- }
- static void gatt_svc_chngd_ccc_write_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- const uint8_t *value, size_t len,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- struct server *server = user_data;
- uint8_t ecode = 0;
- PRLOG("Service Changed CCC Write called\n");
- if (!value || len != 2) {
- ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
- goto done;
- }
- if (offset) {
- ecode = BT_ATT_ERROR_INVALID_OFFSET;
- goto done;
- }
- if (value[0] == 0x00)
- server->svc_chngd_enabled = false;
- else if (value[0] == 0x02)
- server->svc_chngd_enabled = true;
- else
- ecode = 0x80;
- PRLOG("Service Changed Enabled: %s\n",
- server->svc_chngd_enabled ? "true" : "false");
- done:
- gatt_db_attribute_write_result(attrib, id, ecode);
- }
- static void hr_msrmt_ccc_read_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- struct server *server = user_data;
- uint8_t value[2];
- value[0] = server->hr_msrmt_enabled ? 0x01 : 0x00;
- value[1] = 0x00;
- gatt_db_attribute_read_result(attrib, id, 0, value, 2);
- }
- static bool hr_msrmt_cb(void *user_data)
- {
- struct server *server = user_data;
- bool expended_present = !(server->hr_ee_count % 10);
- uint16_t len = 2;
- uint8_t pdu[4];
- uint32_t cur_ee;
- pdu[0] = 0x06;
- pdu[1] = 90 + (rand() % 40);
- if (expended_present) {
- pdu[0] |= 0x08;
- put_le16(server->hr_energy_expended, pdu + 2);
- len += 2;
- }
- bt_gatt_server_send_notification(server->gatt,
- server->hr_msrmt_handle,
- pdu, len, false);
- cur_ee = server->hr_energy_expended;
- server->hr_energy_expended = MIN(UINT16_MAX, cur_ee + 10);
- server->hr_ee_count++;
- return true;
- }
- static void update_hr_msrmt_simulation(struct server *server)
- {
- if (!server->hr_msrmt_enabled || !server->hr_visible) {
- timeout_remove(server->hr_timeout_id);
- return;
- }
- server->hr_timeout_id = timeout_add(1000, hr_msrmt_cb, server, NULL);
- }
- static void hr_msrmt_ccc_write_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- const uint8_t *value, size_t len,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- struct server *server = user_data;
- uint8_t ecode = 0;
- if (!value || len != 2) {
- ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
- goto done;
- }
- if (offset) {
- ecode = BT_ATT_ERROR_INVALID_OFFSET;
- goto done;
- }
- if (value[0] == 0x00)
- server->hr_msrmt_enabled = false;
- else if (value[0] == 0x01) {
- if (server->hr_msrmt_enabled) {
- PRLOG("HR Measurement Already Enabled\n");
- goto done;
- }
- server->hr_msrmt_enabled = true;
- } else
- ecode = 0x80;
- PRLOG("HR: Measurement Enabled: %s\n",
- server->hr_msrmt_enabled ? "true" : "false");
- update_hr_msrmt_simulation(server);
- done:
- gatt_db_attribute_write_result(attrib, id, ecode);
- }
- static void hr_control_point_write_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- const uint8_t *value, size_t len,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
- {
- struct server *server = user_data;
- uint8_t ecode = 0;
- if (!value || len != 1) {
- ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
- goto done;
- }
- if (offset) {
- ecode = BT_ATT_ERROR_INVALID_OFFSET;
- goto done;
- }
- if (value[0] == 1) {
- PRLOG("HR: Energy Expended value reset\n");
- server->hr_energy_expended = 0;
- }
- done:
- gatt_db_attribute_write_result(attrib, id, ecode);
- }
- static void confirm_write(struct gatt_db_attribute *attr, int err,
- void *user_data)
- {
- if (!err)
- return;
- fprintf(stderr, "Error caching attribute %p - err: %d\n", attr, err);
- exit(1);
- }
- static void populate_gap_service(struct server *server)
- {
- bt_uuid_t uuid;
- struct gatt_db_attribute *service, *tmp;
- uint16_t appearance;
- /* Add the GAP service */
- bt_uuid16_create(&uuid, UUID_GAP);
- service = gatt_db_add_service(server->db, &uuid, true, 6);
- /*
- * Device Name characteristic. Make the value dynamically read and
- * written via callbacks.
- */
- bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
- gatt_db_service_add_characteristic(service, &uuid,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
- BT_GATT_CHRC_PROP_READ |
- BT_GATT_CHRC_PROP_EXT_PROP,
- gap_device_name_read_cb,
- gap_device_name_write_cb,
- server);
- bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID);
- gatt_db_service_add_descriptor(service, &uuid, BT_ATT_PERM_READ,
- gap_device_name_ext_prop_read_cb,
- NULL, server);
- /*
- * Appearance characteristic. Reads and writes should obtain the value
- * from the database.
- */
- bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
- tmp = gatt_db_service_add_characteristic(service, &uuid,
- BT_ATT_PERM_READ,
- BT_GATT_CHRC_PROP_READ,
- NULL, NULL, server);
- /*
- * Write the appearance value to the database, since we're not using a
- * callback.
- */
- put_le16(128, &appearance);
- gatt_db_attribute_write(tmp, 0, (void *) &appearance,
- sizeof(appearance),
- BT_ATT_OP_WRITE_REQ,
- NULL, confirm_write,
- NULL);
- gatt_db_service_set_active(service, true);
- }
- static void populate_gatt_service(struct server *server)
- {
- bt_uuid_t uuid;
- struct gatt_db_attribute *service, *svc_chngd;
- /* Add the GATT service */
- bt_uuid16_create(&uuid, UUID_GATT);
- service = gatt_db_add_service(server->db, &uuid, true, 4);
- bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED);
- svc_chngd = gatt_db_service_add_characteristic(service, &uuid,
- BT_ATT_PERM_READ,
- BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_INDICATE,
- gatt_service_changed_cb,
- NULL, server);
- server->gatt_svc_chngd_handle = gatt_db_attribute_get_handle(svc_chngd);
- bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
- gatt_db_service_add_descriptor(service, &uuid,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
- gatt_svc_chngd_ccc_read_cb,
- gatt_svc_chngd_ccc_write_cb, server);
- gatt_db_service_set_active(service, true);
- }
- static void populate_hr_service(struct server *server)
- {
- bt_uuid_t uuid;
- struct gatt_db_attribute *service, *hr_msrmt, *body;
- uint8_t body_loc = 1; /* "Chest" */
- /* Add Heart Rate Service */
- bt_uuid16_create(&uuid, UUID_HEART_RATE);
- service = gatt_db_add_service(server->db, &uuid, true, 8);
- server->hr_handle = gatt_db_attribute_get_handle(service);
- /* HR Measurement Characteristic */
- bt_uuid16_create(&uuid, UUID_HEART_RATE_MSRMT);
- hr_msrmt = gatt_db_service_add_characteristic(service, &uuid,
- BT_ATT_PERM_NONE,
- BT_GATT_CHRC_PROP_NOTIFY,
- NULL, NULL, NULL);
- server->hr_msrmt_handle = gatt_db_attribute_get_handle(hr_msrmt);
- bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
- gatt_db_service_add_descriptor(service, &uuid,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
- hr_msrmt_ccc_read_cb,
- hr_msrmt_ccc_write_cb, server);
- /*
- * Body Sensor Location Characteristic. Make reads obtain the value from
- * the database.
- */
- bt_uuid16_create(&uuid, UUID_HEART_RATE_BODY);
- body = gatt_db_service_add_characteristic(service, &uuid,
- BT_ATT_PERM_READ,
- BT_GATT_CHRC_PROP_READ,
- NULL, NULL, server);
- gatt_db_attribute_write(body, 0, (void *) &body_loc, sizeof(body_loc),
- BT_ATT_OP_WRITE_REQ,
- NULL, confirm_write,
- NULL);
- /* HR Control Point Characteristic */
- bt_uuid16_create(&uuid, UUID_HEART_RATE_CTRL);
- gatt_db_service_add_characteristic(service, &uuid,
- BT_ATT_PERM_WRITE,
- BT_GATT_CHRC_PROP_WRITE,
- NULL, hr_control_point_write_cb,
- server);
- if (server->hr_visible)
- gatt_db_service_set_active(service, true);
- }
- static void populate_db(struct server *server)
- {
- populate_gap_service(server);
- populate_gatt_service(server);
- populate_hr_service(server);
- }
- static struct server *server_create(int fd, uint16_t mtu, bool hr_visible)
- {
- struct server *server;
- size_t name_len = strlen(test_device_name);
- server = new0(struct server, 1);
- if (!server) {
- fprintf(stderr, "Failed to allocate memory for server\n");
- return NULL;
- }
- server->att = bt_att_new(fd, false);
- if (!server->att) {
- fprintf(stderr, "Failed to initialze ATT transport layer\n");
- goto fail;
- }
- if (!bt_att_set_close_on_unref(server->att, true)) {
- fprintf(stderr, "Failed to set up ATT transport layer\n");
- goto fail;
- }
- if (!bt_att_register_disconnect(server->att, att_disconnect_cb, NULL,
- NULL)) {
- fprintf(stderr, "Failed to set ATT disconnect handler\n");
- goto fail;
- }
- server->name_len = name_len + 1;
- server->device_name = malloc(name_len + 1);
- if (!server->device_name) {
- fprintf(stderr, "Failed to allocate memory for device name\n");
- goto fail;
- }
- memcpy(server->device_name, test_device_name, name_len);
- server->device_name[name_len] = '\0';
- server->fd = fd;
- server->db = gatt_db_new();
- if (!server->db) {
- fprintf(stderr, "Failed to create GATT database\n");
- goto fail;
- }
- server->gatt = bt_gatt_server_new(server->db, server->att, mtu, 0);
- if (!server->gatt) {
- fprintf(stderr, "Failed to create GATT server\n");
- goto fail;
- }
- server->hr_visible = hr_visible;
- if (verbose) {
- bt_att_set_debug(server->att, BT_ATT_DEBUG_VERBOSE,
- att_debug_cb, "att: ", NULL);
- bt_gatt_server_set_debug(server->gatt, gatt_debug_cb,
- "server: ", NULL);
- }
- /* Random seed for generating fake Heart Rate measurements */
- srand(time(NULL));
- /* bt_gatt_server already holds a reference */
- populate_db(server);
- return server;
- fail:
- gatt_db_unref(server->db);
- free(server->device_name);
- bt_att_unref(server->att);
- free(server);
- return NULL;
- }
- static void server_destroy(struct server *server)
- {
- timeout_remove(server->hr_timeout_id);
- bt_gatt_server_unref(server->gatt);
- gatt_db_unref(server->db);
- }
- static void usage(void)
- {
- printf("btgatt-server\n");
- printf("Usage:\n\tbtgatt-server [options]\n");
- printf("Options:\n"
- "\t-i, --index <id>\t\tSpecify adapter index, e.g. hci0\n"
- "\t-m, --mtu <mtu>\t\t\tThe ATT MTU to use\n"
- "\t-s, --security-level <sec>\tSet security level (low|"
- "medium|high)\n"
- "\t-t, --type [random|public] \t The source address type\n"
- "\t-v, --verbose\t\t\tEnable extra logging\n"
- "\t-r, --heart-rate\t\tEnable Heart Rate service\n"
- "\t-h, --help\t\t\tDisplay help\n");
- }
- static struct option main_options[] = {
- { "index", 1, 0, 'i' },
- { "mtu", 1, 0, 'm' },
- { "security-level", 1, 0, 's' },
- { "type", 1, 0, 't' },
- { "verbose", 0, 0, 'v' },
- { "heart-rate", 0, 0, 'r' },
- { "help", 0, 0, 'h' },
- { }
- };
- static int l2cap_le_att_listen_and_accept(bdaddr_t *src, int sec,
- uint8_t src_type)
- {
- int sk, nsk;
- struct sockaddr_l2 srcaddr, addr;
- socklen_t optlen;
- struct bt_security btsec;
- char ba[18];
- sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
- if (sk < 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 = src_type;
- bacpy(&srcaddr.l2_bdaddr, src);
- if (bind(sk, (struct sockaddr *) &srcaddr, sizeof(srcaddr)) < 0) {
- perror("Failed to bind L2CAP socket");
- goto fail;
- }
- /* Set the security level */
- memset(&btsec, 0, sizeof(btsec));
- btsec.level = sec;
- if (setsockopt(sk, SOL_BLUETOOTH, BT_SECURITY, &btsec,
- sizeof(btsec)) != 0) {
- fprintf(stderr, "Failed to set L2CAP security level\n");
- goto fail;
- }
- if (listen(sk, 10) < 0) {
- perror("Listening on socket failed");
- goto fail;
- }
- printf("Started listening on ATT channel. Waiting for connections\n");
- memset(&addr, 0, sizeof(addr));
- optlen = sizeof(addr);
- nsk = accept(sk, (struct sockaddr *) &addr, &optlen);
- if (nsk < 0) {
- perror("Accept failed");
- goto fail;
- }
- ba2str(&addr.l2_bdaddr, ba);
- printf("Connect from %s\n", ba);
- close(sk);
- return nsk;
- fail:
- close(sk);
- return -1;
- }
- static void notify_usage(void)
- {
- printf("Usage: notify [options] <value_handle> <value>\n"
- "Options:\n"
- "\t -i, --indicate\tSend indication\n"
- "e.g.:\n"
- "\tnotify 0x0001 00 01 00\n");
- }
- static struct option notify_options[] = {
- { "indicate", 0, 0, 'i' },
- { }
- };
- 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 conf_cb(void *user_data)
- {
- PRLOG("Received confirmation\n");
- }
- static void cmd_notify(struct server *server, char *cmd_str)
- {
- int opt, i;
- char *argvbuf[516];
- char **argv = argvbuf;
- int argc = 1;
- uint16_t handle;
- char *endptr = NULL;
- int length;
- uint8_t *value = NULL;
- bool indicate = false;
- if (!parse_args(cmd_str, 514, argv + 1, &argc)) {
- printf("Too many arguments\n");
- notify_usage();
- return;
- }
- optind = 0;
- argv[0] = "notify";
- while ((opt = getopt_long(argc, argv, "+i", notify_options,
- NULL)) != -1) {
- switch (opt) {
- case 'i':
- indicate = true;
- break;
- default:
- notify_usage();
- return;
- }
- }
- argc -= optind;
- argv += optind;
- if (argc < 1) {
- notify_usage();
- return;
- }
- handle = strtol(argv[0], &endptr, 16);
- if (!endptr || *endptr != '\0' || !handle) {
- printf("Invalid handle: %s\n", argv[0]);
- return;
- }
- length = argc - 1;
- if (length > 0) {
- if (length > UINT16_MAX) {
- printf("Value too long\n");
- return;
- }
- value = malloc(length);
- if (!value) {
- printf("Failed to construct value\n");
- return;
- }
- for (i = 1; i < argc; i++) {
- if (strlen(argv[i]) != 2) {
- printf("Invalid value byte: %s\n",
- argv[i]);
- goto done;
- }
- value[i-1] = strtol(argv[i], &endptr, 16);
- if (endptr == argv[i] || *endptr != '\0'
- || errno == ERANGE) {
- printf("Invalid value byte: %s\n",
- argv[i]);
- goto done;
- }
- }
- }
- if (indicate) {
- if (!bt_gatt_server_send_indication(server->gatt, handle,
- value, length,
- conf_cb, NULL, NULL))
- printf("Failed to initiate indication\n");
- } else if (!bt_gatt_server_send_notification(server->gatt, handle,
- value, length, false))
- printf("Failed to initiate notification\n");
- done:
- free(value);
- }
- static void heart_rate_usage(void)
- {
- printf("Usage: heart-rate on|off\n");
- }
- static void cmd_heart_rate(struct server *server, char *cmd_str)
- {
- bool enable;
- uint8_t pdu[4];
- struct gatt_db_attribute *attr;
- if (!cmd_str) {
- heart_rate_usage();
- return;
- }
- if (strcmp(cmd_str, "on") == 0)
- enable = true;
- else if (strcmp(cmd_str, "off") == 0)
- enable = false;
- else {
- heart_rate_usage();
- return;
- }
- if (enable == server->hr_visible) {
- printf("Heart Rate Service already %s\n",
- enable ? "visible" : "hidden");
- return;
- }
- server->hr_visible = enable;
- attr = gatt_db_get_attribute(server->db, server->hr_handle);
- gatt_db_service_set_active(attr, server->hr_visible);
- update_hr_msrmt_simulation(server);
- if (!server->svc_chngd_enabled)
- return;
- put_le16(server->hr_handle, pdu);
- put_le16(server->hr_handle + 7, pdu + 2);
- server->hr_msrmt_enabled = false;
- update_hr_msrmt_simulation(server);
- bt_gatt_server_send_indication(server->gatt,
- server->gatt_svc_chngd_handle,
- pdu, 4, conf_cb, NULL, NULL);
- }
- 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 server *server = 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(server->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_prop: 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 server *server = 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, server);
- gatt_db_service_foreach_char(attr, print_chrc, NULL);
- printf("\n");
- }
- static void cmd_services(struct server *server, char *cmd_str)
- {
- gatt_db_foreach_service(server->db, NULL, print_service, server);
- }
- 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 <remote csrk>\tRemote CSRK\n"
- "e.g.:\n"
- "\tset-sign-key -c D8515948451FEA320DC05A2E88308188\n");
- }
- static bool remote_counter(uint32_t *sign_cnt, void *user_data)
- {
- static uint32_t cnt = 0;
- if (*sign_cnt < cnt)
- return false;
- cnt = *sign_cnt;
- return true;
- }
- static void cmd_set_sign_key(struct server *server, 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_remote_key(server->att, key, remote_counter,
- server);
- } else
- set_sign_key_usage();
- }
- static void cmd_help(struct server *server, char *cmd_str);
- typedef void (*command_func_t)(struct server *server, char *cmd_str);
- static struct {
- char *cmd;
- command_func_t func;
- char *doc;
- } command[] = {
- { "help", cmd_help, "\tDisplay help message" },
- { "notify", cmd_notify, "\tSend handle-value notification" },
- { "heart-rate", cmd_heart_rate, "\tHide/Unhide Heart Rate Service" },
- { "services", cmd_services, "\tEnumerate all services" },
- { "set-sign-key", cmd_set_sign_key,
- "\tSet remote signing key for signed write command"},
- { }
- };
- static void cmd_help(struct server *server, 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 server *server = user_data;
- int i;
- if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
- mainloop_quit();
- return;
- }
- read = getline(&line, &len, stdin);
- if (read < 0)
- return;
- if (read <= 1) {
- cmd_help(server, 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(server, 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;
- }
- }
- int main(int argc, char *argv[])
- {
- int opt;
- bdaddr_t src_addr;
- int dev_id = -1;
- int fd;
- int sec = BT_SECURITY_LOW;
- uint8_t src_type = BDADDR_LE_PUBLIC;
- uint16_t mtu = 0;
- bool hr_visible = false;
- struct server *server;
- while ((opt = getopt_long(argc, argv, "+hvrs:t:m:i:",
- main_options, NULL)) != -1) {
- switch (opt) {
- case 'h':
- usage();
- return EXIT_SUCCESS;
- case 'v':
- verbose = true;
- break;
- case 'r':
- hr_visible = 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 {
- fprintf(stderr, "Invalid security level\n");
- return EXIT_FAILURE;
- }
- break;
- case 't':
- if (strcmp(optarg, "random") == 0)
- src_type = BDADDR_LE_RANDOM;
- else if (strcmp(optarg, "public") == 0)
- src_type = BDADDR_LE_PUBLIC;
- else {
- fprintf(stderr,
- "Allowed types: random, public\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 '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;
- }
- }
- 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;
- }
- fd = l2cap_le_att_listen_and_accept(&src_addr, sec, src_type);
- if (fd < 0) {
- fprintf(stderr, "Failed to accept L2CAP ATT connection\n");
- return EXIT_FAILURE;
- }
- mainloop_init();
- server = server_create(fd, mtu, hr_visible);
- if (!server) {
- close(fd);
- return EXIT_FAILURE;
- }
- if (mainloop_add_fd(fileno(stdin),
- EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR,
- prompt_read_cb, server, NULL) < 0) {
- fprintf(stderr, "Failed to initialize console\n");
- server_destroy(server);
- return EXIT_FAILURE;
- }
- printf("Running GATT server\n");
- print_prompt();
- mainloop_run_with_signal(signal_cb, NULL);
- printf("\n\nShutting down...\n");
- server_destroy(server);
- return EXIT_SUCCESS;
- }
|