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