363 lines
9.2 KiB
C
363 lines
9.2 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2011 Nokia Corporation
|
|
* Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org>
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
|
|
#include "src/adapter.h"
|
|
#include "src/shared/util.h"
|
|
#include "lib/uuid.h"
|
|
#include "attrib/gattrib.h"
|
|
#include "attrib/att.h"
|
|
#include "attrib/gatt.h"
|
|
#include "attrib/att-database.h"
|
|
#include "src/attrib-server.h"
|
|
#include "attrib/gatt-service.h"
|
|
#include "src/log.h"
|
|
|
|
struct gatt_info {
|
|
bt_uuid_t uuid;
|
|
uint8_t props;
|
|
int authentication;
|
|
int authorization;
|
|
GSList *callbacks;
|
|
unsigned int num_attrs;
|
|
uint16_t *value_handle;
|
|
uint16_t *ccc_handle;
|
|
};
|
|
|
|
struct attrib_cb {
|
|
attrib_event_t event;
|
|
void *fn;
|
|
void *user_data;
|
|
};
|
|
|
|
static inline void put_uuid_le(const bt_uuid_t *src, void *dst)
|
|
{
|
|
if (src->type == BT_UUID16)
|
|
put_le16(src->value.u16, dst);
|
|
else
|
|
/* Convert from 128-bit BE to LE */
|
|
bswap_128(&src->value.u128, dst);
|
|
}
|
|
|
|
static GSList *parse_opts(gatt_option opt1, va_list args)
|
|
{
|
|
gatt_option opt = opt1;
|
|
struct gatt_info *info;
|
|
struct attrib_cb *cb;
|
|
GSList *l = NULL;
|
|
|
|
info = g_new0(struct gatt_info, 1);
|
|
l = g_slist_append(l, info);
|
|
|
|
while (opt != GATT_OPT_INVALID) {
|
|
switch (opt) {
|
|
case GATT_OPT_CHR_UUID16:
|
|
bt_uuid16_create(&info->uuid, va_arg(args, int));
|
|
/* characteristic declaration and value */
|
|
info->num_attrs += 2;
|
|
break;
|
|
case GATT_OPT_CHR_UUID:
|
|
memcpy(&info->uuid, va_arg(args, bt_uuid_t *),
|
|
sizeof(bt_uuid_t));
|
|
/* characteristic declaration and value */
|
|
info->num_attrs += 2;
|
|
break;
|
|
case GATT_OPT_CHR_PROPS:
|
|
info->props = va_arg(args, int);
|
|
|
|
if (info->props & (GATT_CHR_PROP_NOTIFY |
|
|
GATT_CHR_PROP_INDICATE))
|
|
/* client characteristic configuration */
|
|
info->num_attrs += 1;
|
|
|
|
/* TODO: "Extended Properties" property requires a
|
|
* descriptor, but it is not supported yet. */
|
|
break;
|
|
case GATT_OPT_CHR_VALUE_CB:
|
|
cb = g_new0(struct attrib_cb, 1);
|
|
cb->event = va_arg(args, attrib_event_t);
|
|
cb->fn = va_arg(args, void *);
|
|
cb->user_data = va_arg(args, void *);
|
|
info->callbacks = g_slist_append(info->callbacks, cb);
|
|
break;
|
|
case GATT_OPT_CHR_VALUE_GET_HANDLE:
|
|
info->value_handle = va_arg(args, void *);
|
|
break;
|
|
case GATT_OPT_CCC_GET_HANDLE:
|
|
info->ccc_handle = va_arg(args, void *);
|
|
break;
|
|
case GATT_OPT_CHR_AUTHENTICATION:
|
|
info->authentication = va_arg(args, gatt_option);
|
|
break;
|
|
case GATT_OPT_CHR_AUTHORIZATION:
|
|
info->authorization = va_arg(args, gatt_option);
|
|
break;
|
|
default:
|
|
error("Invalid option: %d", opt);
|
|
}
|
|
|
|
opt = va_arg(args, gatt_option);
|
|
if (opt == GATT_OPT_CHR_UUID16 || opt == GATT_OPT_CHR_UUID) {
|
|
info = g_new0(struct gatt_info, 1);
|
|
l = g_slist_append(l, info);
|
|
}
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
static struct attribute *add_service_declaration(struct btd_adapter *adapter,
|
|
uint16_t handle, uint16_t svc, bt_uuid_t *uuid)
|
|
{
|
|
bt_uuid_t bt_uuid;
|
|
uint8_t atval[16];
|
|
int len;
|
|
|
|
put_uuid_le(uuid, &atval[0]);
|
|
len = bt_uuid_len(uuid);
|
|
|
|
bt_uuid16_create(&bt_uuid, svc);
|
|
|
|
return attrib_db_add(adapter, handle, &bt_uuid, ATT_NONE,
|
|
ATT_NOT_PERMITTED, atval, len);
|
|
}
|
|
|
|
static int att_read_req(int authorization, int authentication, uint8_t props)
|
|
{
|
|
if (authorization == GATT_CHR_VALUE_READ ||
|
|
authorization == GATT_CHR_VALUE_BOTH)
|
|
return ATT_AUTHORIZATION;
|
|
else if (authentication == GATT_CHR_VALUE_READ ||
|
|
authentication == GATT_CHR_VALUE_BOTH)
|
|
return ATT_AUTHENTICATION;
|
|
else if (!(props & GATT_CHR_PROP_READ))
|
|
return ATT_NOT_PERMITTED;
|
|
|
|
return ATT_NONE;
|
|
}
|
|
|
|
static int att_write_req(int authorization, int authentication, uint8_t props)
|
|
{
|
|
if (authorization == GATT_CHR_VALUE_WRITE ||
|
|
authorization == GATT_CHR_VALUE_BOTH)
|
|
return ATT_AUTHORIZATION;
|
|
else if (authentication == GATT_CHR_VALUE_WRITE ||
|
|
authentication == GATT_CHR_VALUE_BOTH)
|
|
return ATT_AUTHENTICATION;
|
|
else if (!(props & (GATT_CHR_PROP_WRITE |
|
|
GATT_CHR_PROP_WRITE_WITHOUT_RESP)))
|
|
return ATT_NOT_PERMITTED;
|
|
|
|
return ATT_NONE;
|
|
}
|
|
|
|
static int find_callback(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct attrib_cb *cb = a;
|
|
unsigned int event = GPOINTER_TO_UINT(b);
|
|
|
|
return cb->event - event;
|
|
}
|
|
|
|
static gboolean add_characteristic(struct btd_adapter *adapter,
|
|
uint16_t *handle, struct gatt_info *info)
|
|
{
|
|
int read_req, write_req;
|
|
uint16_t h = *handle;
|
|
struct attribute *a;
|
|
bt_uuid_t bt_uuid;
|
|
uint8_t atval[ATT_MAX_VALUE_LEN];
|
|
GSList *l;
|
|
|
|
if ((info->uuid.type != BT_UUID16 && info->uuid.type != BT_UUID128) ||
|
|
!info->props) {
|
|
error("Characteristic UUID or properties are missing");
|
|
return FALSE;
|
|
}
|
|
|
|
read_req = att_read_req(info->authorization, info->authentication,
|
|
info->props);
|
|
write_req = att_write_req(info->authorization, info->authentication,
|
|
info->props);
|
|
|
|
/* TODO: static characteristic values are not supported, therefore a
|
|
* callback must be always provided if a read/write property is set */
|
|
if (read_req != ATT_NOT_PERMITTED) {
|
|
gpointer reqs = GUINT_TO_POINTER(ATTRIB_READ);
|
|
|
|
if (!g_slist_find_custom(info->callbacks, reqs,
|
|
find_callback)) {
|
|
error("Callback for read required");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (write_req != ATT_NOT_PERMITTED) {
|
|
gpointer reqs = GUINT_TO_POINTER(ATTRIB_WRITE);
|
|
|
|
if (!g_slist_find_custom(info->callbacks, reqs,
|
|
find_callback)) {
|
|
error("Callback for write required");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* characteristic declaration */
|
|
bt_uuid16_create(&bt_uuid, GATT_CHARAC_UUID);
|
|
atval[0] = info->props;
|
|
put_le16(h + 1, &atval[1]);
|
|
put_uuid_le(&info->uuid, &atval[3]);
|
|
if (attrib_db_add(adapter, h++, &bt_uuid, ATT_NONE, ATT_NOT_PERMITTED,
|
|
atval, 3 + info->uuid.type / 8) == NULL)
|
|
return FALSE;
|
|
|
|
/* characteristic value */
|
|
a = attrib_db_add(adapter, h++, &info->uuid, read_req, write_req,
|
|
NULL, 0);
|
|
if (a == NULL)
|
|
return FALSE;
|
|
|
|
for (l = info->callbacks; l != NULL; l = l->next) {
|
|
struct attrib_cb *cb = l->data;
|
|
|
|
switch (cb->event) {
|
|
case ATTRIB_READ:
|
|
a->read_cb = cb->fn;
|
|
break;
|
|
case ATTRIB_WRITE:
|
|
a->write_cb = cb->fn;
|
|
break;
|
|
}
|
|
|
|
a->cb_user_data = cb->user_data;
|
|
}
|
|
|
|
if (info->value_handle != NULL)
|
|
*info->value_handle = a->handle;
|
|
|
|
/* client characteristic configuration descriptor */
|
|
if (info->props & (GATT_CHR_PROP_NOTIFY | GATT_CHR_PROP_INDICATE)) {
|
|
uint8_t cfg_val[2];
|
|
|
|
bt_uuid16_create(&bt_uuid, GATT_CLIENT_CHARAC_CFG_UUID);
|
|
cfg_val[0] = 0x00;
|
|
cfg_val[1] = 0x00;
|
|
a = attrib_db_add(adapter, h++, &bt_uuid, ATT_NONE,
|
|
ATT_AUTHENTICATION, cfg_val, sizeof(cfg_val));
|
|
if (a == NULL)
|
|
return FALSE;
|
|
|
|
if (info->ccc_handle != NULL)
|
|
*info->ccc_handle = a->handle;
|
|
}
|
|
|
|
*handle = h;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void free_gatt_info(void *data)
|
|
{
|
|
struct gatt_info *info = data;
|
|
|
|
g_slist_free_full(info->callbacks, g_free);
|
|
g_free(info);
|
|
}
|
|
|
|
static void service_attr_del(struct btd_adapter *adapter, uint16_t start_handle,
|
|
uint16_t end_handle)
|
|
{
|
|
uint16_t handle;
|
|
|
|
for (handle = start_handle; handle <= end_handle; handle++)
|
|
if (attrib_db_del(adapter, handle) < 0)
|
|
error("Can't delete handle 0x%04x", handle);
|
|
}
|
|
|
|
gboolean gatt_service_add(struct btd_adapter *adapter, uint16_t uuid,
|
|
bt_uuid_t *svc_uuid, gatt_option opt1, ...)
|
|
{
|
|
char uuidstr[MAX_LEN_UUID_STR];
|
|
uint16_t start_handle, h;
|
|
unsigned int size;
|
|
va_list args;
|
|
GSList *chrs, *l;
|
|
|
|
bt_uuid_to_string(svc_uuid, uuidstr, MAX_LEN_UUID_STR);
|
|
|
|
if (svc_uuid->type != BT_UUID16 && svc_uuid->type != BT_UUID128) {
|
|
error("Invalid service uuid: %s", uuidstr);
|
|
return FALSE;
|
|
}
|
|
|
|
va_start(args, opt1);
|
|
chrs = parse_opts(opt1, args);
|
|
va_end(args);
|
|
|
|
/* calculate how many attributes are necessary for this service */
|
|
for (l = chrs, size = 1; l != NULL; l = l->next) {
|
|
struct gatt_info *info = l->data;
|
|
size += info->num_attrs;
|
|
}
|
|
|
|
start_handle = attrib_db_find_avail(adapter, svc_uuid, size);
|
|
if (start_handle == 0) {
|
|
error("Not enough free handles to register service");
|
|
goto fail;
|
|
}
|
|
|
|
DBG("New service: handle 0x%04x, UUID %s, %d attributes",
|
|
start_handle, uuidstr, size);
|
|
|
|
/* service declaration */
|
|
h = start_handle;
|
|
if (add_service_declaration(adapter, h++, uuid, svc_uuid) == NULL)
|
|
goto fail;
|
|
|
|
for (l = chrs; l != NULL; l = l->next) {
|
|
struct gatt_info *info = l->data;
|
|
|
|
DBG("New characteristic: handle 0x%04x", h);
|
|
if (!add_characteristic(adapter, &h, info)) {
|
|
service_attr_del(adapter, start_handle, h - 1);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
g_assert(size < USHRT_MAX);
|
|
g_assert(h == 0 || (h - start_handle == (uint16_t) size));
|
|
g_slist_free_full(chrs, free_gatt_info);
|
|
|
|
return TRUE;
|
|
|
|
fail:
|
|
g_slist_free_full(chrs, free_gatt_info);
|
|
return FALSE;
|
|
}
|