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