Imported Upstream version 1.1.1
[platform/upstream/iotivity.git] / resource / csdk / connectivity / src / bt_le_adapter / linux / caleinterface.c
index 632240a..57aa4eb 100644 (file)
@@ -57,7 +57,9 @@ static bool CALECheckStarted()
 {
     ca_mutex_lock(g_context.lock);
 
-    bool const started = (g_context.event_loop != NULL);
+    bool const started =
+        (g_context.event_loop != NULL
+         && g_main_loop_is_running(g_context.event_loop));
 
     ca_mutex_unlock(g_context.lock);
 
@@ -114,11 +116,6 @@ static void CALEOnInterfaceProxyPropertiesChanged(
     (void)manager;
     (void)object_proxy;
     (void)invalidated_properties;
-    OIC_LOG_V(DEBUG,
-              TAG,
-              "Properties Changed on %s:\n",
-              g_dbus_object_get_object_path(
-                  G_DBUS_OBJECT(object_proxy)));
 
     char const * const interface_name =
         g_dbus_proxy_get_interface_name(interface_proxy);
@@ -135,12 +132,23 @@ static void CALEOnInterfaceProxyPropertiesChanged(
         return;
     }
 
+    OIC_LOG_V(DEBUG,
+              TAG,
+              "%s properties Changed on %s:\n",
+              interface_name,
+              g_dbus_proxy_get_object_path(interface_proxy));
+
     CALEContext * const context = user_data;
 
     GVariantIter iter;
     gchar const * key   = NULL;
     GVariant    * value = NULL;
 
+    /**
+     * @todo Since we're only looking up one value here,
+     *       i.e. "Powered", can't we just use
+     *       g_variant_lookup_value() instead of this while() loop?
+     */
     g_variant_iter_init(&iter, changed_properties);
     while (g_variant_iter_next(&iter, "{&sv}", &key, &value))
     {
@@ -150,26 +158,10 @@ static void CALEOnInterfaceProxyPropertiesChanged(
               Report a change in the availability of the bluetooth
               adapter.
             */
-
             gboolean const powered = g_variant_get_boolean(value);
             CAAdapterState_t const status =
                 (powered ? CA_ADAPTER_ENABLED : CA_ADAPTER_DISABLED);
 
-            CAEndpoint_t info =
-                {
-                    .adapter = CA_ADAPTER_GATT_BTLE,
-                };
-
-            GVariant * const prop =
-                g_dbus_proxy_get_cached_property(interface_proxy,
-                                                 "Address");
-
-            gchar const * const address = g_variant_get_string(prop, NULL);
-
-            OICStrcpy(info.addr, sizeof(info.addr), address);
-
-            g_variant_unref(prop);
-
             /**
              * @todo Should we acquire the context lock here to
              *       prevent the @c CALEDeviceStateChangedCallback
@@ -346,6 +338,11 @@ static void CALEOnInterfacesRemoved(GDBusConnection * connection,
             if (strcmp(path,
                        g_dbus_proxy_get_object_path(proxy)) == 0)
             {
+                /**
+                 * @todo If a BlueZ Device was removed, update the
+                 *       characteristic map, accordingly.
+                 */
+
                 // Found a match!
                 g_object_unref(proxy);
 
@@ -366,40 +363,6 @@ static void CALEOnInterfacesRemoved(GDBusConnection * connection,
     }
 }
 
-static void CALEOnPropertiesChanged(GDBusConnection * connection,
-                                    char const * sender_name,
-                                    char const * object_path,
-                                    char const * interface_name,
-                                    char const * signal_name,
-                                    GVariant   * parameters,
-                                    gpointer     user_data)
-{
-    (void)connection;
-    (void)user_data;
-    CALEDumpDBusSignalParameters(sender_name,
-                                 object_path,
-                                 interface_name,
-                                 signal_name,
-                                 parameters);
-}
-
-static void CALEOnPropertyChanged(GDBusConnection * connection,
-                                  char const * sender_name,
-                                  char const * object_path,
-                                  char const * interface_name,
-                                  char const * signal_name,
-                                  GVariant   * parameters,
-                                  gpointer     user_data)
-{
-    (void)connection;
-    (void)user_data;
-    CALEDumpDBusSignalParameters(sender_name,
-                                 object_path,
-                                 interface_name,
-                                 signal_name,
-                                 parameters);
-}
-
 static void CALESubscribeToSignals(CALEContext * context,
                                    GDBusConnection * connection,
                                    GDBusObjectManager * object_manager)
@@ -437,52 +400,6 @@ static void CALESubscribeToSignals(CALEContext * context,
             NULL,  // user_data
             NULL);
 
-#if GLIB_CHECK_VERSION(2,38,0)
-    /*
-      The G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH flag was introduced in
-      GLib 2.38.
-    */
-    static GDBusSignalFlags const device_signal_flags =
-        G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH;
-#else
-    static GDBusSignalFlags const device_signal_flags =
-        G_DBUS_SIGNAL_FLAGS_NONE;
-#endif
-
-    /**
-     * @todo Verify that this signal subscription is needed.
-     *
-     * @bug The arg0 argument below should be a D-Bus object path, not
-     *      interface name.
-     */
-    guint const properties_changed_sub_id =
-        g_dbus_connection_signal_subscribe(
-            connection,
-            NULL,  // sender
-            "org.freedesktop.DBus.Properties",
-            "PropertiesChanged",
-            NULL,  // object path
-            "org.bluez.Device1",  // arg0
-            device_signal_flags,
-            CALEOnPropertiesChanged,
-            NULL,  // user_data
-            NULL);
-
-    /**
-     * @todo Verify that this signal subscription is needed.
-     */
-    guint const property_changed_sub_id =
-        g_dbus_connection_signal_subscribe(connection,
-                                           NULL,  // sender
-                                           BLUEZ_ADAPTER_INTERFACE,
-                                           "PropertyChanged",
-                                           NULL,  // object path
-                                           NULL,  // arg0
-                                           G_DBUS_SIGNAL_FLAGS_NONE,
-                                           CALEOnPropertyChanged,
-                                           NULL,  // user_data
-                                           NULL);
-
     g_signal_connect(object_manager,
                      "interface-proxy-properties-changed",
                      G_CALLBACK(CALEOnInterfaceProxyPropertiesChanged),
@@ -492,8 +409,6 @@ static void CALESubscribeToSignals(CALEContext * context,
 
     context->interfaces_added_sub_id   = interfaces_added_sub_id;
     context->interfaces_removed_sub_id = interfaces_removed_sub_id;
-    context->properties_changed_sub_id = properties_changed_sub_id;
-    context->property_changed_sub_id   = property_changed_sub_id;
 
     ca_mutex_unlock(context->lock);
 }
@@ -594,13 +509,9 @@ static void CALETearDownDBus(CALEContext * context)
 
     guint const interfaces_added   = context->interfaces_added_sub_id;
     guint const interfaces_removed = context->interfaces_removed_sub_id;
-    guint const properties_changed = context->properties_changed_sub_id;
-    guint const property_changed   = context->property_changed_sub_id;
 
     context->interfaces_added_sub_id   = 0;
     context->interfaces_removed_sub_id = 0;
-    context->properties_changed_sub_id = 0;
-    context->property_changed_sub_id   = 0;
 
     ca_mutex_unlock(context->lock);
 
@@ -626,10 +537,6 @@ static void CALETearDownDBus(CALEContext * context)
                                              interfaces_added);
         g_dbus_connection_signal_unsubscribe(connection,
                                              interfaces_removed);
-        g_dbus_connection_signal_unsubscribe(connection,
-                                             properties_changed);
-        g_dbus_connection_signal_unsubscribe(connection,
-                                             property_changed);
         g_object_unref(connection);
     }
 }
@@ -676,7 +583,6 @@ static bool CALEDeviceFilter(GDBusProxy * device)
     return accepted;
 }
 
