replace : iotivity -> iotivity-sec
[platform/upstream/iotivity.git] / resource / csdk / connectivity / src / bt_le_adapter / linux / client.c
index 4ca2224..299f54e 100644 (file)
 #include "bluez.h"
 #include "utils.h"
 
-#include "cafragmentation.h"
+#include "cagattservice.h"
 #include "logger.h"
 #include "oic_malloc.h"
 #include "oic_string.h"
 
 #include <gio/gio.h>
 
-#include <string.h>
+#include <strings.h>
 #include <assert.h>
 
 
 // Logging tag.
 static char const TAG[] = "BLE_CLIENT";
 
+typedef struct _CAGattClientContext
+{
+    /**
+     * Bluetooth MAC address to GATT characteristic map.
+     *
+     * Hash table that maps Bluetooth MAC address to a OIC Transport
+     * Profile GATT characteristic.  The key is a string containing
+     * the LE peripheral Bluetooth adapter MAC address.   The value is
+     * an interface proxy (@c GDBusProxy) to an
+     * @c org.bluez.GattCharacteristic1 object, i.e. OIC Transport
+     * Profile GATT request characteristic.
+     */
+    GHashTable * characteristic_map;
+
+    /**
+     * Response characteristic object path to Bluetooth MAC address map.
+     *
+     * Hash table that maps OIC Transport Profile GATT response
+     * characteristic D-Bus object path to Bluetooth MAC address.  The
+     * key is the D-Bus object path.  The value is the LE peripheral
+     * Bluetooth adapter MAC address.
+     *
+     * @note This map exists to avoid having to create a hierarchy of
+     *       GLib D-Bus proxies to the client side BlueZ GATT response
+     *       Characteristic, its corresponding GATT Service, and the
+     *       Device object within that Service, along with the
+     *       resulting D-Bus calls, simply so that we obtain the MAC
+     *       address of the remote (peripheral) LE device.  We
+     *       unfortunately need the MAC address since the
+     *       shared/common caleadapter code requires it.
+     */
+    GHashTable * address_map;
+
+    /// Mutex used to synchronize access to context fields.
+    oc_mutex lock;
+
+} CAGattClientContext;
+
+static CAGattClientContext g_context = {
+    .lock = NULL
+};
+
 // ---------------------------------------------------------------------
-//                        GATT Client Set-up
+//                      GATT Response Receive
 // ---------------------------------------------------------------------
-static bool CAGattClientServiceFilter(GDBusProxy * service)
+static void CAGattClientOnCharacteristicPropertiesChanged(
+    GDBusProxy * characteristic,
+    GVariant * changed_properties,
+    GStrv invalidated_properties,
+    gpointer user_data)
 {
     /*
-      On the client side, we only care about the GATT services on
-      remote devices.  Ignore the locally created ones by checking for
-      the existence of the org.bluez.GattService1.Device property in
-      the service proxy.  GATT services on remote devices will have
-      that property.
+      This handler is trigged in a GATT client when receiving data
+      sent by a GATT server through a notification, e.g. such as when
+      a GATT server sent a response.
     */
-    GVariant * const remote_device =
-        g_dbus_proxy_get_cached_property(service, "Device");
 
-    if (remote_device == NULL)
+    (void) invalidated_properties;
+
+    if (g_variant_n_children(changed_properties) < 1)
     {
-        return false;
+        /*
+          No changed properties, only invalidated ones which we don't
+          care about.
+        */
+        return;
     }
 
+    CALEContext * const context = user_data;
+    char const * const object_path =
+        g_dbus_proxy_get_object_path(characteristic);
+
+    oc_mutex_lock(g_context.lock);
+
+    char * const address =
+        g_hash_table_lookup(g_context.address_map, object_path);
+
     /*
-      org.bluez.GattService1.Device property exists, meaning the
-      GATT service was advertised from a remote object.
+      Address lookup could fail if a property changed on a GATT
+      characteristic that isn't an OIC Transport Profile GATT response
+      characteristic.  This isn't necessarily a problem since it's
+      possible other unrelated GATT charactertistics with changed
+      properties are exposed by BlueZ on the D-Bus system bus.
     */
-    g_object_unref(remote_device);
-    return true;
+    if (address != NULL)
+    {
+        CAGattRecvInfo info =
+            {
+                .peer               = address,
+                .on_packet_received = context->on_client_received_data,
+                .context            = context
+            };
+
+        GVariant * const value =
+            g_variant_lookup_value(changed_properties, "Value", NULL);
+
+        if (value != NULL)
+        {
+            // GLib maps an octet to a guchar, which is of size 1.
+            gsize length = 0;
+            gconstpointer const data =
+                g_variant_get_fixed_array(value, &length, 1);
+
+            (void) CAGattRecv(&info, data, length);
+
+            g_variant_unref(value);
+        }
+    }
+
+    oc_mutex_unlock(g_context.lock);
+}
+
+// ---------------------------------------------------------------------
+//                        GATT Client Set-up
+// ---------------------------------------------------------------------
+static bool CAGattClientMapInsert(GHashTable * map,
+                                  gpointer key,
+                                  gpointer value)
+{
+    bool const insert = !g_hash_table_contains(map, key);
+
+    if (insert)
+    {
+        g_hash_table_insert(map, key, value);
+    }
+
+    return insert;
 }
 
