blob: fadaff2ad297748f0b011c2a3dc8bf9b8c2b899c [file] [log] [blame]
/*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2014 Google Inc.
*
*
* 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.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#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);
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);
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, 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))
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;
sigset_t mask;
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");
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
mainloop_set_signal(&mask, signal_cb, NULL, NULL);
print_prompt();
mainloop_run();
printf("\n\nShutting down...\n");
server_destroy(server);
return EXIT_SUCCESS;
}