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>) { 124 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 125 sessions[&conn] = DbusWebsocketSession(); 126 }) 127 .onclose([&](crow::websocket::Connection& conn, const std::string&) { 128 sessions.erase(&conn); 129 }) 130 .onmessage([&](crow::websocket::Connection& conn, 131 const std::string& data, bool) { 132 DbusWebsocketSession& thisSession = sessions[&conn]; 133 BMCWEB_LOG_DEBUG << "Connection " << &conn << " received " << data; 134 nlohmann::json j = nlohmann::json::parse(data, nullptr, false); 135 if (j.is_discarded()) 136 { 137 BMCWEB_LOG_ERROR << "Unable to parse json data for monitor"; 138 conn.close("Unable to parse json request"); 139 return; 140 } 141 nlohmann::json::iterator interfaces = j.find("interfaces"); 142 if (interfaces != j.end()) 143 { 144 thisSession.interfaces.reserve(interfaces->size()); 145 for (auto& interface : *interfaces) 146 { 147 const std::string* str = 148 interface.get_ptr<const std::string*>(); 149 if (str != nullptr) 150 { 151 thisSession.interfaces.insert(*str); 152 } 153 } 154 } 155 156 nlohmann::json::iterator paths = j.find("paths"); 157 if (paths != j.end()) 158 { 159 size_t interfaceCount = thisSession.interfaces.size(); 160 if (interfaceCount == 0) 161 { 162 interfaceCount = 1; 163 } 164 // Reserve our matches upfront. For each path there is 1 for 165 // interfacesAdded, and InterfaceCount number for 166 // PropertiesChanged 167 thisSession.matches.reserve(thisSession.matches.size() + 168 paths->size() * 169 (1U + interfaceCount)); 170 } 171 std::string objectManagerMatchString; 172 std::string propertiesMatchString; 173 std::string objectManagerInterfacesMatchString; 174 // These regexes derived on the rules here: 175 // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names 176 std::regex validPath("^/([A-Za-z0-9_]+/?)*$"); 177 std::regex validInterface( 178 "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)+$"); 179 180 for (const auto& thisPath : *paths) 181 { 182 const std::string* thisPathString = 183 thisPath.get_ptr<const std::string*>(); 184 if (thisPathString == nullptr) 185 { 186 BMCWEB_LOG_ERROR << "subscribe path isn't a string?"; 187 conn.close(); 188 return; 189 } 190 if (!std::regex_match(*thisPathString, validPath)) 191 { 192 BMCWEB_LOG_ERROR << "Invalid path name " << *thisPathString; 193 conn.close(); 194 return; 195 } 196 propertiesMatchString = 197 ("type='signal'," 198 "interface='org.freedesktop.DBus.Properties'," 199 "path_namespace='" + 200 *thisPathString + 201 "'," 202 "member='PropertiesChanged'"); 203 // If interfaces weren't specified, add a single match for all 204 // interfaces 205 if (thisSession.interfaces.size() == 0) 206 { 207 BMCWEB_LOG_DEBUG << "Creating match " 208 << propertiesMatchString; 209 210 thisSession.matches.emplace_back( 211 std::make_unique<sdbusplus::bus::match::match>( 212 *crow::connections::systemBus, 213 propertiesMatchString, onPropertyUpdate, &conn)); 214 } 215 else 216 { 217 // If interfaces were specified, add a match for each 218 // interface 219 for (const std::string& interface : thisSession.interfaces) 220 { 221 if (!std::regex_match(interface, validInterface)) 222 { 223 BMCWEB_LOG_ERROR << "Invalid interface name " 224 << interface; 225 conn.close(); 226 return; 227 } 228 std::string ifaceMatchString = 229 propertiesMatchString + ",arg0='" + interface + "'"; 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 objectManagerMatchString = 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 << objectManagerMatchString; 247 thisSession.matches.emplace_back( 248 std::make_unique<sdbusplus::bus::match::match>( 249 *crow::connections::systemBus, objectManagerMatchString, 250 onPropertyUpdate, &conn)); 251 } 252 }); 253 } 254 } // namespace dbus_monitor 255 } // namespace crow 256