-
 static bool CALESetUpBlueZObjects(CALEContext * context)
 {
     bool success = false;
@@ -734,11 +640,23 @@ static bool CALESetUpBlueZObjects(CALEContext * context)
         ca_mutex_unlock(context->lock);
     }
 
-    /* success = CAGattClientInitialize(context); */
-
     return success;
 }
 
+/**
+ * Inform thread waiting for the event loop to start that the loop has
+ * started.  This is done in the context of the event loop itself so
+ * that we can be certain that the event loop is indeed running.
+ */
+static gboolean CALEEventLoopStarted(gpointer user_data)
+{
+    sem_t * const sem = user_data;
+
+    (void) sem_post(sem);
+
+    return G_SOURCE_REMOVE;
+}
+
 static void CALEStartEventLoop(void * data)
 {
     CALEContext * const context = data;
@@ -749,13 +667,6 @@ static void CALEStartEventLoop(void * data)
     GMainContext * const loop_context = g_main_context_new();
     GMainLoop * const event_loop = g_main_loop_new(loop_context, FALSE);
 
-    ca_mutex_lock(context->lock);
-
-    assert(context->event_loop == NULL);
-    context->event_loop = event_loop;
-
-    ca_mutex_unlock(context->lock);
-
     g_main_context_push_thread_default(loop_context);
 
     /*
@@ -764,12 +675,41 @@ static void CALEStartEventLoop(void * data)
       signal handling occurs in the same thread as the one running the
       GLib event loop.
     */
-    if (!CALESetUpDBus(&g_context))
-        return;
+    if (CALESetUpDBus(&g_context))
+    {
+        ca_mutex_lock(context->lock);
+
+        assert(context->event_loop == NULL);
+        context->event_loop = event_loop;
 
-    ca_cond_signal(g_context.condition);
+        ca_mutex_unlock(context->lock);
+
+        /*
+          Add an idle handler that notifies a thread waiting for the
+          GLib event loop to run that the event loop is actually
+          running.  We do this in the context of the event loop itself
+          to avoid race conditions.
+        */
+        GSource * const source = g_idle_source_new();
+        g_source_set_priority(source, G_PRIORITY_HIGH_IDLE);
+        g_source_set_callback(source,
+                              CALEEventLoopStarted,
+                              &context->le_started,  // data
+                              NULL);                 // notify
+        (void) g_source_attach(source, loop_context);
+        g_source_unref(source);
+
+        g_main_loop_run(event_loop);  // Blocks until loop is quit.
+
+        CALETearDownDBus(&g_context);
+    }
 
-    g_main_loop_run(event_loop);
+    /*
+      Clean up in the same thread to avoid having to explicitly bump
+      the ref count to retain ownership.
+    */
+    g_main_context_unref(loop_context);
+    g_main_loop_unref(event_loop);
 }
 
 static void CALEStopEventLoop(CALEContext * context)
