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