This adds Client Characteristic Configuration descriptor for Service Changed
Characteristic registered on gatt init. This stored value should be persistent
across connections for bonded devices, thus gatt specific api was aded to
bluetooth core profile exposing storage functionality.
We could consider creating more generic functions, allowing to store custom
<key, value> pairs. that other profiles could use aswell. Then on every value
read we would access the file as we do on write, instead of storing it in device
along with other properties.
I'm not sure which aproach would be better. Should we go with the generic
version already or do it later if it turns out that we need to store more
thing for more profiles?
Regards,
Jakub Tyszkowski (3):
android: Add gatt CCC value storage
android/gatt: Store CCC and indicate service change
android/gatt: Make CCC descriptor readable
android/bluetooth.c | 45 +++++++++++++++++
android/bluetooth.h | 4 ++
android/gatt.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++------
3 files changed, 174 insertions(+), 16 deletions(-)
--
1.9.3
Descriptor unlike characteristic should be readable.
---
android/gatt.c | 41 +++++++++++++++++++++++++++++++++++++----
1 file changed, 37 insertions(+), 4 deletions(-)
diff --git a/android/gatt.c b/android/gatt.c
index f0cf013..c53ebd5 100644
--- a/android/gatt.c
+++ b/android/gatt.c
@@ -5499,7 +5499,7 @@ static void register_device_info_service(void)
gatt_db_service_set_active(gatt_db, srvc_handle, true);
}
-static void gatt_srvc_change_register_cb(uint16_t handle, uint16_t offset,
+static void gatt_srvc_change_write_cb(uint16_t handle, uint16_t offset,
const uint8_t *val, size_t len,
uint8_t att_opcode,
bdaddr_t *bdaddr,
@@ -5530,6 +5530,39 @@ static void gatt_srvc_change_register_cb(uint16_t handle, uint16_t offset,
bt_store_gatt_ccc(bdaddr, *val);
}
+static void gatt_srvc_change_read_cb(uint16_t handle, uint16_t offset,
+ uint8_t att_opcode, bdaddr_t *bdaddr,
+ void *user_data)
+{
+ struct pending_request *entry;
+ struct gatt_device *dev;
+ uint16_t ccc = 0;
+
+ dev = find_device_by_addr(bdaddr);
+ if (!dev) {
+ error("gatt: Could not find device ?!");
+ return;
+ }
+
+ entry = queue_find(dev->pending_requests, match_dev_request_by_handle,
+ UINT_TO_PTR(handle));
+ if (!entry)
+ return;
+
+ ccc = bt_get_gatt_ccc(&dev->bdaddr);
+ entry->state = REQUEST_DONE;
+
+ entry->value = (uint8_t *) new0(uint16_t, 1);
+ if (!entry->value) {
+ entry->error = ATT_ECODE_INSUFF_RESOURCES;
+
+ return;
+ }
+
+ entry->length = sizeof(uint16_t);
+ memcpy(entry->value, &ccc, sizeof(ccc));
+}
+
static void register_gatt_service(void)
{
bt_uuid_t uuid;
@@ -5548,9 +5581,9 @@ static void register_gatt_service(void)
bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
gatt_db_add_char_descriptor(gatt_db, srvc_handle, &uuid,
- GATT_PERM_WRITE,
- NULL, gatt_srvc_change_register_cb,
- NULL);
+ GATT_PERM_READ | GATT_PERM_WRITE,
+ gatt_srvc_change_read_cb,
+ gatt_srvc_change_write_cb, NULL);
gatt_db_service_set_active(gatt_db, srvc_handle, true);
}
--
1.9.3
We indicate service change on service stop and service start API calls.
Indication is also send when connecting to bonded device to force it
rebuilding its cache.
---
android/gatt.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 90 insertions(+), 14 deletions(-)
diff --git a/android/gatt.c b/android/gatt.c
index 485a238..f0cf013 100644
--- a/android/gatt.c
+++ b/android/gatt.c
@@ -161,8 +161,6 @@ struct gatt_device {
struct queue *services;
bool partial_srvc_search;
- bool notify_services_changed;
-
guint watch_id;
guint server_id;
@@ -190,6 +188,8 @@ static struct queue *app_connections = NULL;
static struct queue *listen_apps = NULL;
static struct gatt_db *gatt_db = NULL;
+static uint16_t service_changed_handle = 0;
+
static GIOChannel *listening_io = NULL;
static struct bt_crypto *crypto = NULL;
@@ -1084,6 +1084,40 @@ static void send_exchange_mtu_request(struct gatt_device *device)
device_unref(device);
}
+static void notify_att_range_change(struct gatt_device *dev,
+ struct att_range *range)
+{
+ uint16_t length = 0;
+ uint16_t ccc;
+ uint8_t *pdu;
+ size_t mtu;
+
+ ccc = bt_get_gatt_ccc(&dev->bdaddr);
+ if (!ccc)
+ return;
+
+ pdu = g_attrib_get_buffer(dev->attrib, &mtu);
+
+ switch (ccc) {
+ case 0x0001:
+ length = enc_notification(service_changed_handle,
+ (uint8_t *) range,
+ sizeof(range), pdu, mtu);
+ break;
+ case 0x0002:
+ length = enc_indication(service_changed_handle,
+ (uint8_t *) range, sizeof(range), pdu,
+ mtu);
+ break;
+ default:
+ /* 0xfff4 reserved for future use */
+ break;
+ }
+
+ if (length)
+ g_attrib_send(dev->attrib, 0, pdu, length, NULL, NULL, NULL);
+}
+
static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
{
struct gatt_device *dev = user_data;
@@ -1133,6 +1167,24 @@ static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
if (find_app_by_type(APP_CLIENT))
send_exchange_mtu_request(dev);
+ /*
+ * Send service changed if we are acting as server, as most likely
+ * attributes were modified.
+ */
+ if (find_app_by_type(APP_SERVER)) {
+ struct att_range range;
+
+ /*
+ * Service Changed Characteristic and CCC Descriptor handles
+ * should not change if there are bonded devices. We have them
+ * constant all the time, thus they should be excluded from
+ * range indicating changes.
+ */
+ range.start = service_changed_handle + 2;
+ range.end = 0xffff;
+ notify_att_range_change(dev, &range);
+ }
+
status = GATT_SUCCESS;
reply:
@@ -4291,6 +4343,20 @@ failed:
HAL_OP_GATT_SERVER_ADD_DESCRIPTOR, status);
}
+static void notify_service_change(void *data, void *user_data)
+{
+ struct att_range range;
+
+ range.start = PTR_TO_UINT(user_data);
+ range.end = gatt_db_get_end_handle(gatt_db, range.start);
+
+ /* In case of db error */
+ if (!range.end)
+ return;
+
+ notify_att_range_change(data, &range);
+}
+
static void handle_server_start_service(const void *buf, uint16_t len)
{
const struct hal_cmd_gatt_server_start_service *cmd = buf;
@@ -4316,6 +4382,9 @@ static void handle_server_start_service(const void *buf, uint16_t len)
goto failed;
}
+ queue_foreach(gatt_devices, notify_service_change,
+ UINT_TO_PTR(cmd->service_handle));
+
status = HAL_STATUS_SUCCESS;
failed:
@@ -4352,6 +4421,9 @@ static void handle_server_stop_service(const void *buf, uint16_t len)
else
status = HAL_STATUS_SUCCESS;
+ queue_foreach(gatt_devices, notify_service_change,
+ UINT_TO_PTR(cmd->service_handle));
+
failed:
ev.status = status == HAL_STATUS_SUCCESS ? GATT_SUCCESS : GATT_FAILURE;
ev.server_if = cmd->server_if;
@@ -5433,10 +5505,8 @@ static void gatt_srvc_change_register_cb(uint16_t handle, uint16_t offset,
bdaddr_t *bdaddr,
void *user_data)
{
+ struct pending_request *entry;
struct gatt_device *dev;
- uint16_t length;
- size_t mtu;
- uint8_t *pdu;
dev = find_device_by_addr(bdaddr);
if (!dev) {
@@ -5444,16 +5514,20 @@ static void gatt_srvc_change_register_cb(uint16_t handle, uint16_t offset,
return;
}
- pdu = g_attrib_get_buffer(dev->attrib, &mtu);
-
- /* TODO handle CCC */
+ entry = queue_find(dev->pending_requests, match_dev_request_by_handle,
+ UINT_TO_PTR(handle));
+ if (!entry)
+ return;
- /* Set services changed notification flag */
- dev->notify_services_changed = !!(*val);
+ entry->state = REQUEST_DONE;
- length = enc_write_resp(pdu);
+ if (!bt_device_is_bonded(bdaddr)) {
+ entry->error = ATT_ECODE_AUTHORIZATION;
+ return;
+ }
- g_attrib_send(dev->attrib, 0, pdu, length, NULL, NULL, NULL);
+ /* Set services changed indication value */
+ bt_store_gatt_ccc(bdaddr, *val);
}
static void register_gatt_service(void)
@@ -5467,12 +5541,14 @@ static void register_gatt_service(void)
srvc_handle = gatt_db_add_service(gatt_db, &uuid, true, 4);
bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED);
- gatt_db_add_characteristic(gatt_db, srvc_handle, &uuid, GATT_PERM_READ,
+ service_changed_handle = gatt_db_add_characteristic(gatt_db,
+ srvc_handle, &uuid, 0,
GATT_CHR_PROP_INDICATE, NULL, NULL,
NULL);
bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
- gatt_db_add_char_descriptor(gatt_db, srvc_handle, &uuid, GATT_PERM_READ,
+ gatt_db_add_char_descriptor(gatt_db, srvc_handle, &uuid,
+ GATT_PERM_WRITE,
NULL, gatt_srvc_change_register_cb,
NULL);
--
1.9.3
Client Characteristic Configuration for Service Changed Characteristic
should be stored for every bonded device, so we know if we should be
sending value (range affected by changes) indication.
---
android/bluetooth.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
android/bluetooth.h | 4 ++++
2 files changed, 49 insertions(+)
diff --git a/android/bluetooth.c b/android/bluetooth.c
index 8ee2025..dea9be1 100644
--- a/android/bluetooth.c
+++ b/android/bluetooth.c
@@ -158,6 +158,7 @@ struct device {
bool valid_local_csrk;
uint8_t local_csrk[16];
uint32_t local_sign_cnt;
+ uint16_t gatt_ccc;
};
struct browse_req {
@@ -645,6 +646,44 @@ static void mgmt_dev_class_changed_event(uint16_t index, uint16_t length,
/* TODO: Gatt attrib set*/
}
+void bt_store_gatt_ccc(const bdaddr_t *dst, uint16_t value)
+{
+ GKeyFile *key_file;
+ gsize length = 0;
+ char addr[18];
+ char *data;
+
+ key_file = g_key_file_new();
+
+ if (!g_key_file_load_from_file(key_file, DEVICES_FILE, 0, NULL)) {
+ g_key_file_free(key_file);
+ return;
+ }
+
+ ba2str(dst, addr);
+
+ DBG("%s Gatt CCC %d", addr, value);
+
+ g_key_file_set_integer(key_file, addr, "GattCCC", value);
+
+ data = g_key_file_to_data(key_file, &length, NULL);
+ g_file_set_contents(DEVICES_FILE, data, length, NULL);
+ g_free(data);
+
+ g_key_file_free(key_file);
+}
+
+uint16_t bt_get_gatt_ccc(const bdaddr_t *addr)
+{
+ struct device *dev;
+
+ dev = find_device(addr);
+ if (!dev)
+ return 0;
+
+ return dev->gatt_ccc;
+}
+
static void store_link_key(const bdaddr_t *dst, const uint8_t *key,
uint8_t type, uint8_t pin_length)
{
@@ -2408,6 +2447,12 @@ static struct device *create_device_from_info(GKeyFile *key_file,
"RemoteCSRKSignCounter", NULL);
}
+ str = g_key_file_get_string(key_file, peer, "GattCCC", NULL);
+ if (str) {
+ dev->gatt_ccc = atoi(str);
+ g_free(str);
+ }
+
str = g_key_file_get_string(key_file, peer, "Name", NULL);
if (str) {
g_free(dev->name);
diff --git a/android/bluetooth.h b/android/bluetooth.h
index 1c14377..b4a5f32 100644
--- a/android/bluetooth.h
+++ b/android/bluetooth.h
@@ -66,3 +66,7 @@ bool bt_get_csrk(const bdaddr_t *addr, enum bt_csrk_type type,
uint8_t key[16], uint32_t *sign_cnt);
void bt_update_sign_counter(const bdaddr_t *addr, enum bt_csrk_type type);
+
+void bt_store_gatt_ccc(const bdaddr_t *addr, uint16_t value);
+
+uint16_t bt_get_gatt_ccc(const bdaddr_t *addr);
--
1.9.3
Hi Jakub,
On Tue, May 27, 2014, Jakub Tyszkowski wrote:
> This adds Client Characteristic Configuration descriptor for Service Changed
> Characteristic registered on gatt init. This stored value should be persistent
> across connections for bonded devices, thus gatt specific api was aded to
> bluetooth core profile exposing storage functionality.
>
> We could consider creating more generic functions, allowing to store custom
> <key, value> pairs. that other profiles could use aswell. Then on every value
> read we would access the file as we do on write, instead of storing it in device
> along with other properties.
>
> I'm not sure which aproach would be better. Should we go with the generic
> version already or do it later if it turns out that we need to store more
> thing for more profiles?
I'd go with a specific solution now. We can still change this later if
necessary.
Johan