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