1 /**
2  * Copyright © 2024 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 "rail.hpp"
19 
20 #include <nlohmann/json.hpp>
21 
22 #include <cstdint>
23 #include <filesystem>
24 #include <memory>
25 #include <stdexcept>
26 #include <string>
27 #include <vector>
28 
29 namespace phosphor::power::sequencer::config_file_parser
30 {
31 
32 /**
33  * Standard JSON configuration file directory on the BMC.
34  */
35 extern const std::filesystem::path standardConfigFileDirectory;
36 
37 /**
38  * Finds the JSON configuration file for the current system based on the
39  * specified compatible system types.
40  *
41  * This is required when a single BMC firmware image supports multiple system
42  * types and some system types require different configuration files.
43  *
44  * The compatible system types must be ordered from most to least specific.
45  * Example:
46  *   - com.acme.Hardware.Chassis.Model.MegaServer4CPU
47  *   - com.acme.Hardware.Chassis.Model.MegaServer
48  *   - com.acme.Hardware.Chassis.Model.Server
49  *
50  * Throws an exception if an error occurs.
51  *
52  * @param compatibleSystemTypes compatible system types for the current system
53  *                              ordered from most to least specific
54  * @param configFileDir directory containing configuration files
55  * @return path to the JSON configuration file, or an empty path if none was
56  *         found
57  */
58 std::filesystem::path find(
59     const std::vector<std::string>& compatibleSystemTypes,
60     const std::filesystem::path& configFileDir = standardConfigFileDirectory);
61 
62 /**
63  * Parses the specified JSON configuration file.
64  *
65  * Returns the corresponding C++ Rail objects.
66  *
67  * Throws a ConfigFileParserError if an error occurs.
68  *
69  * @param pathName configuration file path name
70  * @return vector of Rail objects
71  */
72 std::vector<std::unique_ptr<Rail>> parse(const std::filesystem::path& pathName);
73 
74 /*
75  * Internal implementation details for parse()
76  */
77 namespace internal
78 {
79 
80 /**
81  * Returns the specified property of the specified JSON element.
82  *
83  * Throws an invalid_argument exception if the property does not exist.
84  *
85  * @param element JSON element
86  * @param property property name
87  */
88 #pragma GCC diagnostic push
89 #if __GNUC__ == 13
90 #pragma GCC diagnostic ignored "-Wdangling-reference"
91 #endif
92 inline const nlohmann::json& getRequiredProperty(const nlohmann::json& element,
93                                                  const std::string& property)
94 {
95     auto it = element.find(property);
96     if (it == element.end())
97     {
98         throw std::invalid_argument{"Required property missing: " + property};
99     }
100     return *it;
101 }
102 #pragma GCC diagnostic pop
103 
104 /**
105  * Parses a JSON element containing a boolean.
106  *
107  * Returns the corresponding C++ boolean value.
108  *
109  * Throws an exception if parsing fails.
110  *
111  * @param element JSON element
112  * @return boolean value
113  */
114 inline bool parseBoolean(const nlohmann::json& element)
115 {
116     // Verify element contains a boolean
117     if (!element.is_boolean())
118     {
119         throw std::invalid_argument{"Element is not a boolean"};
120     }
121     return element.get<bool>();
122 }
123 
124 /**
125  * Parses a JSON element containing a GPIO.
126  *
127  * Returns the corresponding C++ GPIO object.
128  *
129  * Throws an exception if parsing fails.
130  *
131  * @param element JSON element
132  * @return GPIO object
133  */
134 GPIO parseGPIO(const nlohmann::json& element);
135 
136 /**
137  * Parses a JSON element containing a rail.
138  *
139  * Returns the corresponding C++ Rail object.
140  *
141  * Throws an exception if parsing fails.
142  *
143  * @param element JSON element
144  * @return Rail object
145  */
146 std::unique_ptr<Rail> parseRail(const nlohmann::json& element);
147 
148 /**
149  * Parses a JSON element containing an array of rails.
150  *
151  * Returns the corresponding C++ Rail objects.
152  *
153  * Throws an exception if parsing fails.
154  *
155  * @param element JSON element
156  * @return vector of Rail objects
157  */
158 std::vector<std::unique_ptr<Rail>>
159     parseRailArray(const nlohmann::json& element);
160 
161 /**
162  * Parses the JSON root element of the entire configuration file.
163  *
164  * Returns the corresponding C++ Rail objects.
165  *
166  * Throws an exception if parsing fails.
167  *
168  * @param element JSON element
169  * @return vector of Rail objects
170  */
171 std::vector<std::unique_ptr<Rail>> parseRoot(const nlohmann::json& element);
172 
173 /**
174  * Parses a JSON element containing a string.
175  *
176  * Returns the corresponding C++ string.
177  *
178  * Throws an exception if parsing fails.
179  *
180  * @param element JSON element
181  * @param isEmptyValid indicates whether an empty string value is valid
182  * @return string value
183  */
184 inline std::string parseString(const nlohmann::json& element,
185                                bool isEmptyValid = false)
186 {
187     if (!element.is_string())
188     {
189         throw std::invalid_argument{"Element is not a string"};
190     }
191     std::string value = element.get<std::string>();
192     if (value.empty() && !isEmptyValid)
193     {
194         throw std::invalid_argument{"Element contains an empty string"};
195     }
196     return value;
197 }
198 
199 /**
200  * Parses a JSON element containing an 8-bit unsigned integer.
201  *
202  * Returns the corresponding C++ uint8_t value.
203  *
204  * Throws an exception if parsing fails.
205  *
206  * @param element JSON element
207  * @return uint8_t value
208  */
209 inline uint8_t parseUint8(const nlohmann::json& element)
210 {
211     // Verify element contains an integer
212     if (!element.is_number_integer())
213     {
214         throw std::invalid_argument{"Element is not an integer"};
215     }
216     int value = element.get<int>();
217     if ((value < 0) || (value > UINT8_MAX))
218     {
219         throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
220     }
221     return static_cast<uint8_t>(value);
222 }
223 
224 /**
225  * Parses a JSON element containing an unsigned integer.
226  *
227  * Returns the corresponding C++ unsigned int value.
228  *
229  * Throws an exception if parsing fails.
230  *
231  * @param element JSON element
232  * @return unsigned int value
233  */
234 inline unsigned int parseUnsignedInteger(const nlohmann::json& element)
235 {
236     // Verify element contains an unsigned integer
237     if (!element.is_number_unsigned())
238     {
239         throw std::invalid_argument{"Element is not an unsigned integer"};
240     }
241     return element.get<unsigned int>();
242 }
243 
244 /**
245  * Verifies that the specified JSON element is a JSON array.
246  *
247  * Throws an invalid_argument exception if the element is not an array.
248  *
249  * @param element JSON element
250  */
251 inline void verifyIsArray(const nlohmann::json& element)
252 {
253     if (!element.is_array())
254     {
255         throw std::invalid_argument{"Element is not an array"};
256     }
257 }
258 
259 /**
260  * Verifies that the specified JSON element is a JSON object.
261  *
262  * Throws an invalid_argument exception if the element is not an object.
263  *
264  * @param element JSON element
265  */
266 inline void verifyIsObject(const nlohmann::json& element)
267 {
268     if (!element.is_object())
269     {
270         throw std::invalid_argument{"Element is not an object"};
271     }
272 }
273 
274 /**
275  * Verifies that the specified JSON element contains the expected number of
276  * properties.
277  *
278  * Throws an invalid_argument exception if the element contains a different
279  * number of properties.  This indicates the element contains an invalid
280  * property.
281  *
282  * @param element JSON element
283  * @param expectedCount expected number of properties in element
284  */
285 inline void verifyPropertyCount(const nlohmann::json& element,
286                                 unsigned int expectedCount)
287 {
288     if (element.size() != expectedCount)
289     {
290         throw std::invalid_argument{"Element contains an invalid property"};
291     }
292 }
293 
294 } // namespace internal
295 
296 } // namespace phosphor::power::sequencer::config_file_parser
297