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 "cafragmentation.h"
26 #include "cagattservice.h"
28 #include "oic_malloc.h"
29 #include "oic_string.h"
38 static char const TAG[] = "BLE_CLIENT";
40 typedef struct _CAGattClientContext
43 * Bluetooth MAC address to GATT characteristic map.
45 * Hash table that maps Bluetooth MAC address to a OIC Transport
46 * Profile GATT characteristic. The key is a string containing
47 * the LE peripheral Bluetooth adapter MAC address. The value is
48 * an interface proxy (@c GDBusProxy) to an
49 * @c org.bluez.GattCharacteristic1 object, i.e. OIC Transport
50 * Profile GATT request characteristic.
52 GHashTable * characteristic_map;
55 * Response characteristic object path to Bluetooth MAC address map.
57 * Hash table that maps OIC Transport Profile GATT response
58 * characteristic D-Bus object path to Bluetooth MAC address. The
59 * key is the D-Bus object path. The value is the LE peripheral
60 * Bluetooth adapter MAC address.
62 * @note This map exists to avoid having to create a hierarchy of
63 * GLib D-Bus proxies to the client side BlueZ GATT response
64 * Characteristic, its corresponding GATT Service, and the
65 * Device object within that Service, along with the
66 * resulting D-Bus calls, simply so that we obtain the MAC
67 * address of the remote (peripheral) LE device. We
68 * unfortunately need the MAC address since the
69 * shared/common caleadapter code requires it.
71 GHashTable * address_map;
73 /// Mutex used to synchronize access to context fields.
76 } CAGattClientContext;
78 static CAGattClientContext g_context = {
82 // ---------------------------------------------------------------------
83 // GATT Response Receive
84 // ---------------------------------------------------------------------
85 static void CAGattClientOnCharacteristicPropertiesChanged(
86 GDBusProxy * characteristic,
87 GVariant * changed_properties,
88 GStrv invalidated_properties,
92 This handler is trigged in a GATT client when receiving data
93 sent by a GATT server through a notification, e.g. such as when
94 a GATT server sent a response.
97 (void) invalidated_properties;
99 if (g_variant_n_children(changed_properties) < 1)
102 No changed properties, only invalidated ones which we don't
108 CALEContext * const context = user_data;
109 char const * const object_path =
110 g_dbus_proxy_get_object_path(characteristic);
112 ca_mutex_lock(g_context.lock);
114 char * const address =
115 g_hash_table_lookup(g_context.address_map, object_path);
118 Address lookup could fail if a property changed on a GATT
119 characteristic that isn't an OIC Transport Profile GATT response
120 characteristic. This isn't necessarily a problem since it's
121 possible other unrelated GATT charactertistics with changed
122 properties are exposed by BlueZ on the D-Bus system bus.
126 CAGattRecvInfo info =
129 .on_packet_received = context->on_client_received_data,
133 GVariant * const value =
134 g_variant_lookup_value(changed_properties, "Value", NULL);
138 // GLib maps an octet to a guchar, which is of size 1.
140 gconstpointer const data =
141 g_variant_get_fixed_array(value, &length, 1);
143 (void) CAGattRecv(&info, data, length);
145 g_variant_unref(value);
149 ca_mutex_unlock(g_context.lock);
152 // ---------------------------------------------------------------------
153 // GATT Client Set-up
154 // ---------------------------------------------------------------------
155 static bool CAGattClientMapInsert(GHashTable * map,
159 bool const insert = !g_hash_table_contains(map, key);
163 g_hash_table_insert(map, key, value);
169 static bool CAGattClientSetupCharacteristics(
170 GDBusProxy * service,
171 char const * address,
172 GHashTable * characteristic_map,
173 GHashTable * address_map,
174 CALEContext * context)
178 GVariant * const characteristics_prop =
179 g_dbus_proxy_get_cached_property(service, "Characteristics");
182 gchar const ** const characteristic_paths =
183 g_variant_get_objv(characteristics_prop, &length);
190 "Server side OIC GATT Service has no characteristics");
195 Create a proxies to the org.bluez.GattCharacteristic1 D-Bus
196 objects that will later be used to send requests and receive
197 responses on the client side.
199 gchar const * const * const end = characteristic_paths + length;
200 for (gchar const * const * path = characteristic_paths;
201 path != end && success;
204 // Find the request characteristic.
205 GError * error = NULL;
207 GDBusProxy * const characteristic =
208 g_dbus_proxy_new_sync(context->connection,
209 G_DBUS_PROXY_FLAGS_NONE,
210 NULL, // GDBusInterfaceInfo
213 BLUEZ_GATT_CHARACTERISTIC_INTERFACE,
214 NULL, // GCancellable
217 if (characteristic == NULL)
221 "Unable to obtain proxy to GATT characteristic: %s",
231 GVariant * const uuid_prop =
232 g_dbus_proxy_get_cached_property(characteristic, "UUID");
234 char const * const uuid =
235 g_variant_get_string(uuid_prop, NULL);
237 if (strcasecmp(uuid, CA_GATT_REQUEST_CHRC_UUID) == 0)
239 char * const addr = OICStrdup(address);
240 gpointer * const chrc = g_object_ref(characteristic);
242 // Map LE (MAC) address to request characteristic.
243 if (!CAGattClientMapInsert(characteristic_map, addr, chrc))
247 "Possible duplicate OIC GATT "
248 "request characteristic proxy detected.");
250 g_object_unref(chrc);
254 else if (strcasecmp(uuid, CA_GATT_RESPONSE_CHRC_UUID) == 0)
256 char * const p = OICStrdup(*path);
257 char * const addr = OICStrdup(address);
259 // Map GATT service D-Bus object path to client address.
260 if (!CAGattClientMapInsert(address_map, p, addr))
264 "Unable to register duplicate "
265 "peripheral MAC address");
275 Detect changes in GATT characteristic properties.
276 This is only relevant to OIC response
277 characteristics since only their "Value" property
282 "g-properties-changed",
283 G_CALLBACK(CAGattClientOnCharacteristicPropertiesChanged),
286 // Enable notifications.
287 GVariant * const ret =
288 g_dbus_proxy_call_sync(
292 G_DBUS_CALL_FLAGS_NONE,
293 -1, // timeout (default == -1),
301 "Failed to enable GATT notifications: %s",
305 g_hash_table_remove(address_map, address);
310 g_variant_unref(ret);
319 "Unrecognized characteristic UUID "
320 "in OIC GATT service: %s",
325 g_variant_unref(uuid_prop);
326 g_object_unref(characteristic);
329 g_free(characteristic_paths);
330 g_variant_unref(characteristics_prop);
335 static bool CAGattClientSetupService(
337 GHashTable * characteristic_map,
338 GHashTable * address_map,
339 GVariant * services_prop,
340 CALEContext * context)
344 GVariant * const address_prop =
345 g_dbus_proxy_get_cached_property(device, "Address");
347 char const * const address =
348 g_variant_get_string(address_prop, NULL);
351 Create a proxies to the org.bluez.GattService1 D-Bus objects
352 that implement the OIC Transport Profile on the client side.
354 The services_prop argument will be non-NULL if changes to the
355 org.bluez.Device1.GattServices property were detected
356 asynchronously through the PropertiesChanged signal.
358 if (services_prop != NULL)
361 The caller owns the variant so hold on to a reference since
362 we assume ownership in this function.
364 g_variant_ref(services_prop);
368 // Check if GATT services have already been discovered.
370 g_dbus_proxy_get_cached_property(device, "GattServices");
374 char const ** const service_paths =
375 services_prop != NULL
376 ? g_variant_get_objv(services_prop, &length)
382 // GATT services may not yet have been discovered.
385 "GATT services not yet discovered "
386 "on LE peripheral: %s\n",
391 gchar const * const * const end = service_paths + length;
392 for (gchar const * const * path = service_paths;
393 path != end && success;
396 // Find the OIC Transport Profile GATT service.
397 GError * error = NULL;
399 GDBusProxy * const service =
400 g_dbus_proxy_new_sync(context->connection,
401 G_DBUS_PROXY_FLAGS_NONE,
402 NULL, // GDBusInterfaceInfo
405 BLUEZ_GATT_SERVICE_INTERFACE,
406 NULL, // GCancellable
413 "Unable to obtain proxy to GATT service: %s",
423 GVariant * const uuid_prop =
424 g_dbus_proxy_get_cached_property(service, "UUID");
426 char const * const uuid =
427 g_variant_get_string(uuid_prop, NULL);
429 if (strcasecmp(uuid, CA_GATT_SERVICE_UUID) == 0)
431 success = CAGattClientSetupCharacteristics(service,
442 "Characteristic set up for "
443 "GATT service at %s failed.",
449 g_variant_unref(uuid_prop);
450 g_object_unref(service);
453 if (services_prop != NULL)
455 g_variant_unref(services_prop);
458 g_variant_unref(address_prop);
463 static void CAGattClientOnDevicePropertiesChanged(
465 GVariant * changed_properties,
466 GStrv invalidated_properties,
470 This handler is trigged in a GATT client when org.bluez.Device1
471 properties have changed.
474 (void) invalidated_properties;
477 Retrieve the org.bluez.Device1.GattServices property from the
478 changed_properties dictionary parameter (index 1).
480 GVariant * const services_prop =
481 g_variant_lookup_value(changed_properties, "GattServices", NULL);
483 if (services_prop != NULL)
485 CALEContext * const context = user_data;
487 ca_mutex_lock(g_context.lock);
489 CAGattClientSetupService(device,
490 g_context.characteristic_map,
491 g_context.address_map,
495 ca_mutex_unlock(g_context.lock);
497 g_variant_unref(services_prop);
501 CAResult_t CAGattClientInitialize(CALEContext * context)
503 g_context.lock = ca_mutex_new();
506 Map Bluetooth MAC address to OIC Transport Profile
507 request characteristics.
509 GHashTable * const characteristic_map =
510 g_hash_table_new_full(g_str_hash,
516 Map OIC Transport Profile response characteristic D-Bus object
517 path to Bluetooth MAC address.
519 GHashTable * const address_map =
520 g_hash_table_new_full(g_str_hash,
525 ca_mutex_lock(context->lock);
527 for (GList * l = context->devices; l != NULL; l = l->next)
529 GDBusProxy * const device = G_DBUS_PROXY(l->data);
532 Detect changes in BlueZ Device properties. This is
533 predominantly used to detect GATT services that were
534 discovered asynchronously.
538 "g-properties-changed",
539 G_CALLBACK(CAGattClientOnDevicePropertiesChanged),
542 CAGattClientSetupService(device,
549 ca_mutex_unlock(context->lock);
551 ca_mutex_lock(g_context.lock);
553 g_context.characteristic_map = characteristic_map;
554 g_context.address_map = address_map;
556 ca_mutex_unlock(g_context.lock);
561 void CAGattClientDestroy()
563 if (g_context.lock == NULL)
565 return; // Initialization did not complete.
568 ca_mutex_lock(g_context.lock);
570 if (g_context.characteristic_map != NULL)
572 g_hash_table_unref(g_context.characteristic_map);
573 g_context.characteristic_map = NULL;
576 if (g_context.address_map != NULL)
578 g_hash_table_unref(g_context.address_map);
579 g_context.address_map = NULL;
582 ca_mutex_unlock(g_context.lock);
584 ca_mutex_free(g_context.lock);
585 g_context.lock = NULL;
588 We don't explicitly stop notifications on the response
589 characteristic since they should be stopped upon server side
594 // ---------------------------------------------------------------------
595 // GATT Request Data Send
596 // ---------------------------------------------------------------------
598 static CAResult_t CAGattClientSendDataImpl(GDBusProxy * characteristic,
599 uint8_t const * data,
601 CALEContext * context)
603 assert(characteristic != NULL);
604 assert(data != NULL);
605 assert(context != NULL);
607 GVariant * const value =
608 g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
611 1); // sizeof(data[0]) == 1
614 WriteValue() expects a byte array but it must be packed into a
615 tuple for the actual call through the proxy.
617 GVariant * const value_parameter = g_variant_new("(@ay)", value);
619 GError * error = NULL;
621 GVariant * const ret =
622 g_dbus_proxy_call_sync(characteristic,
624 value_parameter, // parameters
625 G_DBUS_CALL_FLAGS_NONE,
626 -1, // timeout (default == -1),
634 "[%p] WriteValue() call failed: %s",
640 ca_mutex_lock(context->lock);
642 if (context->on_client_error != NULL)
645 At this point endpoint and send data information is
648 context->on_client_error(NULL, // endpoint
654 ca_mutex_unlock(context->lock);
656 return CA_STATUS_FAILED;
659 g_variant_unref(ret);
664 CAResult_t CAGattClientSendData(char const * address,
665 uint8_t const * data,
667 CALEContext * context)
669 assert(context != NULL);
671 CAResult_t result = CA_STATUS_FAILED;
673 ca_mutex_lock(g_context.lock);
675 GDBusProxy * const characteristic =
677 g_hash_table_lookup(g_context.characteristic_map,
680 if (characteristic == NULL)
683 GATT request characteristic corresponding to given address
690 result = CAGattClientSendDataImpl(characteristic,
695 ca_mutex_unlock(g_context.lock);
700 CAResult_t CAGattClientSendDataToAll(uint8_t const * data,
702 CALEContext * context)
704 assert(context != NULL);
706 CAResult_t result = CA_STATUS_FAILED;
708 ca_mutex_lock(g_context.lock);
710 if (g_context.characteristic_map == NULL)
712 // Remote OIC GATT service was not found prior to getting here.
713 ca_mutex_unlock(g_context.lock);
718 g_hash_table_iter_init(&iter, g_context.characteristic_map);
720 gpointer characteristic; // Value
723 * @todo Will content of this hash table be potentially changed by
724 * another thread during iteration?
726 while(g_hash_table_iter_next(&iter,
727 NULL, // Key - unused
730 result = CAGattClientSendDataImpl(G_DBUS_PROXY(characteristic),
735 if (result != CA_STATUS_OK)
741 ca_mutex_unlock(g_context.lock);