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