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