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