1 /**
2  * Copyright © 2020 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include "action.hpp"
19 #include "and_action.hpp"
20 #include "chassis.hpp"
21 #include "compare_presence_action.hpp"
22 #include "configuration.hpp"
23 #include "device.hpp"
24 #include "i2c_compare_bit_action.hpp"
25 #include "i2c_compare_byte_action.hpp"
26 #include "i2c_compare_bytes_action.hpp"
27 #include "i2c_interface.hpp"
28 #include "i2c_write_bit_action.hpp"
29 #include "i2c_write_byte_action.hpp"
30 #include "i2c_write_bytes_action.hpp"
31 #include "if_action.hpp"
32 #include "not_action.hpp"
33 #include "or_action.hpp"
34 #include "pmbus_write_vout_command_action.hpp"
35 #include "presence_detection.hpp"
36 #include "rail.hpp"
37 #include "rule.hpp"
38 #include "run_rule_action.hpp"
39 #include "sensor_monitoring.hpp"
40 #include "set_device_action.hpp"
41 
42 #include <nlohmann/json.hpp>
43 
44 #include <cstdint>
45 #include <filesystem>
46 #include <memory>
47 #include <stdexcept>
48 #include <string>
49 #include <tuple>
50 #include <vector>
51 
52 namespace phosphor::power::regulators::config_file_parser
53 {
54 
55 /**
56  * Parses the specified JSON configuration file.
57  *
58  * Returns the corresponding C++ Rule and Chassis objects.
59  *
60  * Throws a ConfigFileParserError if an error occurs.
61  *
62  * @param pathName configuration file path name
63  * @return tuple containing vectors of Rule and Chassis objects
64  */
65 std::tuple<std::vector<std::unique_ptr<Rule>>,
66            std::vector<std::unique_ptr<Chassis>>>
67     parse(const std::filesystem::path& pathName);
68 
69 /*
70  * Internal implementation details for parse()
71  */
72 namespace internal
73 {
74 
75 /**
76  * Returns the specified property of the specified JSON element.
77  *
78  * Throws an invalid_argument exception if the property does not exist.
79  *
80  * @param element JSON element
81  * @param property property name
82  */
83 inline const nlohmann::json& getRequiredProperty(const nlohmann::json& element,
84                                                  const std::string& property)
85 {
86     auto it = element.find(property);
87     if (it == element.end())
88     {
89         throw std::invalid_argument{"Required property missing: " + property};
90     }
91     return *it;
92 }
93 
94 /**
95  * Parses a JSON element containing an action.
96  *
97  * Returns the corresponding C++ Action object.
98  *
99  * Throws an exception if parsing fails.
100  *
101  * @param element JSON element
102  * @return Action object
103  */
104 std::unique_ptr<Action> parseAction(const nlohmann::json& element);
105 
106 /**
107  * Parses a JSON element containing an array of actions.
108  *
109  * Returns the corresponding C++ Action objects.
110  *
111  * Throws an exception if parsing fails.
112  *
113  * @param element JSON element
114  * @return vector of Action objects
115  */
116 std::vector<std::unique_ptr<Action>>
117     parseActionArray(const nlohmann::json& element);
118 
119 /**
120  * Parses a JSON element containing an and action.
121  *
122  * Returns the corresponding C++ AndAction object.
123  *
124  * Throws an exception if parsing fails.
125  *
126  * @param element JSON element
127  * @return AndAction object
128  */
129 std::unique_ptr<AndAction> parseAnd(const nlohmann::json& element);
130 
131 /**
132  * Parses a JSON element containing a bit position (from 0-7).
133  *
134  * Returns the corresponding C++ uint8_t value.
135  *
136  * Throws an exception if parsing fails.
137  *
138  * @param element JSON element
139  * @return uint8_t value
140  */
141 inline uint8_t parseBitPosition(const nlohmann::json& element)
142 {
143     // Verify element contains an integer
144     if (!element.is_number_integer())
145     {
146         throw std::invalid_argument{"Element is not an integer"};
147     }
148     int value = element.get<int>();
149     if ((value < 0) || (value > 7))
150     {
151         throw std::invalid_argument{"Element is not a bit position"};
152     }
153     return static_cast<uint8_t>(value);
154 }
155 
156 /**
157  * Parses a JSON element containing a bit value (0 or 1).
158  *
159  * Returns the corresponding C++ uint8_t value.
160  *
161  * Throws an exception if parsing fails.
162  *
163  * @param element JSON element
164  * @return uint8_t value
165  */
166 inline uint8_t parseBitValue(const nlohmann::json& element)
167 {
168     // Verify element contains an integer
169     if (!element.is_number_integer())
170     {
171         throw std::invalid_argument{"Element is not an integer"};
172     }
173     int value = element.get<int>();
174     if ((value < 0) || (value > 1))
175     {
176         throw std::invalid_argument{"Element is not a bit value"};
177     }
178     return static_cast<uint8_t>(value);
179 }
180 
181 /**
182  * Parses a JSON element containing a boolean.
183  *
184  * Returns the corresponding C++ boolean value.
185  *
186  * Throws an exception if parsing fails.
187  *
188  * @param element JSON element
189  * @return boolean value
190  */
191 inline bool parseBoolean(const nlohmann::json& element)
192 {
193     // Verify element contains a boolean
194     if (!element.is_boolean())
195     {
196         throw std::invalid_argument{"Element is not a boolean"};
197     }
198     return element.get<bool>();
199 }
200 
201 /**
202  * Parses a JSON element containing a chassis.
203  *
204  * Returns the corresponding C++ Chassis object.
205  *
206  * Throws an exception if parsing fails.
207  *
208  * @param element JSON element
209  * @return Chassis object
210  */
211 std::unique_ptr<Chassis> parseChassis(const nlohmann::json& element);
212 
213 /**
214  * Parses a JSON element containing an array of chassis.
215  *
216  * Returns the corresponding C++ Chassis objects.
217  *
218  * Throws an exception if parsing fails.
219  *
220  * @param element JSON element
221  * @return vector of Chassis objects
222  */
223 std::vector<std::unique_ptr<Chassis>>
224     parseChassisArray(const nlohmann::json& element);
225 
226 /**
227  * Parses a JSON element containing a compare_presence action.
228  *
229  * Returns the corresponding C++ ComparePresenceAction object.
230  *
231  * Throws an exception if parsing fails.
232  *
233  * @param element JSON element
234  * @return ComparePresenceAction object
235  */
236 std::unique_ptr<ComparePresenceAction>
237     parseComparePresence(const nlohmann::json& element);
238 
239 /**
240  * Parses a JSON element containing a configuration.
241  *
242  * Returns the corresponding C++ Configuration object.
243  *
244  * Throws an exception if parsing fails.
245  *
246  * @param element JSON element
247  * @return Configuration object
248  */
249 std::unique_ptr<Configuration>
250     parseConfiguration(const nlohmann::json& element);
251 
252 /**
253  * Parses a JSON element containing a device.
254  *
255  * Returns the corresponding C++ Device object.
256  *
257  * Throws an exception if parsing fails.
258  *
259  * @param element JSON element
260  * @return Device object
261  */
262 std::unique_ptr<Device> parseDevice(const nlohmann::json& element);
263 
264 /**
265  * Parses a JSON element containing an array of devices.
266  *
267  * Returns the corresponding C++ Device objects.
268  *
269  * Throws an exception if parsing fails.
270  *
271  * @param element JSON element
272  * @return vector of Device objects
273  */
274 std::vector<std::unique_ptr<Device>>
275     parseDeviceArray(const nlohmann::json& element);
276 
277 /**
278  * Parses a JSON element containing a double (floating point number).
279  *
280  * Returns the corresponding C++ double value.
281  *
282  * Throws an exception if parsing fails.
283  *
284  * @param element JSON element
285  * @return double value
286  */
287 inline double parseDouble(const nlohmann::json& element)
288 {
289     // Verify element contains a number (integer or floating point)
290     if (!element.is_number())
291     {
292         throw std::invalid_argument{"Element is not a number"};
293     }
294     return element.get<double>();
295 }
296 
297 /**
298  * Parses a JSON element containing a byte value expressed as a hexadecimal
299  * string.
300  *
301  * The JSON number data type does not support the hexadecimal format.  For this
302  * reason, hexadecimal byte values are stored as strings in the configuration
303  * file.
304  *
305  * Returns the corresponding C++ uint8_t value.
306  *
307  * Throws an exception if parsing fails.
308  *
309  * @param element JSON element
310  * @return uint8_t value
311  */
312 inline uint8_t parseHexByte(const nlohmann::json& element)
313 {
314     if (!element.is_string())
315     {
316         throw std::invalid_argument{"Element is not a string"};
317     }
318     std::string value = element.get<std::string>();
319 
320     bool isHex = (value.compare(0, 2, "0x") == 0) && (value.size() > 2) &&
321                  (value.size() < 5) &&
322                  (value.find_first_not_of("0123456789abcdefABCDEF", 2) ==
323                   std::string::npos);
324     if (!isHex)
325     {
326         throw std::invalid_argument{"Element is not hexadecimal string"};
327     }
328     return static_cast<uint8_t>(std::stoul(value, 0, 0));
329 }
330 
331 /**
332  * Parses a JSON element containing an array of byte values expressed as a
333  * hexadecimal strings.
334  *
335  * Returns the corresponding C++ uint8_t values.
336  *
337  * Throws an exception if parsing fails.
338  *
339  * @param element JSON element
340  * @return vector of uint8_t
341  */
342 std::vector<uint8_t> parseHexByteArray(const nlohmann::json& element);
343 
344 /**
345  * Parses a JSON element containing an i2c_compare_bit action.
346  *
347  * Returns the corresponding C++ I2CCompareBitAction object.
348  *
349  * Throws an exception if parsing fails.
350  *
351  * @param element JSON element
352  * @return I2CCompareBitAction object
353  */
354 std::unique_ptr<I2CCompareBitAction>
355     parseI2CCompareBit(const nlohmann::json& element);
356 
357 /**
358  * Parses a JSON element containing an i2c_compare_byte action.
359  *
360  * Returns the corresponding C++ I2CCompareByteAction object.
361  *
362  * Throws an exception if parsing fails.
363  *
364  * @param element JSON element
365  * @return I2CCompareByteAction object
366  */
367 std::unique_ptr<I2CCompareByteAction>
368     parseI2CCompareByte(const nlohmann::json& element);
369 
370 /**
371  * Parses a JSON element containing an i2c_compare_bytes action.
372  *
373  * Returns the corresponding C++ I2CCompareBytesAction object.
374  *
375  * Throws an exception if parsing fails.
376  *
377  * @param element JSON element
378  * @return I2CCompareBytesAction object
379  */
380 std::unique_ptr<I2CCompareBytesAction>
381     parseI2CCompareBytes(const nlohmann::json& element);
382 
383 /**
384  * Parses a JSON element containing an i2c_interface.
385  *
386  * Returns the corresponding C++ i2c::I2CInterface object.
387  *
388  * Throws an exception if parsing fails.
389  *
390  * @param element JSON element
391  * @return i2c::I2CInterface object
392  */
393 std::unique_ptr<i2c::I2CInterface>
394     parseI2CInterface(const nlohmann::json& element);
395 
396 /**
397  * Parses a JSON element containing an i2c_write_bit action.
398  *
399  * Returns the corresponding C++ I2CWriteBitAction object.
400  *
401  * Throws an exception if parsing fails.
402  *
403  * @param element JSON element
404  * @return I2CWriteBitAction object
405  */
406 std::unique_ptr<I2CWriteBitAction>
407     parseI2CWriteBit(const nlohmann::json& element);
408 
409 /**
410  * Parses a JSON element containing an i2c_write_byte action.
411  *
412  * Returns the corresponding C++ I2CWriteByteAction object.
413  *
414  * Throws an exception if parsing fails.
415  *
416  * @param element JSON element
417  * @return I2CWriteByteAction object
418  */
419 std::unique_ptr<I2CWriteByteAction>
420     parseI2CWriteByte(const nlohmann::json& element);
421 
422 /**
423  * Parses a JSON element containing an i2c_write_bytes action.
424  *
425  * Returns the corresponding C++ I2CWriteBytesAction object.
426  *
427  * Throws an exception if parsing fails.
428  *
429  * @param element JSON element
430  * @return I2CWriteBytesAction object
431  */
432 std::unique_ptr<I2CWriteBytesAction>
433     parseI2CWriteBytes(const nlohmann::json& element);
434 
435 /**
436  * Parses a JSON element containing an if action.
437  *
438  * Returns the corresponding C++ IfAction object.
439  *
440  * Throws an exception if parsing fails.
441  *
442  * @param element JSON element
443  * @return IfAction object
444  */
445 std::unique_ptr<IfAction> parseIf(const nlohmann::json& element);
446 
447 /**
448  * Parses a JSON element containing an 8-bit signed integer.
449  *
450  * Returns the corresponding C++ int8_t value.
451  *
452  * Throws an exception if parsing fails.
453  *
454  * @param element JSON element
455  * @return int8_t value
456  */
457 inline int8_t parseInt8(const nlohmann::json& element)
458 {
459     // Verify element contains an integer
460     if (!element.is_number_integer())
461     {
462         throw std::invalid_argument{"Element is not an integer"};
463     }
464     int value = element.get<int>();
465     if ((value < INT8_MIN) || (value > INT8_MAX))
466     {
467         throw std::invalid_argument{"Element is not an 8-bit signed integer"};
468     }
469     return static_cast<int8_t>(value);
470 }
471 
472 /**
473  * Parses a JSON element containing a not action.
474  *
475  * Returns the corresponding C++ NotAction object.
476  *
477  * Throws an exception if parsing fails.
478  *
479  * @param element JSON element
480  * @return NotAction object
481  */
482 std::unique_ptr<NotAction> parseNot(const nlohmann::json& element);
483 
484 /**
485  * Parses a JSON element containing an or action.
486  *
487  * Returns the corresponding C++ OrAction object.
488  *
489  * Throws an exception if parsing fails.
490  *
491  * @param element JSON element
492  * @return OrAction object
493  */
494 std::unique_ptr<OrAction> parseOr(const nlohmann::json& element);
495 
496 /**
497  * Parses a JSON element containing a pmbus_write_vout_command action.
498  *
499  * Returns the corresponding C++ PMBusWriteVoutCommandAction object.
500  *
501  * Throws an exception if parsing fails.
502  *
503  * @param element JSON element
504  * @return PMBusWriteVoutCommandAction object
505  */
506 std::unique_ptr<PMBusWriteVoutCommandAction>
507     parsePMBusWriteVoutCommand(const nlohmann::json& element);
508 
509 /**
510  * Parses a JSON element containing a presence detection operation.
511  *
512  * Returns the corresponding C++ PresenceDetection object.
513  *
514  * Throws an exception if parsing fails.
515  *
516  * @param element JSON element
517  * @return PresenceDetection object
518  */
519 std::unique_ptr<PresenceDetection>
520     parsePresenceDetection(const nlohmann::json& element);
521 
522 /**
523  * Parses a JSON element containing a rail.
524  *
525  * Returns the corresponding C++ Rail object.
526  *
527  * Throws an exception if parsing fails.
528  *
529  * @param element JSON element
530  * @return Rail object
531  */
532 std::unique_ptr<Rail> parseRail(const nlohmann::json& element);
533 
534 /**
535  * Parses a JSON element containing an array of rails.
536  *
537  * Returns the corresponding C++ Rail objects.
538  *
539  * Throws an exception if parsing fails.
540  *
541  * @param element JSON element
542  * @return vector of Rail objects
543  */
544 std::vector<std::unique_ptr<Rail>>
545     parseRailArray(const nlohmann::json& element);
546 
547 /**
548  * Parses the JSON root element of the entire configuration file.
549  *
550  * Returns the corresponding C++ Rule and Chassis objects.
551  *
552  * Throws an exception if parsing fails.
553  *
554  * @param element JSON element
555  * @return tuple containing vectors of Rule and Chassis objects
556  */
557 std::tuple<std::vector<std::unique_ptr<Rule>>,
558            std::vector<std::unique_ptr<Chassis>>>
559     parseRoot(const nlohmann::json& element);
560 
561 /**
562  * Parses a JSON element containing a rule.
563  *
564  * Returns the corresponding C++ Rule object.
565  *
566  * Throws an exception if parsing fails.
567  *
568  * @param element JSON element
569  * @return Rule object
570  */
571 std::unique_ptr<Rule> parseRule(const nlohmann::json& element);
572 
573 /**
574  * Parses a JSON element containing an array of rules.
575  *
576  * Returns the corresponding C++ Rule objects.
577  *
578  * Throws an exception if parsing fails.
579  *
580  * @param element JSON element
581  * @return vector of Rule objects
582  */
583 std::vector<std::unique_ptr<Rule>>
584     parseRuleArray(const nlohmann::json& element);
585 
586 /**
587  * Parses the "rule_id" or "actions" property in a JSON element.
588  *
589  * The element must contain one property or the other but not both.
590  *
591  * If the element contains a "rule_id" property, the corresponding C++
592  * RunRuleAction object is returned.
593  *
594  * If the element contains an "actions" property, the corresponding C++ Action
595  * objects are returned.
596  *
597  * Throws an exception if parsing fails.
598  *
599  * @param element JSON element
600  * @return vector of Action objects
601  */
602 std::vector<std::unique_ptr<Action>>
603     parseRuleIDOrActionsProperty(const nlohmann::json& element);
604 
605 /**
606  * Parses a JSON element containing a run_rule action.
607  *
608  * Returns the corresponding C++ RunRuleAction object.
609  *
610  * Throws an exception if parsing fails.
611  *
612  * @param element JSON element
613  * @return RunRuleAction object
614  */
615 std::unique_ptr<RunRuleAction> parseRunRule(const nlohmann::json& element);
616 
617 /**
618  * Parses a JSON element containing a sensor monitoring operation.
619  *
620  * Returns the corresponding C++ SensorMonitoring object.
621  *
622  * Throws an exception if parsing fails.
623  *
624  * @param element JSON element
625  * @return SensorMonitoring object
626  */
627 std::unique_ptr<SensorMonitoring>
628     parseSensorMonitoring(const nlohmann::json& element);
629 
630 /**
631  * Parses a JSON element containing a set_device action.
632  *
633  * Returns the corresponding C++ SetDeviceAction object.
634  *
635  * Throws an exception if parsing fails.
636  *
637  * @param element JSON element
638  * @return SetDeviceAction object
639  */
640 std::unique_ptr<SetDeviceAction> parseSetDevice(const nlohmann::json& element);
641 
642 /**
643  * Parses a JSON element containing a string.
644  *
645  * Returns the corresponding C++ string.
646  *
647  * Throws an exception if parsing fails.
648  *
649  * @param element JSON element
650  * @param isEmptyValid indicates whether an empty string value is valid
651  * @return string value
652  */
653 inline std::string parseString(const nlohmann::json& element,
654                                bool isEmptyValid = false)
655 {
656     if (!element.is_string())
657     {
658         throw std::invalid_argument{"Element is not a string"};
659     }
660     std::string value = element.get<std::string>();
661     if (value.empty() && !isEmptyValid)
662     {
663         throw std::invalid_argument{"Element contains an empty string"};
664     }
665     return value;
666 }
667 
668 /**
669  * Parses a JSON element containing an 8-bit unsigned integer.
670  *
671  * Returns the corresponding C++ uint8_t value.
672  *
673  * Throws an exception if parsing fails.
674  *
675  * @param element JSON element
676  * @return uint8_t value
677  */
678 inline uint8_t parseUint8(const nlohmann::json& element)
679 {
680     // Verify element contains an integer
681     if (!element.is_number_integer())
682     {
683         throw std::invalid_argument{"Element is not an integer"};
684     }
685     int value = element.get<int>();
686     if ((value < 0) || (value > UINT8_MAX))
687     {
688         throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
689     }
690     return static_cast<uint8_t>(value);
691 }
692 
693 /**
694  * Parses a JSON element containing an unsigned integer.
695  *
696  * Returns the corresponding C++ unsigned int value.
697  *
698  * Throws an exception if parsing fails.
699  *
700  * @param element JSON element
701  * @return unsigned int value
702  */
703 inline unsigned int parseUnsignedInteger(const nlohmann::json& element)
704 {
705     // Verify element contains an unsigned integer
706     if (!element.is_number_unsigned())
707     {
708         throw std::invalid_argument{"Element is not an unsigned integer"};
709     }
710     return element.get<unsigned int>();
711 }
712 
713 /**
714  * Verifies that the specified JSON element is a JSON array.
715  *
716  * Throws an invalid_argument exception if the element is not an array.
717  *
718  * @param element JSON element
719  */
720 inline void verifyIsArray(const nlohmann::json& element)
721 {
722     if (!element.is_array())
723     {
724         throw std::invalid_argument{"Element is not an array"};
725     }
726 }
727 
728 /**
729  * Verifies that the specified JSON element is a JSON object.
730  *
731  * Throws an invalid_argument exception if the element is not an object.
732  *
733  * @param element JSON element
734  */
735 inline void verifyIsObject(const nlohmann::json& element)
736 {
737     if (!element.is_object())
738     {
739         throw std::invalid_argument{"Element is not an object"};
740     }
741 }
742 
743 /**
744  * Verifies that the specified JSON element contains the expected number of
745  * properties.
746  *
747  * Throws an invalid_argument exception if the element contains a different
748  * number of properties.  This indicates the element contains an invalid
749  * property.
750  *
751  * @param element JSON element
752  * @param expectedCount expected number of properties in element
753  */
754 inline void verifyPropertyCount(const nlohmann::json& element,
755                                 unsigned int expectedCount)
756 {
757     if (element.size() != expectedCount)
758     {
759         throw std::invalid_argument{"Element contains an invalid property"};
760     }
761 }
762 
763 } // namespace internal
764 
765 } // namespace phosphor::power::regulators::config_file_parser
766