From 97927b795429ddaaaf7d32ab47f58be3577fef86 Mon Sep 17 00:00:00 2001 From: Ossama Othman Date: Wed, 6 Jan 2016 16:40:02 -0800 Subject: [PATCH] [IOT-771] Reintroduce missing Linux BLE adapter code. The IoTivity BLE Linux transport doesn't send data due to missing code, e.g. stub implementations for several functions required by the CA LE interface. A incorrect revision was merged during the code review, likely because the gerrit draft revision with the correct implementation was not published, and ultimately lost. This patch reintroduces implementations for those functions. Change-Id: If9e9f7ebaeebb9072cd7762e94a82ef45809f95b Signed-off-by: Ossama Othman Signed-off-by: jihwan.seo Reviewed-on: https://gerrit.iotivity.org/gerrit/3011 Tested-by: jenkins-iotivity Reviewed-by: Jon A. Cruz --- resource/csdk/connectivity/inc/caleinterface.h | 13 + .../src/bt_le_adapter/android/calenwmonitor.c | 7 + .../src/bt_le_adapter/arduino/cablenwmonitor.cpp | 7 + .../connectivity/src/bt_le_adapter/caleadapter.c | 294 +++++---- .../src/bt_le_adapter/linux/caleinterface.c | 556 +++++++--------- .../connectivity/src/bt_le_adapter/linux/central.h | 3 + .../src/bt_le_adapter/linux/characteristic.c | 184 +----- .../connectivity/src/bt_le_adapter/linux/client.c | 722 +++++++++++++++++---- .../connectivity/src/bt_le_adapter/linux/client.h | 73 +-- .../connectivity/src/bt_le_adapter/linux/context.h | 26 - .../src/bt_le_adapter/linux/descriptor.c | 2 +- .../src/bt_le_adapter/linux/peripheral.c | 68 +- .../src/bt_le_adapter/linux/peripheral.h | 18 + .../connectivity/src/bt_le_adapter/linux/server.c | 128 ++-- .../connectivity/src/bt_le_adapter/linux/server.h | 53 +- .../connectivity/src/bt_le_adapter/linux/service.c | 71 +- .../connectivity/src/bt_le_adapter/linux/service.h | 34 +- tools/valgrind/iotivity.supp | 6 + 18 files changed, 1340 insertions(+), 925 deletions(-) diff --git a/resource/csdk/connectivity/inc/caleinterface.h b/resource/csdk/connectivity/inc/caleinterface.h index 330b666..e0be0dd 100644 --- a/resource/csdk/connectivity/inc/caleinterface.h +++ b/resource/csdk/connectivity/inc/caleinterface.h @@ -99,6 +99,19 @@ CAResult_t CAInitializeLEAdapter(); CAResult_t CAStartLEAdapter(); /** + * Stop the LE adapter layer. + * + * This function will be invoked from the CA layer when the LE + * "network" is unselected via @c CAUnselectNetwork(). It gives an + * opportunity for LE adapter implementations to perform operations + * after stopping a GATT client or server. Most LE adapter + * implementations will simply implement this function as no-op. + * + * @return ::CA_STATUS_OK or Appropriate error code + */ +CAResult_t CAStopLEAdapter(); + +/** * Used to get the current state of the LE adapter. * * @return ::CA_STATUS_OK or Appropriate error code diff --git a/resource/csdk/connectivity/src/bt_le_adapter/android/calenwmonitor.c b/resource/csdk/connectivity/src/bt_le_adapter/android/calenwmonitor.c index 5d0c6dc..368a507 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/android/calenwmonitor.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/android/calenwmonitor.c @@ -87,6 +87,13 @@ CAResult_t CAStartLEAdapter() return CA_STATUS_OK; } +CAResult_t CAStopLEAdapter() +{ + // Nothing to do. + + return CA_STATUS_OK; +} + CAResult_t CAInitLENwkMonitorMutexVaraibles() { OIC_LOG(DEBUG, TAG, "IN"); diff --git a/resource/csdk/connectivity/src/bt_le_adapter/arduino/cablenwmonitor.cpp b/resource/csdk/connectivity/src/bt_le_adapter/arduino/cablenwmonitor.cpp index 0466261..8c3c530 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/arduino/cablenwmonitor.cpp +++ b/resource/csdk/connectivity/src/bt_le_adapter/arduino/cablenwmonitor.cpp @@ -77,6 +77,13 @@ CAResult_t CAStartLEAdapter() return CA_STATUS_OK; } +CAResult_t CAStopLEAdapter() +{ + // Nothing to do. + + return CA_STATUS_OK; +} + CAResult_t CAGetLEAdapterState() { OIC_LOG(DEBUG, TAG, "IN"); diff --git a/resource/csdk/connectivity/src/bt_le_adapter/caleadapter.c b/resource/csdk/connectivity/src/bt_le_adapter/caleadapter.c index 549989d..e5585ae 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/caleadapter.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/caleadapter.c @@ -72,6 +72,14 @@ typedef struct CAEndpoint_t *remoteEndpoint; } CABLESenderInfo_t; +typedef enum +{ + ADAPTER_EMPTY = 1, + ADAPTER_BOTH_CLIENT_SERVER, + ADAPTER_CLIENT, + ADAPTER_SERVER +} CABLEAdapter_t; + /** * Callback to provide the status of the network change to CA layer. */ @@ -86,7 +94,7 @@ static char g_localBLEAddress[18] = { 0 }; /** * Variable to differentiate btw GattServer and GattClient. */ -static bool g_isServer = false; +static CABLEAdapter_t g_adapterType = ADAPTER_EMPTY; /** * Mutex to synchronize the task to be executed on the GattServer @@ -150,28 +158,6 @@ static CANetworkPacketReceivedCallback g_networkPacketReceivedCallback = NULL; static CAErrorHandleCallback g_errorHandler = NULL; /** - * Storing Adapter state information. - */ -static CAAdapterState_t g_bleAdapterState = CA_ADAPTER_DISABLED; - -/** - * BLE Server Status. - * - * This enumeration provides information of LE Adapter Server status. - */ -typedef enum -{ - CA_SERVER_NOTSTARTED = 0, - CA_LISTENING_SERVER, - CA_DISCOVERY_SERVER -} CALeServerStatus; - -/** - * Structure to maintain the status of the server. - */ -static CALeServerStatus gLeServerStatus = CA_SERVER_NOTSTARTED; - -/** * Register network change notification callback. * * @param[in] netCallback CANetworkChangeCallback callback which will @@ -606,20 +592,6 @@ static void CAStopLEQueues() { OIC_LOG(DEBUG, CALEADAPTER_TAG, "IN"); - ca_mutex_lock(g_bleClientSendDataMutex); - if (NULL != g_bleClientSendQueueHandle) - { - CAQueueingThreadStop(g_bleClientSendQueueHandle); - } - ca_mutex_unlock(g_bleClientSendDataMutex); - - ca_mutex_lock(g_bleServerSendDataMutex); - if (NULL != g_bleServerSendQueueHandle) - { - CAQueueingThreadStop(g_bleServerSendQueueHandle); - } - ca_mutex_unlock(g_bleServerSendDataMutex); - ca_mutex_lock(g_bleReceiveDataMutex); if (NULL != g_bleReceiverQueue) { @@ -1621,15 +1593,25 @@ static CAResult_t CALEAdapterGattServerStart() */ if (CA_STATUS_OK == result) { + ca_mutex_lock(g_bleServerSendDataMutex); result = CAQueueingThreadStart(g_bleServerSendQueueHandle); + ca_mutex_unlock(g_bleServerSendDataMutex); if (CA_STATUS_OK != result) { - OIC_LOG(ERROR, - CALEADAPTER_TAG, - "Unable to start server queuing thread"); + OIC_LOG_V(ERROR, + CALEADAPTER_TAG, + "Unable to start server queuing thread (%d)", + result); } } + else + { + OIC_LOG_V(ERROR, + CALEADAPTER_TAG, + "GATT server failed to start (%d)", + result); + } #endif return result; @@ -1638,7 +1620,9 @@ static CAResult_t CALEAdapterGattServerStart() static CAResult_t CALEAdapterGattServerStop() { #ifndef SINGLE_THREAD + ca_mutex_lock(g_bleServerSendDataMutex); CAResult_t result = CAQueueingThreadStop(g_bleServerSendQueueHandle); + ca_mutex_unlock(g_bleServerSendDataMutex); if (CA_STATUS_OK == result) { @@ -1664,7 +1648,9 @@ static CAResult_t CALEAdapterGattClientStart() */ if (CA_STATUS_OK == result) { + ca_mutex_lock(g_bleClientSendDataMutex); result = CAQueueingThreadStart(g_bleClientSendQueueHandle); + ca_mutex_unlock(g_bleClientSendDataMutex); if (CA_STATUS_OK != result) { @@ -1673,6 +1659,13 @@ static CAResult_t CALEAdapterGattClientStart() "Unable to start client queuing thread"); } } + else + { + OIC_LOG_V(ERROR, + CALEADAPTER_TAG, + "GATT client failed to start (%d)", + result); + } #endif return result; @@ -1681,7 +1674,9 @@ static CAResult_t CALEAdapterGattClientStart() static CAResult_t CALEAdapterGattClientStop() { #ifndef SINGLE_THREAD + ca_mutex_lock(g_bleClientSendDataMutex); CAResult_t result = CAQueueingThreadStop(g_bleClientSendQueueHandle); + ca_mutex_unlock(g_bleClientSendDataMutex); if (CA_STATUS_OK == result) { @@ -1790,21 +1785,26 @@ static CAResult_t CAStopLE() #endif ca_mutex_lock(g_bleIsServerMutex); - if (true == g_isServer) - { - CALEAdapterGattServerStop(); - } - else - { - CALEAdapterGattClientStop(); + switch (g_adapterType) + { + case ADAPTER_SERVER: + CALEAdapterGattServerStop(); + break; + case ADAPTER_CLIENT: + CALEAdapterGattClientStop(); + break; + case ADAPTER_BOTH_CLIENT_SERVER: + CALEAdapterGattServerStop(); + CALEAdapterGattClientStop(); + break; + default: + break; } ca_mutex_unlock(g_bleIsServerMutex); - gLeServerStatus = CA_SERVER_NOTSTARTED; - OIC_LOG(DEBUG, CALEADAPTER_TAG, "OUT"); - return CA_STATUS_OK; + return CAStopLEAdapter(); } static void CATerminateLE() @@ -1818,14 +1818,22 @@ static void CATerminateLE() CATerminateLENetworkMonitor(); ca_mutex_lock(g_bleIsServerMutex); - if (true == g_isServer) - { - CATerminateLEGattServer(); - } - else - { - CATerminateLEGattClient(); - } + switch (g_adapterType) + { + case ADAPTER_SERVER: + CATerminateLEGattServer(); + break; + case ADAPTER_CLIENT: + CATerminateLEGattClient(); + break; + case ADAPTER_BOTH_CLIENT_SERVER: + CATerminateLEGattServer(); + CATerminateLEGattClient(); + break; + default: + break; + } + g_adapterType = ADAPTER_EMPTY; ca_mutex_unlock(g_bleIsServerMutex); #ifndef SINGLE_THREAD @@ -1846,30 +1854,43 @@ static CAResult_t CAStartLEListeningServer() if (CA_STATUS_OK != result) { OIC_LOG(ERROR, CALEADAPTER_TAG, "CAInitLEServerQueues failed"); - return CA_STATUS_FAILED; + return result; } #endif result = CAGetLEAdapterState(); - if (CA_ADAPTER_NOT_ENABLED == result) - { - gLeServerStatus = CA_LISTENING_SERVER; - OIC_LOG(DEBUG, CALEADAPTER_TAG, "Listen Server will be started once BT Adapter is enabled"); - return CA_STATUS_OK; - } if (CA_STATUS_FAILED == result) { OIC_LOG(ERROR, CALEADAPTER_TAG, "Bluetooth get state failed!"); - return CA_STATUS_FAILED; + return result; } - result = CALEAdapterGattServerStart(); - ca_mutex_lock(g_bleIsServerMutex); - g_isServer = true; + switch (g_adapterType) + { + case ADAPTER_CLIENT: + g_adapterType = ADAPTER_BOTH_CLIENT_SERVER; + break; + case ADAPTER_BOTH_CLIENT_SERVER: + break; + default: + g_adapterType = ADAPTER_SERVER; + } ca_mutex_unlock(g_bleIsServerMutex); + if (CA_ADAPTER_NOT_ENABLED == result) + { + OIC_LOG(DEBUG, + CALEADAPTER_TAG, + "Listen Server will be started once BT Adapter is enabled"); + result = CA_STATUS_OK; + } + else + { + result = CALEAdapterGattServerStart(); + } + OIC_LOG(DEBUG, CALEADAPTER_TAG, "OUT"); return result; #else @@ -1894,29 +1915,40 @@ static CAResult_t CAStartLEDiscoveryServer() if (CA_STATUS_OK != result) { OIC_LOG(ERROR, CALEADAPTER_TAG, "CAInitLEClientQueues failed"); - return CA_STATUS_FAILED; + return result; } #endif result = CAGetLEAdapterState(); - if (CA_ADAPTER_NOT_ENABLED == result) - { - gLeServerStatus = CA_DISCOVERY_SERVER; - OIC_LOG(DEBUG, CALEADAPTER_TAG, "Listen Server will be started once BT Adapter is enabled"); - return CA_STATUS_OK; - } if (CA_STATUS_FAILED == result) { OIC_LOG(ERROR, CALEADAPTER_TAG, "Bluetooth get state failed!"); - return CA_STATUS_FAILED; + return result; } - result = CALEAdapterGattClientStart(); - ca_mutex_lock(g_bleIsServerMutex); - g_isServer = false; + switch (g_adapterType) + { + case ADAPTER_SERVER: + g_adapterType = ADAPTER_BOTH_CLIENT_SERVER; + break; + case ADAPTER_BOTH_CLIENT_SERVER: + break; + default: + g_adapterType = ADAPTER_CLIENT; + } ca_mutex_unlock(g_bleIsServerMutex); + if (CA_ADAPTER_NOT_ENABLED == result) + { + OIC_LOG(DEBUG, CALEADAPTER_TAG, "Discovery server will be started once BT Adapter is enabled"); + result = CA_STATUS_OK; + } + else + { + result = CALEAdapterGattClientStart(); + } + OIC_LOG(DEBUG, CALEADAPTER_TAG, "OUT"); return result; } @@ -1944,31 +1976,34 @@ static int32_t CASendLEUnicastData(const CAEndpoint_t *endpoint, CAResult_t result = CA_STATUS_FAILED; ca_mutex_lock(g_bleIsServerMutex); - if (true == g_isServer) + if (ADAPTER_SERVER == g_adapterType || ADAPTER_BOTH_CLIENT_SERVER == g_adapterType) { result = CALEAdapterServerSendData(endpoint, data, dataLen); if (CA_STATUS_OK != result) { - OIC_LOG(ERROR, CALEADAPTER_TAG, "Send unicast data failed\n"); + ca_mutex_unlock(g_bleIsServerMutex); + OIC_LOG(ERROR, CALEADAPTER_TAG, "Send unicast data for server failed"); if (g_errorHandler) { g_errorHandler(endpoint, data, dataLen, result); } - ca_mutex_unlock(g_bleIsServerMutex); + return -1; } } - else + + if (ADAPTER_CLIENT == g_adapterType || ADAPTER_BOTH_CLIENT_SERVER == g_adapterType) { result = CALEAdapterClientSendData(endpoint, data, dataLen); if (CA_STATUS_OK != result) { - OIC_LOG(ERROR, CALEADAPTER_TAG, "Send unicast data failed \n"); - if (g_errorHandler) - { - g_errorHandler(endpoint, data, dataLen, result); - } ca_mutex_unlock(g_bleIsServerMutex); + OIC_LOG(ERROR, CALEADAPTER_TAG, "Send unicast data for client failed" ); + + if (g_errorHandler) + { + g_errorHandler(endpoint, data, dataLen, result); + } return -1; } } @@ -1996,14 +2031,15 @@ static int32_t CASendLEMulticastData(const CAEndpoint_t *endpoint, CAResult_t result = CA_STATUS_FAILED; ca_mutex_lock(g_bleIsServerMutex); - if (true == g_isServer) + if (ADAPTER_SERVER == g_adapterType || ADAPTER_BOTH_CLIENT_SERVER == g_adapterType) { result = CALEAdapterServerSendData(NULL, data, dataLen); if (CA_STATUS_OK != result) { - OIC_LOG(ERROR, CALEADAPTER_TAG, "Send multicast data failed" ); - ca_mutex_unlock(g_bleIsServerMutex); + + OIC_LOG(ERROR, CALEADAPTER_TAG, "Send multicast data for server failed" ); + if (g_errorHandler) { g_errorHandler(endpoint, data, dataLen, result); @@ -2011,17 +2047,20 @@ static int32_t CASendLEMulticastData(const CAEndpoint_t *endpoint, return -1; } } - else + + if (ADAPTER_CLIENT == g_adapterType || ADAPTER_BOTH_CLIENT_SERVER == g_adapterType) { result = CALEAdapterClientSendData(NULL, data, dataLen); if (CA_STATUS_OK != result) { - OIC_LOG(ERROR, CALEADAPTER_TAG, "Send Multicast data failed" ); + ca_mutex_unlock(g_bleIsServerMutex); + + OIC_LOG(ERROR, CALEADAPTER_TAG, "Send Multicast data for client failed" ); + if (g_errorHandler) { g_errorHandler(endpoint, data, dataLen, result); } - ca_mutex_unlock(g_bleIsServerMutex); return -1; } } @@ -2127,46 +2166,45 @@ static void CALEDeviceStateChangedCb(CAAdapterState_t adapter_state) g_localBLEAddress); ca_mutex_unlock(g_bleLocalAddressMutex); - g_bleAdapterState = adapter_state; - // Start a GattServer/Client if gLeServerStatus is SET - if (CA_LISTENING_SERVER == gLeServerStatus) + if (CA_ADAPTER_ENABLED == adapter_state) { - const CAResult_t result = - (CA_ADAPTER_ENABLED == adapter_state - ? CALEAdapterGattServerStart() - : CALEAdapterGattServerStop()); - - if (CA_STATUS_OK != result) + ca_mutex_lock(g_bleIsServerMutex); + switch (g_adapterType) { - OIC_LOG_V(ERROR, - CALEADAPTER_TAG, - "GATT server failed to %s (%d)", - (CA_ADAPTER_ENABLED == adapter_state - ? "start" : "stop"), - result); + case ADAPTER_SERVER: + CALEAdapterGattServerStart(); + break; + case ADAPTER_CLIENT: + CALEAdapterGattClientStart(); + break; + case ADAPTER_BOTH_CLIENT_SERVER: + CALEAdapterGattServerStart(); + CALEAdapterGattClientStart(); + break; + default: + break; } + ca_mutex_unlock(g_bleIsServerMutex); } - else if (CA_DISCOVERY_SERVER == gLeServerStatus) + else { - const CAResult_t result = - (CA_ADAPTER_ENABLED == adapter_state - ? CALEAdapterGattClientStart() - : CALEAdapterGattClientStop()); - - if (CA_STATUS_OK != result) + ca_mutex_lock(g_bleIsServerMutex); + switch (g_adapterType) { - OIC_LOG_V(ERROR, - CALEADAPTER_TAG, - "GATT client failed to %s (%d)", - (CA_ADAPTER_ENABLED == adapter_state - ? "start" : "stop"), - result); + case ADAPTER_SERVER: + CALEAdapterGattServerStop(); + break; + case ADAPTER_CLIENT: + CALEAdapterGattClientStop(); + break; + case ADAPTER_BOTH_CLIENT_SERVER: + CALEAdapterGattServerStop(); + CALEAdapterGattClientStop(); + break; + default: + break; } - } - - if (CA_ADAPTER_DISABLED == adapter_state) - { - gLeServerStatus = CA_SERVER_NOTSTARTED; + ca_mutex_unlock(g_bleIsServerMutex); } ca_mutex_lock(g_bleNetworkCbMutex); diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/caleinterface.c b/resource/csdk/connectivity/src/bt_le_adapter/linux/caleinterface.c index 5de0c54..2b43eb5 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/caleinterface.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/caleinterface.c @@ -114,11 +114,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 +130,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 +156,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 +336,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 +361,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 +398,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 +407,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 +507,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 +535,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 +581,6 @@ static bool CALEDeviceFilter(GDBusProxy * device) return accepted; } - static bool CALESetUpBlueZObjects(CALEContext * context) { bool success = false; @@ -734,8 +638,6 @@ static bool CALESetUpBlueZObjects(CALEContext * context) ca_mutex_unlock(context->lock); } - /* success = CAGattClientInitialize(context); */ - return success; } @@ -749,13 +651,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 +659,40 @@ 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)) + { + /* + Notify the upper LE stack of the availability of Bluetooth + adapters, if any are already powered on. + */ + if (CAGetLEAdapterState() == CA_STATUS_OK) + { + /** + * @todo Should we acquire the context lock here to + * prevent the @c CALEDeviceStateChangedCallback + * from being potentially yanked out from under us + * if the CA adapters are stopped/terminated as + * we're about to invoke this callback? + */ + g_context.on_device_state_changed(CA_ADAPTER_ENABLED); + } + + ca_mutex_lock(context->lock); - ca_cond_signal(g_context.condition); + assert(context->event_loop == NULL); + context->event_loop = event_loop; - g_main_loop_run(event_loop); + ca_mutex_unlock(context->lock); + + g_main_loop_run(event_loop); // Blocks until loop is quit. + } + + /* + 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 +714,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 +738,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 @@ -840,41 +760,17 @@ static CAResult_t CALEStop() OIC_LOG(DEBUG, TAG, "Stop Linux BLE adapter."); // Only stop if we were previously started. - if (!CALECheckStarted()) + if (CALECheckStarted()) { - return result; + // Stop the event loop thread regardless of previous errors. + CALEStopEventLoop(&g_context); + CALETearDownDBus(&g_context); + result = CA_STATUS_OK; } - // 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 +785,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; } @@ -906,12 +797,10 @@ CAResult_t CAStartLEAdapter() OIC_LOG(DEBUG, TAG, __func__); - CAResult_t result = CA_STATUS_FAILED; - // Only start if we were previously stopped. if (CALECheckStarted()) { - return result; + return CA_STATUS_FAILED; } /** @@ -926,71 +815,129 @@ 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) - { - return result; - } + return ca_thread_pool_add_task(g_context.client_thread_pool, + CALEStartEventLoop, + &g_context); +} +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; + OIC_LOG(DEBUG, TAG, __func__); - static uint64_t const timeout = - 2 * MICROSECS_PER_SEC; // Microseconds + return CALEStop(); +} + + +CAResult_t CAGetLEAdapterState() +{ + CAResult_t result = CA_ADAPTER_NOT_ENABLED; - if (CALEWaitForNonEmptyList(&g_context.adapters, retries, timeout)) + ca_mutex_lock(g_context.lock); + + for (GList * l = g_context.adapters; l != NULL; l = l->next) { - result = CA_STATUS_OK; + 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 CAGetLEAdapterState() +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. */ - return CA_NOT_SUPPORTED; -} -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. + g_context.lock = ca_mutex_new(); + g_context.condition = ca_cond_new(); + + /* + 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. */ + + /* + 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(); + + 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_cond_free(g_context.condition); + g_context.condition = NULL; + + ca_mutex_unlock(g_context.lock); + + ca_mutex_free(g_context.lock); + g_context.lock = NULL; } -CAResult_t CASetLEAdapterStateChangedCb(CALEDeviceStateChangedCallback callback) +CAResult_t CASetLEAdapterStateChangedCb( + CALEDeviceStateChangedCallback callback) { ca_mutex_lock(g_context.lock); g_context.on_device_state_changed = callback; @@ -1106,15 +1053,7 @@ 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 result; + return CAPeripheralStop(); } CAResult_t CAInitializeLEGattServer() @@ -1124,7 +1063,10 @@ CAResult_t CAInitializeLEGattServer() void CATerminateLEGattServer() { - CALETerminate(); + /* + See CATerminateLENetworkMonitor() to understand why the LE + peripheral is not finalized here. + */ } void CASetLEReqRespServerCallback(CABLEDataReceivedCallback callback) @@ -1138,95 +1080,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); -CAResult_t CAInitializeLEGattClient() -{ - return CA_STATUS_OK; -} - -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); @@ -1234,17 +1106,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. @@ -1257,17 +1118,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; } } @@ -1281,32 +1132,87 @@ 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) +{ + /* + Check if the client has actually started. In some cases, the + caller may not properly handle the asynchronous initialization + or check for successful initialization prior to sending out a + multicast style request. + */ + if (!CALECheckStarted()) + { + OIC_LOG(ERROR, TAG, "Client not yet started."); + + return CA_STATUS_FAILED; + } + + /* + 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? diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/central.h b/resource/csdk/connectivity/src/bt_le_adapter/linux/central.h index 72a8db6..8f70fea 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/central.h +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/central.h @@ -40,6 +40,9 @@ CAResult_t CACentralStart(CALEContext * context); /** * Stop the Linux BLE "central". * + * Shutdown all Linux BLE "central" operations, including stopping + * discovery, and disconnecting from all BLE peripherals. + * * @param[in] context Context containing BlueZ adapter information. * * @return @c CA_STATUS_OK on success. diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/characteristic.c b/resource/csdk/connectivity/src/bt_le_adapter/linux/characteristic.c index 3cf4c1b..5972837 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/characteristic.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/characteristic.c @@ -23,14 +23,10 @@ #include "bluez.h" #include "server.h" -#include "oic_malloc.h" -#include "oic_string.h" #include "logger.h" #include "cagattservice.h" #include "caremotehandler.h" -#include -#include #include @@ -102,58 +98,6 @@ static gboolean CAGattCharacteristicHandleWriteValue( // GATT Response Handling // --------------------------------------------------------------------- /** - * Make the peer address corresponding to the given GATT - * characteristic. - * - * @param[in] c Information about GATT characteristic for which the - * peer (client) @c CAEndpoint_t object is being - * created. - * - * @return @c String containing an encoded address associated with the - * peer connected to the peripheral on which the characteristic - * implementation resides, or @c NULL on error. - */ -static char * CAGattCharacteristicMakePeerAddress( - CAGattCharacteristic * c) -{ - assert(c != NULL); - - /* - Length of stringified pointer in hexadecimal format, plus one - for null terminator. - */ - static size_t const PSEUDO_ADDR_LEN = sizeof(intptr_t) / 4 + 1; - - assert(MAX_ADDR_STR_SIZE_CA > PSEUDO_ADDR_LEN); - - /* - Since there is no direct way to obtain the client endpoint - associated with the GATT characterstics on the server side, - embed a stringified pointer to the response charactertistic of - the form "&ABCDEF01" is the CAEndpoint_t instead. This works - since: - 1) only one LE central is ever connected to an LE peripheral - 2) the CA layer doesn't directly interpret the address - */ - char * const addr = OICMalloc(PSEUDO_ADDR_LEN); - int const count = snprintf(addr, - PSEUDO_ADDR_LEN, - "&%" PRIxPTR, - (uintptr_t) c); - - if (count >= (int) PSEUDO_ADDR_LEN) - { - OIC_LOG(ERROR, - TAG, - "Error creating peer address on server side."); - - return NULL; - } - - return addr; -} - -/** * Handle @c org.bluez.GattCharacterstic1.StartNotify() method call. * * This handler is triggered when the @@ -181,6 +125,8 @@ static gboolean CAGattCharacteristicHandleStartNotify( GDBusMethodInvocation * invocation, gpointer user_data) { + (void) user_data; + /** * Only allow the client to start notifications once. * @@ -197,82 +143,12 @@ static gboolean CAGattCharacteristicHandleStartNotify( return TRUE; } - // Retrieve the response characteristic information. - CAGattCharacteristic * const characteristic = user_data; - - char * const peer = - CAGattCharacteristicMakePeerAddress(characteristic); - - if (peer == NULL) - { - g_dbus_method_invocation_return_dbus_error( - invocation, - "org.bluez.Error.Failed", - "Error creating peer endpoint information"); - - return TRUE; - } - - /* - Create an entry in the endpoint-to-characteristic map so that - responses may be sent to the GATT client through the OIC GATT - response characteristic through this BLE adapter's - CAAdapterSendUnicastData() implementation. - */ - CALEContext * const context = characteristic->context; - - ca_mutex_lock(context->lock); - -#if GLIB_CHECK_VERSION(2,40,0) - /* - GLib hash table functions started returning a boolean result in - version 2.40.x. - */ - bool const inserted = -#endif - g_hash_table_insert(context->characteristic_map, - peer, - characteristic); - - ca_mutex_unlock(context->lock); - -#if GLIB_CHECK_VERSION(2,40,0) - if (!inserted) - { - g_dbus_method_invocation_return_dbus_error( - invocation, - "org.bluez.Error.Failed", - "Unable to set response endpoint."); - - OICFree(peer); - - return TRUE; - } -#endif - /** * @todo Do we need to explicitly emit the @c GObject @c notify or * @c org.freedesktop.Dbus.Properties.PropertiesChanged * signal here? */ gatt_characteristic1_set_notifying(object, TRUE); - - /* - Set the client endpoint field in the request characteristic so - that it may pass the appropriate endpoint object up the stack - through the CA request/response callback once a request has been - completely received and reassembled. - */ - CAGattRecvInfo * const recv_info = - &characteristic->service->request_characteristic.recv_info; - - recv_info->peer = peer; - - ca_mutex_lock(context->lock); - recv_info->on_packet_received = context->on_server_received_data; - recv_info->context = context; - ca_mutex_unlock(context->lock); - gatt_characteristic1_complete_start_notify(object, invocation); return TRUE; @@ -303,7 +179,7 @@ static gboolean CAGattCharacteristicHandleStopNotify( GDBusMethodInvocation * invocation, gpointer user_data) { - assert(user_data != NULL); + (void) user_data; /** * @todo Does BlueZ already prevent redundant calls to @@ -319,44 +195,13 @@ static gboolean CAGattCharacteristicHandleStopNotify( return TRUE; } - CAGattCharacteristic * const characteristic = user_data; - - // Clear the client endpoint from the request characteristic. - CAGattRecvInfo * const recv_info = - &characteristic->service->request_characteristic.recv_info; - - /* - Remove the appropriate entry from the endpoint-to-characteristic - map so that attempts to send a response through it will fail. - */ - CALEContext * const context = characteristic->context; - ca_mutex_lock(context->lock); - - bool const removed = - g_hash_table_remove(context->characteristic_map, recv_info->peer); - - ca_mutex_unlock(context->lock); - - CAGattRecvInfoDestroy(recv_info); - /** * @todo Do we need to explicitly emit the @c GObject @c notify or * @c org.freedesktop.Dbus.Properties.PropertiesChanged * signal here? */ gatt_characteristic1_set_notifying(object, FALSE); - - if (removed) - { - gatt_characteristic1_complete_stop_notify(object, invocation); - } - else - { - g_dbus_method_invocation_return_dbus_error( - invocation, - "org.bluez.Error.Failed", - "Error removing peer address information"); - } + gatt_characteristic1_complete_stop_notify(object, invocation); return TRUE; } @@ -419,7 +264,7 @@ static bool CAGattCharacteristicInitialize( gatt_characteristic1_set_service(c->characteristic, s->object_path); gatt_characteristic1_set_notifying(c->characteristic, FALSE); - char const * flags[] = { flag, NULL }; + char const * const flags[] = { flag, NULL }; gatt_characteristic1_set_flags(c->characteristic, flags); CAGattRecvInfoInitialize(&c->recv_info); @@ -475,7 +320,7 @@ bool CAGattRequestCharacteristicInitialize(struct CAGattService * s, The descriptor object path is not fixed at compile-time. Retrieve the object path that was set at run-time. */ - char const * descriptor_paths[] = { + char const * const descriptor_paths[] = { c->descriptor.object_path, NULL }; @@ -483,6 +328,21 @@ bool CAGattRequestCharacteristicInitialize(struct CAGattService * s, gatt_characteristic1_set_descriptors(c->characteristic, descriptor_paths); + char * const peer = CAGattServiceMakePeerAddress(s); + + if (peer == NULL) + { + CAGattCharacteristicDestroy(c); + return false; + } + + ca_mutex_lock(context->lock); + c->recv_info.on_packet_received = context->on_server_received_data; + ca_mutex_unlock(context->lock); + + c->recv_info.peer = peer; + c->recv_info.context = context; + // The request characteristic only handles writes. g_signal_connect(c->characteristic, "handle-write-value", @@ -530,7 +390,7 @@ bool CAGattResponseCharacteristicInitialize(struct CAGattService * s, enable notifications by writing to the client characteristic configuration descriptor. */ - char const * descriptor_paths[] = { + char const * const descriptor_paths[] = { c->descriptor.object_path, NULL }; diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/client.c b/resource/csdk/connectivity/src/bt_le_adapter/linux/client.c index 373ee50..b61f34c 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/client.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/client.c @@ -23,72 +23,488 @@ #include "utils.h" #include "cafragmentation.h" +#include "cagattservice.h" #include "logger.h" #include "oic_malloc.h" #include "oic_string.h" #include -#include +#include #include // 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. + ca_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); + + ca_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); + } + } + + ca_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) + { + 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) { - return success; + CALEContext * const context = user_data; + + ca_mutex_lock(g_context.lock); + + CAGattClientSetupService(device, + g_context.characteristic_map, + g_context.address_map, + services_prop, + context); + + ca_mutex_unlock(g_context.lock); + + g_variant_unref(services_prop); } +} + +CAResult_t CAGattClientInitialize(CALEContext * context) +{ + g_context.lock = ca_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 +512,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); - // An empty services list is NULL. - if (success && services != NULL) + ca_mutex_lock(context->lock); + + 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; + ca_mutex_unlock(context->lock); + + ca_mutex_lock(g_context.lock); + + g_context.characteristic_map = characteristic_map; + g_context.address_map = address_map; + + ca_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. + } + + ca_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; + } + + ca_mutex_unlock(g_context.lock); + + ca_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, - uint8_t 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 +610,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 @@ -192,89 +653,92 @@ static bool CAGattClientSendRequestData(GDBusProxy * characteristic, ca_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, +CAResult_t CAGattClientSendData(char const * address, uint8_t const * data, - size_t length) + 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, - data, - length); -} + assert(context != NULL); -CAResult_t CAGattClientSendDataToAll(void const * method_info, - uint8_t const * data, - size_t length) -{ - assert(method_info != NULL); + CAResult_t result = CA_STATUS_FAILED; - CAResult_t result = CA_STATUS_OK; + ca_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, - data, - length); + return result; } + result = CAGattClientSendDataImpl(characteristic, + data, + length, + context); + + ca_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)) + ca_mutex_lock(g_context.lock); + + if (g_context.characteristic_map == NULL) { + // Remote OIC GATT service was not found prior to getting here. + ca_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; + } } + + ca_mutex_unlock(g_context.lock); + + return result; } diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/client.h b/resource/csdk/connectivity/src/bt_le_adapter/linux/client.h index 4000405..ad0f052 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/client.h +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/client.h @@ -23,69 +23,52 @@ /** - * Information needed to when sending a request through a GATT - * client. + * Initialize the GATT client. + * + * @param[in] context + * + * @return @c CA_STATUS_OK on success, @c CA_STATUS_FAILED otherwise. */ -typedef struct _CAGattRequestInfo -{ - /** - * Proxy or list of proxies to @c org.bluez.GattCharacteristic1 - * object(s) through which request data will be sent to the GATT - * server. - * - * In the case of a unicast-style send, @c info will be a - * @c GDBusProxy* to an @c org.bluez.GattCharacteristic1 object. - * For a multicast-style send, @c info will be a * @c GList* of - * @c GDBusProxy* to @c GattCharacterstic1 objects on all GATT - * servers to which a connection exists. - */ - void * const characteristic_info; - - /** - * Context containing additional information that may be needed - * when sending a request. - */ - CALEContext * const context; +CAResult_t CAGattClientInitialize(CALEContext * context); -} CAGattRequestInfo; +/** + * Destroy the GATT client. + */ +void CAGattClientDestroy(); /** * Send request data through a single user-specified BLE connection. * - * @param[in] method_info Information necessary to send request. - * @param[in] data Octet array of request data to be sent. - * @param[in] length Length of the @a data octet array. + * @param[in] address MAC address of the BLE peripheral running the + * OIC Transport Profile GATT server to which the + * data will be sent. + * @param[in] data Octet array of request data to be sent. + * @param[in] length Length of the @a data octet array. + * @param[in] context Object containing mutexes and error reporting + * callback used on failure to send. * - * @see @c CAGattSendMethod() for further details. + * @return @c CA_STATUS_OK on success, @c CA_STATUS_FAILED otherwise. */ -bool CAGattSendRequest(void const * method_info, - uint8_t const * data, - size_t length); +CAResult_t CAGattClientSendData(char const * address, + uint8_t const * data, + size_t length, + CALEContext * context); -// --------------------------------------------------------------- -// Multicast-style Request Send -// --------------------------------------------------------------- /** * Send request data through all BLE connections. * * Send the @a data to the GATT server found in all discovered LE * peripherals. * - * @param[in] method_info Information necessary to send request. - * @param[in] data Octet array of request data to be sent. - * @param[in] length Length of the @a data octet array. - - * @note Since a multicast-like operation is being performed, an - * assumption is made that a GATT client is sending data to a - * server. It makes no sense to multicast a response from a - * single GATT server to multiple GATT clients in IoTivity's - * case. + * @param[in] data Octet array of request data to be sent. + * @param[in] length Length of the @a data octet array. + * @param[in] context Object GATT request characteristic map. * * @return @c CA_STATUS_OK on success, @c CA_STATUS_FAILED otherwise. */ -CAResult_t CAGattClientSendDataToAll(void const * method_info, - uint8_t const * data, - size_t length); +CAResult_t CAGattClientSendDataToAll(uint8_t const * data, + size_t length, + CALEContext * context); #endif /* CA_BLE_LINUX_CLIENT_H */ diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/context.h b/resource/csdk/connectivity/src/bt_le_adapter/linux/context.h index ce1ec01..8541fcb 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/context.h +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/context.h @@ -76,28 +76,6 @@ typedef struct _CALEContext GList * devices; /** - * 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 peer Bluetooth adapter MAC address. The value is an - * interface proxy (@c GDBusProxy) to an - * @c org.bluez.GattCharacteristic1 object. - * - * On the client side, this maps a Bluetooth peripheral MAC - * address to the corresponding request characteristic proxy. On - * the server side, this maps Bluetooth central MAC address to the - * corresponding response characteristic proxy. - * - * @note On the server side a map is overkill since only one - * client is ever connected to the server. No? - * - * @todo We may want to have a seperate server-side map to reduce - * contention on this map. - */ - GHashTable * characteristic_map; - - /** * GATT characteristics to Bluetooth MAC address map. * * Hash table that maps OIC Transport Profile GATT characteristic @@ -125,8 +103,6 @@ typedef struct _CALEContext * * @li @c org.freedesktop.DBus.ObjectManager.InterfacesAdded * @li @c org.freedesktop.DBus.ObjectManager.InterfacesRemoved - * @li @c org.freedesktop.DBus.Properties.PropertiesChanged - * @li @c org.bluez.Adapter1.PropertyChanged * * These subscription identifiers are only used when unsubscribing * from the signals when stopping the LE transport. @@ -137,8 +113,6 @@ typedef struct _CALEContext //@{ guint interfaces_added_sub_id; guint interfaces_removed_sub_id; - guint properties_changed_sub_id; - guint property_changed_sub_id; //@} /// Glib event loop that drives D-Bus signal handling. diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/descriptor.c b/resource/csdk/connectivity/src/bt_le_adapter/linux/descriptor.c index 7066c33..eeccd50 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/descriptor.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/descriptor.c @@ -119,7 +119,7 @@ static bool CAGattDescriptorInitialize(CAGattCharacteristic * c, g_variant_new_bytestring(value)); // Readable, no encryption, no authorization. - static char const * flags[] = { "read", NULL }; + static char const * const flags[] = { "read", NULL }; gatt_descriptor1_set_flags(d->descriptor, flags); /* diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/peripheral.c b/resource/csdk/connectivity/src/bt_le_adapter/linux/peripheral.c index 54a5479..588e43c 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/peripheral.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/peripheral.c @@ -20,8 +20,6 @@ #include "utils.h" #include "bluez.h" #include "service.h" -#include "characteristic.h" -#include "descriptor.h" #include "oic_malloc.h" #include "logger.h" @@ -127,7 +125,7 @@ static GList * CAPeripheralInitializeGattServices(CALEContext * context) char const * const hci_name = strrchr(path, '/'); if (hci_name == NULL - || !CAGattServiceInitialize(service, context, hci_name + 1)) + || !CAGattServiceInitialize(service, hci_name + 1, context)) { g_list_free_full(gatt_services, CAPeripheralDestroyGattServices); @@ -138,9 +136,10 @@ static GList * CAPeripheralInitializeGattServices(CALEContext * context) service->gatt_manager = manager; /* - The GattManager1 proxies are now owned by the CAGattService - objects. + The GattManager1 proxy is now owned by the CAGattService + object. */ + GList * const tmp = l; l = l->next; gatt_managers = g_list_delete_link(gatt_managers, tmp); @@ -159,7 +158,7 @@ static bool CAPeripheralRegisterGattServices( { assert(context != NULL); - bool success = false; + bool success = true; ca_mutex_lock(context->lock); @@ -210,8 +209,6 @@ static bool CAPeripheralRegisterGattServices( ca_mutex_unlock(context->lock); - success = true; - return success; } @@ -330,8 +327,13 @@ static void CAPeripheralSetDiscoverable(gpointer data, /* Make sure the adapter is powered on before making it discoverable. + + @todo We used to power off the adapter once we're done with it, + but that isn't always desirable. We should only power it + off if it was off prior to us powering it on. */ - if (!CASetBlueZObjectProperty(adapter, + if (discoverable + && !CASetBlueZObjectProperty(adapter, BLUEZ_ADAPTER_INTERFACE, "Powered", g_variant_new_boolean(discoverable))) @@ -497,6 +499,15 @@ static void CAPeripheralStartEventLoop(void * data) "manager interface."); } + ca_mutex_lock(g_context.lock); + + assert(g_context.event_loop == NULL); + g_context.event_loop = event_loop; + + g_context.base = context; + + g_context.owner_id = owner_id; + /** * Initialize all GATT services. * @@ -509,29 +520,24 @@ static void CAPeripheralStartEventLoop(void * data) * a thread seperate from the one that initiates GATT * service registration. */ - GList * const gatt_services = - CAPeripheralInitializeGattServices(context); - - ca_mutex_lock(g_context.lock); - - assert(g_context.event_loop == NULL); - g_context.event_loop = event_loop; - - g_context.base = context; - - g_context.owner_id = owner_id; + g_context.gatt_services = CAPeripheralInitializeGattServices(context); CALEAdvertisementInitialize(&g_context.advertisement, context->connection, advertising_managers); - g_context.gatt_services = gatt_services; - ca_mutex_unlock(g_context.lock); ca_cond_signal(g_context.condition); - g_main_loop_run(event_loop); + g_main_loop_run(event_loop); // Blocks until loop is quit. + + /* + 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 CAPeripheralStopEventLoop(CAPeripheralContext * context) @@ -553,10 +559,7 @@ static void CAPeripheralStopEventLoop(CAPeripheralContext * context) if (loop_context != NULL) { g_main_context_wakeup(loop_context); - g_main_context_unref(loop_context); } - - g_main_loop_unref(event_loop); } } @@ -628,7 +631,7 @@ CAResult_t CAPeripheralStart(CALEContext * context) { if (ca_cond_wait_for(g_context.condition, g_context.lock, - timeout) == 0) + timeout) == CA_WAIT_SUCCESS) { result = CA_STATUS_OK; } @@ -636,7 +639,7 @@ CAResult_t CAPeripheralStart(CALEContext * context) ca_mutex_unlock(g_context.lock); - if (result == CA_STATUS_FAILED) + if (result != CA_STATUS_OK) { return result; } @@ -706,3 +709,12 @@ CAResult_t CAPeripheralStop() return result; } + +void CAPeripheralForEachService(GFunc func, void * user_data) +{ + ca_mutex_lock(g_context.lock); + + g_list_foreach(g_context.gatt_services, func, user_data); + + ca_mutex_unlock(g_context.lock); +} diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/peripheral.h b/resource/csdk/connectivity/src/bt_le_adapter/linux/peripheral.h index 89d9318..419edb4 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/peripheral.h +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/peripheral.h @@ -124,5 +124,23 @@ CAResult_t CAPeripheralStart(CALEContext * context); */ CAResult_t CAPeripheralStop(); +/** + * Invoke function @a func for each registered GATT service. + * + * For each registered GATT service, invoke the function @a func, + * where the first (data) paramter will be a pointer to the + * ::CAGattService object, and the second will be the @a user_data + * argument provided to this function call. + * + * @param[in] func Function to be invoked for each + * ::CAGattService. + * @param[in] user_data User provided data passed as the second + * argument to the function @a func. + * + * @note This function exists to avoid exposing the BLE peripheral + * internals. + */ +void CAPeripheralForEachService(GFunc func, void * user_data); + #endif /* CA_BLE_LINUX_PERIPHERAL_H */ diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/server.c b/resource/csdk/connectivity/src/bt_le_adapter/linux/server.c index 828183c..be7302d 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/server.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/server.c @@ -1,4 +1,4 @@ -/****************************************************************** +/* **************************************************************** * * Copyright 2015 Intel Corporation All Rights Reserved. * @@ -16,9 +16,11 @@ * ******************************************************************/ + #include "server.h" +#include "service.h" +#include "peripheral.h" -#include "cacommon.h" #include "logger.h" #include @@ -27,34 +29,16 @@ // Logging tag. static char const TAG[] = "BLE_SERVER"; -// --------------------------------------------------------------------- -// GATT Request Handling -// --------------------------------------------------------------------- -void CAGattServerHandleRequestData() -{ -} - -// --------------------------------------------------------------------- -// GATT Response Handling -// --------------------------------------------------------------------- -/** - * Send response data to the GATT client. - * - * Respone data will be sent to the client through the given response - * @a characteristic proxy as a GATT characteristic notification. - * - * @param[in] characteristic The D-Bus proxy for the response - * characteristic through which the - * notification will be sent. - * @param[in] data The byte array to be sent. - * @param[in] length The number of elements in the byte - * array. - */ -static bool CAGattServerSendResponseNotification( - GattCharacteristic1 * characteristic, - char const * data, +static CAResult_t CAGattServerSendResponseNotificationImpl( + CAGattService * service, + uint8_t const * data, size_t length) { + assert(service != NULL); + + GattCharacteristic1 * const characteristic = + service->response_characteristic.characteristic; + if (!gatt_characteristic1_get_notifying(characteristic)) { OIC_LOG(WARNING, @@ -64,7 +48,7 @@ static bool CAGattServerSendResponseNotification( "Client must enable notifications. " "No response was sent."); - return false; + return CA_STATUS_FAILED; } GVariant * const value = @@ -73,38 +57,78 @@ static bool CAGattServerSendResponseNotification( length, sizeof(data[0])); - /** - * Send the response fragment by setting the "Value" property on - * the response characteristic, and emitting the - * @c org.freedesktop.Dbus.Properties.PropertiesChanged signal, - * accordingly. - * - * @todo Do we need to explicitly emit the @c GObject @c notify or - * @c org.freedesktop.Dbus.Properties.PropertiesChanged - * signal here? - */ + /* + Send the response fragment by setting the "Value" property on + the response characteristic, and emitting the + org.freedesktop.Dbus.Properties.PropertiesChanged signal, + accordingly. + + @todo Do we need to explicitly emit the GObject notify or + org.freedesktop.Dbus.Properties.PropertiesChanged + signal here? + + @todo It feels like this function should be part of the + response characteristic implementation. + + @todo Do we need to decrease the ref count after setting the + value, i.e. is ownership of the value transferred to the + characteristic object? + */ gatt_characteristic1_set_value(characteristic, value); - return true; + return CA_STATUS_OK; } -bool CAGattServerSendResponse(void const * method_info, - void const * data, - size_t length) +typedef struct _CAGattServerResponseInfo { - assert(method_info != NULL); + uint8_t const * const data; + size_t const length; + CAResult_t result; - CAGattResponseInfo const * const info = method_info; +} CAGattServerResponseInfo; - GattCharacteristic1 * const characteristic = - info->characteristic; +static void CAGattServerSendResponseNotificationToService(gpointer data, + gpointer user_data) +{ + CAGattService * const service = data; + CAGattServerResponseInfo * const info = user_data; + + CAResult_t const result = + CAGattServerSendResponseNotificationImpl(service, + info->data, + info->length); - if (!CAGattServerSendResponseNotification(characteristic, - (char const *) data, - length)) + if (result != CA_STATUS_OK) { - return false; + // Propagate failure if any send operation fails. + info->result = result; } +} + +CAResult_t CAGattServerSendResponseNotification( + char const * address, + uint8_t const * data, + size_t length) +{ + CAGattService * const s = CAGattServiceDecodeAddress(address); + + return CAGattServerSendResponseNotificationImpl(s, data, length); +} + +CAResult_t CAGattServerSendResponseNotificationToAll( + uint8_t const * data, + size_t length) +{ + CAGattServerResponseInfo info = + { + .data = data, + .length = length, + .result = CA_STATUS_OK + }; + + CAPeripheralForEachService( + CAGattServerSendResponseNotificationToService, + &info); - return true; + return info.result; } diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/server.h b/resource/csdk/connectivity/src/bt_le_adapter/linux/server.h index 91dcc26..c4b707e 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/server.h +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/server.h @@ -19,38 +19,49 @@ #ifndef CA_BLE_LINUX_SERVER_H #define CA_BLE_LINUX_SERVER_H -#include "bluez-glue.h" +#include "cacommon.h" -#include +#include #include + /** - * Information needed to complete a GATT server response send. + * Send response/notification to the GATT client. + * + * Data will be sent to the client through the given response + * @a characteristic proxy as a GATT characteristic notification. + * + * @param[in] characteristic The D-Bus proxy for the response + * characteristic through which the + * notification will be sent. + * @param[in] data The byte array to be sent. + * @param[in] length The number of elements in the byte + * array. + * + * @return ::CA_STATUS_OK on success, ::CA_STATUS_FAILED otherwise. */ -typedef struct _CAGattResponseInfo -{ - /** - * The BlueZ @c org.bluez.GattCharacteristic1 skeleton object - * through which data will be sent. - */ - GattCharacteristic1 * const characteristic; +CAResult_t CAGattServerSendResponseNotification( + char const * address, + uint8_t const * data, + size_t length); -} CAGattResponseInfo; /** - * Send response notification to the GATT client. + * Send notification to all connected GATT clients. + * + * Data will be sent to the client through the given response + * @a characteristic proxy as a GATT characteristic notification. * - * @param[in] method_info Pointer to @c GattResponseInfo object that - * contains information necessary to complete - * send of response. - * @param[in] data Octet array of response data to be sent. - * @param[in] length Length of the @a data octet array. + * @param[in] data The byte array to be sent. + * @param[in] length The number of elements in the byte array. + * @param[in] context Object containing mutexes and error reporting + * callback used on failure to send. * - * @see @c CAGattSendMethod() for further details. + * @return ::CA_STATUS_OK on success, ::CA_STATUS_FAILED otherwise. */ -bool CAGattServerSendResponse(void const * method_info, - void const * data, - size_t length); +CAResult_t CAGattServerSendResponseNotificationToAll( + uint8_t const * data, + size_t length); #endif /* CA_BLE_LINUX_SERVER_H */ diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/service.c b/resource/csdk/connectivity/src/bt_le_adapter/linux/service.c index ef204e5..bf89417 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/service.c +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/service.c @@ -23,7 +23,10 @@ #include "cagattservice.h" #include "logger.h" +#include "oic_malloc.h" +#include +#include #include @@ -158,9 +161,71 @@ static gboolean CAGattServiceHandleGetManagedObjects( return TRUE; } +char * CAGattServiceMakePeerAddress(CAGattService * s) +{ + assert(s != NULL); + + /* + Since there is no direct way to obtain the client address + associated with the GATT characterstics on the server side, + embed a stringified pointer to this GATT service of the form + "&ABCDEF01" as the address instead so that we can use it to + refer to the client. This works since: + 1) only one LE central is ever connected to an LE peripheral + 2) the CA layer doesn't directly interpret the client + address + */ + + /* + Length of stringified pointer in hexadecimal format, plus one + for the leading ampersand, and one more for the null + terminator. + */ + static size_t const PSEUDO_ADDR_LEN = sizeof(uintptr_t) * 2 + 2; + + assert(MAX_ADDR_STR_SIZE_CA > PSEUDO_ADDR_LEN); + + char * const addr = OICMalloc(PSEUDO_ADDR_LEN); + + if (addr == NULL) + { + return addr; + } + + int const count = snprintf(addr, + PSEUDO_ADDR_LEN, + "&%" PRIxPTR, + (uintptr_t) s); + + if (count < 0 || count >= (int) PSEUDO_ADDR_LEN) + { + OIC_LOG(ERROR, + TAG, + "Error creating peer address on server side."); + + OICFree(addr); + + return NULL; + } + + return addr; +} + +CAGattService * CAGattServiceDecodeAddress(char const * address) +{ + /* + The peer address is actually the value of the pointer to the + CAGattService object containing the response characteristic. + */ + uintptr_t s = (uintptr_t) NULL; + (void) sscanf(address, "&%" SCNxPTR, &s); + + return (CAGattService *) s; +} + bool CAGattServiceInitialize(CAGattService * s, - CALEContext * context, - char const * hci_name) + char const * hci_name, + CALEContext * context) { assert(s != NULL); assert(context != NULL); @@ -192,7 +257,7 @@ bool CAGattServiceInitialize(CAGattService * s, The characteristic object paths are not fixed at compile-time. Retrieve the object paths that were set at run-time. */ - char const * characteristic_paths[] = { + char const * const characteristic_paths[] = { s->request_characteristic.object_path, s->response_characteristic.object_path, NULL diff --git a/resource/csdk/connectivity/src/bt_le_adapter/linux/service.h b/resource/csdk/connectivity/src/bt_le_adapter/linux/service.h index 2cfbc3e..ad1f146 100644 --- a/resource/csdk/connectivity/src/bt_le_adapter/linux/service.h +++ b/resource/csdk/connectivity/src/bt_le_adapter/linux/service.h @@ -56,15 +56,40 @@ typedef struct CAGattService } CAGattService; /** + * Make the pseudo-address for the peer connected to the service. + * + * @param[in] c Information about GATT service for which the + * peer (client) address is being created. + * + * @return @c String containing an encoded address associated with the + * peer connected to the peripheral on which the service + * implementation resides, or @c NULL on error. + */ +char * CAGattServiceMakePeerAddress(CAGattService * s); + +/** + * Decode @c CAGattService pointer from peer @c address. + * + * @param[in] address String containing a client/peer pseudo-address + * corresponding to the GATT service through which + * communication is performed. + * + * @return Pointer to ::CAGattService object corresponding to the + * given address. + */ +CAGattService * CAGattServiceDecodeAddress(char const * address); + +/** * Initialize GATT service fields. * * This function initializes the @c CAGattService object fields. * * @param[out] service GATT service information to be initialized. - * @param[in] context Object containing the D-Bus connection to the - * bus on which the service will be exported. * @param[in] hci_name Name of the bluetooth adapter installed on the * system, e.g. @c "hci0". + * @param[in] context Object containing the D-Bus connection to the + * bus on which the service will be exported, as + * well as an address-to-characteristic map. * * @return @c true on success, @c false otherwise. * @@ -73,8 +98,8 @@ typedef struct CAGattService * memory. */ bool CAGattServiceInitialize(CAGattService * service, - CALEContext * context, - char const * hci_name); + char const * hci_name, + CALEContext * context); /** * Destroy GATT service fields. @@ -89,5 +114,4 @@ bool CAGattServiceInitialize(CAGattService * service, */ void CAGattServiceDestroy(CAGattService * service); - #endif // CA_BLE_LINUX_SERVICE_H diff --git a/tools/valgrind/iotivity.supp b/tools/valgrind/iotivity.supp index 474bb6b..dc94e18 100644 --- a/tools/valgrind/iotivity.supp +++ b/tools/valgrind/iotivity.supp @@ -132,4 +132,10 @@ fun:g_malloc0 obj:/usr/lib/*/libgobject-2.0*.so.* } +{ + glib/g-dbus-connection-send-message-with-reply-sync + Memcheck:Leak + ... + fun:g_dbus_connection_send_message_with_reply_sync +} # ************************************************************************ -- 2.7.4