@@ -791,10 +731,7 @@ static void CALEStopEventLoop(CALEContext * context)
         if (loop_context != NULL)
         {
             g_main_context_wakeup(loop_context);
-            g_main_context_unref(loop_context);
         }
-
-        g_main_loop_unref(event_loop);
     }
 }
 
@@ -818,7 +755,7 @@ static bool CALEWaitForNonEmptyList(GList * const * list,
     {
         if (ca_cond_wait_for(g_context.condition,
                              g_context.lock,
-                             timeout) == 0)
+                             timeout) == CA_WAIT_SUCCESS)
         {
             /*
               Condition variable was signaled before the timeout was
@@ -833,48 +770,6 @@ static bool CALEWaitForNonEmptyList(GList * const * list,
     return success;
 }
 
-static CAResult_t CALEStop()
-{
-    CAResult_t result = CA_STATUS_FAILED;
-
-    OIC_LOG(DEBUG, TAG, "Stop Linux BLE adapter.");
-
-    // Only stop if we were previously started.
-    if (!CALECheckStarted())
-    {
-        return result;
-    }
-
-    // Stop the event loop thread regardless of previous errors.
-    CALEStopEventLoop(&g_context);
-
-    CALETearDownDBus(&g_context);
-
-    return result;
-}
-
-static void CALETerminate()
-{
-    OIC_LOG(DEBUG, TAG, "Terminate BLE adapter.");
-
-    CAPeripheralFinalize();
-
-    ca_mutex_lock(g_context.lock);
-
-    g_context.on_device_state_changed = NULL;
-    g_context.on_server_received_data = NULL;
-    g_context.on_client_received_data = NULL;
-    g_context.client_thread_pool      = NULL;
-    g_context.server_thread_pool      = NULL;
-    g_context.on_client_error         = NULL;
-    g_context.on_server_error         = NULL;
-
-    ca_mutex_unlock(g_context.lock);
-
-    ca_cond_free(g_context.condition);
-    ca_mutex_free(g_context.lock);
-}
-
 // -----------------------------------------------------------------------
 
 CAResult_t CAInitializeLEAdapter()
@@ -889,11 +784,6 @@ CAResult_t CAInitializeLEAdapter()
     g_type_init();
 #endif
 
-    g_context.lock      = ca_mutex_new();
-    g_context.condition = ca_cond_new();
-
-    CAPeripheralInitialize();
-
     return CA_STATUS_OK;
 }
 
@@ -911,7 +801,7 @@ CAResult_t CAStartLEAdapter()
     // Only start if we were previously stopped.
     if (CALECheckStarted())
     {
-      return result;
+        return result;
     }
 
     /**
@@ -926,94 +816,191 @@ CAResult_t CAStartLEAdapter()
      *       thread pool before the transport adapters prevents us
      *       from doing that without potentially triggering a
      *       @c pthread_join() call that blocks indefinitely due to
-     *       this event loop not be stopped.  See the comments in the
-     *       @c CAGetLEInterfaceInformation() function below for
+     *       this event loop not being stopped.  See the comments in
+     *       the @c CAGetLEInterfaceInformation() function below for
      *       further details.
      */
     result = ca_thread_pool_add_task(g_context.client_thread_pool,
                                      CALEStartEventLoop,
                                      &g_context);
 
-    if (result != CA_STATUS_OK)
+    /*
+      Wait for the GLib event loop to actually run before returning.
+
+      This addresses corner cases where operations are incorrectly
+      permitted to run in parallel before the event loop is run. For
+      example, the LE transport could have been stopped in a thread
+      parallel to the one starting the event loop start.  In that case
+      the GLib event loop may never exit since the stop operation that
+      causes the event loop to exit occurred before event loop
+      started.  That ultimately causes the CA layer termination to
+      block indefinitely on a pthread_join().  The solution is to only
+      return from the LE transport start operation once we know the
+      event loop is up and running.
+    */
+    struct timespec abs_timeout;
+    if (result == CA_STATUS_OK
+        && clock_gettime(CLOCK_REALTIME, &abs_timeout) == 0)
     {
-        return result;
+        static time_t const relative_timeout = 2;  // seconds
+        abs_timeout.tv_sec += relative_timeout;
+
+        int const wait_result =
+            sem_timedwait(&g_context.le_started, &abs_timeout);
+
+        if (wait_result == 0)
+        {
+            result = CA_STATUS_OK;
+        }
     }
 
+    return result;
+}
+
+CAResult_t CAStopLEAdapter()
+{
     /*
-      Wait until initialization completes before continuing, basically
-      until some Bluetooth adapters were found.
+      This function is called by the connectivity abstraction when
+      CAUnselectNetwork(CA_ADAPTER_GATT_BTLE) is called by the user.
     */
 
-    // Number of times to wait for initialization to complete.
-    static int const retries = 2;
-
-    static uint64_t const timeout =
-        2 * MICROSECS_PER_SEC;  // Microseconds
+    OIC_LOG(DEBUG, TAG, "Stop Linux BLE adapter.");
 
-    if (CALEWaitForNonEmptyList(&g_context.adapters, retries, timeout))
+    // Only stop if we were previously started.
+    if (!CALECheckStarted())
     {
-        result = CA_STATUS_OK;
+        return CA_STATUS_FAILED;
     }
 
-    return result;
+    CALEStopEventLoop(&g_context);
+
+    return CA_STATUS_OK;
 }
 
