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