1 #ifndef DBUS_PROPERTIES_HPP 2 #define DBUS_PROPERTIES_HPP 3 4 #include <dbus/connection.hpp> 5 #include <dbus/filter.hpp> 6 #include <dbus/match.hpp> 7 #include <functional> 8 #include <tuple> 9 #include <type_traits> 10 #include <boost/algorithm/string/predicate.hpp> 11 #include <boost/container/flat_map.hpp> 12 #include <boost/container/flat_set.hpp> 13 14 namespace dbus { 15 struct DbusArgument { 16 DbusArgument(const std::string& direction, const std::string& name, 17 const std::string& type) 18 : direction(direction), name(name), type(type){}; 19 std::string direction; 20 std::string name; 21 std::string type; 22 }; 23 24 class DbusMethod { 25 public: 26 DbusMethod(const std::string& name, std::shared_ptr<dbus::connection>& conn) 27 : name(name), conn(conn){}; 28 virtual void call(dbus::message& m){}; 29 virtual std::vector<DbusArgument> get_args() { return {}; }; 30 std::string name; 31 std::shared_ptr<dbus::connection> conn; 32 }; 33 34 enum class UpdateType { VALUE_CHANGE_ONLY, FORCE }; 35 36 // Base case for when I == the size of the tuple args. Does nothing, as we 37 // should be done 38 template <std::size_t TupleIndex = 0, typename... Tp> 39 inline typename std::enable_if<TupleIndex == sizeof...(Tp), void>::type 40 arg_types(bool in, std::tuple<Tp...>& t, std::vector<DbusArgument>& v, 41 const std::vector<std::string>* arg_names = nullptr) {} 42 43 // Case for when I < the size of tuple args. Unpacks the tuple type into the 44 // dbusargument object and names it appropriately. 45 template <std::size_t TupleIndex = 0, typename... Tp> 46 inline typename std::enable_if < 47 TupleIndex<sizeof...(Tp), void>::type arg_types( 48 bool in, std::tuple<Tp...>& t, std::vector<DbusArgument>& v, 49 const std::vector<std::string>* arg_names = nullptr) { 50 typedef typename std::tuple_element<TupleIndex, std::tuple<Tp...>>::type 51 element_type; 52 auto constexpr sig = element_signature<element_type>::code; 53 std::string name; 54 std::string direction; 55 if (arg_names == nullptr || arg_names->size() <= TupleIndex) { 56 if (in) { 57 name = "arg_" + std::to_string(TupleIndex); 58 } else { 59 name = "out_" + std::to_string(TupleIndex); 60 } 61 } else { 62 name = (*arg_names)[TupleIndex]; 63 } 64 v.emplace_back(in ? "in" : "out", name, &sig[0]); 65 66 arg_types<TupleIndex + 1, Tp...>(in, t, v, arg_names); 67 } 68 69 // Special case for handling raw arguments returned from handlers. Because they 70 // don't get stored in a tuple, special handling is neccesary 71 template <typename Element> 72 void arg_types(bool in, Element& t, std::vector<DbusArgument>& v, 73 const std::vector<std::string>* arg_names = nullptr) { 74 auto constexpr sig = element_signature<Element>::code; 75 std::string name; 76 if (arg_names == nullptr || arg_names->size() < 1) { 77 name.assign("arg_0"); 78 } else { 79 name = (*arg_names)[0]; 80 } 81 82 v.emplace_back(in ? "in" : "out", name, &sig[0]); 83 } 84 85 template <typename Handler> 86 class LambdaDbusMethod : public DbusMethod { 87 public: 88 typedef function_traits<Handler> traits; 89 typedef typename traits::decayed_arg_types InputTupleType; 90 typedef typename traits::result_type ResultType; 91 LambdaDbusMethod(const std::string name, 92 std::shared_ptr<dbus::connection>& conn, Handler h) 93 : DbusMethod(name, conn), h(std::move(h)) { 94 InputTupleType t; 95 arg_types(true, t, args); 96 97 ResultType o; 98 arg_types(false, o, args); 99 } 100 101 LambdaDbusMethod(const std::string& name, 102 const std::vector<std::string>& input_arg_names, 103 const std::vector<std::string>& output_arg_names, 104 std::shared_ptr<dbus::connection>& conn, Handler h) 105 : DbusMethod(name, conn), h(std::move(h)) { 106 InputTupleType t; 107 arg_types(true, t, args, &input_arg_names); 108 109 ResultType o; 110 arg_types(false, o, args, &output_arg_names); 111 } 112 void call(dbus::message& m) override { 113 InputTupleType input_args; 114 if (unpack_into_tuple(input_args, m) == false) { 115 auto err = dbus::message::new_error(m, DBUS_ERROR_INVALID_ARGS, ""); 116 conn->send(err, std::chrono::seconds(0)); 117 return; 118 } 119 try { 120 ResultType r = apply(h, input_args); 121 auto ret = dbus::message::new_return(m); 122 if (pack_tuple_into_msg(r, ret) == false) { 123 auto err = dbus::message::new_error( 124 m, DBUS_ERROR_FAILED, "Handler had issue when packing response"); 125 conn->send(err, std::chrono::seconds(0)); 126 return; 127 } 128 conn->send(ret, std::chrono::seconds(0)); 129 } catch (...) { 130 auto err = dbus::message::new_error( 131 m, DBUS_ERROR_FAILED, 132 "Handler threw exception while handling request."); 133 conn->send(err, std::chrono::seconds(0)); 134 return; 135 } 136 }; 137 138 std::vector<DbusArgument> get_args() override { return args; }; 139 Handler h; 140 std::vector<DbusArgument> args; 141 }; 142 143 class DbusSignal { 144 public: 145 DbusSignal(){}; 146 virtual std::vector<DbusArgument> get_args() { return {}; } 147 }; 148 149 template <typename... Args> 150 class DbusTemplateSignal : public DbusSignal { 151 public: 152 DbusTemplateSignal(const std::string& name, const std::string& object_name, 153 const std::string& interface_name, 154 const std::vector<std::string>& names, 155 std::shared_ptr<dbus::connection>& conn) 156 : DbusSignal(), 157 name(name), 158 object_name(object_name), 159 interface_name(interface_name), 160 conn(conn) { 161 std::tuple<Args...> tu; 162 arg_types(true, tu, args, &names); 163 }; 164 165 void send(const Args&...) { 166 dbus::endpoint endpoint("", object_name, interface_name); 167 auto m = dbus::message::new_signal(endpoint, name); 168 conn->send(m, std::chrono::seconds(0)); 169 } 170 171 std::vector<DbusArgument> get_args() override { return args; }; 172 173 std::vector<DbusArgument> args; 174 std::string name; 175 std::string object_name; 176 std::string interface_name; 177 std::shared_ptr<dbus::connection> conn; 178 }; 179 180 class DbusInterface { 181 public: 182 DbusInterface(std::string interface_name, 183 std::shared_ptr<dbus::connection>& conn) 184 : interface_name(std::move(interface_name)), conn(conn) {} 185 virtual boost::container::flat_map<std::string, std::shared_ptr<DbusSignal>> 186 get_signals() { 187 return dbus_signals; 188 }; 189 virtual boost::container::flat_map<std::string, std::shared_ptr<DbusMethod>> 190 get_methods() { 191 return dbus_methods; 192 }; 193 virtual std::string get_interface_name() { return interface_name; }; 194 virtual const boost::container::flat_map<std::string, dbus_variant> 195 get_properties_map() { 196 return properties_map; 197 }; 198 199 dbus_variant get_property(const std::string& property_name) { 200 auto property = properties_map.find(property_name); 201 if (property == properties_map.end()) { 202 // TODO(ed) property not found error 203 throw std::runtime_error("property not found"); 204 } else { 205 return property->second; 206 } 207 } 208 209 template <typename VALUE_TYPE> 210 void set_property(const std::string& property_name, const VALUE_TYPE value, 211 UpdateType update_mode = UpdateType::VALUE_CHANGE_ONLY) { 212 // Construct a change vector of length 1. if this set_properties is ever 213 // templated for any type, we could probably swap with with a 214 // std::array<pair, 1> 215 std::vector<std::pair<std::string, dbus_variant>> v; 216 v.emplace_back(property_name, value); 217 set_properties(v, update_mode); 218 } 219 220 void set_properties( 221 const std::vector<std::pair<std::string, dbus_variant>>& v, 222 const UpdateType update_mode = UpdateType::VALUE_CHANGE_ONLY) { 223 // TODO(ed) generalize this interface for all "map like" types, basically 224 // anything that will return a const iterator of std::pair<string, 225 // variant> 226 std::vector<std::pair<std::string, dbus_variant>> updates; 227 updates.reserve(v.size()); 228 229 if (update_mode == UpdateType::FORCE) { 230 updates = v; 231 } else { 232 for (auto& property : v) { 233 auto property_map_it = properties_map.find(property.first); 234 if (property_map_it != properties_map.end()) { 235 // Property exists in map 236 if (property_map_it->second != property.second) { 237 properties_map[property.first] = property.second; 238 // if value has changed since last set 239 updates.emplace_back(*property_map_it); 240 } 241 } else { 242 // property doesn't exist, must be new 243 properties_map[property.first] = property.second; 244 updates.emplace_back(property.first, property.second); 245 } 246 } 247 } 248 249 dbus::endpoint endpoint("org.freedesktop.DBus", object_name, 250 "org.freedesktop.DBus.Properties"); 251 252 auto m = dbus::message::new_signal(endpoint, "PropertiesChanged"); 253 254 static const std::vector<std::string> empty; 255 m.pack(get_interface_name(), updates, empty); 256 // TODO(ed) make sure this doesn't block 257 conn->async_send( 258 m, [](const boost::system::error_code ec, dbus::message r) {}); 259 } 260 261 void register_method(std::shared_ptr<DbusMethod> method) { 262 dbus_methods.emplace(method->name, method); 263 } 264 265 template <typename Handler> 266 void register_method(const std::string& name, Handler method) { 267 dbus_methods.emplace(name, 268 new LambdaDbusMethod<Handler>(name, conn, method)); 269 } 270 271 template <typename Handler> 272 void register_method(const std::string& name, 273 const std::vector<std::string>& input_arg_names, 274 const std::vector<std::string>& output_arg_names, 275 Handler method) { 276 dbus_methods.emplace( 277 name, new LambdaDbusMethod<Handler>(name, input_arg_names, 278 output_arg_names, conn, method)); 279 } 280 281 template <typename... Args> 282 std::shared_ptr<DbusTemplateSignal<Args...>> register_signal( 283 const std::string& name, const std::vector<std::string> arg_names) { 284 auto sig = std::make_shared<DbusTemplateSignal<Args...>>( 285 name, object_name, interface_name, arg_names, conn); 286 dbus_signals.emplace(name, sig); 287 return sig; 288 } 289 290 void call(dbus::message& m) { 291 std::string method_name = m.get_member(); 292 auto method = dbus_methods.find(method_name); 293 if (method != dbus_methods.end()) { 294 method->second->call(m); 295 } // TODO(ed) send something when method doesn't exist? 296 } 297 298 std::string object_name; 299 std::string interface_name; 300 boost::container::flat_map<std::string, std::shared_ptr<DbusMethod>> 301 dbus_methods; 302 boost::container::flat_map<std::string, std::shared_ptr<DbusSignal>> 303 dbus_signals; 304 boost::container::flat_map<std::string, dbus_variant> properties_map; 305 std::shared_ptr<dbus::connection> conn; 306 }; 307 308 class DbusObject { 309 public: 310 DbusObject(std::shared_ptr<dbus::connection> conn, std::string object_name) 311 : object_name(std::move(object_name)), conn(conn) { 312 properties_iface = add_interface("org.freedesktop.DBus.Properties"); 313 314 properties_iface->register_method( 315 "Get", {"interface_name", "properties_name"}, {"value"}, 316 [&](const std::string& interface_name, 317 const std::string& property_name) { 318 auto interface_it = interfaces.find(interface_name); 319 if (interface_it == interfaces.end()) { 320 // Interface not found error 321 throw std::runtime_error("interface not found"); 322 } else { 323 auto& properties_map = interface_it->second->get_properties_map(); 324 auto property = properties_map.find(property_name); 325 if (property == properties_map.end()) { 326 // TODO(ed) property not found error 327 throw std::runtime_error("property not found"); 328 } else { 329 return std::tuple<dbus_variant>(property->second); 330 } 331 } 332 }); 333 334 properties_iface->register_method( 335 "GetAll", {"interface_name"}, {"properties"}, 336 [&](const std::string& interface_name) { 337 auto interface_it = interfaces.find(interface_name); 338 if (interface_it == interfaces.end()) { 339 // Interface not found error 340 throw std::runtime_error("interface not found"); 341 } else { 342 std::vector<std::pair<std::string, dbus_variant>> v; 343 for (auto& element : interface_it->second->get_properties_map()) { 344 v.emplace_back(element.first, element.second); 345 } 346 return std::tuple< 347 std::vector<std::pair<std::string, dbus_variant>>>(v); 348 } 349 }); 350 properties_iface->register_method( 351 "Set", {"interface_name", "properties_name", "value"}, {}, 352 [&](const std::string& interface_name, const std::string& property_name, 353 const dbus_variant& value) { 354 auto interface_it = interfaces.find(interface_name); 355 if (interface_it == interfaces.end()) { 356 // Interface not found error 357 throw std::runtime_error("interface not found"); 358 } else { 359 // Todo, the set propery (signular) interface should support 360 // handing a variant. The below is expensive 361 std::vector<std::pair<std::string, dbus_variant>> v; 362 v.emplace_back(property_name, value); 363 interface_it->second->set_properties(v); 364 return std::tuple<>(); 365 } 366 }); 367 368 properties_iface->register_signal< 369 std::string, std::vector<std::pair<std::string, dbus_variant>>, 370 std::vector<std::string>>( 371 "PropertiesChanged", 372 {"interface_name", "changed_properties", "invalidated_properties"}); 373 } 374 375 std::shared_ptr<DbusInterface> add_interface(const std::string& name) { 376 auto x = std::make_shared<DbusInterface>(name, conn); 377 register_interface(x); 378 return x; 379 } 380 381 void register_interface(std::shared_ptr<DbusInterface>& interface) { 382 interfaces[interface->get_interface_name()] = interface; 383 interface->object_name = object_name; 384 const static dbus::endpoint endpoint("", object_name, 385 "org.freedesktop.DBus.ObjectManager"); 386 387 auto m = message::new_signal(endpoint, "InterfacesAdded"); 388 typedef std::vector<std::pair<std::string, dbus_variant>> properties_dict; 389 std::vector<std::pair<std::string, properties_dict>> sig; 390 sig.emplace_back(interface->get_interface_name(), properties_dict()); 391 auto& prop_dict = sig.back().second; 392 for (auto& property : interface->get_properties_map()) { 393 prop_dict.emplace_back(property); 394 } 395 396 m.pack(object_name, sig); 397 398 conn->send(m, std::chrono::seconds(0)); 399 } 400 401 auto get_interfaces() { return interfaces; } 402 403 void call(dbus::message& m) { 404 auto interface = interfaces.find(m.get_interface()); 405 if (interface != interfaces.end()) { 406 interface->second->call(m); 407 } // TODO(ed) send something when interface doesn't exist? 408 } 409 410 std::string object_name; 411 std::shared_ptr<dbus::connection> conn; 412 413 // dbus::filter properties_filter; 414 std::shared_ptr<DbusInterface> properties_iface; 415 416 std::shared_ptr<DbusInterface> object_manager_iface; 417 418 std::function<void(boost::system::error_code, message)> callback; 419 boost::container::flat_map<std::string, std::shared_ptr<DbusInterface>> 420 interfaces; 421 }; 422 423 class DbusObjectServer { 424 public: 425 DbusObjectServer(std::shared_ptr<dbus::connection>& conn) : conn(conn) { 426 introspect_filter = 427 std::make_unique<dbus::filter>(conn, [](dbus::message m) { 428 if (m.get_type() != "method_call") { 429 return false; 430 } 431 if (m.get_interface() != "org.freedesktop.DBus.Introspectable") { 432 return false; 433 } 434 if (m.get_member() != "Introspect") { 435 return false; 436 }; 437 return true; 438 }); 439 440 introspect_filter->async_dispatch( 441 [&](const boost::system::error_code ec, dbus::message m) { 442 on_introspect(ec, m); 443 }); 444 445 object_manager_filter = 446 std::make_unique<dbus::filter>(conn, [](dbus::message m) { 447 448 if (m.get_type() != "method_call") { 449 return false; 450 } 451 if (m.get_interface() != "org.freedesktop.DBus.ObjectManager") { 452 return false; 453 } 454 if (m.get_member() != "GetManagedObjects") { 455 return false; 456 }; 457 return true; 458 }); 459 460 object_manager_filter->async_dispatch( 461 [&](const boost::system::error_code ec, dbus::message m) { 462 on_get_managed_objects(ec, m); 463 }); 464 465 method_filter = std::make_unique<dbus::filter>(conn, [](dbus::message m) { 466 467 if (m.get_type() != "method_call") { 468 return false; 469 } 470 return true; 471 }); 472 473 method_filter->async_dispatch( 474 [&](const boost::system::error_code ec, dbus::message m) { 475 on_method_call(ec, m); 476 }); 477 }; 478 479 std::shared_ptr<dbus::connection>& get_connection() { return conn; } 480 void on_introspect(const boost::system::error_code ec, dbus::message m) { 481 auto xml = get_xml_for_path(m.get_path()); 482 auto ret = dbus::message::new_return(m); 483 ret.pack(xml); 484 conn->async_send( 485 ret, [](const boost::system::error_code ec, dbus::message r) {}); 486 487 introspect_filter->async_dispatch( 488 [&](const boost::system::error_code ec, dbus::message m) { 489 on_introspect(ec, m); 490 }); 491 } 492 493 void on_method_call(const boost::system::error_code ec, dbus::message m) { 494 if (ec) { 495 std::cerr << "on_method_call error: " << ec << "\n"; 496 } else { 497 auto path = m.get_path(); 498 // TODO(ed) objects should be a map 499 for (auto& object : objects) { 500 if (object->object_name == path) { 501 object->call(m); 502 break; 503 } 504 } 505 } 506 method_filter->async_dispatch( 507 [&](const boost::system::error_code ec, dbus::message m) { 508 on_method_call(ec, m); 509 }); 510 } 511 512 void on_get_managed_objects(const boost::system::error_code ec, 513 dbus::message m) { 514 typedef std::vector<std::pair<std::string, dbus::dbus_variant>> 515 properties_dict; 516 517 typedef std::vector<std::pair<std::string, properties_dict>> 518 interfaces_dict; 519 520 std::vector<std::pair<object_path, interfaces_dict>> dict; 521 522 for (auto& object : objects) { 523 interfaces_dict i; 524 for (auto& interface : object->get_interfaces()) { 525 properties_dict p; 526 527 for (auto& property : interface.second->get_properties_map()) { 528 p.push_back(property); 529 } 530 i.emplace_back(interface.second->get_interface_name(), std::move(p)); 531 } 532 dict.emplace_back(object_path{object->object_name}, std::move(i)); 533 } 534 auto ret = dbus::message::new_return(m); 535 ret.pack(dict); 536 conn->async_send( 537 ret, [](const boost::system::error_code ec, dbus::message r) {}); 538 539 object_manager_filter->async_dispatch( 540 [&](const boost::system::error_code ec, dbus::message m) { 541 on_get_managed_objects(ec, m); 542 }); 543 } 544 545 std::shared_ptr<DbusObject> add_object(const std::string& name) { 546 auto x = std::make_shared<DbusObject>(conn, name); 547 register_object(x); 548 return x; 549 } 550 551 void register_object(std::shared_ptr<DbusObject> object) { 552 objects.emplace_back(object); 553 } 554 555 std::string get_xml_for_path(const std::string& path) { 556 std::string newpath(path); 557 558 if (newpath == "/") { 559 newpath.assign(""); 560 } 561 562 boost::container::flat_set<std::string> node_names; 563 std::string xml( 564 "<!DOCTYPE node PUBLIC " 565 "\"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" " 566 "\"http://www.freedesktop.org/standards/dbus/1.0/" 567 "introspect.dtd\">\n<node>"); 568 for (auto& object : objects) { 569 std::string& object_name = object->object_name; 570 // exact match 571 if (object->object_name == newpath) { 572 xml += 573 " <interface name=\"org.freedesktop.DBus.Peer\">" 574 " <method name=\"Ping\"/>" 575 " <method name=\"GetMachineId\">" 576 " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>" 577 " </method>" 578 " </interface>"; 579 580 xml += 581 " <interface name=\"org.freedesktop.DBus.ObjectManager\">" 582 " <method name=\"GetManagedObjects\">" 583 " <arg type=\"a{oa{sa{sv}}}\" " 584 " name=\"object_paths_interfaces_and_properties\" " 585 " direction=\"out\"/>" 586 " </method>" 587 " <signal name=\"InterfacesAdded\">" 588 " <arg type=\"o\" name=\"object_path\"/>" 589 " <arg type=\"a{sa{sv}}\" " 590 "name=\"interfaces_and_properties\"/>" 591 " </signal>" 592 " <signal name=\"InterfacesRemoved\">" 593 " <arg type=\"o\" name=\"object_path\"/>" 594 " <arg type=\"as\" name=\"interfaces\"/>" 595 " </signal>" 596 " </interface>"; 597 598 xml += 599 "<interface name=\"org.freedesktop.DBus.Introspectable\">" 600 " <method name=\"Introspect\">" 601 " <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>" 602 " </method>" 603 "</interface>"; 604 605 for (auto& interface_pair : object->interfaces) { 606 xml += "<interface name=\""; 607 xml += interface_pair.first; 608 xml += "\">"; 609 for (auto& method : interface_pair.second->get_methods()) { 610 xml += "<method name=\""; 611 xml += method.first; 612 xml += "\">"; 613 for (auto& arg : method.second->get_args()) { 614 xml += "<arg name=\""; 615 xml += arg.name; 616 xml += "\" type=\""; 617 xml += arg.type; 618 xml += "\" direction=\""; 619 xml += arg.direction; 620 xml += "\"/>"; 621 } 622 xml += "</method>"; 623 } 624 625 for (auto& signal : interface_pair.second->get_signals()) { 626 xml += "<signal name=\""; 627 xml += signal.first; 628 xml += "\">"; 629 for (auto& arg : signal.second->get_args()) { 630 xml += "<arg name=\""; 631 xml += arg.name; 632 xml += "\" type=\""; 633 xml += arg.type; 634 xml += "\"/>"; 635 } 636 637 xml += "</signal>"; 638 } 639 640 for (auto& property : interface_pair.second->get_properties_map()) { 641 xml += "<property name=\""; 642 xml += property.first; 643 xml += "\" type=\""; 644 645 std::string type = std::string(boost::apply_visitor( 646 [&](auto val) { 647 static const auto constexpr sig = 648 element_signature<decltype(val)>::code; 649 return &sig[0]; 650 }, 651 property.second)); 652 xml += type; 653 xml += "\" access=\""; 654 // TODO direction can be readwrite, read, or write. Need to 655 // make this configurable 656 xml += "readwrite"; 657 xml += "\"/>"; 658 } 659 xml += "</interface>"; 660 } 661 } else if (boost::starts_with(object_name, newpath)) { 662 auto slash_index = object_name.find("/", newpath.size() + 1); 663 auto subnode = object_name.substr(newpath.size() + 1, 664 slash_index - newpath.size() - 1); 665 if (node_names.find(subnode) == node_names.end()) { 666 node_names.insert(subnode); 667 xml += "<node name=\""; 668 xml += subnode; 669 xml += "\">"; 670 xml += "</node>"; 671 } 672 } 673 } 674 xml += "</node>"; 675 return xml; 676 } 677 678 private: 679 std::shared_ptr<dbus::connection> conn; 680 std::vector<std::shared_ptr<DbusObject>> objects; 681 std::unique_ptr<dbus::filter> introspect_filter; 682 std::unique_ptr<dbus::filter> object_manager_filter; 683 std::unique_ptr<dbus::filter> method_filter; 684 }; 685 } 686 687 #endif /* DBUS_PROPERTIES_HPP */ 688