+
 CAResult_t CAGetLEAdapterState()
 {
-    /**
-     * @todo To be implemented shortly as part of the effort to
-     *       address a critical code review that stated this BLE
-     *       transport should implement the interface defined in
-     *       caleinterface.h.
-     */
-    return CA_NOT_SUPPORTED;
+    CAResult_t result = CA_ADAPTER_NOT_ENABLED;
+
+    ca_mutex_lock(g_context.lock);
+
+    for (GList * l = g_context.adapters; l != NULL; l = l->next)
+    {
+        GDBusProxy * const adapter = G_DBUS_PROXY(l->data);
+        GVariant * const prop =
+            g_dbus_proxy_get_cached_property(adapter, "Powered");
+
+        if (prop == NULL)
+        {
+            // This should never happen!
+            result = CA_STATUS_FAILED;
+            break;
+        }
+
+        gboolean const powered = g_variant_get_boolean(prop);
+        g_variant_unref(prop);
+
+        if (powered)
+        {
+            result = CA_STATUS_OK;
+            break;
+
+            /*
+              No need to continue iterating since we have at least
+              one enabled Bluetooth adapter.
+            */
+        }
+    }
+
+    ca_mutex_unlock(g_context.lock);
+
+    return result;
 }
 
 CAResult_t CAInitializeLENetworkMonitor()
 {
     /**
-     * @todo To be implemented shortly as part of the effort to
-     *       address a critical code review that stated this BLE
-     *       transport should implement the interface defined in
-     *       caleinterface.h.
+     * @note "Network monitor" operations are started in the
+     *       @c CAStartLEAdapter() function rather than this function
+     *       due to glib/D-Bus signal handling threads related
+     *       issues.
+     *
+     * @see @c CAStartLEAdapter() for further details.
+     */
+
+    g_context.lock      = ca_mutex_new();
+    g_context.condition = ca_cond_new();
+
+    static int const PSHARED        = 0;  // shared between threads
+    static unsigned int const VALUE = 0;  // force sem_wait() to block
+
+    if (sem_init(&g_context.le_started, PSHARED, VALUE) != 0)
+    {
+        return CA_STATUS_FAILED;
+    }
+
+    /*
+      The CA LE interface doesn't expose a CAInitializeLEGattServer()
+      function so perform initialization here.
      */
+    CAPeripheralInitialize();
+
     return CA_STATUS_OK;
 }
 
 void CATerminateLENetworkMonitor()
 {
     /**
-     * @todo To be implemented shortly as part of the effort to
-     *       address a critical code review that stated this BLE
-     *       transport should implement the interface defined in
-     *       caleinterface.h.
+     * @note "Network monitor" operations are stopped in @c CALEStop()
+     *       since they are started in @c CAStartLEAdapter() rather
+     *       than @c CAInitializeLENetworkMonitor().
+     *
+     * @see @c CAStartLEAdapter() for further details.
      */
-}
 
