--- Makefile.am | 2 +- attrib/gatt-service.c | 279 +++++++++++++++++++++++++++++++++++++++++++++++++ attrib/gatt-service.h | 45 ++++++++ 3 files changed, 325 insertions(+), 1 deletions(-) create mode 100644 attrib/gatt-service.c create mode 100644 attrib/gatt-service.h diff --git a/Makefile.am b/Makefile.am index a63c469..76bf828 100644 --- a/Makefile.am +++ b/Makefile.am @@ -108,7 +108,7 @@ endif attrib_sources = attrib/att.h attrib/att.c attrib/gatt.h attrib/gatt.c \ attrib/gattrib.h attrib/gattrib.c attrib/client.h \ - attrib/client.c + attrib/client.c attrib/gatt-service.h attrib/gatt-service.c gdbus_sources = gdbus/gdbus.h gdbus/mainloop.c gdbus/watch.c \ gdbus/object.c gdbus/polkit.c diff --git a/attrib/gatt-service.c b/attrib/gatt-service.c new file mode 100644 index 0000000..c6da71a --- /dev/null +++ b/attrib/gatt-service.c @@ -0,0 +1,279 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@xxxxxxxxxxxx> + * + * + * 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-helper.h" + +struct gatt_info { + bt_uuid_t uuid; + uint8_t props; + int authentication; + int authorization; + GSList *callbacks; + unsigned int num_attrs; +}; + +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_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; + } + } + + /* 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; + attrib_db_add(h++, &bt_uuid, ATT_NONE, ATT_AUTHENTICATION, + cfg_val, sizeof(cfg_val)); + } + + *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); +} + +void 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"); + goto done; + } + + 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)) + goto done; + } + + g_assert(size < USHRT_MAX); + g_assert(h - start_handle == (uint16_t) size); + +done: + g_slist_free_full(chrs, free_gatt_info); +} diff --git a/attrib/gatt-service.h b/attrib/gatt-service.h new file mode 100644 index 0000000..83e0bea --- /dev/null +++ b/attrib/gatt-service.h @@ -0,0 +1,45 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@xxxxxxxxxxxx> + * + * + * 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 + * + */ + +typedef enum { + GATT_OPT_INVALID = 0, + GATT_OPT_CHR_UUID, + GATT_OPT_CHR_PROPS, + GATT_OPT_CHR_VALUE_CB, + GATT_OPT_CHR_AUTHENTICATION, + GATT_OPT_CHR_AUTHORIZATION, + + /* arguments for authentication/authorization */ + GATT_CHR_VALUE_READ, + GATT_CHR_VALUE_WRITE, + GATT_CHR_VALUE_BOTH, +} gatt_option; + +typedef enum { + ATTRIB_READ, + ATTRIB_WRITE, +} attrib_event_t; + +extern void gatt_service_add(uint16_t uuid, uint16_t svc_uuid, gatt_option opt1, + ...); -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html