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