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
return CA_STATUS_OK;
}
+CAResult_t CAStopLEAdapter()
+{
+ // Nothing to do.
+
+ return CA_STATUS_OK;
+}
+
CAResult_t CAInitLENwkMonitorMutexVaraibles()
{
OIC_LOG(DEBUG, TAG, "IN");
return CA_STATUS_OK;
}
+CAResult_t CAStopLEAdapter()
+{
+ // Nothing to do.
+
+ return CA_STATUS_OK;
+}
+
CAResult_t CAGetLEAdapterState()
{
OIC_LOG(DEBUG, TAG, "IN");
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.
*/
/**
* 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
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
{
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)
{
*/
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;
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)
{
*/
if (CA_STATUS_OK == result)
{
+ ca_mutex_lock(g_bleClientSendDataMutex);
result = CAQueueingThreadStart(g_bleClientSendQueueHandle);
+ ca_mutex_unlock(g_bleClientSendDataMutex);
if (CA_STATUS_OK != result)
{
"Unable to start client queuing thread");
}
}
+ else
+ {
+ OIC_LOG_V(ERROR,
+ CALEADAPTER_TAG,
+ "GATT client failed to start (%d)",
+ result);
+ }
#endif
return result;
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)
{
#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()
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
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
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;
}
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;
}
}
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);
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;
}
}
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);
(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);
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))
{
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
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);
}
}
-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)
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),
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);
}
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);
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);
}
}
return accepted;
}
-
static bool CALESetUpBlueZObjects(CALEContext * context)
{
bool success = false;
ca_mutex_unlock(context->lock);
}
- /* success = CAGattClientInitialize(context); */
-
return success;
}
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);
/*
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)
if (loop_context != NULL)
{
g_main_context_wakeup(loop_context);
- g_main_context_unref(loop_context);
}
-
- g_main_loop_unref(event_loop);
}
}
{
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
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()
g_type_init();
#endif
- g_context.lock = ca_mutex_new();
- g_context.condition = ca_cond_new();
-
- CAPeripheralInitialize();
-
return CA_STATUS_OK;
}
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;
}
/**
* 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;
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()
void CATerminateLEGattServer()
{
- CALETerminate();
+ /*
+ See CATerminateLENetworkMonitor() to understand why the LE
+ peripheral is not finalized here.
+ */
}
void CASetLEReqRespServerCallback(CABLEDataReceivedCallback callback)
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);
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.
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;
}
}
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?
/**
* 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.
#include "bluez.h"
#include "server.h"
-#include "oic_malloc.h"
-#include "oic_string.h"
#include "logger.h"
#include "cagattservice.h"
#include "caremotehandler.h"
-#include <inttypes.h>
-#include <string.h>
#include <assert.h>
// 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
GDBusMethodInvocation * invocation,
gpointer user_data)
{
+ (void) user_data;
+
/**
* Only allow the client to start notifications once.
*
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;
GDBusMethodInvocation * invocation,
gpointer user_data)
{
- assert(user_data != NULL);
+ (void) user_data;
/**
* @todo Does BlueZ already prevent redundant calls to
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;
}
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);
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
};
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",
enable notifications by writing to the client characteristic
configuration descriptor.
*/
- char const * descriptor_paths[] = {
+ char const * const descriptor_paths[] = {
c->descriptor.object_path,
NULL
};
#include "utils.h"
#include "cafragmentation.h"
+#include "cagattservice.h"
#include "logger.h"
#include "oic_malloc.h"
#include "oic_string.h"
#include <gio/gio.h>
-#include <string.h>
+#include <strings.h>
#include <assert.h>
// Logging tag.
static char const TAG[] = "BLE_CLIENT";
+typedef struct _CAGattClientContext
+{
+ /**
+ * Bluetooth MAC address to GATT characteristic map.
+ *
+ * Hash table that maps Bluetooth MAC address to a OIC Transport
+ * Profile GATT characteristic. The key is a string containing
+ * the LE peripheral Bluetooth adapter MAC address. The value is
+ * an interface proxy (@c GDBusProxy) to an
+ * @c org.bluez.GattCharacteristic1 object, i.e. OIC Transport
+ * Profile GATT request characteristic.
+ */
+ GHashTable * characteristic_map;
+
+ /**
+ * Response characteristic object path to Bluetooth MAC address map.
+ *
+ * Hash table that maps OIC Transport Profile GATT response
+ * characteristic D-Bus object path to Bluetooth MAC address. The
+ * key is the D-Bus object path. The value is the LE peripheral
+ * Bluetooth adapter MAC address.
+ *
+ * @note This map exists to avoid having to create a hierarchy of
+ * GLib D-Bus proxies to the client side BlueZ GATT response
+ * Characteristic, its corresponding GATT Service, and the
+ * Device object within that Service, along with the
+ * resulting D-Bus calls, simply so that we obtain the MAC
+ * address of the remote (peripheral) LE device. We
+ * unfortunately need the MAC address since the
+ * shared/common caleadapter code requires it.
+ */
+ GHashTable * address_map;
+
+ /// Mutex used to synchronize access to context fields.
+ 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,
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 =
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
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;
}
/**
- * 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 */
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
*
* @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.
//@{
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.
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);
/*
#include "utils.h"
#include "bluez.h"
#include "service.h"
-#include "characteristic.h"
-#include "descriptor.h"
#include "oic_malloc.h"
#include "logger.h"
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);
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);
{
assert(context != NULL);
- bool success = false;
+ bool success = true;
ca_mutex_lock(context->lock);
ca_mutex_unlock(context->lock);
- success = true;
-
return success;
}
/*
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)))
"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.
*
* 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)
if (loop_context != NULL)
{
g_main_context_wakeup(loop_context);
- g_main_context_unref(loop_context);
}
-
- g_main_loop_unref(event_loop);
}
}
{
if (ca_cond_wait_for(g_context.condition,
g_context.lock,
- timeout) == 0)
+ timeout) == CA_WAIT_SUCCESS)
{
result = CA_STATUS_OK;
}
ca_mutex_unlock(g_context.lock);
- if (result == CA_STATUS_FAILED)
+ if (result != CA_STATUS_OK)
{
return result;
}
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);
+}
*/
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 */
-/******************************************************************
+/* ****************************************************************
*
* Copyright 2015 Intel Corporation All Rights Reserved.
*
*
******************************************************************/
+
#include "server.h"
+#include "service.h"
+#include "peripheral.h"
-#include "cacommon.h"
#include "logger.h"
#include <assert.h>
// 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,
"Client must enable notifications. "
"No response was sent.");
- return false;
+ return CA_STATUS_FAILED;
}
GVariant * const value =
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;
}
#ifndef CA_BLE_LINUX_SERVER_H
#define CA_BLE_LINUX_SERVER_H
-#include "bluez-glue.h"
+#include "cacommon.h"
-#include <stdbool.h>
+#include <stdint.h>
#include <sys/types.h>
+
/**
- * 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 */
#include "cagattservice.h"
#include "logger.h"
+#include "oic_malloc.h"
+#include <inttypes.h>
+#include <stdio.h>
#include <assert.h>
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);
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
} 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.
*
* memory.
*/
bool CAGattServiceInitialize(CAGattService * service,
- CALEContext * context,
- char const * hci_name);
+ char const * hci_name,
+ CALEContext * context);
/**
* Destroy GATT service fields.
*/
void CAGattServiceDestroy(CAGattService * service);
-
#endif // CA_BLE_LINUX_SERVICE_H
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
+}
# ************************************************************************