core: Introduce btd_gatt_database

This patch introduces src/gatt-database.* which handles incoming ATT
connections, manages per-adapter shared/gatt-db instances, and routes
connections to the corresponding device object. This is the layer that
will perform all the CCC management and Service Changed handling.
This commit is contained in:
Arman Uguray 2015-02-11 19:17:33 -08:00 committed by Luiz Augusto von Dentz
parent 66b407e1f2
commit d330725947
4 changed files with 709 additions and 0 deletions

View File

@ -167,6 +167,7 @@ src_bluetoothd_SOURCES = $(builtin_sources) \
src/sdpd-server.c src/sdpd-request.c \
src/sdpd-service.c src/sdpd-database.c \
src/attrib-server.h src/attrib-server.c \
src/gatt-database.h src/gatt-database.c \
src/sdp-xml.h src/sdp-xml.c \
src/sdp-client.h src/sdp-client.c \
src/textfile.h src/textfile.c \

View File

@ -302,6 +302,7 @@ static void dev_class_changed_callback(uint16_t index, uint16_t length,
appearance[1] = rp->val[1] & 0x1f; /* removes service class */
appearance[2] = rp->val[2];
/* TODO: Do this through btd_gatt_database instead */
attrib_gap_set(adapter, GATT_CHARAC_APPEARANCE, appearance, 2);
}
@ -4014,6 +4015,7 @@ static void convert_sdp_entry(char *key, char *value, void *user_data)
if (record_has_uuid(rec, att_uuid))
goto failed;
/* TODO: Do this through btd_gatt_database */
if (!gatt_parse_record(rec, &uuid, &psm, &start, &end))
goto failed;
@ -6590,6 +6592,7 @@ static int adapter_register(struct btd_adapter *adapter)
agent_unref(agent);
}
/* TODO: Migrate to use btd_gatt_database */
btd_adapter_gatt_server_start(adapter);
load_config(adapter);

680
src/gatt-database.c Normal file
View File