-bool CAGattClientsInitialize(CALEContext * context)
+static bool CAGattClientSetupCharacteristics(
+    GDBusProxy * service,
+    char const * address,
+    GHashTable * characteristic_map,
+    GHashTable * address_map,
+    CALEContext * context)
 {
+    bool success = true;
+
+    GVariant * const characteristics_prop =
+        g_dbus_proxy_get_cached_property(service, "Characteristics");
+
+    gsize length = 0;
+    gchar const ** const characteristic_paths =
+        g_variant_get_objv(characteristics_prop, &length);
+
+#ifdef TB_LOG
+    if (length == 0)
+    {
+        OIC_LOG(ERROR,
+                TAG,
+                "Server side OIC GATT Service has no characteristics");
+    }
+#endif
+
     /*
-      Create a proxies to the org.bluez.GattService1 D-Bus objects that
-      will later be used to send requests and receive responses on the
-      client side.
+      Create a proxies to the org.bluez.GattCharacteristic1 D-Bus
+      objects that will later be used to send requests and receive
+      responses on the client side.
     */
-    GList * services = NULL;
-    bool success =
-        CAGetBlueZManagedObjectProxies(&services,
-                                       BLUEZ_GATT_SERVICE_INTERFACE,
-                                       context,
-                                       CAGattClientServiceFilter);
+    gchar const * const * const end = characteristic_paths + length;
+    for (gchar const * const * path = characteristic_paths;
+         path != end && success;
+         ++path)
+    {
+        // Find the request characteristic.
+        GError * error = NULL;
+
+        GDBusProxy * const characteristic =
+            g_dbus_proxy_new_sync(context->connection,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  NULL, // GDBusInterfaceInfo
+                                  BLUEZ_NAME,
+                                  *path,
+                                  BLUEZ_GATT_CHARACTERISTIC_INTERFACE,
+                                  NULL, // GCancellable
+                                  &error);
+
+        if (characteristic == NULL)
+        {
+            OIC_LOG_V(ERROR,
+                      TAG,
+                      "Unable to obtain proxy to GATT characteristic: %s",
+                      error->message);
 
-    /**
-     * @todo Is this really an error?
-     */
-    if (!success)
+            g_error_free(error);
+
+            success = false;
+
+            break;
+        }
+
+        GVariant * const uuid_prop =
+            g_dbus_proxy_get_cached_property(characteristic, "UUID");
+
+        char const * const uuid =
+            g_variant_get_string(uuid_prop, NULL);
+
+        if (strcasecmp(uuid, CA_GATT_REQUEST_CHRC_UUID) == 0)
+        {
+            char     * const addr = OICStrdup(address);
+            gpointer * const chrc = g_object_ref(characteristic);
+
+            // Map LE (MAC) address to request characteristic.
+            if (!CAGattClientMapInsert(characteristic_map, addr, chrc))
+            {
+                OIC_LOG_V(WARNING,
+                          TAG,
+                          "Possible duplicate OIC GATT "
+                          "request characteristic proxy detected.");
+
+                g_object_unref(chrc);
+                OICFree(addr);
+            }
+        }
+        else if (strcasecmp(uuid, CA_GATT_RESPONSE_CHRC_UUID) == 0)
+        {
+            char * const p    = OICStrdup(*path);
+            char * const addr = OICStrdup(address);
+
+            // Map GATT service D-Bus object path to client address.
+            if (!CAGattClientMapInsert(address_map, p, addr))
+            {
+                OIC_LOG_V(WARNING,
+                          TAG,
+                          "Unable to register duplicate "
+                          "peripheral MAC address");
+
+                success = false;
+
+                OICFree(addr);
+                OICFree(p);
+            }
+            else
+            {
+                /*
+                  Detect changes in GATT characteristic properties.
+                  This is only relevant to OIC response
+                  characteristics since only their "Value" property
+                  will ever change.
+                */
+                g_signal_connect(
+                    characteristic,
+                    "g-properties-changed",
+                    G_CALLBACK(CAGattClientOnCharacteristicPropertiesChanged),
+                    context);
+
+                // Enable notifications.
+                GVariant * const ret =
+                    g_dbus_proxy_call_sync(
+                        characteristic,
+                        "StartNotify",
+                        NULL,  // parameters
+                        G_DBUS_CALL_FLAGS_NONE,
+                        -1,    // timeout (default == -1),
+                        NULL,  // cancellable
+                        &error);
+
+                if (ret == NULL)
+                {
+                    OIC_LOG_V(ERROR,
+                              TAG,
+                              "Failed to enable GATT notifications: %s",
+                              error->message);
+
+                    g_error_free(error);
+                    g_hash_table_remove(address_map, address);
+                    success = false;
+                }
+                else
+                {
+                    g_variant_unref(ret);
+                }
+            }
+        }
+#ifdef TB_LOG
+        else
+        {
+            OIC_LOG_V(WARNING,
+                      TAG,
+                      "Unrecognized characteristic UUID "
+                      "in OIC GATT service: %s",
+                      uuid);
+        }
+#endif
+
+        g_variant_unref(uuid_prop);
+        g_object_unref(characteristic);
+    }
+
+    g_free(characteristic_paths);
+    g_variant_unref(characteristics_prop);
+
+    return success;
+}
+
+static bool CAGattClientSetupService(
+    GDBusProxy * device,
+    GHashTable * characteristic_map,
+    GHashTable * address_map,
+    GVariant   * services_prop,
+    CALEContext * context)
+{
+    bool success = true;
+
+    GVariant * const address_prop =
+        g_dbus_proxy_get_cached_property(device, "Address");
+
+    char const * const address =
+        g_variant_get_string(address_prop, NULL);
+
+    /*
+      Create a proxies to the org.bluez.GattService1 D-Bus objects
+      that implement the OIC Transport Profile on the client side.
+
+      The services_prop argument will be non-NULL if changes to the
+      org.bluez.Device1.GattServices property were detected
+      asynchronously through the PropertiesChanged signal.
+    */
+    if (services_prop != NULL)
+    {
+        /*
+          The caller owns the variant so hold on to a reference since
+          we assume ownership in this function.
+        */
+        g_variant_ref(services_prop);
+    }
+    else
+    {
+        // Check if GATT services have already been discovered.
+        services_prop =
+            g_dbus_proxy_get_cached_property(device, "GattServices");
+    }
+
+    gsize length = 0;
+    char const ** const service_paths =
+        services_prop != NULL
+        ? g_variant_get_objv(services_prop, &length)
+        : NULL;
+
+#ifdef TB_LOG
+    if (length == 0)
+    {
+        // GATT services may not yet have been discovered.
+        OIC_LOG_V(INFO,
+                  TAG,
+                  "GATT services not yet discovered "
+                  "on LE peripheral: %s\n",
+                  address);
+    }
+#endif
+
+    gchar const * const * const end = service_paths + length;
+    for (gchar const * const * path = service_paths;
+         path != end && success;
+         ++path)
+    {
+        // Find the OIC Transport Profile GATT service.
+        GError * error = NULL;
+
+        GDBusProxy * const service =
+            g_dbus_proxy_new_sync(context->connection,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  NULL, // GDBusInterfaceInfo
+                                  BLUEZ_NAME,
+                                  *path,
+                                  BLUEZ_GATT_SERVICE_INTERFACE,
+                                  NULL, // GCancellable
+                                  &error);
+
+        if (service == NULL)
+        {
+            OIC_LOG_V(ERROR,
+                      TAG,
+                      "Unable to obtain proxy to GATT service: %s",
+                      error->message);
+
+            g_error_free(error);
+
+            success = false;
+
+            break;
+        }
+
+        GVariant * const uuid_prop =
+            g_dbus_proxy_get_cached_property(service, "UUID");
+
+        char const * const uuid =
+            g_variant_get_string(uuid_prop, NULL);
+
+        if (strcasecmp(uuid, CA_GATT_SERVICE_UUID) == 0)
+        {
+            success = CAGattClientSetupCharacteristics(service,
+                                                       address,
+                                                       characteristic_map,
+                                                       address_map,
+                                                       context);
+
+#ifdef TB_LOG
+            if (!success)
+            {
+                OIC_LOG_V(ERROR,
+                          TAG,
+                          "Characteristic set up for "
+                          "GATT service at %s failed.",
+                          address);
+            }
+#endif  // TB_LOG
+        }
+
+        g_variant_unref(uuid_prop);
+        g_object_unref(service);
+    }
+
+    if (services_prop != NULL)
     {
-        return success;
+        g_variant_unref(services_prop);
     }
 
+    g_variant_unref(address_prop);
+
+    return success;
+}
+
+static void CAGattClientOnDevicePropertiesChanged(
+    GDBusProxy * device,
+    GVariant * changed_properties,
+    GStrv invalidated_properties,
+    gpointer user_data)
+{
+    /*
+      This handler is trigged in a GATT client when org.bluez.Device1
+      properties have changed.
+    */
+
+    (void) invalidated_properties;
+
+    /*
+      Retrieve the org.bluez.Device1.GattServices property from the
+      changed_properties dictionary parameter (index 1).
+    */
+    GVariant * const services_prop =
+        g_variant_lookup_value(changed_properties, "GattServices", NULL);
+
+    if (services_prop != NULL)
+    {
+        CALEContext * const context = user_data;
+
+        oc_mutex_lock(g_context.lock);
+
+        CAGattClientSetupService(device,
+                                 g_context.characteristic_map,
+                                 g_context.address_map,
+                                 services_prop,
+                                 context);
+
+        oc_mutex_unlock(g_context.lock);
+
+        g_variant_unref(services_prop);
+    }
+}
+
+CAResult_t CAGattClientInitialize(CALEContext * context)
+{
+    g_context.lock = oc_mutex_new();
+
     /*
       Map Bluetooth MAC address to OIC Transport Profile
-      characteristics.
+      request characteristics.
     */
     GHashTable * const characteristic_map =
         g_hash_table_new_full(g_str_hash,
@@ -96,57 +511,96 @@ bool CAGattClientsInitialize(CALEContext * context)
                               OICFree,
                               g_object_unref);
 
-    char const * const address = NULL;  // OICMalloc(...);
-    GDBusProxy * const client = NULL;
-
-#if GLIB_CHECK_VERSION(2,40,0)
     /*
-      GLib hash table functions started returning a boolean result in
-       version 2.40.x.
+      Map OIC Transport Profile response characteristic D-Bus object
+      path to Bluetooth MAC address.
     */
-    success =
-#endif
-        g_hash_table_insert(characteristic_map,
-                            OICStrdup(address),
-                            client);
+    GHashTable * const address_map =
+        g_hash_table_new_full(g_str_hash,
+                              g_str_equal,
+                              OICFree,
+                              OICFree);
+
+    oc_mutex_lock(context->lock);
 
-    // An empty services list is NULL.
-    if (success && services != NULL)
+    for (GList * l = context->devices; l != NULL; l = l->next)
     {
-        ca_mutex_lock(context->lock);
-        context->characteristic_map = characteristic_map;
-        ca_mutex_unlock(context->lock);
+        GDBusProxy * const device = G_DBUS_PROXY(l->data);
+
+        /*
+          Detect changes in BlueZ Device properties.  This is
+          predominantly used to detect GATT services that were
+          discovered asynchronously.
+        */
+        g_signal_connect(
+            device,
+            "g-properties-changed",
+            G_CALLBACK(CAGattClientOnDevicePropertiesChanged),
+            context);
+
+        CAGattClientSetupService(device,
+                                 characteristic_map,
+                                 address_map,
+                                 NULL,
+                                 context);
     }
 
-    return success;
+    oc_mutex_unlock(context->lock);
+
+    oc_mutex_lock(g_context.lock);
+
+    g_context.characteristic_map = characteristic_map;
+    g_context.address_map = address_map;
+
+    oc_mutex_unlock(g_context.lock);
+
+    return CA_STATUS_OK;
 }
 
