|
Packit |
34410b |
/*
|
|
Packit |
34410b |
*
|
|
Packit |
34410b |
* BlueZ - Bluetooth protocol stack for Linux
|
|
Packit |
34410b |
*
|
|
Packit |
34410b |
* Copyright (C) 2010 Nokia Corporation
|
|
Packit |
34410b |
* Copyright (C) 2010 Marcel Holtmann <marcel@holtmann.org>
|
|
Packit |
34410b |
*
|
|
Packit |
34410b |
*
|
|
Packit |
34410b |
* This program is free software; you can redistribute it and/or modify
|
|
Packit |
34410b |
* it under the terms of the GNU General Public License as published by
|
|
Packit |
34410b |
* the Free Software Foundation; either version 2 of the License, or
|
|
Packit |
34410b |
* (at your option) any later version.
|
|
Packit |
34410b |
*
|
|
Packit |
34410b |
* This program is distributed in the hope that it will be useful,
|
|
Packit |
34410b |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
34410b |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit |
34410b |
* GNU General Public License for more details.
|
|
Packit |
34410b |
*
|
|
Packit |
34410b |
* You should have received a copy of the GNU General Public License
|
|
Packit |
34410b |
* along with this program; if not, write to the Free Software
|
|
Packit |
34410b |
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
Packit |
34410b |
*
|
|
Packit |
34410b |
*/
|
|
Packit |
34410b |
|
|
Packit |
34410b |
#ifdef HAVE_CONFIG_H
|
|
Packit |
34410b |
#include <config.h>
|
|
Packit |
34410b |
#endif
|
|
Packit |
34410b |
|
|
Packit |
34410b |
#include <errno.h>
|
|
Packit |
34410b |
#include <stdint.h>
|
|
Packit |
34410b |
#include <stdbool.h>
|
|
Packit |
34410b |
#include <string.h>
|
|
Packit |
34410b |
#include <unistd.h>
|
|
Packit |
34410b |
#include <glib.h>
|
|
Packit |
34410b |
#include <sys/stat.h>
|
|
Packit |
34410b |
|
|
Packit |
34410b |
#include "lib/bluetooth.h"
|
|
Packit |
34410b |
#include "lib/sdp.h"
|
|
Packit |
34410b |
#include "lib/sdp_lib.h"
|
|
Packit |
34410b |
#include "lib/uuid.h"
|
|
Packit |
34410b |
|
|
Packit |
34410b |
#include "btio/btio.h"
|
|
Packit |
34410b |
#include "log.h"
|
|
Packit |
34410b |
#include "backtrace.h"
|
|
Packit |
34410b |
#include "adapter.h"
|
|
Packit |
34410b |
#include "device.h"
|
|
Packit |
34410b |
#include "src/shared/util.h"
|
|
Packit |
34410b |
#include "attrib/gattrib.h"
|
|
Packit |
34410b |
#include "attrib/att.h"
|
|
Packit |
34410b |
#include "attrib/gatt.h"
|
|
Packit |
34410b |
#include "attrib/att-database.h"
|
|
Packit |
34410b |
#include "textfile.h"
|
|
Packit |
34410b |
#include "storage.h"
|
|
Packit |
34410b |
|
|
Packit |
34410b |
#include "attrib-server.h"
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static GSList *servers = NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
struct gatt_server {
|
|
Packit |
34410b |
struct btd_adapter *adapter;
|
|
Packit |
34410b |
GIOChannel *l2cap_io;
|
|
Packit |
34410b |
GIOChannel *le_io;
|
|
Packit |
34410b |
uint32_t gatt_sdp_handle;
|
|
Packit |
34410b |
uint32_t gap_sdp_handle;
|
|
Packit |
34410b |
GList *database;
|
|
Packit |
34410b |
GSList *clients;
|
|
Packit |
34410b |
uint16_t name_handle;
|
|
Packit |
34410b |
uint16_t appearance_handle;
|
|
Packit |
34410b |
};
|
|
Packit |
34410b |
|
|
Packit |
34410b |
struct gatt_channel {
|
|
Packit |
34410b |
GAttrib *attrib;
|
|
Packit |
34410b |
guint mtu;
|
|
Packit |
34410b |
gboolean le;
|
|
Packit |
34410b |
guint id;
|
|
Packit |
34410b |
gboolean encrypted;
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
guint cleanup_id;
|
|
Packit |
34410b |
struct btd_device *device;
|
|
Packit |
34410b |
};
|
|
Packit |
34410b |
|
|
Packit |
34410b |
struct group_elem {
|
|
Packit |
34410b |
uint16_t handle;
|
|
Packit |
34410b |
uint16_t end;
|
|
Packit |
34410b |
uint8_t *data;
|
|
Packit |
34410b |
uint16_t len;
|
|
Packit |
34410b |
};
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static bt_uuid_t prim_uuid = {
|
|
Packit |
34410b |
.type = BT_UUID16,
|
|
Packit |
34410b |
.value.u16 = GATT_PRIM_SVC_UUID
|
|
Packit |
34410b |
};
|
|
Packit |
34410b |
static bt_uuid_t snd_uuid = {
|
|
Packit |
34410b |
.type = BT_UUID16,
|
|
Packit |
34410b |
.value.u16 = GATT_SND_SVC_UUID
|
|
Packit |
34410b |
};
|
|
Packit |
34410b |
static bt_uuid_t ccc_uuid = {
|
|
Packit |
34410b |
.type = BT_UUID16,
|
|
Packit |
34410b |
.value.u16 = GATT_CLIENT_CHARAC_CFG_UUID
|
|
Packit |
34410b |
};
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static void attrib_free(void *data)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct attribute *a = data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_free(a->data);
|
|
Packit |
34410b |
g_free(a);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static void channel_free(struct gatt_channel *channel)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (channel->cleanup_id)
|
|
Packit |
34410b |
g_source_remove(channel->cleanup_id);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (channel->device)
|
|
Packit |
34410b |
btd_device_unref(channel->device);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_attrib_unref(channel->attrib);
|
|
Packit |
34410b |
g_free(channel);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static void gatt_server_free(struct gatt_server *server)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
g_list_free_full(server->database, attrib_free);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (server->l2cap_io != NULL) {
|
|
Packit |
34410b |
g_io_channel_shutdown(server->l2cap_io, FALSE, NULL);
|
|
Packit |
34410b |
g_io_channel_unref(server->l2cap_io);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (server->le_io != NULL) {
|
|
Packit |
34410b |
g_io_channel_shutdown(server->le_io, FALSE, NULL);
|
|
Packit |
34410b |
g_io_channel_unref(server->le_io);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_slist_free_full(server->clients, (GDestroyNotify) channel_free);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (server->gatt_sdp_handle > 0)
|
|
Packit |
34410b |
adapter_service_remove(server->adapter,
|
|
Packit |
34410b |
server->gatt_sdp_handle);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (server->gap_sdp_handle > 0)
|
|
Packit |
34410b |
adapter_service_remove(server->adapter, server->gap_sdp_handle);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (server->adapter != NULL)
|
|
Packit |
34410b |
btd_adapter_unref(server->adapter);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_free(server);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static int adapter_cmp_addr(gconstpointer a, gconstpointer b)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
const struct gatt_server *server = a;
|
|
Packit |
34410b |
const bdaddr_t *bdaddr = b;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return bacmp(btd_adapter_get_address(server->adapter), bdaddr);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static int adapter_cmp(gconstpointer a, gconstpointer b)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
const struct gatt_server *server = a;
|
|
Packit |
34410b |
const struct btd_adapter *adapter = b;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (server->adapter == adapter)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return -1;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static struct gatt_server *find_gatt_server(const bdaddr_t *bdaddr)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, bdaddr, adapter_cmp_addr);
|
|
Packit |
34410b |
if (l == NULL) {
|
|
Packit |
34410b |
char addr[18];
|
|
Packit |
34410b |
|
|
Packit |
34410b |
ba2str(bdaddr, addr);
|
|
Packit |
34410b |
error("No GATT server found in %s", addr);
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return l->data;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static sdp_record_t *server_record_new(uuid_t *uuid, uint16_t start, uint16_t end)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
sdp_list_t *svclass_id, *apseq, *proto[2], *root, *aproto;
|
|
Packit |
34410b |
uuid_t root_uuid, proto_uuid, l2cap;
|
|
Packit |
34410b |
sdp_record_t *record;
|
|
Packit |
34410b |
sdp_data_t *psm, *sh, *eh;
|
|
Packit |
34410b |
uint16_t lp = ATT_PSM;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (uuid == NULL)
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (start > end)
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
record = sdp_record_alloc();
|
|
Packit |
34410b |
if (record == NULL)
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
|
|
Packit |
34410b |
root = sdp_list_append(NULL, &root_uuid);
|
|
Packit |
34410b |
sdp_set_browse_groups(record, root);
|
|
Packit |
34410b |
sdp_list_free(root, NULL);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
svclass_id = sdp_list_append(NULL, uuid);
|
|
Packit |
34410b |
sdp_set_service_classes(record, svclass_id);
|
|
Packit |
34410b |
sdp_list_free(svclass_id, NULL);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
sdp_uuid16_create(&l2cap, L2CAP_UUID);
|
|
Packit |
34410b |
proto[0] = sdp_list_append(NULL, &l2cap);
|
|
Packit |
34410b |
psm = sdp_data_alloc(SDP_UINT16, &lp);
|
|
Packit |
34410b |
proto[0] = sdp_list_append(proto[0], psm);
|
|
Packit |
34410b |
apseq = sdp_list_append(NULL, proto[0]);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
sdp_uuid16_create(&proto_uuid, ATT_UUID);
|
|
Packit |
34410b |
proto[1] = sdp_list_append(NULL, &proto_uuid);
|
|
Packit |
34410b |
sh = sdp_data_alloc(SDP_UINT16, &start;;
|
|
Packit |
34410b |
proto[1] = sdp_list_append(proto[1], sh);
|
|
Packit |
34410b |
eh = sdp_data_alloc(SDP_UINT16, &end;;
|
|
Packit |
34410b |
proto[1] = sdp_list_append(proto[1], eh);
|
|
Packit |
34410b |
apseq = sdp_list_append(apseq, proto[1]);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
aproto = sdp_list_append(NULL, apseq);
|
|
Packit |
34410b |
sdp_set_access_protos(record, aproto);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
sdp_data_free(psm);
|
|
Packit |
34410b |
sdp_data_free(sh);
|
|
Packit |
34410b |
sdp_data_free(eh);
|
|
Packit |
34410b |
sdp_list_free(proto[0], NULL);
|
|
Packit |
34410b |
sdp_list_free(proto[1], NULL);
|
|
Packit |
34410b |
sdp_list_free(apseq, NULL);
|
|
Packit |
34410b |
sdp_list_free(aproto, NULL);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return record;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static int handle_cmp(gconstpointer a, gconstpointer b)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
const struct attribute *attrib = a;
|
|
Packit |
34410b |
uint16_t handle = GPOINTER_TO_UINT(b);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return attrib->handle - handle;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static int attribute_cmp(gconstpointer a1, gconstpointer a2)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
const struct attribute *attrib1 = a1;
|
|
Packit |
34410b |
const struct attribute *attrib2 = a2;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return attrib1->handle - attrib2->handle;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static struct attribute *find_svc_range(struct gatt_server *server,
|
|
Packit |
34410b |
uint16_t start, uint16_t *end)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct attribute *attrib;
|
|
Packit |
34410b |
guint h = start;
|
|
Packit |
34410b |
GList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (end == NULL)
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_list_find_custom(server->database, GUINT_TO_POINTER(h),
|
|
Packit |
34410b |
handle_cmp);
|
|
Packit |
34410b |
if (!l)
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
attrib = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(&attrib->uuid, &prim_uuid) != 0 &&
|
|
Packit |
34410b |
bt_uuid_cmp(&attrib->uuid, &snd_uuid) != 0)
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
*end = start;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (l = l->next; l; l = l->next) {
|
|
Packit |
34410b |
struct attribute *a = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 ||
|
|
Packit |
34410b |
bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
*end = a->handle;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return attrib;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint32_t attrib_create_sdp_new(struct gatt_server *server,
|
|
Packit |
34410b |
uint16_t handle, const char *name)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
sdp_record_t *record;
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
uint16_t end = 0;
|
|
Packit |
34410b |
uuid_t svc, gap_uuid;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = find_svc_range(server, handle, &end;;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a == NULL)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->len == 2)
|
|
Packit |
34410b |
sdp_uuid16_create(&svc, get_le16(a->data));
|
|
Packit |
34410b |
else if (a->len == 16) {
|
|
Packit |
34410b |
uint8_t be128[16];
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* Converting from LE to BE */
|
|
Packit |
34410b |
bswap_128(a->data, be128);
|
|
Packit |
34410b |
sdp_uuid128_create(&svc, be128);
|
|
Packit |
34410b |
} else
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
record = server_record_new(&svc, handle, end);
|
|
Packit |
34410b |
if (record == NULL)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (name != NULL)
|
|
Packit |
34410b |
sdp_set_info_attr(record, name, "BlueZ", NULL);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
sdp_uuid16_create(&gap_uuid, GENERIC_ACCESS_PROFILE_ID);
|
|
Packit |
34410b |
if (sdp_uuid_cmp(&svc, &gap_uuid) == 0) {
|
|
Packit |
34410b |
sdp_set_url_attr(record, "http://www.bluez.org/",
|
|
Packit |
34410b |
"http://www.bluez.org/",
|
|
Packit |
34410b |
"http://www.bluez.org/");
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (adapter_service_add(server->adapter, record) == 0)
|
|
Packit |
34410b |
return record->handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
sdp_record_free(record);
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static struct attribute *attrib_db_add_new(struct gatt_server *server,
|
|
Packit |
34410b |
uint16_t handle, bt_uuid_t *uuid,
|
|
Packit |
34410b |
int read_req, int write_req,
|
|
Packit |
34410b |
const uint8_t *value, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
guint h = handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
DBG("handle=0x%04x", handle);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (g_list_find_custom(server->database, GUINT_TO_POINTER(h),
|
|
Packit |
34410b |
handle_cmp))
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = g_new0(struct attribute, 1);
|
|
Packit |
34410b |
a->len = len;
|
|
Packit |
34410b |
a->data = g_memdup(value, len);
|
|
Packit |
34410b |
a->handle = handle;
|
|
Packit |
34410b |
a->uuid = *uuid;
|
|
Packit |
34410b |
a->read_req = read_req;
|
|
Packit |
34410b |
a->write_req = write_req;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server->database = g_list_insert_sorted(server->database, a,
|
|
Packit |
34410b |
attribute_cmp);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return a;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static bool g_attrib_is_encrypted(GAttrib *attrib)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
BtIOSecLevel sec_level;
|
|
Packit |
34410b |
GIOChannel *io = g_attrib_get_channel(attrib);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (!bt_io_get(io, NULL, BT_IO_OPT_SEC_LEVEL, &sec_level,
|
|
Packit |
34410b |
BT_IO_OPT_INVALID))
|
|
Packit |
34410b |
return FALSE;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return sec_level > BT_IO_SEC_LOW;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint8_t att_check_reqs(struct gatt_channel *channel, uint8_t opcode,
|
|
Packit |
34410b |
int reqs)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
/* FIXME: currently, it is assumed an encrypted link is enough for
|
|
Packit |
34410b |
* authentication. This will allow to enable the SMP negotiation once
|
|
Packit |
34410b |
* it is on upstream kernel. High security level should be mapped
|
|
Packit |
34410b |
* to authentication and medium to encryption permission. */
|
|
Packit |
34410b |
if (!channel->encrypted)
|
|
Packit |
34410b |
channel->encrypted = g_attrib_is_encrypted(channel->attrib);
|
|
Packit |
34410b |
if (reqs == ATT_AUTHENTICATION && !channel->encrypted)
|
|
Packit |
34410b |
return ATT_ECODE_AUTHENTICATION;
|
|
Packit |
34410b |
else if (reqs == ATT_AUTHORIZATION)
|
|
Packit |
34410b |
return ATT_ECODE_AUTHORIZATION;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
switch (opcode) {
|
|
Packit |
34410b |
case ATT_OP_READ_BY_GROUP_REQ:
|
|
Packit |
34410b |
case ATT_OP_READ_BY_TYPE_REQ:
|
|
Packit |
34410b |
case ATT_OP_READ_REQ:
|
|
Packit |
34410b |
case ATT_OP_READ_BLOB_REQ:
|
|
Packit |
34410b |
case ATT_OP_READ_MULTI_REQ:
|
|
Packit |
34410b |
if (reqs == ATT_NOT_PERMITTED)
|
|
Packit |
34410b |
return ATT_ECODE_READ_NOT_PERM;
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_PREP_WRITE_REQ:
|
|
Packit |
34410b |
case ATT_OP_WRITE_REQ:
|
|
Packit |
34410b |
case ATT_OP_WRITE_CMD:
|
|
Packit |
34410b |
if (reqs == ATT_NOT_PERMITTED)
|
|
Packit |
34410b |
return ATT_ECODE_WRITE_NOT_PERM;
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t read_by_group(struct gatt_channel *channel, uint16_t start,
|
|
Packit |
34410b |
uint16_t end, bt_uuid_t *uuid,
|
|
Packit |
34410b |
uint8_t *pdu, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct att_data_list *adl;
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
struct group_elem *cur, *old = NULL;
|
|
Packit |
34410b |
GSList *l, *groups;
|
|
Packit |
34410b |
GList *dl, *database;
|
|
Packit |
34410b |
uint16_t length, last_handle, last_size = 0;
|
|
Packit |
34410b |
uint8_t status;
|
|
Packit |
34410b |
int i;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (start > end || start == 0x0000)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_INVALID_HANDLE, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/*
|
|
Packit |
34410b |
* Only <<Primary Service>> and <<Secondary Service>> grouping
|
|
Packit |
34410b |
* types may be used in the Read By Group Type Request.
|
|
Packit |
34410b |
*/
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(uuid, &prim_uuid) != 0 &&
|
|
Packit |
34410b |
bt_uuid_cmp(uuid, &snd_uuid) != 0)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, 0x0000,
|
|
Packit |
34410b |
ATT_ECODE_UNSUPP_GRP_TYPE, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
last_handle = end;
|
|
Packit |
34410b |
database = channel->server->database;
|
|
Packit |
34410b |
for (dl = database, groups = NULL, cur = NULL; dl; dl = dl->next) {
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = dl->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle < start)
|
|
Packit |
34410b |
continue;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle >= end)
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* The old group ends when a new one starts */
|
|
Packit |
34410b |
if (old && (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 ||
|
|
Packit |
34410b |
bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)) {
|
|
Packit |
34410b |
old->end = last_handle;
|
|
Packit |
34410b |
old = NULL;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(&a->uuid, uuid) != 0) {
|
|
Packit |
34410b |
/* Still inside a service, update its last handle */
|
|
Packit |
34410b |
if (old)
|
|
Packit |
34410b |
last_handle = a->handle;
|
|
Packit |
34410b |
continue;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (last_size && (last_size != a->len))
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
status = att_check_reqs(channel, ATT_OP_READ_BY_GROUP_REQ,
|
|
Packit |
34410b |
a->read_req);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (status == 0x00 && a->read_cb)
|
|
Packit |
34410b |
status = a->read_cb(a, channel->device,
|
|
Packit |
34410b |
a->cb_user_data);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (status) {
|
|
Packit |
34410b |
g_slist_free_full(groups, g_free);
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ,
|
|
Packit |
34410b |
a->handle, status, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
cur = g_new0(struct group_elem, 1);
|
|
Packit |
34410b |
cur->handle = a->handle;
|
|
Packit |
34410b |
cur->data = a->data;
|
|
Packit |
34410b |
cur->len = a->len;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* Attribute Grouping Type found */
|
|
Packit |
34410b |
groups = g_slist_append(groups, cur);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
last_size = a->len;
|
|
Packit |
34410b |
old = cur;
|
|
Packit |
34410b |
last_handle = cur->handle;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (groups == NULL)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_ATTR_NOT_FOUND, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (dl == NULL)
|
|
Packit |
34410b |
cur->end = a->handle;
|
|
Packit |
34410b |
else
|
|
Packit |
34410b |
cur->end = last_handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = g_slist_length(groups);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
adl = att_data_list_alloc(length, last_size + 4);
|
|
Packit |
34410b |
if (adl == NULL) {
|
|
Packit |
34410b |
g_slist_free_full(groups, g_free);
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_UNLIKELY, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (i = 0, l = groups; l; l = l->next, i++) {
|
|
Packit |
34410b |
uint8_t *value;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
cur = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
value = (void *) adl->data[i];
|
|
Packit |
34410b |
|
|
Packit |
34410b |
put_le16(cur->handle, value);
|
|
Packit |
34410b |
put_le16(cur->end, &value[2]);
|
|
Packit |
34410b |
/* Attribute Value */
|
|
Packit |
34410b |
memcpy(&value[4], cur->data, cur->len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = enc_read_by_grp_resp(adl, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
att_data_list_free(adl);
|
|
Packit |
34410b |
g_slist_free_full(groups, g_free);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return length;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t read_by_type(struct gatt_channel *channel, uint16_t start,
|
|
Packit |
34410b |
uint16_t end, bt_uuid_t *uuid,
|
|
Packit |
34410b |
uint8_t *pdu, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct att_data_list *adl;
|
|
Packit |
34410b |
GSList *l, *types;
|
|
Packit |
34410b |
GList *dl, *database;
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
uint16_t num, length;
|
|
Packit |
34410b |
uint8_t status;
|
|
Packit |
34410b |
int i;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (start > end || start == 0x0000)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_INVALID_HANDLE, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
database = channel->server->database;
|
|
Packit |
34410b |
for (dl = database, length = 0, types = NULL; dl; dl = dl->next) {
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = dl->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle < start)
|
|
Packit |
34410b |
continue;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle > end)
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(&a->uuid, uuid) != 0)
|
|
Packit |
34410b |
continue;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
status = att_check_reqs(channel, ATT_OP_READ_BY_TYPE_REQ,
|
|
Packit |
34410b |
a->read_req);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (status == 0x00 && a->read_cb)
|
|
Packit |
34410b |
status = a->read_cb(a, channel->device,
|
|
Packit |
34410b |
a->cb_user_data);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (status) {
|
|
Packit |
34410b |
g_slist_free(types);
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ,
|
|
Packit |
34410b |
a->handle, status, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* All elements must have the same length */
|
|
Packit |
34410b |
if (length == 0)
|
|
Packit |
34410b |
length = a->len;
|
|
Packit |
34410b |
else if (a->len != length)
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
types = g_slist_append(types, a);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (types == NULL)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_ATTR_NOT_FOUND, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
num = g_slist_length(types);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* Handle length plus attribute value length */
|
|
Packit |
34410b |
length += 2;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
adl = att_data_list_alloc(num, length);
|
|
Packit |
34410b |
if (adl == NULL) {
|
|
Packit |
34410b |
g_slist_free(types);
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_UNLIKELY, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (i = 0, l = types; l; i++, l = l->next) {
|
|
Packit |
34410b |
uint8_t *value;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
value = (void *) adl->data[i];
|
|
Packit |
34410b |
|
|
Packit |
34410b |
put_le16(a->handle, value);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* Attribute Value */
|
|
Packit |
34410b |
memcpy(&value[2], a->data, a->len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = enc_read_by_type_resp(adl, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
att_data_list_free(adl);
|
|
Packit |
34410b |
g_slist_free(types);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return length;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t find_info(struct gatt_channel *channel, uint16_t start,
|
|
Packit |
34410b |
uint16_t end, uint8_t *pdu, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
struct att_data_list *adl;
|
|
Packit |
34410b |
GSList *l, *info;
|
|
Packit |
34410b |
GList *dl, *database;
|
|
Packit |
34410b |
uint8_t format, last_type = BT_UUID_UNSPEC;
|
|
Packit |
34410b |
uint16_t length, num;
|
|
Packit |
34410b |
int i;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (start > end || start == 0x0000)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_FIND_INFO_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_INVALID_HANDLE, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
database = channel->server->database;
|
|
Packit |
34410b |
for (dl = database, info = NULL, num = 0; dl; dl = dl->next) {
|
|
Packit |
34410b |
a = dl->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle < start)
|
|
Packit |
34410b |
continue;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle > end)
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (last_type == BT_UUID_UNSPEC)
|
|
Packit |
34410b |
last_type = a->uuid.type;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->uuid.type != last_type)
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
info = g_slist_append(info, a);
|
|
Packit |
34410b |
num++;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
last_type = a->uuid.type;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (info == NULL)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_FIND_INFO_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_ATTR_NOT_FOUND, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (last_type == BT_UUID16) {
|
|
Packit |
34410b |
length = 2;
|
|
Packit |
34410b |
format = 0x01;
|
|
Packit |
34410b |
} else if (last_type == BT_UUID128) {
|
|
Packit |
34410b |
length = 16;
|
|
Packit |
34410b |
format = 0x02;
|
|
Packit |
34410b |
} else {
|
|
Packit |
34410b |
g_slist_free(info);
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
adl = att_data_list_alloc(num, length + 2);
|
|
Packit |
34410b |
if (adl == NULL) {
|
|
Packit |
34410b |
g_slist_free(info);
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_FIND_INFO_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_UNLIKELY, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (i = 0, l = info; l; i++, l = l->next) {
|
|
Packit |
34410b |
uint8_t *value;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
value = (void *) adl->data[i];
|
|
Packit |
34410b |
|
|
Packit |
34410b |
put_le16(a->handle, value);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* Attribute Value */
|
|
Packit |
34410b |
bt_uuid_to_le(&a->uuid, &value[2]);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = enc_find_info_resp(format, adl, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
att_data_list_free(adl);
|
|
Packit |
34410b |
g_slist_free(info);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return length;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t find_by_type(struct gatt_channel *channel, uint16_t start,
|
|
Packit |
34410b |
uint16_t end, bt_uuid_t *uuid,
|
|
Packit |
34410b |
const uint8_t *value, size_t vlen,
|
|
Packit |
34410b |
uint8_t *opdu, size_t mtu)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
struct att_range *range;
|
|
Packit |
34410b |
GSList *matches;
|
|
Packit |
34410b |
GList *dl, *database;
|
|
Packit |
34410b |
uint16_t len;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (start > end || start == 0x0000)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_INVALID_HANDLE, opdu, mtu);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* Searching first requested handle number */
|
|
Packit |
34410b |
database = channel->server->database;
|
|
Packit |
34410b |
for (dl = database, matches = NULL, range = NULL; dl; dl = dl->next) {
|
|
Packit |
34410b |
a = dl->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle < start)
|
|
Packit |
34410b |
continue;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle > end)
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* Primary service? Attribute value matches? */
|
|
Packit |
34410b |
if ((bt_uuid_cmp(&a->uuid, uuid) == 0) && (a->len == vlen) &&
|
|
Packit |
34410b |
(memcmp(a->data, value, vlen) == 0)) {
|
|
Packit |
34410b |
|
|
Packit |
34410b |
range = g_new0(struct att_range, 1);
|
|
Packit |
34410b |
range->start = a->handle;
|
|
Packit |
34410b |
/* It is allowed to have end group handle the same as
|
|
Packit |
34410b |
* start handle, for groups with only one attribute. */
|
|
Packit |
34410b |
range->end = a->handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
matches = g_slist_append(matches, range);
|
|
Packit |
34410b |
} else if (range) {
|
|
Packit |
34410b |
/* Update the last found handle or reset the pointer
|
|
Packit |
34410b |
* to track that a new group started: Primary or
|
|
Packit |
34410b |
* Secondary service. */
|
|
Packit |
34410b |
if (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 ||
|
|
Packit |
34410b |
bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)
|
|
Packit |
34410b |
range = NULL;
|
|
Packit |
34410b |
else
|
|
Packit |
34410b |
range->end = a->handle;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (matches == NULL)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start,
|
|
Packit |
34410b |
ATT_ECODE_ATTR_NOT_FOUND, opdu, mtu);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
len = enc_find_by_type_resp(matches, opdu, mtu);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_slist_free_full(matches, g_free);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return len;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static int read_device_ccc(struct btd_device *device, uint16_t handle,
|
|
Packit |
34410b |
uint16_t *value)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
char *filename;
|
|
Packit |
34410b |
GKeyFile *key_file;
|
|
Packit |
34410b |
char group[6];
|
|
Packit |
34410b |
char *str;
|
|
Packit |
34410b |
unsigned int config;
|
|
Packit |
34410b |
int err = 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
filename = btd_device_get_storage_path(device, "ccc");
|
|
Packit |
34410b |
if (!filename) {
|
|
Packit |
34410b |
warn("Unable to get ccc storage path for device");
|
|
Packit |
34410b |
return -ENOENT;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
key_file = g_key_file_new();
|
|
Packit |
34410b |
g_key_file_load_from_file(key_file, filename, 0, NULL);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
sprintf(group, "%hu", handle);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
str = g_key_file_get_string(key_file, group, "Value", NULL);
|
|
Packit |
34410b |
if (!str || sscanf(str, "%04X", &config) != 1)
|
|
Packit |
34410b |
err = -ENOENT;
|
|
Packit |
34410b |
else
|
|
Packit |
34410b |
*value = config;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_free(str);
|
|
Packit |
34410b |
g_free(filename);
|
|
Packit |
34410b |
g_key_file_free(key_file);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return err;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t read_value(struct gatt_channel *channel, uint16_t handle,
|
|
Packit |
34410b |
uint8_t *pdu, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
uint8_t status;
|
|
Packit |
34410b |
GList *l;
|
|
Packit |
34410b |
uint16_t cccval;
|
|
Packit |
34410b |
guint h = handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_list_find_custom(channel->server->database,
|
|
Packit |
34410b |
GUINT_TO_POINTER(h), handle_cmp);
|
|
Packit |
34410b |
if (!l)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_REQ, handle,
|
|
Packit |
34410b |
ATT_ECODE_INVALID_HANDLE, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(&ccc_uuid, &a->uuid) == 0 &&
|
|
Packit |
34410b |
read_device_ccc(channel->device, handle, &cccval) == 0) {
|
|
Packit |
34410b |
uint8_t config[2];
|
|
Packit |
34410b |
|
|
Packit |
34410b |
put_le16(cccval, config);
|
|
Packit |
34410b |
return enc_read_resp(config, sizeof(config), pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
status = att_check_reqs(channel, ATT_OP_READ_REQ, a->read_req);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (status == 0x00 && a->read_cb)
|
|
Packit |
34410b |
status = a->read_cb(a, channel->device, a->cb_user_data);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (status)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_REQ, handle, status, pdu,
|
|
Packit |
34410b |
len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return enc_read_resp(a->data, a->len, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t read_blob(struct gatt_channel *channel, uint16_t handle,
|
|
Packit |
34410b |
uint16_t offset, uint8_t *pdu, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
uint8_t status;
|
|
Packit |
34410b |
GList *l;
|
|
Packit |
34410b |
uint16_t cccval;
|
|
Packit |
34410b |
guint h = handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_list_find_custom(channel->server->database,
|
|
Packit |
34410b |
GUINT_TO_POINTER(h), handle_cmp);
|
|
Packit |
34410b |
if (!l)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle,
|
|
Packit |
34410b |
ATT_ECODE_INVALID_HANDLE, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->len < offset)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle,
|
|
Packit |
34410b |
ATT_ECODE_INVALID_OFFSET, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(&ccc_uuid, &a->uuid) == 0 &&
|
|
Packit |
34410b |
read_device_ccc(channel->device, handle, &cccval) == 0) {
|
|
Packit |
34410b |
uint8_t config[2];
|
|
Packit |
34410b |
|
|
Packit |
34410b |
put_le16(cccval, config);
|
|
Packit |
34410b |
return enc_read_blob_resp(config, sizeof(config), offset,
|
|
Packit |
34410b |
pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
status = att_check_reqs(channel, ATT_OP_READ_BLOB_REQ, a->read_req);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (status == 0x00 && a->read_cb)
|
|
Packit |
34410b |
status = a->read_cb(a, channel->device, a->cb_user_data);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (status)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle, status,
|
|
Packit |
34410b |
pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return enc_read_blob_resp(a->data, a->len, offset, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t write_value(struct gatt_channel *channel, uint16_t handle,
|
|
Packit |
34410b |
const uint8_t *value, size_t vlen,
|
|
Packit |
34410b |
uint8_t *pdu, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
uint8_t status;
|
|
Packit |
34410b |
GList *l;
|
|
Packit |
34410b |
guint h = handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_list_find_custom(channel->server->database,
|
|
Packit |
34410b |
GUINT_TO_POINTER(h), handle_cmp);
|
|
Packit |
34410b |
if (!l)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_WRITE_REQ, handle,
|
|
Packit |
34410b |
ATT_ECODE_INVALID_HANDLE, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
status = att_check_reqs(channel, ATT_OP_WRITE_REQ, a->write_req);
|
|
Packit |
34410b |
if (status)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_WRITE_REQ, handle, status, pdu,
|
|
Packit |
34410b |
len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(&ccc_uuid, &a->uuid) != 0) {
|
|
Packit |
34410b |
|
|
Packit |
34410b |
attrib_db_update(channel->server->adapter, handle, NULL,
|
|
Packit |
34410b |
value, vlen, NULL);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->write_cb) {
|
|
Packit |
34410b |
status = a->write_cb(a, channel->device,
|
|
Packit |
34410b |
a->cb_user_data);
|
|
Packit |
34410b |
if (status)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_WRITE_REQ, handle,
|
|
Packit |
34410b |
status, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
} else {
|
|
Packit |
34410b |
uint16_t cccval = get_le16(value);
|
|
Packit |
34410b |
char *filename;
|
|
Packit |
34410b |
GKeyFile *key_file;
|
|
Packit |
34410b |
char group[6], value[5];
|
|
Packit |
34410b |
char *data;
|
|
Packit |
34410b |
gsize length = 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
filename = btd_device_get_storage_path(channel->device, "ccc");
|
|
Packit |
34410b |
if (!filename) {
|
|
Packit |
34410b |
warn("Unable to get ccc storage path for device");
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_WRITE_REQ, handle,
|
|
Packit |
34410b |
ATT_ECODE_WRITE_NOT_PERM,
|
|
Packit |
34410b |
pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
key_file = g_key_file_new();
|
|
Packit |
34410b |
g_key_file_load_from_file(key_file, filename, 0, NULL);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
sprintf(group, "%hu", handle);
|
|
Packit |
34410b |
sprintf(value, "%hX", cccval);
|
|
Packit |
34410b |
g_key_file_set_string(key_file, group, "Value", value);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
data = g_key_file_to_data(key_file, &length, NULL);
|
|
Packit |
34410b |
if (length > 0) {
|
|
Packit |
34410b |
create_file(filename, S_IRUSR | S_IWUSR);
|
|
Packit |
34410b |
g_file_set_contents(filename, data, length, NULL);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_free(data);
|
|
Packit |
34410b |
g_free(filename);
|
|
Packit |
34410b |
g_key_file_free(key_file);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return enc_write_resp(pdu);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t mtu_exchange(struct gatt_channel *channel, uint16_t mtu,
|
|
Packit |
34410b |
uint8_t *pdu, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
GError *gerr = NULL;
|
|
Packit |
34410b |
GIOChannel *io;
|
|
Packit |
34410b |
uint16_t imtu;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (mtu < ATT_DEFAULT_LE_MTU)
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_MTU_REQ, 0,
|
|
Packit |
34410b |
ATT_ECODE_REQ_NOT_SUPP, pdu, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
io = g_attrib_get_channel(channel->attrib);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
bt_io_get(io, &gerr, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_INVALID);
|
|
Packit |
34410b |
if (gerr) {
|
|
Packit |
34410b |
error("bt_io_get: %s", gerr->message);
|
|
Packit |
34410b |
g_error_free(gerr);
|
|
Packit |
34410b |
return enc_error_resp(ATT_OP_MTU_REQ, 0, ATT_ECODE_UNLIKELY,
|
|
Packit |
34410b |
pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
channel->mtu = MIN(mtu, imtu);
|
|
Packit |
34410b |
g_attrib_set_mtu(channel->attrib, channel->mtu);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return enc_mtu_resp(imtu, pdu, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static void channel_remove(struct gatt_channel *channel)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
channel->server->clients = g_slist_remove(channel->server->clients,
|
|
Packit |
34410b |
channel);
|
|
Packit |
34410b |
channel_free(channel);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static gboolean channel_watch_cb(GIOChannel *io, GIOCondition cond,
|
|
Packit |
34410b |
gpointer user_data)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
channel_remove(user_data);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return FALSE;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static void channel_handler(const uint8_t *ipdu, uint16_t len,
|
|
Packit |
34410b |
gpointer user_data)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_channel *channel = user_data;
|
|
Packit |
34410b |
uint8_t opdu[channel->mtu];
|
|
Packit |
34410b |
uint16_t length, start, end, mtu, offset;
|
|
Packit |
34410b |
bt_uuid_t uuid;
|
|
Packit |
34410b |
uint8_t status = 0;
|
|
Packit |
34410b |
size_t vlen;
|
|
Packit |
34410b |
uint8_t *value = g_attrib_get_buffer(channel->attrib, &vlen);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
DBG("op 0x%02x", ipdu[0]);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (len > vlen) {
|
|
Packit |
34410b |
error("Too much data on ATT socket");
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
switch (ipdu[0]) {
|
|
Packit |
34410b |
case ATT_OP_READ_BY_GROUP_REQ:
|
|
Packit |
34410b |
length = dec_read_by_grp_req(ipdu, len, &start, &end, &uuid);
|
|
Packit |
34410b |
if (length == 0) {
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = read_by_group(channel, start, end, &uuid, opdu,
|
|
Packit |
34410b |
channel->mtu);
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_READ_BY_TYPE_REQ:
|
|
Packit |
34410b |
length = dec_read_by_type_req(ipdu, len, &start, &end, &uuid);
|
|
Packit |
34410b |
if (length == 0) {
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = read_by_type(channel, start, end, &uuid, opdu,
|
|
Packit |
34410b |
channel->mtu);
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_READ_REQ:
|
|
Packit |
34410b |
length = dec_read_req(ipdu, len, &start;;
|
|
Packit |
34410b |
if (length == 0) {
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = read_value(channel, start, opdu, channel->mtu);
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_READ_BLOB_REQ:
|
|
Packit |
34410b |
length = dec_read_blob_req(ipdu, len, &start, &offset);
|
|
Packit |
34410b |
if (length == 0) {
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = read_blob(channel, start, offset, opdu, channel->mtu);
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_MTU_REQ:
|
|
Packit |
34410b |
if (!channel->le) {
|
|
Packit |
34410b |
status = ATT_ECODE_REQ_NOT_SUPP;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = dec_mtu_req(ipdu, len, &mtu);
|
|
Packit |
34410b |
if (length == 0) {
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = mtu_exchange(channel, mtu, opdu, channel->mtu);
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_FIND_INFO_REQ:
|
|
Packit |
34410b |
length = dec_find_info_req(ipdu, len, &start, &end;;
|
|
Packit |
34410b |
if (length == 0) {
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = find_info(channel, start, end, opdu, channel->mtu);
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_WRITE_REQ:
|
|
Packit |
34410b |
length = dec_write_req(ipdu, len, &start, value, &vlen);
|
|
Packit |
34410b |
if (length == 0) {
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = write_value(channel, start, value, vlen, opdu,
|
|
Packit |
34410b |
channel->mtu);
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_WRITE_CMD:
|
|
Packit |
34410b |
length = dec_write_cmd(ipdu, len, &start, value, &vlen);
|
|
Packit |
34410b |
if (length > 0)
|
|
Packit |
34410b |
write_value(channel, start, value, vlen, opdu,
|
|
Packit |
34410b |
channel->mtu);
|
|
Packit |
34410b |
return;
|
|
Packit |
34410b |
case ATT_OP_FIND_BY_TYPE_REQ:
|
|
Packit |
34410b |
length = dec_find_by_type_req(ipdu, len, &start, &end,
|
|
Packit |
34410b |
&uuid, value, &vlen);
|
|
Packit |
34410b |
if (length == 0) {
|
|
Packit |
34410b |
status = ATT_ECODE_INVALID_PDU;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
length = find_by_type(channel, start, end, &uuid, value, vlen,
|
|
Packit |
34410b |
opdu, channel->mtu);
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case ATT_OP_HANDLE_CNF:
|
|
Packit |
34410b |
return;
|
|
Packit |
34410b |
case ATT_OP_HANDLE_IND:
|
|
Packit |
34410b |
case ATT_OP_HANDLE_NOTIFY:
|
|
Packit |
34410b |
/* The attribute client is already handling these */
|
|
Packit |
34410b |
return;
|
|
Packit |
34410b |
case ATT_OP_READ_MULTI_REQ:
|
|
Packit |
34410b |
case ATT_OP_PREP_WRITE_REQ:
|
|
Packit |
34410b |
case ATT_OP_EXEC_WRITE_REQ:
|
|
Packit |
34410b |
default:
|
|
Packit |
34410b |
DBG("Unsupported request 0x%02x", ipdu[0]);
|
|
Packit |
34410b |
status = ATT_ECODE_REQ_NOT_SUPP;
|
|
Packit |
34410b |
goto done;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (length == 0)
|
|
Packit |
34410b |
status = ATT_ECODE_IO;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
done:
|
|
Packit |
34410b |
if (status)
|
|
Packit |
34410b |
length = enc_error_resp(ipdu[0], 0x0000, status, opdu,
|
|
Packit |
34410b |
channel->mtu);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_attrib_send(channel->attrib, 0, opdu, length, NULL, NULL, NULL);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
GAttrib *attrib_from_device(struct btd_device *device)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct btd_adapter *adapter = device_get_adapter(device);
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (!l)
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (l = server->clients; l; l = l->next) {
|
|
Packit |
34410b |
struct gatt_channel *channel = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (channel->device == device)
|
|
Packit |
34410b |
return g_attrib_ref(channel->attrib);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
guint attrib_channel_attach(GAttrib *attrib)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
struct btd_device *device;
|
|
Packit |
34410b |
struct gatt_channel *channel;
|
|
Packit |
34410b |
bdaddr_t src, dst;
|
|
Packit |
34410b |
GIOChannel *io;
|
|
Packit |
34410b |
GError *gerr = NULL;
|
|
Packit |
34410b |
uint8_t bdaddr_type;
|
|
Packit |
34410b |
uint16_t cid;
|
|
Packit |
34410b |
guint mtu = 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
io = g_attrib_get_channel(attrib);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
bt_io_get(io, &gerr,
|
|
Packit |
34410b |
BT_IO_OPT_SOURCE_BDADDR, &src,
|
|
Packit |
34410b |
BT_IO_OPT_DEST_BDADDR, &dst,
|
|
Packit |
34410b |
BT_IO_OPT_DEST_TYPE, &bdaddr_type,
|
|
Packit |
34410b |
BT_IO_OPT_CID, &cid,
|
|
Packit |
34410b |
BT_IO_OPT_IMTU, &mtu,
|
|
Packit |
34410b |
BT_IO_OPT_INVALID);
|
|
Packit |
34410b |
if (gerr) {
|
|
Packit |
34410b |
error("bt_io_get: %s", gerr->message);
|
|
Packit |
34410b |
g_error_free(gerr);
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = find_gatt_server(&src;;
|
|
Packit |
34410b |
if (server == NULL)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
channel = g_new0(struct gatt_channel, 1);
|
|
Packit |
34410b |
channel->server = server;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
device = btd_adapter_find_device(server->adapter, &dst, bdaddr_type);
|
|
Packit |
34410b |
if (device == NULL) {
|
|
Packit |
34410b |
error("Device object not found for attrib server");
|
|
Packit |
34410b |
g_free(channel);
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (!device_is_bonded(device, bdaddr_type)) {
|
|
Packit |
34410b |
char *filename;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
filename = btd_device_get_storage_path(device, "ccc");
|
|
Packit |
34410b |
if (filename) {
|
|
Packit |
34410b |
unlink(filename);
|
|
Packit |
34410b |
g_free(filename);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (cid != ATT_CID) {
|
|
Packit |
34410b |
channel->le = FALSE;
|
|
Packit |
34410b |
channel->mtu = mtu;
|
|
Packit |
34410b |
} else {
|
|
Packit |
34410b |
channel->le = TRUE;
|
|
Packit |
34410b |
channel->mtu = ATT_DEFAULT_LE_MTU;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
channel->attrib = g_attrib_ref(attrib);
|
|
Packit |
34410b |
channel->id = g_attrib_register(channel->attrib, GATTRIB_ALL_REQS,
|
|
Packit |
34410b |
GATTRIB_ALL_HANDLES, channel_handler, channel, NULL);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
channel->cleanup_id = g_io_add_watch(io, G_IO_HUP, channel_watch_cb,
|
|
Packit |
34410b |
channel);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
channel->device = btd_device_ref(device);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server->clients = g_slist_append(server->clients, channel);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return channel->id;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static struct gatt_channel *find_channel(guint id)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (l = servers; l; l = g_slist_next(l)) {
|
|
Packit |
34410b |
struct gatt_server *server = l->data;
|
|
Packit |
34410b |
GSList *c;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (c = server->clients; c; c = g_slist_next(c)) {
|
|
Packit |
34410b |
struct gatt_channel *channel = c->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (channel->id == id)
|
|
Packit |
34410b |
return channel;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
gboolean attrib_channel_detach(GAttrib *attrib, guint id)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_channel *channel;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
channel = find_channel(id);
|
|
Packit |
34410b |
if (channel == NULL)
|
|
Packit |
34410b |
return FALSE;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
g_attrib_unregister(channel->attrib, channel->id);
|
|
Packit |
34410b |
channel_remove(channel);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return TRUE;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static void connect_event(GIOChannel *io, GError *gerr, void *user_data)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct btd_adapter *adapter;
|
|
Packit |
34410b |
struct btd_device *device;
|
|
Packit |
34410b |
uint8_t dst_type;
|
|
Packit |
34410b |
bdaddr_t src, dst;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
DBG("");
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (gerr) {
|
|
Packit |
34410b |
error("%s", gerr->message);
|
|
Packit |
34410b |
return;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
bt_io_get(io, &gerr,
|
|
Packit |
34410b |
BT_IO_OPT_SOURCE_BDADDR, &src,
|
|
Packit |
34410b |
BT_IO_OPT_DEST_BDADDR, &dst,
|
|
Packit |
34410b |
BT_IO_OPT_DEST_TYPE, &dst_type,
|
|
Packit |
34410b |
BT_IO_OPT_INVALID);
|
|
Packit |
34410b |
if (gerr) {
|
|
Packit |
34410b |
error("bt_io_get: %s", gerr->message);
|
|
Packit |
34410b |
g_error_free(gerr);
|
|
Packit |
34410b |
return;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
adapter = adapter_find(&src;;
|
|
Packit |
34410b |
if (!adapter)
|
|
Packit |
34410b |
return;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
device = btd_adapter_get_device(adapter, &dst, dst_type);
|
|
Packit |
34410b |
if (!device)
|
|
Packit |
34410b |
return;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
device_attach_att(device, io);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static gboolean register_core_services(struct gatt_server *server)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
uint8_t atval[256];
|
|
Packit |
34410b |
bt_uuid_t uuid;
|
|
Packit |
34410b |
uint16_t appearance = 0x0000;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* GAP service: primary service definition */
|
|
Packit |
34410b |
bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
|
|
Packit |
34410b |
put_le16(GENERIC_ACCESS_PROFILE_ID, &atval[0]);
|
|
Packit |
34410b |
attrib_db_add_new(server, 0x0001, &uuid, ATT_NONE, ATT_NOT_PERMITTED,
|
|
Packit |
34410b |
atval, 2);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* GAP service: device name characteristic */
|
|
Packit |
34410b |
server->name_handle = 0x0006;
|
|
Packit |
34410b |
bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
|
|
Packit |
34410b |
atval[0] = GATT_CHR_PROP_READ;
|
|
Packit |
34410b |
put_le16(server->name_handle, &atval[1]);
|
|
Packit |
34410b |
put_le16(GATT_CHARAC_DEVICE_NAME, &atval[3]);
|
|
Packit |
34410b |
attrib_db_add_new(server, 0x0004, &uuid, ATT_NONE, ATT_NOT_PERMITTED,
|
|
Packit |
34410b |
atval, 5);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* GAP service: device name attribute */
|
|
Packit |
34410b |
bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
|
|
Packit |
34410b |
attrib_db_add_new(server, server->name_handle, &uuid, ATT_NONE,
|
|
Packit |
34410b |
ATT_NOT_PERMITTED, NULL, 0);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* GAP service: device appearance characteristic */
|
|
Packit |
34410b |
server->appearance_handle = 0x0008;
|
|
Packit |
34410b |
bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
|
|
Packit |
34410b |
atval[0] = GATT_CHR_PROP_READ;
|
|
Packit |
34410b |
put_le16(server->appearance_handle, &atval[1]);
|
|
Packit |
34410b |
put_le16(GATT_CHARAC_APPEARANCE, &atval[3]);
|
|
Packit |
34410b |
attrib_db_add_new(server, 0x0007, &uuid, ATT_NONE, ATT_NOT_PERMITTED,
|
|
Packit |
34410b |
atval, 5);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* GAP service: device appearance attribute */
|
|
Packit |
34410b |
bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
|
|
Packit |
34410b |
put_le16(appearance, &atval[0]);
|
|
Packit |
34410b |
attrib_db_add_new(server, server->appearance_handle, &uuid, ATT_NONE,
|
|
Packit |
34410b |
ATT_NOT_PERMITTED, atval, 2);
|
|
Packit |
34410b |
server->gap_sdp_handle = attrib_create_sdp_new(server, 0x0001,
|
|
Packit |
34410b |
"Generic Access Profile");
|
|
Packit |
34410b |
if (server->gap_sdp_handle == 0) {
|
|
Packit |
34410b |
error("Failed to register GAP service record");
|
|
Packit |
34410b |
return FALSE;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* GATT service: primary service definition */
|
|
Packit |
34410b |
bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
|
|
Packit |
34410b |
put_le16(GENERIC_ATTRIB_PROFILE_ID, &atval[0]);
|
|
Packit |
34410b |
attrib_db_add_new(server, 0x0010, &uuid, ATT_NONE, ATT_NOT_PERMITTED,
|
|
Packit |
34410b |
atval, 2);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server->gatt_sdp_handle = attrib_create_sdp_new(server, 0x0010,
|
|
Packit |
34410b |
"Generic Attribute Profile");
|
|
Packit |
34410b |
if (server->gatt_sdp_handle == 0) {
|
|
Packit |
34410b |
error("Failed to register GATT service record");
|
|
Packit |
34410b |
return FALSE;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return TRUE;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
int btd_adapter_gatt_server_start(struct btd_adapter *adapter)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
GError *gerr = NULL;
|
|
Packit |
34410b |
const bdaddr_t *addr;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
DBG("Start GATT server in hci%d", btd_adapter_get_index(adapter));
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = g_new0(struct gatt_server, 1);
|
|
Packit |
34410b |
server->adapter = btd_adapter_ref(adapter);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
addr = btd_adapter_get_address(server->adapter);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* BR/EDR socket */
|
|
Packit |
34410b |
server->l2cap_io = bt_io_listen(connect_event, NULL, NULL, NULL, &gerr,
|
|
Packit |
34410b |
BT_IO_OPT_SOURCE_BDADDR, addr,
|
|
Packit |
34410b |
BT_IO_OPT_PSM, ATT_PSM,
|
|
Packit |
34410b |
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
|
|
Packit |
34410b |
BT_IO_OPT_INVALID);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (server->l2cap_io == NULL) {
|
|
Packit |
34410b |
error("%s", gerr->message);
|
|
Packit |
34410b |
g_error_free(gerr);
|
|
Packit |
34410b |
gatt_server_free(server);
|
|
Packit |
34410b |
return -1;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (!register_core_services(server)) {
|
|
Packit |
34410b |
gatt_server_free(server);
|
|
Packit |
34410b |
return -1;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* LE socket */
|
|
Packit |
34410b |
server->le_io = bt_io_listen(connect_event, NULL,
|
|
Packit |
34410b |
&server->le_io, NULL, &gerr,
|
|
Packit |
34410b |
BT_IO_OPT_SOURCE_BDADDR, addr,
|
|
Packit |
34410b |
BT_IO_OPT_SOURCE_TYPE,
|
|
Packit |
34410b |
btd_adapter_get_address_type(adapter),
|
|
Packit |
34410b |
BT_IO_OPT_CID, ATT_CID,
|
|
Packit |
34410b |
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
|
|
Packit |
34410b |
BT_IO_OPT_INVALID);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (server->le_io == NULL) {
|
|
Packit |
34410b |
error("%s", gerr->message);
|
|
Packit |
34410b |
g_error_free(gerr);
|
|
Packit |
34410b |
/* Doesn't have LE support, continue */
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
servers = g_slist_prepend(servers, server);
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
void btd_adapter_gatt_server_stop(struct btd_adapter *adapter)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (l == NULL)
|
|
Packit |
34410b |
return;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
DBG("Stop GATT server in hci%d", btd_adapter_get_index(adapter));
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = l->data;
|
|
Packit |
34410b |
servers = g_slist_remove(servers, server);
|
|
Packit |
34410b |
gatt_server_free(server);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
uint32_t attrib_create_sdp(struct btd_adapter *adapter, uint16_t handle,
|
|
Packit |
34410b |
const char *name)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (l == NULL)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return attrib_create_sdp_new(l->data, handle, name);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
void attrib_free_sdp(struct btd_adapter *adapter, uint32_t sdp_handle)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
adapter_service_remove(adapter, sdp_handle);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t find_uuid16_avail(struct btd_adapter *adapter, uint16_t nitems)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
uint16_t handle;
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
GList *dl;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (l == NULL)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = l->data;
|
|
Packit |
34410b |
if (server->database == NULL)
|
|
Packit |
34410b |
return 0x0001;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (dl = server->database, handle = 0x0001; dl; dl = dl->next) {
|
|
Packit |
34410b |
struct attribute *a = dl->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if ((bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 ||
|
|
Packit |
34410b |
bt_uuid_cmp(&a->uuid, &snd_uuid) == 0) &&
|
|
Packit |
34410b |
a->handle - handle >= nitems)
|
|
Packit |
34410b |
/* Note: the range above excludes the current handle */
|
|
Packit |
34410b |
return handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->len == 16 && (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 ||
|
|
Packit |
34410b |
bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)) {
|
|
Packit |
34410b |
/* 128 bit UUID service definition */
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle == 0xffff)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
handle = a->handle + 1;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (0xffff - handle + 1 >= nitems)
|
|
Packit |
34410b |
return handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
static uint16_t find_uuid128_avail(struct btd_adapter *adapter, uint16_t nitems)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
uint16_t handle = 0, end = 0xffff;
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
GList *dl;
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (l == NULL)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = l->data;
|
|
Packit |
34410b |
if (server->database == NULL)
|
|
Packit |
34410b |
return 0xffff - nitems + 1;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
for (dl = g_list_last(server->database); dl; dl = dl->prev) {
|
|
Packit |
34410b |
struct attribute *a = dl->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (handle == 0)
|
|
Packit |
34410b |
handle = a->handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (bt_uuid_cmp(&a->uuid, &prim_uuid) != 0 &&
|
|
Packit |
34410b |
bt_uuid_cmp(&a->uuid, &snd_uuid) != 0)
|
|
Packit |
34410b |
continue;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (end - handle >= nitems)
|
|
Packit |
34410b |
return end - nitems + 1;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->len == 2) {
|
|
Packit |
34410b |
/* 16 bit UUID service definition */
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (a->handle == 0x0001)
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
end = a->handle - 1;
|
|
Packit |
34410b |
handle = 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (end - 0x0001 >= nitems)
|
|
Packit |
34410b |
return end - nitems + 1;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
uint16_t attrib_db_find_avail(struct btd_adapter *adapter, bt_uuid_t *svc_uuid,
|
|
Packit |
34410b |
uint16_t nitems)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
btd_assert(nitems > 0);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (svc_uuid->type == BT_UUID16)
|
|
Packit |
34410b |
return find_uuid16_avail(adapter, nitems);
|
|
Packit |
34410b |
else if (svc_uuid->type == BT_UUID128)
|
|
Packit |
34410b |
return find_uuid128_avail(adapter, nitems);
|
|
Packit |
34410b |
else {
|
|
Packit |
34410b |
char uuidstr[MAX_LEN_UUID_STR];
|
|
Packit |
34410b |
|
|
Packit |
34410b |
bt_uuid_to_string(svc_uuid, uuidstr, MAX_LEN_UUID_STR);
|
|
Packit |
34410b |
error("Service uuid: %s is neither a 16-bit nor a 128-bit uuid",
|
|
Packit |
34410b |
uuidstr);
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
struct attribute *attrib_db_add(struct btd_adapter *adapter, uint16_t handle,
|
|
Packit |
34410b |
bt_uuid_t *uuid, int read_req,
|
|
Packit |
34410b |
int write_req, const uint8_t *value,
|
|
Packit |
34410b |
size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (l == NULL)
|
|
Packit |
34410b |
return NULL;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return attrib_db_add_new(l->data, handle, uuid, read_req, write_req,
|
|
Packit |
34410b |
value, len);
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
int attrib_db_update(struct btd_adapter *adapter, uint16_t handle,
|
|
Packit |
34410b |
bt_uuid_t *uuid, const uint8_t *value,
|
|
Packit |
34410b |
size_t len, struct attribute **attr)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
GList *dl;
|
|
Packit |
34410b |
guint h = handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (l == NULL)
|
|
Packit |
34410b |
return -ENOENT;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
DBG("handle=0x%04x", handle);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
dl = g_list_find_custom(server->database, GUINT_TO_POINTER(h),
|
|
Packit |
34410b |
handle_cmp);
|
|
Packit |
34410b |
if (dl == NULL)
|
|
Packit |
34410b |
return -ENOENT;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = dl->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a->data = g_try_realloc(a->data, len);
|
|
Packit |
34410b |
if (len && a->data == NULL)
|
|
Packit |
34410b |
return -ENOMEM;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a->len = len;
|
|
Packit |
34410b |
memcpy(a->data, value, len);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (uuid != NULL)
|
|
Packit |
34410b |
a->uuid = *uuid;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
if (attr)
|
|
Packit |
34410b |
*attr = a;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
int attrib_db_del(struct btd_adapter *adapter, uint16_t handle)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
struct attribute *a;
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
GList *dl;
|
|
Packit |
34410b |
guint h = handle;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (l == NULL)
|
|
Packit |
34410b |
return -ENOENT;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
DBG("handle=0x%04x", handle);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
dl = g_list_find_custom(server->database, GUINT_TO_POINTER(h),
|
|
Packit |
34410b |
handle_cmp);
|
|
Packit |
34410b |
if (dl == NULL)
|
|
Packit |
34410b |
return -ENOENT;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
a = dl->data;
|
|
Packit |
34410b |
server->database = g_list_remove(server->database, a);
|
|
Packit |
34410b |
g_free(a->data);
|
|
Packit |
34410b |
g_free(a);
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return 0;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
int attrib_gap_set(struct btd_adapter *adapter, uint16_t uuid,
|
|
Packit |
34410b |
const uint8_t *value, size_t len)
|
|
Packit |
34410b |
{
|
|
Packit |
34410b |
struct gatt_server *server;
|
|
Packit |
34410b |
uint16_t handle;
|
|
Packit |
34410b |
GSList *l;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
l = g_slist_find_custom(servers, adapter, adapter_cmp);
|
|
Packit |
34410b |
if (l == NULL)
|
|
Packit |
34410b |
return -ENOENT;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
server = l->data;
|
|
Packit |
34410b |
|
|
Packit |
34410b |
/* FIXME: Missing Privacy and Reconnection Address */
|
|
Packit |
34410b |
|
|
Packit |
34410b |
switch (uuid) {
|
|
Packit |
34410b |
case GATT_CHARAC_DEVICE_NAME:
|
|
Packit |
34410b |
handle = server->name_handle;
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
case GATT_CHARAC_APPEARANCE:
|
|
Packit |
34410b |
handle = server->appearance_handle;
|
|
Packit |
34410b |
break;
|
|
Packit |
34410b |
default:
|
|
Packit |
34410b |
return -ENOSYS;
|
|
Packit |
34410b |
}
|
|
Packit |
34410b |
|
|
Packit |
34410b |
return attrib_db_update(adapter, handle, NULL, value, len, NULL);
|
|
Packit |
34410b |
}
|