-CAResult_t CASetLEAdapterStateChangedCb(CALEDeviceStateChangedCallback callback)
-{
+    /*
+      Since the CA LE interface doesn't expose a
+      CAInitializeLEGattServer() function, finalize the LE server
+      (peripheral) here rather than in CATerminateLEGattServer() to
+      ensure finalization is correctly paired with initialization.
+     */
+    CAPeripheralFinalize();
+
+    (void) sem_destroy(&g_context.le_started);
+
     ca_mutex_lock(g_context.lock);
-    g_context.on_device_state_changed = callback;
+
+    g_context.on_device_state_changed = NULL;
+    g_context.on_server_received_data = NULL;
+    g_context.on_client_received_data = NULL;
+    g_context.client_thread_pool      = NULL;
+    g_context.server_thread_pool      = NULL;
+    g_context.on_client_error         = NULL;
+    g_context.on_server_error         = NULL;
+
+    ca_cond_free(g_context.condition);
+    g_context.condition = NULL;
+
     ca_mutex_unlock(g_context.lock);
 
-    return CA_STATUS_OK;
+    ca_mutex_free(g_context.lock);
+    g_context.lock = NULL;
 }
 
-CAResult_t CAInitLENetworkMonitorMutexVariables()
+CAResult_t CASetLEAdapterStateChangedCb(
+    CALEDeviceStateChangedCallback callback)
 {
-    /*
-      This CA LE interface implementation doesn't use a network
-      monitor as the other platform implementationd do.
-    */
+    ca_mutex_lock(g_context.lock);
+    g_context.on_device_state_changed = callback;
+    ca_mutex_unlock(g_context.lock);
+
     return CA_STATUS_OK;
 }
 
