blob: b904ce6b64bb4e8d97d67f2721d3e2f7771dd402 [file] [log] [blame]
/*
*
* 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 <bluetooth/uuid.h>
#include <bluetooth/sdp.h>
#include "att.h"
#include "gattrib.h"
#include "attrib-server.h"
#include "gatt-service.h"
#include "log.h"
#include "glib-compat.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;
};
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_UUID:
bt_uuid16_create(&info->uuid, va_arg(args, int));
/* characteristic declaration and value */
info->num_attrs += 2;
break;
case GATT_OPT_CHR_PROPS:
info->props = va_arg(args, int);
if (info->props & (ATT_CHAR_PROPER_NOTIFY |
ATT_CHAR_PROPER_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 *);
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_UUID) {
info = g_new0(struct gatt_info, 1);
l = g_slist_append(l, info);
}
}
return l;
}
static int att_read_reqs(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 & ATT_CHAR_PROPER_READ))
return ATT_NOT_PERMITTED;
return ATT_NONE;
}
static int att_write_reqs(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 & (ATT_CHAR_PROPER_WRITE |
ATT_CHAR_PROPER_WRITE_WITHOUT_RESP)))
return ATT_NOT_PERMITTED;
return ATT_NONE;
}
static gint 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(uint16_t *handle, struct gatt_info *info)
{
int read_reqs, write_reqs;
uint16_t h = *handle;
struct attribute *a;
bt_uuid_t bt_uuid;
uint8_t atval[5];
GSList *l;
if (!info->uuid.value.u16 || !info->props) {
error("Characteristic UUID or properties are missing");
return FALSE;
}
read_reqs = att_read_reqs(info->authorization, info->authentication,
info->props);
write_reqs = att_write_reqs(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_reqs != 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_reqs != 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;
att_put_u16(h + 1, &atval[1]);
att_put_u16(info->uuid.value.u16, &atval[3]);
attrib_db_add(h++, &bt_uuid, ATT_NONE, ATT_NOT_PERMITTED, atval,
sizeof(atval));
/* characteristic value */
a = attrib_db_add(h++, &info->uuid, read_reqs, write_reqs, NULL, 0);
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;
}
}
if (info->value_handle != NULL)
*info->value_handle = a->handle;
/* client characteristic configuration descriptor */
if (info->props & (ATT_CHAR_PROPER_NOTIFY | ATT_CHAR_PROPER_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(h++, &bt_uuid, ATT_NONE, ATT_AUTHENTICATION,
cfg_val, sizeof(cfg_val));
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);
}
gboolean gatt_service_add(uint16_t uuid, uint16_t svc_uuid, gatt_option opt1, ...)
{
uint16_t start_handle, h;
unsigned int size;
bt_uuid_t bt_uuid;
uint8_t atval[2];
va_list args;
GSList *chrs, *l;
va_start(args, opt1);
chrs = parse_opts(opt1, 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;
}
va_end(args);
start_handle = attrib_db_find_avail(size);
if (start_handle == 0) {
error("Not enough free handles to register service");
g_slist_free_full(chrs, free_gatt_info);
return FALSE;
}
DBG("New service: handle 0x%04x, UUID 0x%04x, %d attributes",
start_handle, svc_uuid, size);
/* service declaration */
h = start_handle;
bt_uuid16_create(&bt_uuid, uuid);
att_put_u16(svc_uuid, &atval[0]);
attrib_db_add(h++, &bt_uuid, ATT_NONE, ATT_NOT_PERMITTED, atval,
sizeof(atval));
for (l = chrs; l != NULL; l = l->next) {
struct gatt_info *info = l->data;
DBG("New characteristic: handle 0x%04x", h);
if (!add_characteristic(&h, info)) {
g_slist_free_full(chrs, free_gatt_info);
return FALSE;
}
}
g_assert(size < USHRT_MAX);
g_assert(h - start_handle == (uint16_t) size);
g_slist_free_full(chrs, free_gatt_info);
return TRUE;
}