blob: 44a7a944b1ee0da2d4b40f3024088fb7b9190366 [file] [log] [blame]
Ed Tanous911ac312017-08-15 09:37:42 -07001#pragma once
Ed Tanous911ac312017-08-15 09:37:42 -07002#include <crow/app.h>
Ed Tanous9b243a42018-08-03 14:33:10 -07003#include <crow/websocket.h>
Ed Tanous1abe55e2018-09-05 08:30:59 -07004
Ed Tanous911ac312017-08-15 09:37:42 -07005#include <boost/container/flat_map.hpp>
Ed Tanous9b243a42018-08-03 14:33:10 -07006#include <boost/container/flat_set.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07007#include <dbus_singleton.hpp>
8#include <sdbusplus/bus/match.hpp>
William A. Kennington III0a63b1c2018-10-18 13:37:19 -07009#include <sdbusplus/message/types.hpp>
Ed Tanous9b243a42018-08-03 14:33:10 -070010
Ed Tanous1abe55e2018-09-05 08:30:59 -070011namespace nlohmann
12{
Ed Tanous9b243a42018-08-03 14:33:10 -070013template <typename... Args>
Ed Tanous1abe55e2018-09-05 08:30:59 -070014struct adl_serializer<sdbusplus::message::variant<Args...>>
15{
16 static void to_json(json& j, const sdbusplus::message::variant<Args...>& v)
17 {
William A. Kennington III0a63b1c2018-10-18 13:37:19 -070018 sdbusplus::message::variant_ns::visit([&](auto&& val) { j = val; }, v);
Ed Tanous1abe55e2018-09-05 08:30:59 -070019 }
Ed Tanous9b243a42018-08-03 14:33:10 -070020};
Ed Tanous1abe55e2018-09-05 08:30:59 -070021} // namespace nlohmann
Ed Tanous911ac312017-08-15 09:37:42 -070022
Ed Tanous1abe55e2018-09-05 08:30:59 -070023namespace crow
24{
25namespace dbus_monitor
26{
Ed Tanous911ac312017-08-15 09:37:42 -070027
Ed Tanous1abe55e2018-09-05 08:30:59 -070028struct DbusWebsocketSession
29{
30 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
31 boost::container::flat_set<std::string> interfaces;
Ed Tanous911ac312017-08-15 09:37:42 -070032};
33
Ed Tanous55c7b7a2018-05-22 15:27:24 -070034static boost::container::flat_map<crow::websocket::Connection*,
Ed Tanous911ac312017-08-15 09:37:42 -070035 DbusWebsocketSession>
36 sessions;
37
Ed Tanous9b243a42018-08-03 14:33:10 -070038inline int onPropertyUpdate(sd_bus_message* m, void* userdata,
Ed Tanous1abe55e2018-09-05 08:30:59 -070039 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;
Ed Tanous9b243a42018-08-03 14:33:10 -070045 }
Ed Tanous1abe55e2018-09-05 08:30:59 -070046 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 }
Ed Tanous9b243a42018-08-03 14:33:10 -070092
Ed Tanous1abe55e2018-09-05 08:30:59 -070093 connection->sendText(j.dump());
94 return 0;
Ed Tanous911ac312017-08-15 09:37:42 -070095};
96
Ed Tanous1abe55e2018-09-05 08:30:59 -070097template <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");
Ed Tanous9b243a42018-08-03 14:33:10 -0700116 return;
Ed Tanous9b243a42018-08-03 14:33:10 -0700117 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700118 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 });
Ed Tanous911ac312017-08-15 09:37:42 -0700231}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700232} // namespace dbus_monitor
233} // namespace crow