xref: /openbmc/bmcweb/include/dbus_monitor.hpp (revision d78572018fc2022091ff8b8eb5a7fef2172ba3d6)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3911ac317SEd Tanous #pragma once
43ccb3adbSEd Tanous #include "app.hpp"
53ccb3adbSEd Tanous #include "dbus_singleton.hpp"
6*d7857201SEd Tanous #include "logging.hpp"
73ccb3adbSEd Tanous #include "openbmc_dbus_rest.hpp"
8faf100f9SEd Tanous #include "websocket.hpp"
93ccb3adbSEd Tanous 
10*d7857201SEd Tanous #include <systemd/sd-bus.h>
11*d7857201SEd Tanous 
12911ac317SEd Tanous #include <boost/container/flat_map.hpp>
139b243a4eSEd Tanous #include <boost/container/flat_set.hpp>
14*d7857201SEd Tanous #include <nlohmann/json.hpp>
151abe55efSEd Tanous #include <sdbusplus/bus/match.hpp>
16*d7857201SEd Tanous #include <sdbusplus/message.hpp>
171214b7e7SGunnar Mills 
18*d7857201SEd Tanous #include <cstddef>
19*d7857201SEd Tanous #include <cstring>
20*d7857201SEd Tanous #include <functional>
21*d7857201SEd Tanous #include <memory>
22*d7857201SEd Tanous #include <regex>
23*d7857201SEd Tanous #include <string>
24*d7857201SEd Tanous #include <vector>
259b243a4eSEd Tanous 
261abe55efSEd Tanous namespace crow
271abe55efSEd Tanous {
281abe55efSEd Tanous namespace dbus_monitor
291abe55efSEd Tanous {
30911ac317SEd Tanous 
311abe55efSEd Tanous struct DbusWebsocketSession
321abe55efSEd Tanous {
3359d494eeSPatrick Williams     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches;
3424b2fe81SEd Tanous     boost::container::flat_set<std::string, std::less<>,
3524b2fe81SEd Tanous                                std::vector<std::string>>
3624b2fe81SEd Tanous         interfaces;
37911ac317SEd Tanous };
38911ac317SEd Tanous 
39cf9e417dSEd Tanous using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
40cf9e417dSEd Tanous                                               DbusWebsocketSession>;
41cf9e417dSEd Tanous 
42cf9e417dSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
43cf9e417dSEd Tanous static SessionMap sessions;
44911ac317SEd Tanous 
onPropertyUpdate(sd_bus_message * m,void * userdata,sd_bus_error * retError)459b243a4eSEd Tanous inline int onPropertyUpdate(sd_bus_message* m, void* userdata,
4681ce609eSEd Tanous                             sd_bus_error* retError)
471abe55efSEd Tanous {
48e662eae8SEd Tanous     if (retError == nullptr || (sd_bus_error_is_set(retError) != 0))
491abe55efSEd Tanous     {
5062598e31SEd Tanous         BMCWEB_LOG_ERROR("Got sdbus error on match");
519b243a4eSEd Tanous         return 0;
529b243a4eSEd Tanous     }
539b243a4eSEd Tanous     crow::websocket::Connection* connection =
549b243a4eSEd Tanous         static_cast<crow::websocket::Connection*>(userdata);
559b243a4eSEd Tanous     auto thisSession = sessions.find(connection);
561abe55efSEd Tanous     if (thisSession == sessions.end())
571abe55efSEd Tanous     {
5862598e31SEd Tanous         BMCWEB_LOG_ERROR("Couldn't find dbus connection {}",
5962598e31SEd Tanous                          logPtr(connection));
60aa2e59c1SEd Tanous         return 0;
61aa2e59c1SEd Tanous     }
6259d494eeSPatrick Williams     sdbusplus::message_t message(m);
631476687dSEd Tanous     nlohmann::json json;
641476687dSEd Tanous     json["event"] = message.get_member();
651476687dSEd Tanous     json["path"] = message.get_path();
661abe55efSEd Tanous     if (strcmp(message.get_member(), "PropertiesChanged") == 0)
671abe55efSEd Tanous     {
68715748a1SMatt Spinler         nlohmann::json data;
69715748a1SMatt Spinler         int r = openbmc_mapper::convertDBusToJSON("sa{sv}as", message, data);
70715748a1SMatt Spinler         if (r < 0)
71715748a1SMatt Spinler         {
7262598e31SEd Tanous             BMCWEB_LOG_ERROR("convertDBusToJSON failed with {}", r);
73715748a1SMatt Spinler             return 0;
74715748a1SMatt Spinler         }
75715748a1SMatt Spinler         if (!data.is_array())
76715748a1SMatt Spinler         {
7762598e31SEd Tanous             BMCWEB_LOG_ERROR("No data in PropertiesChanged signal");
78715748a1SMatt Spinler             return 0;
79715748a1SMatt Spinler         }
80715748a1SMatt Spinler 
81715748a1SMatt Spinler         // data is type sa{sv}as and is an array[3] of string, object, array
821476687dSEd Tanous         json["interface"] = data[0];
831476687dSEd Tanous         json["properties"] = data[1];
841abe55efSEd Tanous     }
851abe55efSEd Tanous     else if (strcmp(message.get_member(), "InterfacesAdded") == 0)
861abe55efSEd Tanous     {
87715748a1SMatt Spinler         nlohmann::json data;
88715748a1SMatt Spinler         int r = openbmc_mapper::convertDBusToJSON("oa{sa{sv}}", message, data);
89715748a1SMatt Spinler         if (r < 0)
901abe55efSEd Tanous         {
9162598e31SEd Tanous             BMCWEB_LOG_ERROR("convertDBusToJSON failed with {}", r);
92715748a1SMatt Spinler             return 0;
93715748a1SMatt Spinler         }
940bdda665SEd Tanous         nlohmann::json::array_t* arr = data.get_ptr<nlohmann::json::array_t*>();
950bdda665SEd Tanous         if (arr == nullptr)
960bdda665SEd Tanous         {
970bdda665SEd Tanous             BMCWEB_LOG_ERROR("No data in InterfacesAdded signal");
980bdda665SEd Tanous             return 0;
990bdda665SEd Tanous         }
1000bdda665SEd Tanous         if (arr->size() < 2)
101715748a1SMatt Spinler         {
10262598e31SEd Tanous             BMCWEB_LOG_ERROR("No data in InterfacesAdded signal");
103715748a1SMatt Spinler             return 0;
104715748a1SMatt Spinler         }
105715748a1SMatt Spinler 
1060bdda665SEd Tanous         nlohmann::json::object_t* obj =
1070bdda665SEd Tanous             (*arr)[1].get_ptr<nlohmann::json::object_t*>();
1080bdda665SEd Tanous         if (obj == nullptr)
109715748a1SMatt Spinler         {
1100bdda665SEd Tanous             BMCWEB_LOG_ERROR("No data in InterfacesAdded signal");
1110bdda665SEd Tanous             return 0;
1120bdda665SEd Tanous         }
1130bdda665SEd Tanous         // data is type oa{sa{sv}} which is an array[2] of string, object
1140bdda665SEd Tanous         for (const auto& entry : *obj)
1150bdda665SEd Tanous         {
1160bdda665SEd Tanous             auto it = thisSession->second.interfaces.find(entry.first);
1171abe55efSEd Tanous             if (it != thisSession->second.interfaces.end())
1181abe55efSEd Tanous             {
1190bdda665SEd Tanous                 json["interfaces"][entry.first] = entry.second;
120911ac317SEd Tanous             }
1219b243a4eSEd Tanous         }
1221abe55efSEd Tanous     }
1231abe55efSEd Tanous     else
1241abe55efSEd Tanous     {
12562598e31SEd Tanous         BMCWEB_LOG_CRITICAL("message {} was unexpected", message.get_member());
1269b243a4eSEd Tanous         return 0;
1279b243a4eSEd Tanous     }
1289b243a4eSEd Tanous 
12971f52d96SEd Tanous     connection->sendText(
1301476687dSEd Tanous         json.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
1319b243a4eSEd Tanous     return 0;
132271584abSEd Tanous }
133911ac317SEd Tanous 
requestRoutes(App & app)13423a21a1cSEd Tanous inline void requestRoutes(App& app)
1351abe55efSEd Tanous {
1369b243a4eSEd Tanous     BMCWEB_ROUTE(app, "/subscribe")
137432a890cSEd Tanous         .privileges({{"Login"}})
138911ac317SEd Tanous         .websocket()
13925ce6206SEd Tanous         .onopen([](crow::websocket::Connection& conn) {
14062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
14124b2fe81SEd Tanous             sessions.try_emplace(&conn);
142911ac317SEd Tanous         })
14325ce6206SEd Tanous         .onclose([](crow::websocket::Connection& conn, const std::string&) {
144cb13a392SEd Tanous             sessions.erase(&conn);
145cb13a392SEd Tanous         })
14625ce6206SEd Tanous         .onmessage([](crow::websocket::Connection& conn,
14762598e31SEd Tanous                       const std::string& data, bool) {
14824b2fe81SEd Tanous             const auto sessionPair = sessions.find(&conn);
14924b2fe81SEd Tanous             if (sessionPair == sessions.end())
15024b2fe81SEd Tanous             {
15124b2fe81SEd Tanous                 conn.close("Internal error");
15224b2fe81SEd Tanous             }
15324b2fe81SEd Tanous             DbusWebsocketSession& thisSession = sessionPair->second;
15462598e31SEd Tanous             BMCWEB_LOG_DEBUG("Connection {} received {}", logPtr(&conn), data);
1559b243a4eSEd Tanous             nlohmann::json j = nlohmann::json::parse(data, nullptr, false);
1561abe55efSEd Tanous             if (j.is_discarded())
1571abe55efSEd Tanous             {
15862598e31SEd Tanous                 BMCWEB_LOG_ERROR("Unable to parse json data for monitor");
1599b243a4eSEd Tanous                 conn.close("Unable to parse json request");
1609b243a4eSEd Tanous                 return;
1619b243a4eSEd Tanous             }
1629b243a4eSEd Tanous             nlohmann::json::iterator interfaces = j.find("interfaces");
1631abe55efSEd Tanous             if (interfaces != j.end())
1641abe55efSEd Tanous             {
1659b243a4eSEd Tanous                 thisSession.interfaces.reserve(interfaces->size());
1661abe55efSEd Tanous                 for (auto& interface : *interfaces)
1671abe55efSEd Tanous                 {
1681abe55efSEd Tanous                     const std::string* str =
1691abe55efSEd Tanous                         interface.get_ptr<const std::string*>();
1701abe55efSEd Tanous                     if (str != nullptr)
1711abe55efSEd Tanous                     {
1729b243a4eSEd Tanous                         thisSession.interfaces.insert(*str);
1739b243a4eSEd Tanous                     }
1749b243a4eSEd Tanous                 }
1759b243a4eSEd Tanous             }
1769b243a4eSEd Tanous 
1779b243a4eSEd Tanous             nlohmann::json::iterator paths = j.find("paths");
178418b934aSP Dheeraj Srujan Kumar             if (paths == j.end())
1791abe55efSEd Tanous             {
18062598e31SEd Tanous                 BMCWEB_LOG_ERROR("Unable to find paths in json data");
181418b934aSP Dheeraj Srujan Kumar                 conn.close("Unable to find paths in json data");
182418b934aSP Dheeraj Srujan Kumar                 return;
183418b934aSP Dheeraj Srujan Kumar             }
184418b934aSP Dheeraj Srujan Kumar 
185271584abSEd Tanous             size_t interfaceCount = thisSession.interfaces.size();
1861abe55efSEd Tanous             if (interfaceCount == 0)
1871abe55efSEd Tanous             {
1889b243a4eSEd Tanous                 interfaceCount = 1;
1899b243a4eSEd Tanous             }
190f8fe53e7SEd Tanous 
1919b243a4eSEd Tanous             // These regexes derived on the rules here:
1929b243a4eSEd Tanous             // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names
1934b242749SEd Tanous             static std::regex validPath("^/([A-Za-z0-9_]+/?)*$");
1944b242749SEd Tanous             static std::regex validInterface(
1959b243a4eSEd Tanous                 "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)+$");
1969b243a4eSEd Tanous 
1971abe55efSEd Tanous             for (const auto& thisPath : *paths)
1981abe55efSEd Tanous             {
1999b243a4eSEd Tanous                 const std::string* thisPathString =
2009b243a4eSEd Tanous                     thisPath.get_ptr<const std::string*>();
2011abe55efSEd Tanous                 if (thisPathString == nullptr)
2021abe55efSEd Tanous                 {
20362598e31SEd Tanous                     BMCWEB_LOG_ERROR("subscribe path isn't a string?");
2049b243a4eSEd Tanous                     conn.close();
2059b243a4eSEd Tanous                     return;
2069b243a4eSEd Tanous                 }
2071abe55efSEd Tanous                 if (!std::regex_match(*thisPathString, validPath))
2081abe55efSEd Tanous                 {
20962598e31SEd Tanous                     BMCWEB_LOG_ERROR("Invalid path name {}", *thisPathString);
2109b243a4eSEd Tanous                     conn.close();
2119b243a4eSEd Tanous                     return;
2129b243a4eSEd Tanous                 }
213f8fe53e7SEd Tanous                 std::string propertiesMatchString =
2149b243a4eSEd Tanous                     ("type='signal',"
2159b243a4eSEd Tanous                      "interface='org.freedesktop.DBus.Properties',"
2169b243a4eSEd Tanous                      "path_namespace='" +
2179b243a4eSEd Tanous                      *thisPathString +
2189b243a4eSEd Tanous                      "',"
2199b243a4eSEd Tanous                      "member='PropertiesChanged'");
2209b243a4eSEd Tanous                 // If interfaces weren't specified, add a single match for all
2219b243a4eSEd Tanous                 // interfaces
22226f6976fSEd Tanous                 if (thisSession.interfaces.empty())
2231abe55efSEd Tanous                 {
224bd79bce8SPatrick Williams                     BMCWEB_LOG_DEBUG("Creating match {}",
225bd79bce8SPatrick Williams                                      propertiesMatchString);
2269b243a4eSEd Tanous 
2279b243a4eSEd Tanous                     thisSession.matches.emplace_back(
22859d494eeSPatrick Williams                         std::make_unique<sdbusplus::bus::match_t>(
229bd79bce8SPatrick Williams                             *crow::connections::systemBus,
230bd79bce8SPatrick Williams                             propertiesMatchString, onPropertyUpdate, &conn));
2311abe55efSEd Tanous                 }
2321abe55efSEd Tanous                 else
2331abe55efSEd Tanous                 {
2341abe55efSEd Tanous                     // If interfaces were specified, add a match for each
2351abe55efSEd Tanous                     // interface
2361abe55efSEd Tanous                     for (const std::string& interface : thisSession.interfaces)
2371abe55efSEd Tanous                     {
2381abe55efSEd Tanous                         if (!std::regex_match(interface, validInterface))
2391abe55efSEd Tanous                         {
24062598e31SEd Tanous                             BMCWEB_LOG_ERROR("Invalid interface name {}",
24162598e31SEd Tanous                                              interface);
2429b243a4eSEd Tanous                             conn.close();
2439b243a4eSEd Tanous                             return;
2449b243a4eSEd Tanous                         }
245f23b7296SEd Tanous                         std::string ifaceMatchString = propertiesMatchString;
246f23b7296SEd Tanous                         ifaceMatchString += ",arg0='";
247f23b7296SEd Tanous                         ifaceMatchString += interface;
248f23b7296SEd Tanous                         ifaceMatchString += "'";
24962598e31SEd Tanous                         BMCWEB_LOG_DEBUG("Creating match {}", ifaceMatchString);
2509b243a4eSEd Tanous                         thisSession.matches.emplace_back(
25159d494eeSPatrick Williams                             std::make_unique<sdbusplus::bus::match_t>(
2529b243a4eSEd Tanous                                 *crow::connections::systemBus, ifaceMatchString,
2539b243a4eSEd Tanous                                 onPropertyUpdate, &conn));
2549b243a4eSEd Tanous                     }
2559b243a4eSEd Tanous                 }
256f8fe53e7SEd Tanous                 std::string objectManagerMatchString =
2579b243a4eSEd Tanous                     ("type='signal',"
2589b243a4eSEd Tanous                      "interface='org.freedesktop.DBus.ObjectManager',"
2599b243a4eSEd Tanous                      "path_namespace='" +
2609b243a4eSEd Tanous                      *thisPathString +
2619b243a4eSEd Tanous                      "',"
2629b243a4eSEd Tanous                      "member='InterfacesAdded'");
26362598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Creating match {}", objectManagerMatchString);
2649b243a4eSEd Tanous                 thisSession.matches.emplace_back(
26559d494eeSPatrick Williams                     std::make_unique<sdbusplus::bus::match_t>(
2662c70f800SEd Tanous                         *crow::connections::systemBus, objectManagerMatchString,
2672c70f800SEd Tanous                         onPropertyUpdate, &conn));
2689b243a4eSEd Tanous             }
269911ac317SEd Tanous         });
270911ac317SEd Tanous }
271aa2e59c1SEd Tanous } // namespace dbus_monitor
272911ac317SEd Tanous } // namespace crow
273