The open source OpenXR runtime
1// Copyright 2019-2020, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief BLE implementation based on Linux Bluez/dbus.
6 * @author Pete Black <pete.black@collabora.com>
7 * @author Jakob Bornecrantz <jakob@collabora.com>
8 * @ingroup aux_os
9 */
10
11#include "os_ble.h"
12
13#include "util/u_misc.h"
14#include "util/u_logging.h"
15
16#include <poll.h>
17#include <errno.h>
18#include <stdio.h>
19#include <fcntl.h>
20#include <unistd.h>
21#include <string.h>
22#include <time.h>
23#include <dbus/dbus.h>
24
25
26/*
27 *
28 * Defines.
29 *
30 */
31
32#define I(bch, ...) U_LOG_I(__VA_ARGS__)
33#define E(bch, ...) U_LOG_E(__VA_ARGS__)
34
35
36/*
37 *
38 * Structs.
39 *
40 */
41
42/*!
43 * Small helper that keeps track of a connection and a error.
44 */
45struct ble_conn_helper
46{
47 DBusConnection *conn;
48 DBusError err;
49};
50
51/*!
52 * An implementation of @ref os_ble_device using a DBus connection to BlueZ.
53 * @implements os_ble_device
54 */
55struct ble_notify
56{
57 struct os_ble_device base;
58 struct ble_conn_helper bch;
59 int fd;
60};
61
62
63/*
64 *
65 * Send helpers.
66 *
67 */
68
69static void
70add_single_byte_array(DBusMessage *msg, uint8_t value)
71{
72 // Create an array of bytes.
73 const char *container_signature = "y"; // dbus type signature string
74 DBusMessageIter iter;
75 DBusMessageIter array;
76
77 // attach it to our dbus message
78 dbus_message_iter_init_append(msg, &iter);
79 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, container_signature, &array);
80 dbus_message_iter_append_basic(&array, DBUS_TYPE_BYTE, &value);
81 dbus_message_iter_close_container(&iter, &array);
82}
83
84static void
85add_empty_dict_sv(DBusMessage *msg)
86{
87 // Create an empty array of string variant dicts.
88 const char *container_signature = "{sv}"; // dbus type signature string
89 DBusMessageIter iter;
90 DBusMessageIter options;
91
92 // attach it to our dbus message
93 dbus_message_iter_init_append(msg, &iter);
94 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, container_signature, &options);
95 dbus_message_iter_close_container(&iter, &options);
96}
97
98static int
99send_message(DBusConnection *conn, DBusError *err, DBusMessage **msg_ptr)
100{
101 DBusPendingCall *pending;
102
103 // Take the message and null it.
104 DBusMessage *msg = *msg_ptr;
105 *msg_ptr = NULL;
106
107 if (msg == NULL) {
108 U_LOG_E("Message Null after construction\n");
109 return -1;
110 }
111
112 // send message and get a handle for a reply
113 if (!dbus_connection_send_with_reply(conn, msg, &pending, -1)) {
114 // -1 is default timeout
115 U_LOG_E("Out Of Memory!\n");
116 return -1;
117 }
118 if (pending == NULL) {
119 U_LOG_E("Pending Call Null\n");
120 return -1;
121 }
122 dbus_connection_flush(conn);
123
124 // Unref the message.
125 dbus_message_unref(msg);
126 msg = NULL;
127
128 // block until we receive a reply
129 dbus_pending_call_block(pending);
130
131 // get the reply message
132 msg = dbus_pending_call_steal_reply(pending);
133
134 // free the pending message handle
135 dbus_pending_call_unref(pending);
136 pending = NULL;
137
138 if (msg == NULL) {
139 U_LOG_E("Reply Null\n");
140 return -1;
141 }
142
143 *msg_ptr = msg;
144 return 0;
145}
146
147
148/*
149 *
150 * Dump functions
151 *
152 */
153
154static void
155dump_recurse(DBusMessageIter *parent, DBusMessageIter *sub, int level);
156
157static int
158dump_one_element(DBusMessageIter *element, int level)
159{
160 int type = dbus_message_iter_get_arg_type(element);
161 char *str;
162
163 switch (type) {
164 case DBUS_TYPE_INVALID: {
165 U_LOG_E("%*s<>", level, "");
166 return -1;
167 }
168 case DBUS_TYPE_BOOLEAN: {
169 int val;
170 dbus_message_iter_get_basic(element, &val);
171 U_LOG_D("%*sBOOLEAN: %s", level, "", val == 0 ? "false" : "true");
172 return 0;
173 }
174 case DBUS_TYPE_BYTE: {
175 int8_t val;
176 dbus_message_iter_get_basic(element, &val);
177 U_LOG_D("%*sBYTE: %02x", level, "", val);
178 return 0;
179 }
180 case DBUS_TYPE_INT32: {
181 int32_t val;
182 dbus_message_iter_get_basic(element, &val);
183 U_LOG_D("%*sINT32: %" PRIi32, level, "", val);
184 return 0;
185 }
186 case DBUS_TYPE_UINT32: {
187 uint32_t val;
188 dbus_message_iter_get_basic(element, &val);
189 U_LOG_D("%*sUINT32: %" PRIu32, level, "", val);
190 return 0;
191 }
192 case DBUS_TYPE_INT64: {
193 int64_t val;
194 dbus_message_iter_get_basic(element, &val);
195 U_LOG_D("%*sINT64: %" PRIi64, level, "", val);
196 return 0;
197 }
198 case DBUS_TYPE_UINT64: {
199 uint64_t val;
200 dbus_message_iter_get_basic(element, &val);
201 U_LOG_D("%*sUINT32: %" PRIu64, level, "", val);
202 return 0;
203 }
204 case DBUS_TYPE_STRING: {
205 dbus_message_iter_get_basic(element, &str);
206 U_LOG_D("%*sSTRING: %s", level, "", str);
207 return 0;
208 }
209 case DBUS_TYPE_OBJECT_PATH: {
210 dbus_message_iter_get_basic(element, &str);
211 U_LOG_D("%*sOBJECT_PATH: %s", level, "", str);
212 return 0;
213 }
214 case DBUS_TYPE_ARRAY: {
215 int elm_type = dbus_message_iter_get_element_type(element);
216 int elm_count = dbus_message_iter_get_element_count(element);
217 U_LOG_D("%*sARRAY: %c:%i", level, "", elm_type, elm_count);
218 DBusMessageIter sub;
219 dbus_message_iter_recurse(element, &sub);
220 dump_recurse(element, &sub, level + 2);
221 return 0;
222 }
223 case DBUS_TYPE_VARIANT: {
224 DBusMessageIter var;
225 dbus_message_iter_recurse(element, &var);
226 int var_type = dbus_message_iter_get_arg_type(&var);
227 U_LOG_D("%*sVARIANT: %c", level, "", var_type);
228 dump_one_element(&var, level + 2);
229 return 0;
230 }
231 case DBUS_TYPE_DICT_ENTRY: {
232 U_LOG_D("%*sDICT", level, "");
233 DBusMessageIter sub;
234 dbus_message_iter_recurse(element, &sub);
235 dump_recurse(element, &sub, level + 2);
236 return 0;
237 }
238 default:
239 U_LOG_D("%*sGot! %c", level, "", type); // line break
240 return 0;
241 }
242}
243
244static void
245dump_recurse(DBusMessageIter *parent, DBusMessageIter *sub, int level)
246{
247 while (true) {
248 if (dump_one_element(sub, level) < 0) {
249 return;
250 }
251 dbus_message_iter_next(sub);
252 }
253}
254
255
256/*
257 *
258 * DBus iterator helper functions.
259 *
260 */
261
262/*!
263 * Checks if a string starts with, has extra slash and room for more.
264 */
265static bool
266starts_with_and_has_slash(const char *str, const char *beginning)
267{
268 size_t str_len = strlen(str);
269 size_t beginning_len = strlen(beginning);
270
271 if (str_len <= beginning_len + 1) {
272 return false;
273 }
274
275 size_t i = 0;
276 for (; i < beginning_len; i++) {
277 if (str[i] != beginning[i]) {
278 return false;
279 }
280 }
281
282 if (str[i] != '/') {
283 return false;
284 }
285
286 return true;
287}
288
289static int
290dict_get_string_and_varient_child(DBusMessageIter *dict, const char **out_str, DBusMessageIter *out_child)
291{
292 DBusMessageIter child;
293 int type = dbus_message_iter_get_arg_type(dict);
294 if (type != DBUS_TYPE_DICT_ENTRY) {
295 U_LOG_E("Expected dict got '%c'!\n", type);
296 return -1;
297 }
298
299 dbus_message_iter_recurse(dict, &child);
300 type = dbus_message_iter_get_arg_type(&child);
301 if (type != DBUS_TYPE_STRING && type != DBUS_TYPE_OBJECT_PATH) {
302 U_LOG_E(
303 "Expected dict first thing to be string or object path, "
304 "got '%c'\n",
305 type);
306 return -1;
307 }
308
309 dbus_message_iter_get_basic(&child, out_str);
310 dbus_message_iter_next(&child);
311
312 type = dbus_message_iter_get_arg_type(&child);
313 if (type != DBUS_TYPE_VARIANT) {
314 U_LOG_E("Expected variant got '%c'\n", type);
315 return -1;
316 }
317
318 dbus_message_iter_recurse(&child, out_child);
319
320 return 0;
321}
322
323static int
324dict_get_string_and_array_elm(const DBusMessageIter *in_dict, const char **out_str, DBusMessageIter *out_array_elm)
325{
326 DBusMessageIter dict = *in_dict;
327 DBusMessageIter child;
328 int type = dbus_message_iter_get_arg_type(&dict);
329 if (type != DBUS_TYPE_DICT_ENTRY) {
330 U_LOG_E("Expected dict got '%c'!\n", type);
331 return -1;
332 }
333
334 dbus_message_iter_recurse(&dict, &child);
335 type = dbus_message_iter_get_arg_type(&child);
336 if (type != DBUS_TYPE_STRING && type != DBUS_TYPE_OBJECT_PATH) {
337 U_LOG_E(
338 "Expected dict first thing to be string or object path, "
339 "got '%c'\n",
340 type);
341 return -1;
342 }
343
344 dbus_message_iter_get_basic(&child, out_str);
345 dbus_message_iter_next(&child);
346
347 type = dbus_message_iter_get_arg_type(&child);
348 if (type != DBUS_TYPE_ARRAY) {
349 U_LOG_E("Expected array got '%c'\n", type);
350 return -1;
351 }
352
353 dbus_message_iter_recurse(&child, out_array_elm);
354
355 return 0;
356}
357
358#define for_each(i, first) \
359 for (DBusMessageIter i = first; dbus_message_iter_get_arg_type(&i) != DBUS_TYPE_INVALID; \
360 dbus_message_iter_next(&i))
361
362/*!
363 * Ensures that the @p parent is a array and has a element type the given type,
364 * outputs the first element of the array on success.
365 */
366static int
367array_get_first_elem_of_type(const DBusMessageIter *in_parent, int of_type, DBusMessageIter *out_elm)
368{
369 DBusMessageIter parent = *in_parent;
370 int type = dbus_message_iter_get_arg_type(&parent);
371 if (type != DBUS_TYPE_ARRAY) {
372 U_LOG_E("Expected array got '%c'!\n", type);
373 return -1;
374 }
375
376 DBusMessageIter elm;
377 dbus_message_iter_recurse(&parent, &elm);
378
379 type = dbus_message_iter_get_arg_type(&elm);
380 if (type != of_type) {
381 U_LOG_E("Expected elem type of '%c' got '%c'!\n", of_type, type);
382 return -1;
383 }
384
385 *out_elm = elm;
386
387 return 1;
388}
389
390/*!
391 * Given a the first element in a array of dict, loop over them and check if
392 * the key matches its string value. Returns positive if a match is found,
393 * zero if not found and negative on failure. The argument @p out_value holds
394 * the value of the dict pair.
395 */
396static int
397array_find_variant_value(const DBusMessageIter *first_elm, const char *key, DBusMessageIter *out_value)
398{
399 const char *str;
400
401 for_each(elm, *first_elm)
402 {
403 dict_get_string_and_varient_child(&elm, &str, out_value);
404
405 if (strcmp(key, str) == 0) {
406 return 1;
407 }
408 }
409
410 return 0;
411}
412
413/*!
414 * Given a array which elements are of type string, loop over them and check if
415 * any of them matches the given @p key. Returns positive if a match is found,
416 * zero if not found and negative on failure.
417 */
418static int
419array_match_string_element(const DBusMessageIter *in_array, const char *key)
420{
421 DBusMessageIter array = *in_array;
422 int type = dbus_message_iter_get_arg_type(&array);
423 if (type != DBUS_TYPE_ARRAY) {
424 U_LOG_E("Expected array type ('%c')\n", type);
425 return -1;
426 }
427
428 int elm_type = dbus_message_iter_get_element_type(&array);
429 if (elm_type != DBUS_TYPE_STRING) {
430 U_LOG_E("Expected string element type ('%c')\n", type);
431 return -1;
432 }
433
434 DBusMessageIter first_elm;
435 dbus_message_iter_recurse(&array, &first_elm);
436
437 for_each(elm, first_elm)
438 {
439 const char *str = NULL;
440 dbus_message_iter_get_basic(&elm, &str);
441
442 if (strcmp(key, str) == 0) {
443 return 1;
444 }
445 }
446
447 return 0;
448}
449
450
451/*
452 *
453 * D-BUS helpers.
454 *
455 */
456
457static int
458dbus_has_name(DBusConnection *conn, const char *name)
459{
460 DBusMessage *msg;
461 DBusError err;
462
463 msg = dbus_message_new_method_call("org.freedesktop.DBus", // target for the method call
464 "/org/freedesktop/DBus", // object to call on
465 "org.freedesktop.DBus", // interface to call on
466 "ListNames"); // method name
467 if (send_message(conn, &err, &msg) != 0) {
468 return -1;
469 }
470
471 DBusMessageIter args;
472 dbus_message_iter_init(msg, &args);
473
474 // Check if this is a error message.
475 int type = dbus_message_iter_get_arg_type(&args);
476 if (type == DBUS_TYPE_STRING) {
477 char *response = NULL;
478 dbus_message_iter_get_basic(&args, &response);
479 U_LOG_E("Error getting calling ListNames:\n%s\n", response);
480 response = NULL;
481
482 // free reply
483 dbus_message_unref(msg);
484 return -1;
485 }
486
487 DBusMessageIter first_elm;
488 int ret = array_get_first_elem_of_type(&args, DBUS_TYPE_STRING, &first_elm);
489 if (ret < 0) {
490 // free reply
491 dbus_message_unref(msg);
492 return -1;
493 }
494
495 for_each(elm, first_elm)
496 {
497 char *response = NULL;
498 dbus_message_iter_get_basic(&elm, &response);
499 if (strcmp(response, name) == 0) {
500 // free reply
501 dbus_message_unref(msg);
502 return 0;
503 }
504 }
505
506 // free reply
507 dbus_message_unref(msg);
508 return -1;
509}
510
511
512/*
513 *
514 * Bluez helpers iterator helpers.
515 *
516 */
517
518/*!
519 * Returns true if the object implements the `org.bluez.Device1` interface,
520 * and one of its `UUIDs` matches the given @p uuid.
521 */
522static int
523device_has_uuid(const DBusMessageIter *dict, const char *uuid, const char **out_path_str)
524{
525 DBusMessageIter iface_elm;
526 DBusMessageIter first_elm;
527 const char *iface_str;
528 const char *path_str;
529
530 int ret = dict_get_string_and_array_elm(dict, &path_str, &first_elm);
531 if (ret < 0) {
532 return ret;
533 }
534
535 for_each(elm, first_elm)
536 {
537 dict_get_string_and_array_elm(&elm, &iface_str, &iface_elm);
538
539 if (strcmp(iface_str, "org.bluez.Device1") != 0) {
540 continue;
541 }
542
543 DBusMessageIter value;
544 int ret = array_find_variant_value(&iface_elm, "UUIDs", &value);
545 if (ret <= 0) {
546 continue;
547 }
548
549 ret = array_match_string_element(&value, uuid);
550 if (ret <= 0) {
551 continue;
552 }
553
554 *out_path_str = path_str;
555 return 1;
556 }
557
558 return 0;
559}
560
561
562/*!
563 * On a gatt interface object get its Flags property and check if notify is
564 * set, returns positive if it found that Flags property, zero on not finding it
565 * and negative on error.
566 */
567static int
568gatt_iface_get_flag_notifiable(const DBusMessageIter *iface_elm, bool *out_bool)
569{
570 DBusMessageIter value;
571 int ret = array_find_variant_value(iface_elm, "Flags", &value);
572 if (ret <= 0) {
573 return ret;
574 }
575
576 ret = array_match_string_element(&value, "notify");
577 if (ret < 0) {
578 // Error
579 return ret;
580 }
581 if (ret > 0) {
582 // Found the notify field!
583 *out_bool = true;
584 }
585
586 // We found the Flags field.
587 return 1;
588}
589
590/*!
591 * On a gatt interface object get its UUID string property, returns positive
592 * if found, zero on not finding it and negative on error.
593 */
594static int
595gatt_iface_get_uuid(const DBusMessageIter *iface_elm, const char **out_str)
596{
597 DBusMessageIter value;
598 int ret = array_find_variant_value(iface_elm, "UUID", &value);
599 if (ret <= 0) {
600 return ret;
601 }
602
603 int type = dbus_message_iter_get_arg_type(&value);
604 if (type != DBUS_TYPE_STRING) {
605 U_LOG_E("Invalid UUID value type ('%c')\n", type);
606 return -1;
607 }
608
609 dbus_message_iter_get_basic(&value, out_str);
610 return 1;
611}
612
613/*!
614 * Returns positive value if the object implements the
615 * `org.bluez.GattCharacteristic1` interface, its `UUID` matches the given @p
616 * uuid.
617 */
618static int
619gatt_char_has_uuid(const DBusMessageIter *dict, const char *uuid, const char **out_path_str, bool *out_notifiable)
620{
621 DBusMessageIter first_elm;
622 DBusMessageIter iface_elm;
623 const char *iface_str;
624 const char *path_str;
625 const char *uuid_str;
626
627 int ret = dict_get_string_and_array_elm(dict, &path_str, &first_elm);
628 if (ret < 0) {
629 return ret;
630 }
631
632 for_each(elm, first_elm)
633 {
634 dict_get_string_and_array_elm(&elm, &iface_str, &iface_elm);
635
636 if (strcmp(iface_str, "org.bluez.GattCharacteristic1") != 0) {
637 continue;
638 }
639
640 if (gatt_iface_get_uuid(&iface_elm, &uuid_str) <= 0) {
641 continue;
642 }
643
644 if (strcmp(uuid_str, uuid) != 0) {
645 continue;
646 }
647
648 bool notifiable = false;
649 ret = gatt_iface_get_flag_notifiable(&iface_elm, ¬ifiable);
650 if (ret <= 0) {
651 continue;
652 }
653
654 *out_path_str = path_str;
655 *out_notifiable = notifiable;
656 return 1;
657 }
658
659 return 0;
660}
661
662
663/*
664 *
665 * Bluez helpers..
666 *
667 */
668
669static void
670ble_close(struct ble_conn_helper *bch)
671{
672 if (bch->conn == NULL) {
673 return;
674 }
675
676 dbus_error_free(&bch->err);
677 dbus_connection_unref(bch->conn);
678 bch->conn = NULL;
679}
680
681static int
682ble_init(struct ble_conn_helper *bch)
683{
684 dbus_error_init(&bch->err);
685 bch->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &bch->err);
686 if (dbus_error_is_set(&bch->err)) {
687 E(bch, "DBUS Connection Error: %s\n", bch->err.message);
688 dbus_error_free(&bch->err);
689 }
690 if (bch->conn == NULL) {
691 return -1;
692 }
693
694 // Check if org.bluez is running.
695 int ret = dbus_has_name(bch->conn, "org.bluez");
696 if (ret != 0) {
697 ble_close(bch);
698 return -1;
699 }
700
701 return 0;
702}
703
704static int
705ble_dbus_send(struct ble_conn_helper *bch, DBusMessage **out_msg)
706{
707 return send_message(bch->conn, &bch->err, out_msg);
708}
709
710static int
711ble_get_managed_objects(struct ble_conn_helper *bch, DBusMessage **out_msg)
712{
713 DBusMessageIter args;
714 DBusMessage *msg;
715 int type;
716
717 msg = dbus_message_new_method_call("org.bluez", // target for the method call
718 "/", // object to call on
719 "org.freedesktop.DBus.ObjectManager", // interface to call on
720 "GetManagedObjects"); // method name
721 if (msg == NULL) {
722 E(bch, "Could not create new message!");
723 return -1;
724 }
725
726 int ret = ble_dbus_send(bch, &msg);
727 if (ret != 0) {
728 E(bch, "Could send message '%i'!", ret);
729 dbus_message_unref(msg);
730 return ret;
731 }
732
733 dbus_message_iter_init(msg, &args);
734
735 // Check if this is a error message.
736 type = dbus_message_iter_get_arg_type(&args);
737 if (type == DBUS_TYPE_STRING) {
738 char *response = NULL;
739 dbus_message_iter_get_basic(&args, &response);
740 E(bch, "Error getting objects:\n%s\n", response);
741 response = NULL;
742
743 // free reply
744 dbus_message_unref(msg);
745 return -1;
746 }
747
748 // Message is verified.
749 *out_msg = msg;
750
751 return 0;
752}
753
754static int
755ble_connect(struct ble_conn_helper *bch, const char *dbus_address)
756{
757 DBusMessage *msg = NULL;
758 DBusMessageIter args;
759 char *response = NULL;
760 int ret;
761 int type;
762
763 I(bch, "Connecting '%s'", dbus_address);
764
765 msg = dbus_message_new_method_call("org.bluez", // target for the method call
766 dbus_address, // object to call on
767 "org.bluez.Device1", // interface to call on
768 "Connect"); // method name
769 if (msg == NULL) {
770 E(bch, "Message NULL after construction\n");
771 return -1;
772 }
773
774 // Send the message, consumes our message and returns what we received.
775 ret = ble_dbus_send(bch, &msg);
776 if (ret != 0) {
777 E(bch, "Failed to send message '%i'\n", ret);
778 return -1;
779 }
780
781 // Function returns nothing on success, check for error message.
782 dbus_message_iter_init(msg, &args);
783 type = dbus_message_iter_get_arg_type(&args);
784 if (type == DBUS_TYPE_STRING) {
785 dbus_message_iter_get_basic(&args, &response);
786 E(bch, "DBus call returned message: %s\n", response);
787
788 // Free reply.
789 dbus_message_unref(msg);
790 return -1;
791 }
792
793 // Free reply
794 dbus_message_unref(msg);
795
796 return 0;
797}
798
799static int
800ble_connect_all_devices_with_service_uuid(struct ble_conn_helper *bch, const char *service_uuid)
801{
802 DBusMessageIter args;
803 DBusMessageIter first_elm;
804 DBusMessage *msg = NULL;
805
806 int ret = ble_get_managed_objects(bch, &msg);
807 if (ret != 0) {
808 return ret;
809 }
810
811 dbus_message_iter_init(msg, &args);
812 ret = array_get_first_elem_of_type(&args, DBUS_TYPE_DICT_ENTRY, &first_elm);
813 if (ret < 0) {
814 // free reply
815 dbus_message_unref(msg);
816 return -1;
817 }
818
819 for_each(elm, first_elm)
820 {
821 const char *dev_path_str;
822 ret = device_has_uuid(&elm, service_uuid, &dev_path_str);
823 if (ret <= 0) {
824 continue;
825 }
826
827 ble_connect(bch, dev_path_str);
828 }
829
830 // free reply
831 dbus_message_unref(msg);
832
833 return 0;
834}
835
836static int
837ble_write_value(struct ble_conn_helper *bch, const char *dbus_address, uint8_t value)
838{
839 DBusMessage *msg = NULL;
840 DBusMessageIter args;
841 char *response = NULL;
842 int ret;
843 int type;
844
845 msg = dbus_message_new_method_call("org.bluez", // target for the method call
846 dbus_address, // object to call on
847 "org.bluez.GattCharacteristic1", // interface to call on
848 "WriteValue"); // method name
849 if (msg == NULL) {
850 E(bch, "Message NULL after construction\n");
851 return -1;
852 }
853
854 // Write has a argument of Array of Bytes, Array of Dicts.
855 add_single_byte_array(msg, value);
856 add_empty_dict_sv(msg);
857
858 // Send the message, consumes our message and returns what we received.
859 ret = ble_dbus_send(bch, &msg);
860 if (ret != 0) {
861 E(bch, "Failed to send message '%i'\n", ret);
862 return -1;
863 }
864
865 // Function returns nothing on success, check for error message.
866 dbus_message_iter_init(msg, &args);
867 type = dbus_message_iter_get_arg_type(&args);
868 if (type == DBUS_TYPE_STRING) {
869 dbus_message_iter_get_basic(&args, &response);
870 E(bch, "DBus call returned message: %s\n", response);
871
872 // Free reply.
873 dbus_message_unref(msg);
874 return -1;
875 }
876
877 // Free reply
878 dbus_message_unref(msg);
879
880 return 0;
881}
882
883static ssize_t
884get_path_to_notify_char(
885 struct ble_conn_helper *bch, const char *dev_uuid, const char *char_uuid, char *output, size_t output_len)
886{
887 DBusMessageIter args;
888 DBusMessageIter first_elm;
889 DBusMessage *msg;
890
891 int ret = ble_get_managed_objects(bch, &msg);
892 if (ret != 0) {
893 return ret;
894 }
895
896 dbus_message_iter_init(msg, &args);
897 ret = array_get_first_elem_of_type(&args, DBUS_TYPE_DICT_ENTRY, &first_elm);
898 if (ret < 0) {
899 // free reply
900 dbus_message_unref(msg);
901 return -1;
902 }
903
904 for_each(elm, first_elm)
905 {
906 const char *dev_path_str;
907 const char *char_path_str;
908 bool notifiable;
909 ret = device_has_uuid(&elm, dev_uuid, &dev_path_str);
910 if (ret <= 0) {
911 continue;
912 }
913
914 for_each(c, first_elm)
915 {
916 ret = gatt_char_has_uuid(&c, char_uuid, &char_path_str, ¬ifiable);
917 if (ret <= 0 || !notifiable) {
918 continue;
919 }
920 if (!starts_with_and_has_slash(char_path_str, dev_path_str)) {
921 continue;
922 }
923
924 ssize_t written = snprintf(output, output_len, "%s", char_path_str);
925
926 // free reply
927 dbus_message_unref(msg);
928
929 return written;
930 }
931 }
932
933 // free reply
934 dbus_message_unref(msg);
935
936 return 0;
937}
938
939static int
940init_ble_notify(const char *dev_uuid, const char *char_uuid, struct ble_notify *bledev)
941{
942 DBusMessage *msg;
943 int ret;
944
945 ret = ble_init(&bledev->bch);
946 if (ret != 0) {
947 return ret;
948 }
949
950 char dbus_address[256]; // should be long enough
951 XRT_MAYBE_UNUSED ssize_t written =
952 get_path_to_notify_char(&bledev->bch, dev_uuid, char_uuid, dbus_address, sizeof(dbus_address));
953 if (written == 0) {
954 return -1;
955 }
956 if (written < 0) {
957 return -1;
958 }
959
960 msg = dbus_message_new_method_call("org.bluez", // target for the method call
961 dbus_address, // object to call on
962 "org.bluez.GattCharacteristic1", // interface to call on
963 "AcquireNotify"); // method name
964 if (msg == NULL) {
965 E(&bledev->bch, "Message Null after construction\n");
966 return -1;
967 }
968
969 // AcquireNotify has a argument of Array of Dicts.
970 add_empty_dict_sv(msg);
971
972 // Send the message, consumes our message and returns what we received.
973 if (ble_dbus_send(&bledev->bch, &msg) != 0) {
974 return -1;
975 }
976
977 DBusMessageIter args;
978 char *response = NULL;
979 dbus_message_iter_init(msg, &args);
980 while (true) {
981 int type = dbus_message_iter_get_arg_type(&args);
982 if (type == DBUS_TYPE_INVALID) {
983 break;
984 }
985 if (type == DBUS_TYPE_STRING) {
986 dbus_message_iter_get_basic(&args, &response);
987 E(&bledev->bch, "DBus call returned message: %s\n", response);
988 } else if (type == DBUS_TYPE_UNIX_FD) {
989 dbus_message_iter_get_basic(&args, &bledev->fd);
990 }
991 dbus_message_iter_next(&args);
992 }
993
994 // free reply
995 dbus_message_unref(msg);
996
997 // We didn't get a fd.
998 if (bledev->fd == -1) {
999 return -1;
1000 }
1001
1002 return 0;
1003}
1004
1005
1006/*
1007 *
1008 * BLE notify object implementation.
1009 *
1010 */
1011
1012static int
1013os_ble_notify_read(struct os_ble_device *bdev, uint8_t *data, size_t length, int milliseconds)
1014{
1015 struct ble_notify *dev = (struct ble_notify *)bdev;
1016 struct pollfd fds;
1017 int ret;
1018
1019 if (milliseconds >= 0) {
1020 fds.fd = dev->fd;
1021 fds.events = POLLIN;
1022 fds.revents = 0;
1023 ret = poll(&fds, 1, milliseconds);
1024
1025 if (ret == -1 || ret == 0) {
1026 // Error or timeout.
1027 return ret;
1028 }
1029 if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) {
1030 // Device disconnect?
1031 return -1;
1032 }
1033 }
1034
1035 ret = read(dev->fd, data, length);
1036
1037 if (ret < 0 && (errno == EAGAIN || errno == EINPROGRESS)) {
1038 // Process most likely received a signal.
1039 ret = 0;
1040 }
1041
1042 return ret;
1043}
1044
1045static void
1046os_ble_notify_destroy(struct os_ble_device *bdev)
1047{
1048 struct ble_notify *dev = (struct ble_notify *)bdev;
1049
1050 if (dev->fd != -1) {
1051 close(dev->fd);
1052 dev->fd = -1;
1053 }
1054
1055 ble_close(&dev->bch);
1056
1057 free(dev);
1058}
1059
1060
1061/*
1062 *
1063 * 'Exported' functions.
1064 *
1065 */
1066
1067int
1068os_ble_notify_open(const char *dev_uuid, const char *char_uuid, struct os_ble_device **out_ble)
1069{
1070 struct ble_notify *bledev = U_TYPED_CALLOC(struct ble_notify);
1071 bledev->base.read = os_ble_notify_read;
1072 bledev->base.destroy = os_ble_notify_destroy;
1073 bledev->fd = -1;
1074
1075 int ret = init_ble_notify(dev_uuid, char_uuid, bledev);
1076 if (ret != 0) {
1077 os_ble_notify_destroy(&bledev->base);
1078 return -1;
1079 }
1080
1081 *out_ble = &bledev->base;
1082
1083 return 1;
1084}
1085
1086int
1087os_ble_broadcast_write_value(const char *service_uuid, const char *char_uuid, uint8_t value)
1088{
1089 struct ble_conn_helper bch = {0};
1090 DBusMessageIter args;
1091 DBusMessageIter first_elm;
1092 DBusMessage *msg = NULL;
1093 int ret = 0;
1094
1095
1096 /*
1097 * Init dbus
1098 */
1099
1100 ret = ble_init(&bch);
1101 if (ret != 0) {
1102 return ret;
1103 }
1104
1105 /*
1106 * Connect devices
1107 */
1108
1109 // Connect all of the devices so we can write to them.
1110 ble_connect_all_devices_with_service_uuid(&bch, service_uuid);
1111
1112
1113 /*
1114 * Write to all connected devices.
1115 */
1116
1117 /*
1118 * We get the objects again, because their services and characteristics
1119 * might not have been created before.
1120 */
1121 ret = ble_get_managed_objects(&bch, &msg);
1122 if (ret != 0) {
1123 ble_close(&bch);
1124 return ret;
1125 }
1126
1127 dbus_message_iter_init(msg, &args);
1128 ret = array_get_first_elem_of_type(&args, DBUS_TYPE_DICT_ENTRY, &first_elm);
1129 if (ret < 0) {
1130 // free reply
1131 dbus_message_unref(msg);
1132 ble_close(&bch);
1133 return -1;
1134 }
1135
1136 for_each(elm, first_elm)
1137 {
1138 const char *dev_path_str;
1139 const char *char_path_str;
1140 bool notifiable;
1141 ret = device_has_uuid(&elm, service_uuid, &dev_path_str);
1142 if (ret <= 0) {
1143 continue;
1144 }
1145
1146 for_each(c, first_elm)
1147 {
1148 ret = gatt_char_has_uuid(&c, char_uuid, &char_path_str, ¬ifiable);
1149 if (ret <= 0) {
1150 continue;
1151 }
1152 if (!starts_with_and_has_slash(char_path_str, dev_path_str)) {
1153 continue;
1154 }
1155
1156 ble_write_value(&bch, char_path_str, value);
1157 }
1158 }
1159
1160 // free reply
1161 dbus_message_unref(msg);
1162 ble_close(&bch);
1163
1164 return 0;
1165}