-bool CAGattClientsDestroy(CALEContext * context)
+void CAGattClientDestroy()
 {
-    (void)context;
-    /* g_hash_table_destroy(...); */   // FIXME
-    return false;
+    if (g_context.lock == NULL)
+    {
+        return;  // Initialization did not complete.
+    }
+
+    oc_mutex_lock(g_context.lock);
+
+    if (g_context.characteristic_map != NULL)
+    {
+        g_hash_table_unref(g_context.characteristic_map);
+        g_context.characteristic_map = NULL;
+    }
+
+    if (g_context.address_map != NULL)
+    {
+        g_hash_table_unref(g_context.address_map);
+        g_context.address_map = NULL;
+    }
+
+    oc_mutex_unlock(g_context.lock);
+
+    oc_mutex_free(g_context.lock);
+    g_context.lock = NULL;
+
+    /*
+      We don't explicitly stop notifications on the response
+      characteristic since they should be stopped upon server side
+      disconnection.
+     */
 }
 
 // ---------------------------------------------------------------------
-//                        GATT Request Send
+//                      GATT Request Data Send
 // ---------------------------------------------------------------------
-/**
- * Send data to the GATT server through the given request
- * @a characteristic proxy.
- *
- * @param[in] characteristic The D-Bus proxy of the request
- *                           characteristic through which the
- *                           @c WriteValue() method will be invoked.
- * @param[in] data           The byte array to be sent.
- * @param[in] length         The number of elements in the byte
- *                           array.
- */
-static bool CAGattClientSendRequestData(GDBusProxy * characteristic,
-                                        CALEContext * context,
-                                        char const * data,
-                                        size_t length)
+
+static CAResult_t CAGattClientSendDataImpl(GDBusProxy * characteristic,
+                                           uint8_t const * data,
+                                           size_t length,
+                                           CALEContext * context)
 {
+    assert(characteristic != NULL);
+    assert(data != NULL);
     assert(context != NULL);
 
     GVariant * const value =
@@ -155,12 +609,18 @@ static bool CAGattClientSendRequestData(GDBusProxy * characteristic,
                                   length,
                                   1);  // sizeof(data[0]) == 1
 
+    /*
+      WriteValue() expects a byte array but it must be packed into a
+      tuple for the actual call through the proxy.
+    */
+    GVariant * const value_parameter = g_variant_new("(@ay)", value);
+
     GError * error = NULL;
 
     GVariant * const ret =
         g_dbus_proxy_call_sync(characteristic,
                                "WriteValue",
-                               value,  // parameters
+                               value_parameter,  // parameters
                                G_DBUS_CALL_FLAGS_NONE,
                                -1,    // timeout (default == -1),
                                NULL,  // cancellable
@@ -176,7 +636,7 @@ static bool CAGattClientSendRequestData(GDBusProxy * characteristic,
 
         g_error_free(error);
 
-        ca_mutex_lock(context->lock);
+        oc_mutex_lock(context->lock);
 
         if (context->on_client_error != NULL)
         {
@@ -190,91 +650,94 @@ static bool CAGattClientSendRequestData(GDBusProxy * characteristic,
                                      CA_STATUS_FAILED);
         }
 
-        ca_mutex_unlock(context->lock);
+        oc_mutex_unlock(context->lock);
 
-        return false;
+        return CA_STATUS_FAILED;
     }
 
     g_variant_unref(ret);
 
-    return true;
+    return CA_STATUS_OK;
 }
 
-CAResult_t CAGattClientSendData(void const * method_info,
-                                void const * data,
-                                size_t length)
+CAResult_t CAGattClientSendData(char const * address,
+                                uint8_t const * data,
+                                size_t length,
+                                CALEContext * context)
 {
-    assert(method_info != NULL);
-
-    CAGattRequestInfo const * const info = method_info;
-
-    GDBusProxy * const characteristic =
-        G_DBUS_PROXY(info->characteristic_info);
-
-    return CAGattClientSendRequestData(characteristic,
-                                       info->context,
-                                       (char const *) data,
-                                       length);
-}
+    assert(context != NULL);
 
-CAResult_t CAGattClientSendDataToAll(void const * method_info,
-                                     void const * data,
-                                     size_t length)
-{
-    assert(method_info != NULL);
+    CAResult_t result = CA_STATUS_FAILED;
 
-    CAResult_t result = CA_STATUS_OK;
+    oc_mutex_lock(g_context.lock);
 
-    CAGattRequestInfo const * const info = method_info;
+    GDBusProxy * const characteristic =
+        G_DBUS_PROXY(
+            g_hash_table_lookup(g_context.characteristic_map,
+                                address));
 
-    for (GList const * l = info->characteristic_info;
-         l != NULL && result == CA_STATUS_OK;
-         l = l->next)
+    if (characteristic == NULL)
     {
-        GDBusProxy * const characteristic = G_DBUS_PROXY(l->data);
+        /*
+          GATT request characteristic corresponding to given address
+          was not found.
+        */
 
-        result = CAGattClientSendRequestData(characteristic,
-                                             info->context,
-                                             (char const *) data,
-                                             length);
+        return result;
     }
 
+    result = CAGattClientSendDataImpl(characteristic,
+                                      data,
+                                      length,
+                                      context);
+
+    oc_mutex_unlock(g_context.lock);
+
     return result;
 }
 
-// ---------------------------------------------------------------------
-//                      GATT Response Receive
-// ---------------------------------------------------------------------
-void CAGattReceiveResponse(GDBusConnection * connection,
-                           char const * sender_name,
-                           char const * object_path,
-                           char const * interface_name,
-                           char const * signal_name,
-                           GVariant   * parameters,
-                           gpointer     user_data)
+CAResult_t CAGattClientSendDataToAll(uint8_t const * data,
+                                     size_t length,
+                                     CALEContext * context)
 {
-    (void)connection;
-    (void)sender_name;
-    (void)object_path;
-    (void)interface_name;
-    (void)signal_name;
-    /*
-      This handler is only trigged in a GATT client when receiving
-      data sent by a GATT server through a notification, e.g. such as
-      when a GATT server sent a response.
-    */
-    gsize fragment_len = 0;
-    gconstpointer const fragment =
-        g_variant_get_fixed_array(parameters,
-                                  &fragment_len,
-                                  1);  // sizeof(guchar) == 1
+    assert(context != NULL);
 
-    CAGattRecvInfo * const info = user_data;
+    CAResult_t result = CA_STATUS_FAILED;
 
-    if (CAGattRecv(info, fragment, fragment_len))
+    oc_mutex_lock(g_context.lock);
+
+    if (g_context.characteristic_map == NULL)
     {
+        // Remote OIC GATT service was not found prior to getting here.
+        oc_mutex_unlock(g_context.lock);
+        return result;
     }
-    else
+
+    GHashTableIter iter;
+    g_hash_table_iter_init(&iter, g_context.characteristic_map);
+
+    gpointer characteristic;  // Value
+
+    /**
+     * @todo Will content of this hash table be potentially changed by
+     *       another thread during iteration?
+     */
+    while(g_hash_table_iter_next(&iter,
+                                 NULL,  // Key - unused
+                                 &characteristic))
     {
+        result = CAGattClientSendDataImpl(G_DBUS_PROXY(characteristic),
+                                          data,
+                                          length,
+                                          context);
+
+        if (result != CA_STATUS_OK)
+        {
+            break;
+        }
     }
+
+    oc_mutex_unlock(g_context.lock);
+
+    return result;
 }