blob: 5dcd5ca3629f969627f5bf24ba3c8c99c3a0fe11 [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>
Ed Tanous9b243a42018-08-03 14:33:10 -07009
Ed Tanous1abe55e2018-09-05 08:30:59 -070010namespace nlohmann
11{
Ed Tanous9b243a42018-08-03 14:33:10 -070012template <typename... Args>
Ed Tanous1abe55e2018-09-05 08:30:59 -070013struct 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 }
Ed Tanous9b243a42018-08-03 14:33:10 -070019};
Ed Tanous1abe55e2018-09-05 08:30:59 -070020} // namespace nlohmann
Ed Tanous911ac312017-08-15 09:37:42 -070021
Ed Tanous1abe55e2018-09-05 08:30:59 -070022namespace crow
23{
24namespace dbus_monitor
25{
Ed Tanous911ac312017-08-15 09:37:42 -070026
Ed Tanous1abe55e2018-09-05 08:30:59 -070027struct DbusWebsocketSession
28{
29 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
30 boost::container::flat_set<std::string> interfaces;
Ed Tanous911ac312017-08-15 09:37:42 -070031};
32
Ed Tanous55c7b7a2018-05-22 15:27:24 -070033static boost::container::flat_map<crow::websocket::Connection*,
Ed Tanous911ac312017-08-15 09:37:42 -070034 DbusWebsocketSession>
35 sessions;
36
Ed Tanous9b243a42018-08-03 14:33:10 -070037inline int onPropertyUpdate(sd_bus_message* m, void* userdata,
Ed Tanous1abe55e2018-09-05 08:30:59 -070038 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;
Ed Tanous9b243a42018-08-03 14:33:10 -070044 }
Ed Tanous1abe55e2018-09-05 08:30:59 -070045 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 }
Ed Tanous9b243a42018-08-03 14:33:10 -070091
Ed Tanous1abe55e2018-09-05 08:30:59 -070092 connection->sendText(j.dump());
93 return 0;
Ed Tanous911ac312017-08-15 09:37:42 -070094};
95
Ed Tanous1abe55e2018-09-05 08:30:59 -070096template <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");
Ed Tanous9b243a42018-08-03 14:33:10 -0700115 return;
Ed Tanous9b243a42018-08-03 14:33:10 -0700116 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700117 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 });
Ed Tanous911ac312017-08-15 09:37:42 -0700230}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700231} // namespace dbus_monitor
232} // namespace crow