-void CATerminateLENetworkMonitorMutexVariables()
+CAResult_t CASetLENWConnectionStateChangedCb(CALEConnectionStateChangedCallback callback)
 {
-    /*
-      This CA LE interface implementation doesn't use a network
-      monitor as the other platform implementationd do.
-    */
+    (void)callback;
+    return CA_NOT_SUPPORTED;
 }
 
 CAResult_t CAGetLEAddress(char **local_address)
@@ -1123,20 +1110,20 @@ CAResult_t CAStartLEGattServer()
 
 CAResult_t CAStopLEGattServer()
 {
-    CAResult_t result    = CAPeripheralStop();
-    CAResult_t const tmp = CALEStop();
-
-    if (result == CA_STATUS_OK && tmp != CA_STATUS_OK)
-    {
-        result = tmp;
-    }
+    return CAPeripheralStop();
+}
 
-    return result;
+CAResult_t CAInitializeLEGattServer()
+{
+    return CA_STATUS_OK;
 }
 
 void CATerminateLEGattServer()
 {
-    CALETerminate();
+    /*
+      See CATerminateLENetworkMonitor() to understand why the LE
+      peripheral is not finalized here.
+     */
 }
 
 void CASetLEReqRespServerCallback(CABLEDataReceivedCallback callback)
@@ -1150,90 +1137,25 @@ CAResult_t CAUpdateCharacteristicsToGattClient(char const * address,
                                                uint8_t const * value,
                                                uint32_t valueLen)
 {
-    (void)address;
-    (void)value;
-    (void)valueLen;
-    /**
-     * @todo To be implemented shortly as part of the effort to
-     *       address a critical code review that stated this BLE
-     *       transport should implement the interface defined in
-     *       caleinterface.h.
-     */
-    return CA_NOT_SUPPORTED;
+    return CAGattServerSendResponseNotification(address,
+                                                value,
+                                                valueLen);
 }
 
 CAResult_t CAUpdateCharacteristicsToAllGattClients(uint8_t const * value,
                                                    uint32_t valueLen)
 {
-    (void)value;
-    (void)valueLen;
-    /**
-     * @todo To be implemented shortly as part of the effort to
-     *       address a critical code review that stated this BLE
-     *       transport should implement the interface defined in
-     *       caleinterface.h.
-     */
-    return CA_NOT_SUPPORTED;
+    return CAGattServerSendResponseNotificationToAll(value, valueLen);
 }
 
 CAResult_t CAStartLEGattClient()
 {
-    return CACentralStart(&g_context);
-}
-
-void CAStopLEGattClient()
-{
-    (void) CACentralStop(&g_context);
-    (void) CALEStop();
-}
+    CAResult_t result = CACentralStart(&g_context);
 
