1 /******************************************************************
3 * Copyright 2015 Intel Corporation All Rights Reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 ******************************************************************/
25 #include "cagattservice.h"
27 #include "oic_malloc.h"
28 #include "oic_string.h"
37 static char const TAG[] = "BLE_CLIENT";
39 typedef struct _CAGattClientContext
42 * Bluetooth MAC address to GATT characteristic map.
44 * Hash table that maps Bluetooth MAC address to a OIC Transport
45 * Profile GATT characteristic. The key is a string containing
46 * the LE peripheral Bluetooth adapter MAC address. The value is
47 * an interface proxy (@c GDBusProxy) to an
48 * @c org.bluez.GattCharacteristic1 object, i.e. OIC Transport
49 * Profile GATT request characteristic.
51 GHashTable * characteristic_map;
54 * Response characteristic object path to Bluetooth MAC address map.
56 * Hash table that maps OIC Transport Profile GATT response
57 * characteristic D-Bus object path to Bluetooth MAC address. The
58 * key is the D-Bus object path. The value is the LE peripheral
59 * Bluetooth adapter MAC address.
61 * @note This map exists to avoid having to create a hierarchy of
62 * GLib D-Bus proxies to the client side BlueZ GATT response
63 * Characteristic, its corresponding GATT Service, and the
64 * Device object within that Service, along with the
65 * resulting D-Bus calls, simply so that we obtain the MAC
66 * address of the remote (peripheral) LE device. We
67 * unfortunately need the MAC address since the
68 * shared/common caleadapter code requires it.
70 GHashTable * address_map;
72 /// Mutex used to synchronize access to context fields.
75 } CAGattClientContext;
77 static CAGattClientContext g_context = {
81 // ---------------------------------------------------------------------
82 // GATT Response Receive
83 // ---------------------------------------------------------------------
84 static void CAGattClientOnCharacteristicPropertiesChanged(
85 GDBusProxy * characteristic,
86 GVariant * changed_properties,
87 GStrv invalidated_properties,
91 This handler is trigged in a GATT client when receiving data
92 sent by a GATT server through a notification, e.g. such as when
93 a GATT server sent a response.
96 (void) invalidated_properties;
98 if (g_variant_n_children(changed_properties) < 1)
101 No changed properties, only invalidated ones which we don't
107 CALEContext * const context = user_data;
108 char const * const object_path =
109 g_dbus_proxy_get_object_path(characteristic);
111 oc_mutex_lock(g_context.lock);
113 char * const address =
114 g_hash_table_lookup(g_context.address_map, object_path);
117 Address lookup could fail if a property changed on a GATT
118 characteristic that isn't an OIC Transport Profile GATT response
119 characteristic. This isn't necessarily a problem since it's
120 possible other unrelated GATT charactertistics with changed
121 properties are exposed by BlueZ on the D-Bus system bus.
125 CAGattRecvInfo info =
128 .on_packet_received = context->on_client_received_data,
132 GVariant * const value =
133 g_variant_lookup_value(changed_properties, "Value", NULL);
137 // GLib maps an octet to a guchar, which is of size 1.
139 gconstpointer const data =
140 g_variant_get_fixed_array(value, &length, 1);
142 (void) CAGattRecv(&info, data, length);
144 g_variant_unref(value);
148 oc_mutex_unlock(g_context.lock);
151 // ---------------------------------------------------------------------
152 // GATT Client Set-up
153 // ---------------------------------------------------------------------
154 static bool CAGattClientMapInsert(GHashTable * map,
158 bool const insert = !g_hash_table_contains(map, key);
162 g_hash_table_insert(map, key, value);
168 static bool CAGattClientSetupCharacteristics(
169 GDBusProxy * service,
170 char const * address,
171 GHashTable * characteristic_map,
172 GHashTable * address_map,
173 CALEContext * context)
177 GVariant * const characteristics_prop =
178 g_dbus_proxy_get_cached_property(service, "Characteristics");
181 gchar const ** const characteristic_paths =
182 g_variant_get_objv(characteristics_prop, &length);
189 "Server side OIC GATT Service has no characteristics");
194 Create a proxies to the org.bluez.GattCharacteristic1 D-Bus
195 objects that will later be used to send requests and receive
196 responses on the client side.
198 gchar const * const * const end = characteristic_paths + length;
199 for (gchar const * const * path = characteristic_paths;
200 path != end && success;
203 // Find the request characteristic.
204 GError * error = NULL;
206 GDBusProxy * const characteristic =
207 g_dbus_proxy_new_sync(context->connection,
208 G_DBUS_PROXY_FLAGS_NONE,
209 NULL, // GDBusInterfaceInfo
212 BLUEZ_GATT_CHARACTERISTIC_INTERFACE,
213 NULL, // GCancellable
216 if (characteristic == NULL)
220 "Unable to obtain proxy to GATT characteristic: %s",
230 GVariant * const uuid_prop =
231 g_dbus_proxy_get_cached_property(characteristic, "UUID");
233 char const * const uuid =
234 g_variant_get_string(uuid_prop, NULL);
236 if (strcasecmp(uuid, CA_GATT_REQUEST_CHRC_UUID) == 0)
238 char * const addr = OICStrdup(address);
239 gpointer * const chrc = g_object_ref(characteristic);
241 // Map LE (MAC) address to request characteristic.
242 if (!CAGattClientMapInsert(characteristic_map, addr, chrc))
246 "Possible duplicate OIC GATT "
247 "request characteristic proxy detected.");
249 g_object_unref(chrc);
253 else if (strcasecmp(uuid, CA_GATT_RESPONSE_CHRC_UUID) == 0)
255 char * const p = OICStrdup(*path);
256 char * const addr = OICStrdup(address);
258 // Map GATT service D-Bus object path to client address.
259 if (!CAGattClientMapInsert(address_map, p, addr))
263 "Unable to register duplicate "
264 "peripheral MAC address");
274 Detect changes in GATT characteristic properties.
275 This is only relevant to OIC response
276 characteristics since only their "Value" property
281 "g-properties-changed",
282 G_CALLBACK(CAGattClientOnCharacteristicPropertiesChanged),
285 // Enable notifications.
286 GVariant * const ret =
287 g_dbus_proxy_call_sync(
291 G_DBUS_CALL_FLAGS_NONE,
292 -1, // timeout (default == -1),
300 "Failed to enable GATT notifications: %s",
304 g_hash_table_remove(address_map, address);
309 g_variant_unref(ret);
318 "Unrecognized characteristic UUID "
319 "in OIC GATT service: %s",
324 g_variant_unref(uuid_prop);
325 g_object_unref(characteristic);
328 g_free(characteristic_paths);
329 g_variant_unref(characteristics_prop);
334 static bool CAGattClientSetupService(
336 GHashTable * characteristic_map,
337 GHashTable * address_map,
338 GVariant * services_prop,
339 CALEContext * context)
343 GVariant * const address_prop =
344 g_dbus_proxy_get_cached_property(device, "Address");
346 char const * const address =
347 g_variant_get_string(address_prop, NULL);
350 Create a proxies to the org.bluez.GattService1 D-Bus objects
351 that implement the OIC Transport Profile on the client side.
353 The services_prop argument will be non-NULL if changes to the
354 org.bluez.Device1.GattServices property were detected
355 asynchronously through the PropertiesChanged signal.
357 if (services_prop != NULL)
360 The caller owns the variant so hold on to a reference since
361 we assume ownership in this function.
363 g_variant_ref(services_prop);
367 // Check if GATT services have already been discovered.
369 g_dbus_proxy_get_cached_property(device, "GattServices");
373 char const ** const service_paths =
374 services_prop != NULL
375 ? g_variant_get_objv(services_prop, &length)
381 // GATT services may not yet have been discovered.
384 "GATT services not yet discovered "
385 "on LE peripheral: %s\n",
390 gchar const * const * const end = service_paths + length;
391 for (gchar const * const * path = service_paths;
392 path != end && success;
395 // Find the OIC Transport Profile GATT service.
396 GError * error = NULL;
398 GDBusProxy * const service =
399 g_dbus_proxy_new_sync(context->connection,
400 G_DBUS_PROXY_FLAGS_NONE,
401 NULL, // GDBusInterfaceInfo
404 BLUEZ_GATT_SERVICE_INTERFACE,
405 NULL, // GCancellable
412 "Unable to obtain proxy to GATT service: %s",
422 GVariant * const uuid_prop =
423 g_dbus_proxy_get_cached_property(service, "UUID");
425 char const * const uuid =
426 g_variant_get_string(uuid_prop, NULL);
428 if (strcasecmp(uuid, CA_GATT_SERVICE_UUID) == 0)
430 success = CAGattClientSetupCharacteristics(service,
441 "Characteristic set up for "
442 "GATT service at %s failed.",
448 g_variant_unref(uuid_prop);
449 g_object_unref(service);
452 if (services_prop != NULL)
454 g_variant_unref(services_prop);
457 g_variant_unref(address_prop);
462 static void CAGattClientOnDevicePropertiesChanged(
464 GVariant * changed_properties,
465 GStrv invalidated_properties,
469 This handler is trigged in a GATT client when org.bluez.Device1
470 properties have changed.
473 (void) invalidated_properties;
476 Retrieve the org.bluez.Device1.GattServices property from the
477 changed_properties dictionary parameter (index 1).
479 GVariant * const services_prop =
480 g_variant_lookup_value(changed_properties, "GattServices", NULL);
482 if (services_prop != NULL)
484 CALEContext * const context = user_data;
486 oc_mutex_lock(g_context.lock);
488 CAGattClientSetupService(device,
489 g_context.characteristic_map,
490 g_context.address_map,
494 oc_mutex_unlock(g_context.lock);
496 g_variant_unref(services_prop);
500 CAResult_t CAGattClientInitialize(CALEContext * context)
502 g_context.lock = oc_mutex_new();
505 Map Bluetooth MAC address to OIC Transport Profile
506 request characteristics.
508 GHashTable * const characteristic_map =
509 g_hash_table_new_full(g_str_hash,
515 Map OIC Transport Profile response characteristic D-Bus object
516 path to Bluetooth MAC address.
518 GHashTable * const address_map =
519 g_hash_table_new_full(g_str_hash,
524 oc_mutex_lock(context->lock);
526 for (GList * l = context->devices; l != NULL; l = l->next)
528 GDBusProxy * const device = G_DBUS_PROXY(l->data);
531 Detect changes in BlueZ Device properties. This is
532 predominantly used to detect GATT services that were
533 discovered asynchronously.
537 "g-properties-changed",
538 G_CALLBACK(CAGattClientOnDevicePropertiesChanged),
541 CAGattClientSetupService(device,
548 oc_mutex_unlock(context->lock);
550 oc_mutex_lock(g_context.lock);
552 g_context.characteristic_map = characteristic_map;
553 g_context.address_map = address_map;
555 oc_mutex_unlock(g_context.lock);
560 void CAGattClientDestroy()
562 if (g_context.lock == NULL)
564 return; // Initialization did not complete.
567 oc_mutex_lock(g_context.lock);
569 if (g_context.characteristic_map != NULL)
571 g_hash_table_unref(g_context.characteristic_map);
572 g_context.characteristic_map = NULL;
575 if (g_context.address_map != NULL)
577 g_hash_table_unref(g_context.address_map);
578 g_context.address_map = NULL;
581 oc_mutex_unlock(g_context.lock);
583 oc_mutex_free(g_context.lock);
584 g_context.lock = NULL;
587 We don't explicitly stop notifications on the response
588 characteristic since they should be stopped upon server side
593 // ---------------------------------------------------------------------
594 // GATT Request Data Send
595 // ---------------------------------------------------------------------
597 static CAResult_t CAGattClientSendDataImpl(GDBusProxy * characteristic,
598 uint8_t const * data,
600 CALEContext * context)
602 assert(characteristic != NULL);
603 assert(data != NULL);
604 assert(context != NULL);
606 GVariant * const value =
607 g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
610 1); // sizeof(data[0]) == 1
613 WriteValue() expects a byte array but it must be packed into a
614 tuple for the actual call through the proxy.
616 GVariant * const value_parameter = g_variant_new("(@ay)", value);
618 GError * error = NULL;
620 GVariant * const ret =
621 g_dbus_proxy_call_sync(characteristic,
623 value_parameter, // parameters
624 G_DBUS_CALL_FLAGS_NONE,
625 -1, // timeout (default == -1),
633 "[%p] WriteValue() call failed: %s",
639 oc_mutex_lock(context->lock);
641 if (context->on_client_error != NULL)
644 At this point endpoint and send data information is
647 context->on_client_error(NULL, // endpoint
653 oc_mutex_unlock(context->lock);
655 return CA_STATUS_FAILED;
658 g_variant_unref(ret);
663 CAResult_t CAGattClientSendData(char const * address,
664 uint8_t const * data,
666 CALEContext * context)
668 assert(context != NULL);
670 CAResult_t result = CA_STATUS_FAILED;
672 oc_mutex_lock(g_context.lock);
674 GDBusProxy * const characteristic =
676 g_hash_table_lookup(g_context.characteristic_map,
679 if (characteristic == NULL)
682 GATT request characteristic corresponding to given address
689 result = CAGattClientSendDataImpl(characteristic,
694 oc_mutex_unlock(g_context.lock);
699 CAResult_t CAGattClientSendDataToAll(uint8_t const * data,
701 CALEContext * context)
703 assert(context != NULL);
705 CAResult_t result = CA_STATUS_FAILED;
707 oc_mutex_lock(g_context.lock);
709 if (g_context.characteristic_map == NULL)
711 // Remote OIC GATT service was not found prior to getting here.
712 oc_mutex_unlock(g_context.lock);
717 g_hash_table_iter_init(&iter, g_context.characteristic_map);
719 gpointer characteristic; // Value
722 * @todo Will content of this hash table be potentially changed by
723 * another thread during iteration?
725 while(g_hash_table_iter_next(&iter,
726 NULL, // Key - unused
729 result = CAGattClientSendDataImpl(G_DBUS_PROXY(characteristic),
734 if (result != CA_STATUS_OK)
740 oc_mutex_unlock(g_context.lock);