xref: /openbmc/bmcweb/include/dbus_monitor.hpp (revision 683f7276)
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