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