1 /*****************************************************************
3 * Copyright 2015 Intel Corporation All Rights Reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 ******************************************************************/
19 #include "characteristic.h"
21 #include "gatt_dbus.h"
26 #include "oic_malloc.h"
27 #include "oic_string.h"
29 #include "cagattservice.h"
30 #include "caremotehandler.h"
38 static char const TAG[] = "BLE_CHARACTERISTIC";
40 // ---------------------------------------------------------------------
41 // GATT Request Handling
42 // ---------------------------------------------------------------------
44 * Handle @c org.bluez.GattCharacterstic1.WriteValue() method call.
46 * This handler is triggered when the
47 * @c org.bluez.GattCharacterstic1.WriteValue() method is called by a
48 * client on the BlueZ-based OIC GATT request characteristic. In
49 * particular, IoTivity request data is sent by the GATT client to the
50 * GATT server through this call. The server will retrieve the data
51 * that was sent from the @a value argument. Reassembly of any
52 * request data fragments will begin here.
54 * @param[in] object @c org.bluez.GattCharacteristic1 skeleton
55 * object associated with this method call.
56 * @param[in] invocation D-Bus method invocation related object used
57 * when sending results/errors asynchronously.
58 * @param[in] value The @c GVariant containing the byte array
59 * (ay) with th request data inside.
60 * @param[in] user_data Pointer to request
61 * @c CAGattCharacteristic object.
63 * @return @c TRUE to indicate that
64 * @c org.bluez.Characteristic.WriteValue() method is
67 static gboolean CAGattCharacteristicHandleWriteValue(
68 GattCharacteristic1 * object,
69 GDBusMethodInvocation * invocation,
74 This method is only trigged in a GATT server when receiving
75 data sent by a GATT client, i.e. the client wrote a value to
76 the server, and the server is handling that write request.
79 // GLib maps an octet to a guchar, which is of size 1.
81 gconstpointer const data =
82 g_variant_get_fixed_array(value, &len, 1);
84 CAGattCharacteristic * const c = user_data;
86 if (CAGattRecv(&c->recv_info, data, (uint32_t) len))
88 gatt_characteristic1_complete_write_value(object, invocation);
92 g_dbus_method_invocation_return_dbus_error(
94 "org.bluez.Error.Failed",
95 "Error when handling GATT request data fragment");
101 // ---------------------------------------------------------------------
102 // GATT Response Handling
103 // ---------------------------------------------------------------------
105 * Make the peer address corresponding to the given GATT
108 * @param[in] c Information about GATT characteristic for which the
109 * peer (client) @c CAEndpoint_t object is being
112 * @return @c String containing an encoded address associated with the
113 * peer connected to the peripheral on which the characteristic
114 * implementation resides, or @c NULL on error.
116 static char * CAGattCharacteristicMakePeerAddress(
117 CAGattCharacteristic * c)
122 Length of stringified pointer in hexadecimal format, plus one
125 static size_t const PSEUDO_ADDR_LEN = sizeof(intptr_t) / 4 + 1;
127 assert(MAX_ADDR_STR_SIZE_CA > PSEUDO_ADDR_LEN);
130 Since there is no direct way to obtain the client endpoint
131 associated with the GATT characterstics on the server side,
132 embed a stringified pointer to the response charactertistic of
133 the form "&ABCDEF01" is the CAEndpoint_t instead. This works
135 1) only one LE central is ever connected to an LE peripheral
136 2) the CA layer doesn't directly interpret the address
138 char * const addr = OICMalloc(PSEUDO_ADDR_LEN);
139 int const count = snprintf(addr,
144 if (count >= (int) PSEUDO_ADDR_LEN)
148 "Error creating peer address on server side.");
157 * Handle @c org.bluez.GattCharacterstic1.StartNotify() method call.
159 * This handler is triggered when the
160 * @c org.bluez.GattCharacterstic1.StartNotify() method is called by a
161 * client on the BlueZ-based OIC GATT response characteristic. It
162 * sets the @c org.bluez.GattCharacterstic1.Notifying property to
163 * @c TRUE, and enables responses from the server.
165 * @note This handler is not available in the OIC GATT request
166 * characteristic implementation.
168 * @param[in] object @c org.bluez.GattCharacteristic1 skeleton
169 * object associated with this method call.
170 * @param[in] invocation D-Bus method invocation related object used
171 * when sending results/errors asynchronously.
172 * @param[in] user_data Pointer to the response
173 * @c CAGattCharacteristic object.
175 * @return @c TRUE to indicate that
176 * @c org.bluez.Characteristic.StartNotify() method is
179 static gboolean CAGattCharacteristicHandleStartNotify(
180 GattCharacteristic1 * object,
181 GDBusMethodInvocation * invocation,
185 * Only allow the client to start notifications once.
187 * @todo Does BlueZ already prevent redundant calls to
188 * @c org.bluez.GattCharacteristic1.StartNotify()?
190 if (gatt_characteristic1_get_notifying(object))
192 g_dbus_method_invocation_return_dbus_error(
194 "org.bluez.Error.Failed",
195 "Notifications are already enabled.");
200 // Retrieve the response characteristic information.
201 CAGattCharacteristic * const characteristic = user_data;
204 CAGattCharacteristicMakePeerAddress(characteristic);
208 g_dbus_method_invocation_return_dbus_error(
210 "org.bluez.Error.Failed",
211 "Error creating peer endpoint information");
217 Create an entry in the endpoint-to-characteristic map so that
218 responses may be sent to the GATT client through the OIC GATT
219 response characteristic through this BLE adapter's
220 CAAdapterSendUnicastData() implementation.
222 CALEContext * const context = characteristic->context;
224 ca_mutex_lock(context->lock);
226 #if GLIB_CHECK_VERSION(2,40,0)
228 GLib hash table functions started returning a boolean result in
231 bool const inserted =
233 g_hash_table_insert(context->characteristic_map,
237 ca_mutex_unlock(context->lock);
239 #if GLIB_CHECK_VERSION(2,40,0)
242 g_dbus_method_invocation_return_dbus_error(
244 "org.bluez.Error.Failed",
245 "Unable to set response endpoint.");
254 * @todo Do we need to explicitly emit the @c GObject @c notify or
255 * @c org.freedesktop.Dbus.Properties.PropertiesChanged
258 gatt_characteristic1_set_notifying(object, TRUE);
261 Set the client endpoint field in the request characteristic so
262 that it may pass the appropriate endpoint object up the stack
263 through the CA request/response callback once a request has been
264 completely received and reassembled.
266 CAGattRecvInfo * const recv_info =
267 &characteristic->service->request_characteristic.recv_info;
269 recv_info->peer = peer;
271 ca_mutex_lock(context->lock);
272 recv_info->on_packet_received = context->on_server_received_data;
273 recv_info->context = context;
274 ca_mutex_unlock(context->lock);
276 gatt_characteristic1_complete_start_notify(object, invocation);
282 * Handle @c org.bluez.GattCharacterstic1.StopNotify() method call.
284 * This handler is triggered when the
285 * @c org.bluez.GattCharacterstic1.StopNotify() method is called by a
286 * client on the BlueZ-based OIC GATT response characteristic. It
287 * sets the @c org.bluez.GattCharacterstic1.Notifying property to
288 * @c FALSE, and disables responses from the server.
290 * @param[in] object @c org.bluez.GattCharacteristic1 skeleton
291 * object associated with this method call.
292 * @param[in] invocation D-Bus method invocation related object used
293 * when sending results/errors asynchronously.
294 * @param[in] user_data Pointer to the response
295 * @c CAGattCharacteristic object.
297 * @return @c TRUE to indicate that
298 * @c org.bluez.Characteristic.StopNotify() method is
301 static gboolean CAGattCharacteristicHandleStopNotify(
302 GattCharacteristic1 * object,
303 GDBusMethodInvocation * invocation,
306 assert(user_data != NULL);
309 * @todo Does BlueZ already prevent redundant calls to
310 * @c org.bluez.GattCharacteristic1.StopNotify()?
312 if (!gatt_characteristic1_get_notifying(object))
314 g_dbus_method_invocation_return_dbus_error(
316 "org.bluez.Error.Failed",
317 "Notifications were not previously enabled.");
322 CAGattCharacteristic * const characteristic = user_data;
324 // Clear the client endpoint from the request characteristic.
325 CAGattRecvInfo * const recv_info =
326 &characteristic->service->request_characteristic.recv_info;
329 Remove the appropriate entry from the endpoint-to-characteristic
330 map so that attempts to send a response through it will fail.
332 CALEContext * const context = characteristic->context;
333 ca_mutex_lock(context->lock);
336 g_hash_table_remove(context->characteristic_map, recv_info->peer);
338 ca_mutex_unlock(context->lock);
340 CAGattRecvInfoDestroy(recv_info);
343 * @todo Do we need to explicitly emit the @c GObject @c notify or
344 * @c org.freedesktop.Dbus.Properties.PropertiesChanged
347 gatt_characteristic1_set_notifying(object, FALSE);
351 gatt_characteristic1_complete_stop_notify(object, invocation);
355 g_dbus_method_invocation_return_dbus_error(
357 "org.bluez.Error.Failed",
358 "Error removing peer address information");
364 // ---------------------------------------------------------------------
365 // GATT Characteristic Lifecyle
366 // ---------------------------------------------------------------------
369 * Initialize GATT characteristic fields.
371 * This function initializes the @c CAGattCharacteristic object fields.
373 * @param[out] c GATT characteristic information to
375 * @param[in] context Object containing the D-Bus
376 * connection to the bus on which the
377 * characteristic will be exported, as
378 * well as the list of connected
380 * @param[in] s Information about GATT service
381 * to which the characteristic
383 * @param[in] characteristic_path @c GattCharacteristic1 object
385 * @param[in] uuid GATT characteristic UUID.
386 * @param[in] flag GATT characteristic property flag,
387 * i.e. @c "write-without-response"
388 * for the request characteristic, and
389 * @c "notify" for the response
392 * @note This function does not allocate the @a characteristic object
393 * itself. The caller is responsible for allocating that
396 * @todo Too many parameters. Perhaps pass in a pointer to a struct
399 static bool CAGattCharacteristicInitialize(
400 CAGattCharacteristic * c,
401 CALEContext * context,
403 char const * characteristic_path,
407 // Path of the form /org/iotivity/gatt/hci0/service0/char0.
409 g_strdup_printf("%s/%s", s->object_path, characteristic_path);
411 assert(g_variant_is_object_path(c->object_path));
413 c->context = context;
416 c->characteristic = gatt_characteristic1_skeleton_new();
418 gatt_characteristic1_set_uuid(c->characteristic, uuid);
419 gatt_characteristic1_set_service(c->characteristic, s->object_path);
420 gatt_characteristic1_set_notifying(c->characteristic, FALSE);
422 char const * flags[] = { flag, NULL };
423 gatt_characteristic1_set_flags(c->characteristic, flags);
425 CAGattRecvInfoInitialize(&c->recv_info);
427 // Export the characteristic interface on the bus.
428 GError * error = NULL;
429 if (!g_dbus_interface_skeleton_export(
430 G_DBUS_INTERFACE_SKELETON(c->characteristic),
435 CAGattCharacteristicDestroy(c);
439 "Unable to export D-Bus GATT characteristic "
451 // ------------------------------------------------------------
453 bool CAGattRequestCharacteristicInitialize(struct CAGattService * s,
454 CALEContext * context)
456 CAGattCharacteristic * const c = &s->request_characteristic;
458 if (!CAGattCharacteristicInitialize(c,
461 CA_GATT_REQUEST_CHRC_PATH,
462 CA_GATT_REQUEST_CHRC_UUID,
463 "write-without-response"))
468 if (!CAGattRequestDescriptorInitialize(s, context->connection))
470 CAGattCharacteristicDestroy(c);
475 The descriptor object path is not fixed at compile-time.
476 Retrieve the object path that was set at run-time.
478 char const * descriptor_paths[] = {
479 c->descriptor.object_path,
483 gatt_characteristic1_set_descriptors(c->characteristic,
486 // The request characteristic only handles writes.
487 g_signal_connect(c->characteristic,
488 "handle-write-value",
489 G_CALLBACK(CAGattCharacteristicHandleWriteValue),
495 // ------------------------------------------------------------
497 bool CAGattResponseCharacteristicInitialize(struct CAGattService * s,
498 CALEContext * context)
500 CAGattCharacteristic * const c = &s->response_characteristic;
502 if (!CAGattCharacteristicInitialize(c,
505 CA_GATT_RESPONSE_CHRC_PATH,
506 CA_GATT_RESPONSE_CHRC_UUID,
514 if (!CAGattResponseDescriptorInitialize(s, context->connection))
516 CAGattCharacteristicDestroy(c);
521 The descriptor object path is not fixed at compile-time.
522 Retrieve the object path that was set at run-time.
524 Note that we don't explicitly add the Client Characteristic
525 Configuration Descriptor for the response characteristic since
526 that is done by BlueZ when the "notify" property is set.
527 Furthermore, a client requests notifications by calling the
528 org.bluez.GattCharacteristic1.StartNotify() method.
529 Consequently, there is no need for the client to explicitly
530 enable notifications by writing to the client characteristic
531 configuration descriptor.
533 char const * descriptor_paths[] = {
534 c->descriptor.object_path,
538 gatt_characteristic1_set_descriptors(c->characteristic,
541 // The response characteristic only handles notifications.
544 "handle-start-notify",
545 G_CALLBACK(CAGattCharacteristicHandleStartNotify),
550 "handle-stop-notify",
551 G_CALLBACK(CAGattCharacteristicHandleStopNotify),
557 void CAGattCharacteristicDestroy(CAGattCharacteristic * c)
559 assert(c != NULL); // As designed, c is always non-NULL.
561 CAGattRecvInfoDestroy(&c->recv_info);
563 CAGattDescriptorDestroy(&c->descriptor);
565 g_clear_object(&c->characteristic);
567 g_free(c->object_path);
568 c->object_path = NULL;
574 // ---------------------------------------------------------------------
575 // GATT Characteristic Properties
576 // ---------------------------------------------------------------------
579 GVariant * CAGattCharacteristicGetProperties(
580 GattCharacteristic1 * characteristic)
583 * Create a variant containing the @c GattCharacteristic1
584 * properties, of the form @c a{sa{sv}}.
586 * @note We don't care about the "Value" property here since it is
587 * automatically made available by BlueZ on the client
590 * @todo Should we care about "Notifying" property here?
594 Populate the property table, and create the variant to be
595 embedded in the results of the
596 org.freedesktop.Dbus.ObjectManager.GetManagedObjects() method
599 CADBusSkeletonProperty const properties[] = {
601 g_variant_new_string(
602 gatt_characteristic1_get_uuid(characteristic)) },
604 g_variant_new_object_path(
605 gatt_characteristic1_get_service(characteristic)) },
608 gatt_characteristic1_get_flags(characteristic),
612 gatt_characteristic1_get_descriptors(characteristic),
617 CAMakePropertyDictionary(
618 BLUEZ_GATT_CHARACTERISTIC_INTERFACE,
620 sizeof(properties) / sizeof(properties[0]));