1 #pragma once 2 #include <dbus_singleton.hpp> 3 #include <sdbusplus/bus/match.hpp> 4 #include <crow/app.h> 5 #include <crow/websocket.h> 6 #include <boost/container/flat_map.hpp> 7 #include <boost/container/flat_set.hpp> 8 9 namespace nlohmann { 10 template <typename... Args> 11 struct adl_serializer<sdbusplus::message::variant<Args...>> { 12 static void to_json(json& j, const sdbusplus::message::variant<Args...>& v) { 13 mapbox::util::apply_visitor([&](auto&& val) { j = val; }, v); 14 } 15 }; 16 } // namespace nlohmann 17 18 namespace crow { 19 namespace dbus_monitor { 20 21 struct DbusWebsocketSession { 22 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches; 23 boost::container::flat_set<std::string> interfaces; 24 }; 25 26 static boost::container::flat_map<crow::websocket::Connection*, 27 DbusWebsocketSession> 28 sessions; 29 30 inline int onPropertyUpdate(sd_bus_message* m, void* userdata, 31 sd_bus_error* ret_error) { 32 if (ret_error == nullptr || sd_bus_error_is_set(ret_error)) { 33 BMCWEB_LOG_ERROR << "Got sdbus error on match"; 34 return 0; 35 } 36 crow::websocket::Connection* connection = 37 static_cast<crow::websocket::Connection*>(userdata); 38 auto thisSession = sessions.find(connection); 39 if (thisSession == sessions.end()) { 40 BMCWEB_LOG_ERROR << "Couldn't find dbus connection " << connection; 41 return 0; 42 } 43 sdbusplus::message::message message(m); 44 using VariantType = 45 sdbusplus::message::variant<std::string, bool, int64_t, uint64_t, double>; 46 nlohmann::json j{{"event", message.get_member()}, 47 {"path", message.get_path()}}; 48 if (strcmp(message.get_member(), "PropertiesChanged") == 0) { 49 std::string interface_name; 50 boost::container::flat_map<std::string, VariantType> values; 51 message.read(interface_name, values); 52 j["properties"] = values; 53 j["interface"] = std::move(interface_name); 54 55 } else if (strcmp(message.get_member(), "InterfacesAdded") == 0) { 56 std::string object_name; 57 boost::container::flat_map< 58 std::string, boost::container::flat_map<std::string, VariantType>> 59 values; 60 message.read(object_name, values); 61 for (const std::pair<std::string, 62 boost::container::flat_map<std::string, VariantType>>& 63 paths : values) { 64 auto it = thisSession->second.interfaces.find(paths.first); 65 if (it != thisSession->second.interfaces.end()) { 66 j["interfaces"][paths.first] = paths.second; 67 } 68 } 69 } else { 70 BMCWEB_LOG_CRITICAL << "message " << message.get_member() 71 << " was unexpected"; 72 return 0; 73 } 74 75 connection->sendText(j.dump()); 76 return 0; 77 }; 78 79 template <typename... Middlewares> 80 void requestRoutes(Crow<Middlewares...>& app) { 81 BMCWEB_ROUTE(app, "/subscribe") 82 .websocket() 83 .onopen([&](crow::websocket::Connection& conn) { 84 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 85 sessions[&conn] = DbusWebsocketSession(); 86 }) 87 .onclose([&](crow::websocket::Connection& conn, 88 const std::string& reason) { sessions.erase(&conn); }) 89 .onmessage([&](crow::websocket::Connection& conn, const std::string& data, 90 bool is_binary) { 91 DbusWebsocketSession& thisSession = sessions[&conn]; 92 BMCWEB_LOG_DEBUG << "Connection " << &conn << " recevied " << data; 93 nlohmann::json j = nlohmann::json::parse(data, nullptr, false); 94 if (j.is_discarded()) { 95 BMCWEB_LOG_ERROR << "Unable to parse json data for monitor"; 96 conn.close("Unable to parse json request"); 97 return; 98 } 99 nlohmann::json::iterator interfaces = j.find("interfaces"); 100 if (interfaces != j.end()) { 101 thisSession.interfaces.reserve(interfaces->size()); 102 for (auto& interface : *interfaces) { 103 const std::string* str = interface.get_ptr<const std::string*>(); 104 if (str != nullptr) { 105 thisSession.interfaces.insert(*str); 106 } 107 } 108 } 109 110 nlohmann::json::iterator paths = j.find("paths"); 111 if (paths != j.end()) { 112 int interfaceCount = thisSession.interfaces.size(); 113 if (interfaceCount == 0) { 114 interfaceCount = 1; 115 } 116 // Reserve our matches upfront. For each path there is 1 for 117 // interfacesAdded, and InterfaceCount number for PropertiesChanged 118 thisSession.matches.reserve(thisSession.matches.size() + 119 paths->size() * (1 + interfaceCount)); 120 } 121 std::string object_manager_match_string; 122 std::string properties_match_string; 123 std::string object_manager_interfaces_match_string; 124 // These regexes derived on the rules here: 125 // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names 126 std::regex validPath("^/([A-Za-z0-9_]+/?)*$"); 127 std::regex validInterface( 128 "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)+$"); 129 130 for (const auto& thisPath : *paths) { 131 const std::string* thisPathString = 132 thisPath.get_ptr<const std::string*>(); 133 if (thisPathString == nullptr) { 134 BMCWEB_LOG_ERROR << "subscribe path isn't a string?"; 135 conn.close(); 136 return; 137 } 138 if (!std::regex_match(*thisPathString, validPath)) { 139 BMCWEB_LOG_ERROR << "Invalid path name " << *thisPathString; 140 conn.close(); 141 return; 142 } 143 properties_match_string = 144 ("type='signal'," 145 "interface='org.freedesktop.DBus.Properties'," 146 "path_namespace='" + 147 *thisPathString + 148 "'," 149 "member='PropertiesChanged'"); 150 // If interfaces weren't specified, add a single match for all 151 // interfaces 152 if (thisSession.interfaces.size() == 0) { 153 BMCWEB_LOG_DEBUG << "Creating match " << properties_match_string; 154 155 thisSession.matches.emplace_back( 156 std::make_unique<sdbusplus::bus::match::match>( 157 *crow::connections::systemBus, properties_match_string, 158 onPropertyUpdate, &conn)); 159 } else { 160 // If interfaces were specified, add a match for each interface 161 for (const std::string& interface : thisSession.interfaces) { 162 if (!std::regex_match(interface, validInterface)) { 163 BMCWEB_LOG_ERROR << "Invalid interface name " << interface; 164 conn.close(); 165 return; 166 } 167 std::string ifaceMatchString = 168 properties_match_string + ",arg0='" + interface + "'"; 169 BMCWEB_LOG_DEBUG << "Creating match " << ifaceMatchString; 170 thisSession.matches.emplace_back( 171 std::make_unique<sdbusplus::bus::match::match>( 172 *crow::connections::systemBus, ifaceMatchString, 173 onPropertyUpdate, &conn)); 174 } 175 } 176 object_manager_match_string = 177 ("type='signal'," 178 "interface='org.freedesktop.DBus.ObjectManager'," 179 "path_namespace='" + 180 *thisPathString + 181 "'," 182 "member='InterfacesAdded'"); 183 BMCWEB_LOG_DEBUG << "Creating match " << object_manager_match_string; 184 thisSession.matches.emplace_back( 185 std::make_unique<sdbusplus::bus::match::match>( 186 *crow::connections::systemBus, object_manager_match_string, 187 onPropertyUpdate, &conn)); 188 } 189 }); 190 } 191 } // namespace dbus_monitor 192 } // namespace crow 193