2888c7b80c102e6170f000475134c368b10d2002
[platform/upstream/iotivity.git] / resource / csdk / connectivity / src / bt_le_adapter / linux / client.c
1 /******************************************************************
2  *
3  * Copyright 2015 Intel Corporation All Rights Reserved.
4  *
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
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  ******************************************************************/
18
19 #include "client.h"
20 #include "recv.h"
21 #include "context.h"
22 #include "bluez.h"
23 #include "utils.h"
24
25 #include "cagattservice.h"
26 #include "logger.h"
27 #include "oic_malloc.h"
28 #include "oic_string.h"
29
30 #include <gio/gio.h>
31
32 #include <strings.h>
33 #include <assert.h>
34
35
36 // Logging tag.
37 static char const TAG[] = "BLE_CLIENT";
38
39 typedef struct _CAGattClientContext
40 {
41     /**
42      * Bluetooth MAC address to GATT characteristic map.
43      *
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.
50      */
51     GHashTable * characteristic_map;
52
53     /**
54      * Response characteristic object path to Bluetooth MAC address map.
55      *
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.
60      *
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.
69      */
70     GHashTable * address_map;
71
72     /// Mutex used to synchronize access to context fields.
73     ca_mutex lock;
74
75 } CAGattClientContext;
76
77 static CAGattClientContext g_context = {
78     .lock = NULL
79 };
80
81 // ---------------------------------------------------------------------
82 //                      GATT Response Receive
83 // ---------------------------------------------------------------------
84 static void CAGattClientOnCharacteristicPropertiesChanged(
85     GDBusProxy * characteristic,
86     GVariant * changed_properties,
87     GStrv invalidated_properties,
88     gpointer user_data)
89 {
90     /*
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.
94     */
95
96     (void) invalidated_properties;
97
98     if (g_variant_n_children(changed_properties) < 1)
99     {
100         /*
101           No changed properties, only invalidated ones which we don't
102           care about.
103         */
104         return;
105     }
106
107     CALEContext * const context = user_data;
108     char const * const object_path =
109         g_dbus_proxy_get_object_path(characteristic);
110
111     ca_mutex_lock(g_context.lock);
112
113     char * const address =
114         g_hash_table_lookup(g_context.address_map, object_path);
115
116     /*
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.
122     */
123     if (address != NULL)
124     {
125         CAGattRecvInfo info =
126             {
127                 .peer               = address,
128                 .on_packet_received = context->on_client_received_data,
129                 .context            = context
130             };
131
132         GVariant * const value =
133             g_variant_lookup_value(changed_properties, "Value", NULL);
134
135         if (value != NULL)
136         {
137             // GLib maps an octet to a guchar, which is of size 1.
138             gsize length = 0;
139             gconstpointer const data =
140                 g_variant_get_fixed_array(value, &length, 1);
141
142             (void) CAGattRecv(&info, data, length);
143
144             g_variant_unref(value);
145         }
146     }
147
148     ca_mutex_unlock(g_context.lock);
149 }
150
151 // ---------------------------------------------------------------------
152 //                        GATT Client Set-up
153 // ---------------------------------------------------------------------
154 static bool CAGattClientMapInsert(GHashTable * map,
155                                   gpointer key,
156                                   gpointer value)
157 {
158     bool const insert = !g_hash_table_contains(map, key);
159
160     if (insert)
161     {
162         g_hash_table_insert(map, key, value);
163     }
164
165     return insert;
166 }
167
168 static bool CAGattClientSetupCharacteristics(
169     GDBusProxy * service,
170     char const * address,
171     GHashTable * characteristic_map,
172     GHashTable * address_map,
173     CALEContext * context)
174 {
175     bool success = true;
176
177     GVariant * const characteristics_prop =
178         g_dbus_proxy_get_cached_property(service, "Characteristics");
179
180     gsize length = 0;
181     gchar const ** const characteristic_paths =
182         g_variant_get_objv(characteristics_prop, &length);
183
184 #ifdef TB_LOG
185     if (length == 0)
186     {
187         OIC_LOG(ERROR,
188                 TAG,
189                 "Server side OIC GATT Service has no characteristics");
190     }
191 #endif
192
193     /*
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.
197     */
198     gchar const * const * const end = characteristic_paths + length;
199     for (gchar const * const * path = characteristic_paths;
200          path != end && success;
201          ++path)
202     {
203         // Find the request characteristic.
204         GError * error = NULL;
205
206         GDBusProxy * const characteristic =
207             g_dbus_proxy_new_sync(context->connection,
208                                   G_DBUS_PROXY_FLAGS_NONE,
209                                   NULL, // GDBusInterfaceInfo
210                                   BLUEZ_NAME,
211                                   *path,
212                                   BLUEZ_GATT_CHARACTERISTIC_INTERFACE,
213                                   NULL, // GCancellable
214                                   &error);
215
216         if (characteristic == NULL)
217         {
218             OIC_LOG_V(ERROR,
219                       TAG,
220                       "Unable to obtain proxy to GATT characteristic: %s",
221                       error->message);
222
223             g_error_free(error);
224
225             success = false;
226
227             break;
228         }
229
230         GVariant * const uuid_prop =
231             g_dbus_proxy_get_cached_property(characteristic, "UUID");
232
233         char const * const uuid =
234             g_variant_get_string(uuid_prop, NULL);
235
236         if (strcasecmp(uuid, CA_GATT_REQUEST_CHRC_UUID) == 0)
237         {
238             char     * const addr = OICStrdup(address);
239             gpointer * const chrc = g_object_ref(characteristic);
240
241             // Map LE (MAC) address to request characteristic.
242             if (!CAGattClientMapInsert(characteristic_map, addr, chrc))
243             {
244                 OIC_LOG_V(WARNING,
245                           TAG,
246                           "Possible duplicate OIC GATT "
247                           "request characteristic proxy detected.");
248
249                 g_object_unref(chrc);
250                 OICFree(addr);
251             }
252         }
253         else if (strcasecmp(uuid, CA_GATT_RESPONSE_CHRC_UUID) == 0)
254         {
255             char * const p    = OICStrdup(*path);
256             char * const addr = OICStrdup(address);
257
258             // Map GATT service D-Bus object path to client address.
259             if (!CAGattClientMapInsert(address_map, p, addr))
260             {
261                 OIC_LOG_V(WARNING,
262                           TAG,
263                           "Unable to register duplicate "
264                           "peripheral MAC address");
265
266                 success = false;
267
268                 OICFree(addr);
269                 OICFree(p);
270             }
271             else
272             {
273                 /*
274                   Detect changes in GATT characteristic properties.
275                   This is only relevant to OIC response
276                   characteristics since only their "Value" property
277                   will ever change.
278                 */
279                 g_signal_connect(
280                     characteristic,
281                     "g-properties-changed",
282                     G_CALLBACK(CAGattClientOnCharacteristicPropertiesChanged),
283                     context);
284
285                 // Enable notifications.
286                 GVariant * const ret =
287                     g_dbus_proxy_call_sync(
288                         characteristic,
289                         "StartNotify",
290                         NULL,  // parameters
291                         G_DBUS_CALL_FLAGS_NONE,
292                         -1,    // timeout (default == -1),
293                         NULL,  // cancellable
294                         &error);
295
296                 if (ret == NULL)
297                 {
298                     OIC_LOG_V(ERROR,
299                               TAG,
300                               "Failed to enable GATT notifications: %s",
301                               error->message);
302
303                     g_error_free(error);
304                     g_hash_table_remove(address_map, address);
305                     success = false;
306                 }
307                 else
308                 {
309                     g_variant_unref(ret);
310                 }
311             }
312         }
313 #ifdef TB_LOG
314         else
315         {
316             OIC_LOG_V(WARNING,
317                       TAG,
318                       "Unrecognized characteristic UUID "
319                       "in OIC GATT service: %s",
320                       uuid);
321         }
322 #endif
323
324         g_variant_unref(uuid_prop);
325         g_object_unref(characteristic);
326     }
327
328     g_free(characteristic_paths);
329     g_variant_unref(characteristics_prop);
330
331     return success;
332 }
333
334 static bool CAGattClientSetupService(
335     GDBusProxy * device,
336     GHashTable * characteristic_map,
337     GHashTable * address_map,
338     GVariant   * services_prop,
339     CALEContext * context)
340 {
341     bool success = true;
342
343     GVariant * const address_prop =
344         g_dbus_proxy_get_cached_property(device, "Address");
345
346     char const * const address =
347         g_variant_get_string(address_prop, NULL);
348
349     /*
350       Create a proxies to the org.bluez.GattService1 D-Bus objects
351       that implement the OIC Transport Profile on the client side.
352
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.
356     */
357     if (services_prop != NULL)
358     {
359         /*
360           The caller owns the variant so hold on to a reference since
361           we assume ownership in this function.
362         */
363         g_variant_ref(services_prop);
364     }
365     else
366     {
367         // Check if GATT services have already been discovered.
368         services_prop =
369             g_dbus_proxy_get_cached_property(device, "GattServices");
370     }
371
372     gsize length = 0;
373     char const ** const service_paths =
374         services_prop != NULL
375         ? g_variant_get_objv(services_prop, &length)
376         : NULL;
377
378 #ifdef TB_LOG
379     if (length == 0)
380     {
381         // GATT services may not yet have been discovered.
382         OIC_LOG_V(INFO,
383                   TAG,
384                   "GATT services not yet discovered "
385                   "on LE peripheral: %s\n",
386                   address);
387     }
388 #endif
389
390     gchar const * const * const end = service_paths + length;
391     for (gchar const * const * path = service_paths;
392          path != end && success;
393          ++path)
394     {
395         // Find the OIC Transport Profile GATT service.
396         GError * error = NULL;
397
398         GDBusProxy * const service =
399             g_dbus_proxy_new_sync(context->connection,
400                                   G_DBUS_PROXY_FLAGS_NONE,
401                                   NULL, // GDBusInterfaceInfo
402                                   BLUEZ_NAME,
403                                   *path,
404                                   BLUEZ_GATT_SERVICE_INTERFACE,
405                                   NULL, // GCancellable
406                                   &error);
407
408         if (service == NULL)
409         {
410             OIC_LOG_V(ERROR,
411                       TAG,
412                       "Unable to obtain proxy to GATT service: %s",
413                       error->message);
414
415             g_error_free(error);
416
417             success = false;
418
419             break;
420         }
421
422         GVariant * const uuid_prop =
423             g_dbus_proxy_get_cached_property(service, "UUID");
424
425         char const * const uuid =
426             g_variant_get_string(uuid_prop, NULL);
427
428         if (strcasecmp(uuid, CA_GATT_SERVICE_UUID) == 0)
429         {
430             success = CAGattClientSetupCharacteristics(service,
431                                                        address,
432                                                        characteristic_map,
433                                                        address_map,
434                                                        context);
435
436 #ifdef TB_LOG
437             if (!success)
438             {
439                 OIC_LOG_V(ERROR,
440                           TAG,
441                           "Characteristic set up for "
442                           "GATT service at %s failed.",
443                           address);
444             }
445 #endif  // TB_LOG
446         }
447
448         g_variant_unref(uuid_prop);
449         g_object_unref(service);
450     }
451
452     if (services_prop != NULL)
453     {
454         g_variant_unref(services_prop);
455     }
456
457     g_variant_unref(address_prop);
458
459     return success;
460 }
461
462 static void CAGattClientOnDevicePropertiesChanged(
463     GDBusProxy * device,
464     GVariant * changed_properties,
465     GStrv invalidated_properties,
466     gpointer user_data)
467 {
468     /*
469       This handler is trigged in a GATT client when org.bluez.Device1
470       properties have changed.
471     */
472
473     (void) invalidated_properties;
474
475     /*
476       Retrieve the org.bluez.Device1.GattServices property from the
477       changed_properties dictionary parameter (index 1).
478     */
479     GVariant * const services_prop =
480         g_variant_lookup_value(changed_properties, "GattServices", NULL);
481
482     if (services_prop != NULL)
483     {
484         CALEContext * const context = user_data;
485
486         ca_mutex_lock(g_context.lock);
487
488         CAGattClientSetupService(device,
489                                  g_context.characteristic_map,
490                                  g_context.address_map,
491                                  services_prop,
492                                  context);
493
494         ca_mutex_unlock(g_context.lock);
495
496         g_variant_unref(services_prop);
497     }
498 }
499
500 CAResult_t CAGattClientInitialize(CALEContext * context)
501 {
502     g_context.lock = ca_mutex_new();
503
504     /*
505       Map Bluetooth MAC address to OIC Transport Profile
506       request characteristics.
507     */
508     GHashTable * const characteristic_map =
509         g_hash_table_new_full(g_str_hash,
510                               g_str_equal,
511                               OICFree,
512                               g_object_unref);
513
514     /*
515       Map OIC Transport Profile response characteristic D-Bus object
516       path to Bluetooth MAC address.
517     */
518     GHashTable * const address_map =
519         g_hash_table_new_full(g_str_hash,
520                               g_str_equal,
521                               OICFree,
522                               OICFree);
523
524     ca_mutex_lock(context->lock);
525
526     for (GList * l = context->devices; l != NULL; l = l->next)
527     {
528         GDBusProxy * const device = G_DBUS_PROXY(l->data);
529
530         /*
531           Detect changes in BlueZ Device properties.  This is
532           predominantly used to detect GATT services that were
533           discovered asynchronously.
534         */
535         g_signal_connect(
536             device,
537             "g-properties-changed",
538             G_CALLBACK(CAGattClientOnDevicePropertiesChanged),
539             context);
540
541         CAGattClientSetupService(device,
542                                  characteristic_map,
543                                  address_map,
544                                  NULL,
545                                  context);
546     }
547
548     ca_mutex_unlock(context->lock);
549
550     ca_mutex_lock(g_context.lock);
551
552     g_context.characteristic_map = characteristic_map;
553     g_context.address_map = address_map;
554
555     ca_mutex_unlock(g_context.lock);
556
557     return CA_STATUS_OK;
558 }
559
560 void CAGattClientDestroy()
561 {
562     if (g_context.lock == NULL)
563     {
564         return;  // Initialization did not complete.
565     }
566
567     ca_mutex_lock(g_context.lock);
568
569     if (g_context.characteristic_map != NULL)
570     {
571         g_hash_table_unref(g_context.characteristic_map);
572         g_context.characteristic_map = NULL;
573     }
574
575     if (g_context.address_map != NULL)
576     {
577         g_hash_table_unref(g_context.address_map);
578         g_context.address_map = NULL;
579     }
580
581     ca_mutex_unlock(g_context.lock);
582
583     ca_mutex_free(g_context.lock);
584     g_context.lock = NULL;
585
586     /*
587       We don't explicitly stop notifications on the response
588       characteristic since they should be stopped upon server side
589       disconnection.
590      */
591 }
592
593 // ---------------------------------------------------------------------
594 //                      GATT Request Data Send
595 // ---------------------------------------------------------------------
596
597 static CAResult_t CAGattClientSendDataImpl(GDBusProxy * characteristic,
598                                            uint8_t const * data,
599                                            size_t length,
600                                            CALEContext * context)
601 {
602     assert(characteristic != NULL);
603     assert(data != NULL);
604     assert(context != NULL);
605
606     GVariant * const value =
607         g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
608                                   data,
609                                   length,
610                                   1);  // sizeof(data[0]) == 1
611
612     /*
613       WriteValue() expects a byte array but it must be packed into a
614       tuple for the actual call through the proxy.
615     */
616     GVariant * const value_parameter = g_variant_new("(@ay)", value);
617
618     GError * error = NULL;
619
620     GVariant * const ret =
621         g_dbus_proxy_call_sync(characteristic,
622                                "WriteValue",
623                                value_parameter,  // parameters
624                                G_DBUS_CALL_FLAGS_NONE,
625                                -1,    // timeout (default == -1),
626                                NULL,  // cancellable
627                                &error);
628
629     if (ret == NULL)
630     {
631         OIC_LOG_V(ERROR,
632                   TAG,
633                   "[%p] WriteValue() call failed: %s",
634                   characteristic,
635                   error->message);
636
637         g_error_free(error);
638
639         ca_mutex_lock(context->lock);
640
641         if (context->on_client_error != NULL)
642         {
643             /*
644               At this point endpoint and send data information is
645               available.
646             */
647             context->on_client_error(NULL,   // endpoint
648                                      data,
649                                      length,
650                                      CA_STATUS_FAILED);
651         }
652
653         ca_mutex_unlock(context->lock);
654
655         return CA_STATUS_FAILED;
656     }
657
658     g_variant_unref(ret);
659
660     return CA_STATUS_OK;
661 }
662
663 CAResult_t CAGattClientSendData(char const * address,
664                                 uint8_t const * data,
665                                 size_t length,
666                                 CALEContext * context)
667 {
668     assert(context != NULL);
669
670     CAResult_t result = CA_STATUS_FAILED;
671
672     ca_mutex_lock(g_context.lock);
673
674     GDBusProxy * const characteristic =
675         G_DBUS_PROXY(
676             g_hash_table_lookup(g_context.characteristic_map,
677                                 address));
678
679     if (characteristic == NULL)
680     {
681         /*
682           GATT request characteristic corresponding to given address
683           was not found.
684         */
685
686         return result;
687     }
688
689     result = CAGattClientSendDataImpl(characteristic,
690                                       data,
691                                       length,
692                                       context);
693
694     ca_mutex_unlock(g_context.lock);
695
696     return result;
697 }
698
699 CAResult_t CAGattClientSendDataToAll(uint8_t const * data,
700                                      size_t length,
701                                      CALEContext * context)
702 {
703     assert(context != NULL);
704
705     CAResult_t result = CA_STATUS_FAILED;
706
707     ca_mutex_lock(g_context.lock);
708
709     if (g_context.characteristic_map == NULL)
710     {
711         // Remote OIC GATT service was not found prior to getting here.
712         ca_mutex_unlock(g_context.lock);
713         return result;
714     }
715
716     GHashTableIter iter;
717     g_hash_table_iter_init(&iter, g_context.characteristic_map);
718
719     gpointer characteristic;  // Value
720
721     /**
722      * @todo Will content of this hash table be potentially changed by
723      *       another thread during iteration?
724      */
725     while(g_hash_table_iter_next(&iter,
726                                  NULL,  // Key - unused
727                                  &characteristic))
728     {
729         result = CAGattClientSendDataImpl(G_DBUS_PROXY(characteristic),
730                                           data,
731                                           length,
732                                           context);
733
734         if (result != CA_STATUS_OK)
735         {
736             break;
737         }
738     }
739
740     ca_mutex_unlock(g_context.lock);
741
742     return result;
743 }