@ -0,0 +1,680 @@
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2015 Google Inc.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdint.h>
#include <stdlib.h>
#include "lib/uuid.h"
#include "btio/btio.h"
#include "src/shared/util.h"
#include "src/shared/queue.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "src/shared/gatt-server.h"
#include "log.h"
#include "adapter.h"
#include "device.h"
#include "gatt-database.h"
#ifndef ATT_CID
#define ATT_CID 4
#endif
#define UUID_GAP 0x1800
#define UUID_GATT 0x1801
struct btd_gatt_database {
struct btd_adapter *adapter;
struct gatt_db *db;
unsigned int db_id;
GIOChannel *le_io;
struct queue *device_states;
struct gatt_db_attribute *svc_chngd;
struct gatt_db_attribute *svc_chngd_ccc;
};
struct device_state {
bdaddr_t bdaddr;
uint8_t bdaddr_type;
struct queue *ccc_states;
};
struct ccc_state {
uint16_t handle;
uint8_t value[2];
};
struct device_info {
bdaddr_t bdaddr;
uint8_t bdaddr_type;
};
static bool dev_state_match(const void *a, const void *b)
{
const struct device_state *dev_state = a;
const struct device_info *dev_info = b;
return bacmp(&dev_state->bdaddr, &dev_info->bdaddr) == 0 &&
dev_state->bdaddr_type == dev_info->bdaddr_type;
}
static struct device_state *
find_device_state(struct btd_gatt_database *database, bdaddr_t *bdaddr,
uint8_t bdaddr_type)
{
struct device_info dev_info;
memset(&dev_info, 0, sizeof(dev_info));
bacpy(&dev_info.bdaddr, bdaddr);
dev_info.bdaddr_type = bdaddr_type;
return queue_find(database->device_states, dev_state_match, &dev_info);
}
static bool ccc_state_match(const void *a, const void *b)
{
const struct ccc_state *ccc = a;
uint16_t handle = PTR_TO_UINT(b);
return ccc->handle == handle;
}
static struct ccc_state *find_ccc_state(struct device_state *dev_state,
uint16_t handle)
{
return queue_find(dev_state->ccc_states, ccc_state_match,
UINT_TO_PTR(handle));
}
static struct device_state *device_state_create(bdaddr_t *bdaddr,
uint8_t bdaddr_type)
{
struct device_state *dev_state;
dev_state = new0(struct device_state, 1);
if (!dev_state)
return NULL;
dev_state->ccc_states = queue_new();
if (!dev_state->ccc_states) {
free(dev_state);
return NULL;
}
bacpy(&dev_state->bdaddr, bdaddr);
dev_state->bdaddr_type = bdaddr_type;
return dev_state;
}
static struct device_state *get_device_state(struct btd_gatt_database *database,
bdaddr_t *bdaddr,
uint8_t bdaddr_type)
{
struct device_state *dev_state;
/*
* Find and return a device state. If a matching state doesn't exist,
* then create a new one.
*/
dev_state = find_device_state(database, bdaddr, bdaddr_type);
if (dev_state)
return dev_state;
dev_state = device_state_create(bdaddr, bdaddr_type);
if (!dev_state)
return NULL;
queue_push_tail(database->device_states, dev_state);
return dev_state;
}
static struct ccc_state *get_ccc_state(struct btd_gatt_database *database,
bdaddr_t *bdaddr,
uint8_t bdaddr_type,
uint16_t handle)
{
struct device_state *dev_state;
struct ccc_state *ccc;
dev_state = get_device_state(database, bdaddr, bdaddr_type);
if (!dev_state)
return NULL;
ccc = find_ccc_state(dev_state, handle);
if (ccc)
return ccc;
ccc = new0(struct ccc_state, 1);
if (!ccc)
return NULL;
ccc->handle = handle;
queue_push_tail(dev_state->ccc_states, ccc);
return ccc;
}
static void device_state_free(void *data)
{
struct device_state *state = data;
queue_destroy(state->ccc_states, free);
free(state);
}
static void gatt_database_free(void *data)
{
struct btd_gatt_database *database = data;
if (database->le_io) {
g_io_channel_shutdown(database->le_io, FALSE, NULL);
g_io_channel_unref(database->le_io);
}
/* TODO: Persistently store CCC states before freeing them */
queue_destroy(database->device_states, device_state_free);
gatt_db_unregister(database->db, database->db_id);
gatt_db_unref(database->db);
btd_adapter_unref(database->adapter);
free(database);
}
static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
{
struct btd_adapter *adapter;
struct btd_device *device;
uint8_t dst_type;
bdaddr_t src, dst;
DBG("New incoming LE ATT connection");
if (gerr) {
error("%s", gerr->message);
return;
}
bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src,
BT_IO_OPT_DEST_BDADDR, &dst,
BT_IO_OPT_DEST_TYPE, &dst_type,
BT_IO_OPT_INVALID);
if (gerr) {
error("bt_io_get: %s", gerr->message);
g_error_free(gerr);
return;
}
adapter = adapter_find(&src);
if (!adapter)
return;
device = btd_adapter_get_device(adapter, &dst, dst_type);
if (!device)
return;
/* TODO: create bt_gatt_server instance */
device_attach_att(device, io);
}
static void gap_device_name_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct btd_gatt_database *database = user_data;
uint8_t error = 0;
size_t len = 0;
const uint8_t *value = NULL;
const char *device_name;
DBG("GAP Device Name read request\n");
device_name = btd_adapter_get_name(database->adapter);
len = strlen(device_name);
if (offset > len) {
error = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
len -= offset;
value = len ? (const uint8_t *) &device_name[offset] : NULL;
done:
gatt_db_attribute_read_result(attrib, id, error, value, len);
}
static void gap_appearance_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct btd_gatt_database *database = user_data;
uint8_t error = 0;
size_t len = 2;
const uint8_t *value = NULL;
uint8_t appearance[2];
uint32_t dev_class;
DBG("GAP Appearance read request\n");
dev_class = btd_adapter_get_class(database->adapter);
if (offset > 2) {
error = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
appearance[0] = dev_class & 0x00ff;
appearance[1] = (dev_class >> 8) & 0x001f;
len -= offset;
value = len ? &appearance[offset] : NULL;
done:
gatt_db_attribute_read_result(attrib, id, error, value, len);
}
static void populate_gap_service(struct btd_gatt_database *database)
{
bt_uuid_t uuid;
struct gatt_db_attribute *service;
/* Add the GAP service */
bt_uuid16_create(&uuid, UUID_GAP);
service = gatt_db_add_service(database->db, &uuid, true, 5);
/*
* Device Name characteristic.
*/
bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ,
gap_device_name_read_cb,
NULL, database);
/*
* Device Appearance characteristic.
*/
bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ,
gap_appearance_read_cb,
NULL, database);
gatt_db_service_set_active(service, true);
}
static bool get_dst_info(struct bt_att *att, bdaddr_t *dst, uint8_t *dst_type)
{
GIOChannel *io = NULL;
GError *gerr = NULL;
io = g_io_channel_unix_new(bt_att_get_fd(att));
if (!io)
return false;
bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst,
BT_IO_OPT_DEST_TYPE, dst_type,
BT_IO_OPT_INVALID);
if (gerr) {
error("gatt: bt_io_get: %s", gerr->message);
g_error_free(gerr);
g_io_channel_unref(io);
return false;
}
g_io_channel_unref(io);
return true;
}
static void gatt_ccc_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct btd_gatt_database *database = user_data;
struct ccc_state *ccc;
uint16_t handle;
uint8_t ecode = 0;
const uint8_t *value = NULL;
size_t len = 0;
bdaddr_t bdaddr;
uint8_t bdaddr_type;
handle = gatt_db_attribute_get_handle(attrib);
DBG("CCC read called for handle: 0x%04x", handle);
if (offset > 2) {
ecode = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
if (!get_dst_info(att, &bdaddr, &bdaddr_type)) {
ecode = BT_ATT_ERROR_UNLIKELY;
goto done;
}
ccc = get_ccc_state(database, &bdaddr, bdaddr_type, handle);
if (!ccc) {
ecode = BT_ATT_ERROR_UNLIKELY;
goto done;
}
len -= offset;
value = len ? &ccc->value[offset] : NULL;
done:
gatt_db_attribute_read_result(attrib, id, ecode, value, len);
}
static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct btd_gatt_database *database = user_data;
struct ccc_state *ccc;
uint16_t handle;
uint8_t ecode = 0;
bdaddr_t bdaddr;
uint8_t bdaddr_type;
handle = gatt_db_attribute_get_handle(attrib);
DBG("CCC read called for handle: 0x%04x", handle);
if (!value || len != 2) {
ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
goto done;
}
if (offset > 2) {
ecode = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
if (!get_dst_info(att, &bdaddr, &bdaddr_type)) {
ecode = BT_ATT_ERROR_UNLIKELY;
goto done;
}
ccc = get_ccc_state(database, &bdaddr, bdaddr_type, handle);
if (!ccc) {
ecode = BT_ATT_ERROR_UNLIKELY;
goto done;
}
/*
* TODO: Perform this after checking with a callback to the upper
* layer.
*/
ccc->value[0] = value[0];
ccc->value[1] = value[1];
done:
gatt_db_attribute_write_result(attrib, id, ecode);
}
static struct gatt_db_attribute *
gatt_database_add_ccc(struct btd_gatt_database *database,
uint16_t service_handle)
{
struct gatt_db_attribute *service;
bt_uuid_t uuid;
if (!database || !service_handle)
return NULL;
service = gatt_db_get_attribute(database->db, service_handle);
if (!service) {
error("No service exists with handle: 0x%04x", service_handle);
return NULL;
}
bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
return gatt_db_service_add_descriptor(service, &uuid,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
gatt_ccc_read_cb, gatt_ccc_write_cb, database);
}
static void populate_gatt_service(struct btd_gatt_database *database)
{
bt_uuid_t uuid;
struct gatt_db_attribute *service;
uint16_t start_handle;
/* Add the GATT service */
bt_uuid16_create(&uuid, UUID_GATT);
service = gatt_db_add_service(database->db, &uuid, true, 4);
gatt_db_attribute_get_service_handles(service, &start_handle, NULL);
bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED);
database->svc_chngd = gatt_db_service_add_characteristic(service, &uuid,
BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_INDICATE,
NULL, NULL, database);
database->svc_chngd_ccc = gatt_database_add_ccc(database, start_handle);
gatt_db_service_set_active(service, true);
}
static void register_core_services(struct btd_gatt_database *database)
{
populate_gap_service(database);
populate_gatt_service(database);
}
struct not_data {
struct btd_gatt_database *database;
uint16_t handle, ccc_handle;
const uint8_t *value;
uint16_t len;
bool indicate;
};
static void send_notification_to_device(void *data, void *user_data)
{
struct device_state *device_state = data;
struct not_data *not_data = user_data;
struct ccc_state *ccc;
struct btd_device *device;
ccc = find_ccc_state(device_state, not_data->ccc_handle);
if (!ccc)
return;
if (!ccc->value[0] || (not_data->indicate && !(ccc->value[0] & 0x02)))
return;
device = btd_adapter_get_device(not_data->database->adapter,
&device_state->bdaddr,
device_state->bdaddr_type);
if (!device)
return;
/*
* TODO: Notify device via bt_gatt_server
*/
}
static void send_notification_to_devices(struct btd_gatt_database *database,
uint16_t handle, const uint8_t *value,
uint16_t len, uint16_t ccc_handle,
bool indicate)
{
struct not_data not_data;
memset(&not_data, 0, sizeof(not_data));
not_data.database = database;
not_data.handle = handle;
not_data.ccc_handle = ccc_handle;
not_data.value = value;
not_data.len = len;
not_data.indicate = indicate;
queue_foreach(database->device_states, send_notification_to_device,
&not_data);
}
static void send_service_changed(struct btd_gatt_database *database,
struct gatt_db_attribute *attrib)
{
uint16_t start, end;
uint8_t value[4];
uint16_t handle, ccc_handle;
if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) {
error("Failed to obtain changed service handles");
return;
}
handle = gatt_db_attribute_get_handle(database->svc_chngd);
ccc_handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc);
if (!handle || !ccc_handle) {
error("Failed to obtain handles for \"Service Changed\""
" characteristic");
return;
}
put_le16(start, value);
put_le16(end, value + 2);
send_notification_to_devices(database, handle, value, sizeof(value),
ccc_handle, true);
}
static void gatt_db_service_added(struct gatt_db_attribute *attrib,
void *user_data)
{
struct btd_gatt_database *database = user_data;
DBG("GATT Service added to local database");
send_service_changed(database, attrib);
}
static bool ccc_match_service(const void *data, const void *match_data)
{
const struct ccc_state *ccc = data;
const struct gatt_db_attribute *attrib = match_data;
uint16_t start, end;
if (!gatt_db_attribute_get_service_handles(attrib, &start, &end))
return false;
return ccc->handle >= start && ccc->handle <= end;
}
static void remove_device_ccc(void *data, void *user_data)
{
struct device_state *state = data;
queue_remove_all(state->ccc_states, ccc_match_service, user_data, free);
}
static void gatt_db_service_removed(struct gatt_db_attribute *attrib,
void *user_data)
{
struct btd_gatt_database *database = user_data;
DBG("Local GATT service removed");
send_service_changed(database, attrib);
queue_foreach(database->device_states, remove_device_ccc, attrib);
}
struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter)
{
struct btd_gatt_database *database;
GError *gerr = NULL;
const bdaddr_t *addr;
if (!adapter)
return NULL;
database = new0(struct btd_gatt_database, 1);
if (!database)
return NULL;
database->adapter = btd_adapter_ref(adapter);
database->db = gatt_db_new();
if (!database->db)
goto fail;
database->device_states = queue_new();
if (!database->device_states)
goto fail;
database->db_id = gatt_db_register(database->db, gatt_db_service_added,
gatt_db_service_removed,
database, NULL);
if (!database->db_id)
goto fail;
addr = btd_adapter_get_address(adapter);
database->le_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr,
BT_IO_OPT_SOURCE_BDADDR, addr,
BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC,
BT_IO_OPT_CID, ATT_CID,
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
BT_IO_OPT_INVALID);
if (!database->le_io) {
error("Failed to start listening: %s", gerr->message);
g_error_free(gerr);
goto fail;
}
register_core_services(database);
return database;
fail:
gatt_database_free(database);
return NULL;
}
void btd_gatt_database_destroy(struct btd_gatt_database *database)
{
if (!database)
return;
gatt_database_free(database);
}
struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database)
{
if (!database)
return NULL;
return database->db;
}

25
src/gatt-database.h Normal file
View File

@ -0,0 +1,25 @@
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2015 Google Inc.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
struct btd_gatt_database;
struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter);
void btd_gatt_database_destroy(struct btd_gatt_database *database);
struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database);