1 #pragma once
2 
3 #include "dbus_types.hpp"
4 
5 #include <sdbusplus/bus/match.hpp>
6 
7 namespace openpower::pels
8 {
9 
10 namespace match_rules = sdbusplus::bus::match::rules;
11 
12 /**
13  * @class DBusWatcher
14  *
15  * The base class for the PropertyWatcher and InterfaceWatcher classes.
16  */
17 class DBusWatcher
18 {
19   public:
20     DBusWatcher() = delete;
21     virtual ~DBusWatcher() = default;
22     DBusWatcher(const DBusWatcher&) = default;
23     DBusWatcher& operator=(const DBusWatcher&) = default;
24     DBusWatcher(DBusWatcher&&) = default;
25     DBusWatcher& operator=(DBusWatcher&&) = default;
26 
27     /**
28      * @brief Constructor
29      *
30      * @param[in] path - The D-Bus path that will be watched
31      * @param[in] interface - The D-Bus interface that will be watched
32      */
33     DBusWatcher(const std::string& path, const std::string& interface) :
34         _path(path), _interface(interface)
35     {}
36 
37   protected:
38     /**
39      * @brief The D-Bus path
40      */
41     std::string _path;
42 
43     /**
44      * @brief The D-Bus interface
45      */
46     std::string _interface;
47 
48     /**
49      * @brief The match objects for the propertiesChanged and
50      *        interfacesAdded signals.
51      */
52     std::vector<sdbusplus::bus::match_t> _matches;
53 };
54 
55 /**
56  * @class PropertyWatcher
57  *
58  * This class allows the user to be kept up to data with a D-Bus
59  * property's value.  It does this by calling a user specified function
60  * that is passed the variant that contains the property's value when:
61  *
62  * 1) The property is read when the class is constructed, if
63  *    the property is on D-Bus at the time.
64  * 2) The property changes (via a property changed signal).
65  * 3) An interfacesAdded signal is received with that property.
66  *
67  * The DataInterface class is used to access D-Bus, and is a template
68  * to avoid any circular include issues as that class is one of the
69  * users of this one.
70  */
71 template <typename DataIface>
72 class PropertyWatcher : public DBusWatcher
73 {
74   public:
75     PropertyWatcher() = delete;
76     ~PropertyWatcher() = default;
77     PropertyWatcher(const PropertyWatcher&) = delete;
78     PropertyWatcher& operator=(const PropertyWatcher&) = delete;
79     PropertyWatcher(PropertyWatcher&&) = delete;
80     PropertyWatcher& operator=(PropertyWatcher&&) = delete;
81 
82     using PropertySetFunc = std::function<void(const DBusValue&)>;
83 
84     /**
85      * @brief Constructor
86      *
87      * Reads the property if it is on D-Bus, and sets up the match
88      * objects for the propertiesChanged and interfacesAdded signals.
89      *
90      * @param[in] bus - The sdbusplus bus object
91      * @param[in] path - The D-Bus path of the property
92      * @param[in] interface - The D-Bus interface that contains the property
93      * @param[in] propertyName - The property name
94      * @param[in] service - The D-Bus service to use for the property read.
95      *                      Can be empty to look it up instead.
96      * @param[in] dataIface - The DataInterface object
97      * @param[in] func - The callback used any time the property is read
98      */
99     PropertyWatcher(sdbusplus::bus_t& bus, const std::string& path,
100                     const std::string& interface,
101                     const std::string& propertyName, const std::string& service,
102                     const DataIface& dataIface, PropertySetFunc func) :
103         DBusWatcher(path, interface),
104         _name(propertyName), _setFunc(func)
105     {
106         _matches.emplace_back(
107             bus, match_rules::propertiesChanged(_path, _interface),
108             std::bind(std::mem_fn(&PropertyWatcher::propChanged), this,
109                       std::placeholders::_1));
110 
111         _matches.emplace_back(
112             bus,
113             match_rules::interfacesAdded() + match_rules::argNpath(0, _path),
114             std::bind(std::mem_fn(&PropertyWatcher::interfaceAdded), this,
115                       std::placeholders::_1));
116 
117         try
118         {
119             read(dataIface, service);
120         }
121         catch (const sdbusplus::exception_t& e)
122         {
123             // Path doesn't exist now
124         }
125     }
126 
127     /**
128      * @brief Constructor
129      *
130      * Reads the property if it is on D-Bus, and sets up the match
131      * objects for the propertiesChanged and interfacesAdded signals.
132      *
133      * Unlike the other constructor, this contructor doesn't take the
134      * service to use for the property read so it will look it up with
135      * an ObjectMapper GetObject call.
136      *
137      * @param[in] bus - The sdbusplus bus object
138      * @param[in] path - The D-Bus path of the property
139      * @param[in] interface - The D-Bus interface that contains the property
140      * @param[in] propertyName - The property name
141      * @param[in] dataIface - The DataInterface object
142      * @param[in] func - The callback used any time the property is read
143      */
144     PropertyWatcher(sdbusplus::bus_t& bus, const std::string& path,
145                     const std::string& interface,
146                     const std::string& propertyName, const DataIface& dataIface,
147                     PropertySetFunc func) :
148         PropertyWatcher(bus, path, interface, propertyName, "", dataIface, func)
149     {}
150 
151     /**
152      * @brief Reads the property on D-Bus, and calls
153      *        the user defined function with the value.
154      *
155      * If the passed in service is empty, look up the service to use.
156      *
157      * @param[in] dataIface - The DataInterface object
158      * @param[in] service - The D-Bus service to make the getProperty
159      *                      call with, if not empty
160      */
161     void read(const DataIface& dataIface, std::string service)
162     {
163         if (service.empty())
164         {
165             service = dataIface.getService(_path, _interface);
166         }
167 
168         if (!service.empty())
169         {
170             DBusValue value;
171             dataIface.getProperty(service, _path, _interface, _name, value);
172 
173             _setFunc(value);
174         }
175     }
176 
177     /**
178      * @brief The propertiesChanged callback
179      *
180      * Calls the user defined function with the property value
181      *
182      * @param[in] msg - The sdbusplus message object
183      */
184     void propChanged(sdbusplus::message_t& msg)
185     {
186         DBusInterface interface;
187         DBusPropertyMap properties;
188 
189         msg.read(interface, properties);
190 
191         auto prop = properties.find(_name);
192         if (prop != properties.end())
193         {
194             _setFunc(prop->second);
195         }
196     }
197 
198     /**
199      * @brief The interfacesAdded callback
200      *
201      * Calls the user defined function with the property value
202      *
203      * @param[in] msg - The sdbusplus message object
204      */
205     void interfaceAdded(sdbusplus::message_t& msg)
206     {
207         sdbusplus::message::object_path path;
208         DBusInterfaceMap interfaces;
209 
210         msg.read(path, interfaces);
211 
212         auto iface = interfaces.find(_interface);
213         if (iface != interfaces.end())
214         {
215             auto prop = iface->second.find(_name);
216             if (prop != iface->second.end())
217             {
218                 _setFunc(prop->second);
219             }
220         }
221     }
222 
223   private:
224     /**
225      * @brief The D-Bus property name
226      */
227     std::string _name;
228 
229     /**
230      * @brief The function that will be called any time the
231      *        property is read.
232      */
233     PropertySetFunc _setFunc;
234 };
235 
236 /**
237  * @class InterfaceWatcher
238  *
239  * This class allows the user to be kept up to data with a D-Bus
240  * interface's properties..  It does this by calling a user specified
241  * function that is passed a map of the D-Bus property names and values
242  * on that interface when:
243  *
244  * 1) The interface is read when the class is constructed, if
245  *    the interface is on D-Bus at the time.
246  * 2) The interface has a property that changes (via a property changed signal).
247  * 3) An interfacesAdded signal is received.
248  *
249  * The DataInterface class is used to access D-Bus, and is a template
250  * to avoid any circular include issues as that class is one of the
251  * users of this one.
252  */
253 template <typename DataIface>
254 class InterfaceWatcher : public DBusWatcher
255 {
256   public:
257     InterfaceWatcher() = delete;
258     ~InterfaceWatcher() = default;
259     InterfaceWatcher(const InterfaceWatcher&) = delete;
260     InterfaceWatcher& operator=(const InterfaceWatcher&) = delete;
261     InterfaceWatcher(InterfaceWatcher&&) = delete;
262     InterfaceWatcher& operator=(InterfaceWatcher&&) = delete;
263 
264     using InterfaceSetFunc = std::function<void(const DBusPropertyMap&)>;
265 
266     /**
267      * @brief Constructor
268      *
269      * Reads all properties on the interface if it is on D-Bus,
270      * and sets up the match objects for the propertiesChanged
271      * and interfacesAdded signals.
272      *
273      * @param[in] bus - The sdbusplus bus object
274      * @param[in] path - The D-Bus path of the property
275      * @param[in] interface - The D-Bus interface that contains the property
276      * @param[in] dataIface - The DataInterface object
277      * @param[in] func - The callback used any time the property is read
278      */
279     InterfaceWatcher(sdbusplus::bus_t& bus, const std::string& path,
280                      const std::string& interface, const DataIface& dataIface,
281                      InterfaceSetFunc func) :
282         DBusWatcher(path, interface),
283         _setFunc(func)
284     {
285         _matches.emplace_back(
286             bus, match_rules::propertiesChanged(_path, _interface),
287             std::bind(std::mem_fn(&InterfaceWatcher::propChanged), this,
288                       std::placeholders::_1));
289 
290         _matches.emplace_back(
291             bus,
292             match_rules::interfacesAdded() + match_rules::argNpath(0, _path),
293             std::bind(std::mem_fn(&InterfaceWatcher::interfaceAdded), this,
294                       std::placeholders::_1));
295 
296         try
297         {
298             read(dataIface);
299         }
300         catch (const sdbusplus::exception_t& e)
301         {
302             // Path doesn't exist now
303         }
304     }
305 
306     /**
307      * @brief Reads the interface's properties on D-Bus, and
308      * calls the the user defined function with the property map.
309      *
310      * @param[in] dataIface - The DataInterface object
311      */
312     void read(const DataIface& dataIface)
313     {
314         auto service = dataIface.getService(_path, _interface);
315         if (!service.empty())
316         {
317             auto properties = dataIface.getAllProperties(service, _path,
318                                                          _interface);
319 
320             _setFunc(properties);
321         }
322     }
323 
324     /**
325      * @brief The propertiesChanged callback
326      *
327      * Calls the user defined function with the property map.  Only the
328      * properties that changed will be in the map.
329      *
330      * @param[in] msg - The sdbusplus message object
331      */
332     void propChanged(sdbusplus::message_t& msg)
333     {
334         DBusInterface interface;
335         DBusPropertyMap properties;
336 
337         msg.read(interface, properties);
338 
339         _setFunc(properties);
340     }
341 
342     /**
343      * @brief The interfacesAdded callback
344      *
345      * Calls the user defined function with the property map
346      *
347      * @param[in] msg - The sdbusplus message object
348      */
349     void interfaceAdded(sdbusplus::message_t& msg)
350     {
351         sdbusplus::message::object_path path;
352         DBusInterfaceMap interfaces;
353 
354         msg.read(path, interfaces);
355 
356         auto iface = interfaces.find(_interface);
357         if (iface != interfaces.end())
358         {
359             _setFunc(iface->second);
360         }
361     }
362 
363   private:
364     /**
365      * @brief The function that will be called any time the
366      *        interface is read.
367      */
368     InterfaceSetFunc _setFunc;
369 };
370 
371 } // namespace openpower::pels
372