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