The open source OpenXR runtime
at main 1165 lines 26 kB view raw
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, &notifiable); 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, &notifiable); 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, &notifiable); 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}