-void CATerminateLEGattClient()
-{
-    CALETerminate();
-}
-
-void CACheckLEData()
-{
-    /*
-      This function is only used in single-threaded builds, but this
-      CA LE adapter implementation is multi-threaded.  Consequently,
-      this function is a no-op.
-     */
-}
-
-CAResult_t CAUpdateCharacteristicsToGattServer(
-    char const * remoteAddress,
-    uint8_t const * data,
-    uint32_t dataLen,
-    CALETransferType_t type,
-    int32_t position)
-{
-    (void)remoteAddress;
-    (void)data;
-    (void)dataLen;
-    (void)type;
-    (void)position;
-    /**
-     * @todo To be implemented shortly as part of the effort to
-     *       address a critical code review that stated this BLE
-     *       transport should implement the interface defined in
-     *       caleinterface.h.
-     */
-    return CA_NOT_SUPPORTED;
-}
-
-CAResult_t CAUpdateCharacteristicsToAllGattServers(uint8_t const * data,
-                                                   uint32_t length)
-{
-    OIC_LOG(DEBUG, TAG, "Send data to all");
-
-    /*
-      Multicast data is only sent when a request is sent from a client
-      across all endpoints.  We need not worry about sending a
-      response from a server here.
-    */
-
-    CAResult_t result = CA_STATUS_FAILED;
+    if (result != CA_STATUS_OK)
+    {
+        return result;
+    }
 
     ca_mutex_lock(g_context.lock);
     bool found_peripherals = (g_context.devices != NULL);
@@ -1241,17 +1163,6 @@ CAResult_t CAUpdateCharacteristicsToAllGattServers(uint8_t const * data,
 
     if (!found_peripherals)
     {
-        /*
-          Start discovery of LE peripherals that advertise the OIC
-          Transport Profile.
-        */
-        result = CACentralStartDiscovery(&g_context);
-
-        if (result != CA_STATUS_OK)
-        {
-            return -1;
-        }
-
         // Wait for LE peripherals to be discovered.
 
         // Number of times to wait for discovery to complete.
@@ -1264,17 +1175,7 @@ CAResult_t CAUpdateCharacteristicsToAllGattServers(uint8_t const * data,
                                      retries,
                                      timeout))
         {
-            return -1;
-        }
-
-        ca_mutex_lock(g_context.lock);
-        found_peripherals = (g_context.devices == NULL);
-        ca_mutex_unlock(g_context.lock);
-
-        if (!found_peripherals)
-        {
-            // No peripherals discovered!
-            return -1;
+            return result;
         }
     }
 
@@ -1288,32 +1189,74 @@ CAResult_t CAUpdateCharacteristicsToAllGattServers(uint8_t const * data,
 
     if (result != CA_STATUS_OK)
     {
-        return -1;
+        return result;
     }
 
     bool const connected = CACentralConnectToAll(&g_context);
 
     if (!connected)
     {
-        return -1;
+        return result;
     }
 
     /**
-     * @todo Start notifications on all response characteristics.
+     * @todo Verify notifications have been enabled on all response
+     *       characteristics.
      */
 
+    return CAGattClientInitialize(&g_context);
+}
+
+void CAStopLEGattClient()
+{
+    CAGattClientDestroy();
+    (void) CACentralStop(&g_context);
+}
+
+CAResult_t CAInitializeLEGattClient()
+{
+    return CA_STATUS_OK;
+}
+
+void CATerminateLEGattClient()
+{
+}
+
+CAResult_t CAUpdateCharacteristicsToGattServer(
+    char const * remoteAddress,
+    uint8_t const * data,
+    uint32_t dataLen,
+    CALETransferType_t type,
+    int32_t position)
+{
+    (void) position;
+
+    if (type != LE_UNICAST)
+    {
+        return CA_STATUS_INVALID_PARAM;
+    }
+
     /*
-      Now send the request through all BLE connections through the
-      corresponding OIC GATT request characterstics.
+      We can assume that we're already connected to the BLE device
+      with the given remote address - we wouldn't have a remote
+      address otherwise - so there is no need to start scanning for
+      BLE devices.
     */
 
-    CAGattRequestInfo const info =
-        {
-            .characteristic_info = NULL,  // g_context.characteristics
-            .context = &g_context
-        };
+    return CAGattClientSendData(remoteAddress,
+                                data,
+                                dataLen,
+                                &g_context);
+}
 
-    return CAGattClientSendDataToAll(&info, data, length);
+CAResult_t CAUpdateCharacteristicsToAllGattServers(uint8_t const * data,
+                                                   uint32_t length)
+{
+    /*
+      Now send the request through all BLE connections through the
+      corresponding OIC GATT request characterstics.
+    */
+    return CAGattClientSendDataToAll(data, length, &g_context);
 
     /**
      * @todo Should we restart discovery after the send?