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