1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2020 IBM Corporation
3
4 #include "user_data_json.hpp"
5
6 #include "json_utils.hpp"
7 #include "pel_types.hpp"
8 #include "pel_values.hpp"
9 #include "stream.hpp"
10 #include "user_data_formats.hpp"
11
12 #include <Python.h>
13
14 #include <nlohmann/json.hpp>
15 #include <phosphor-logging/lg2.hpp>
16
17 #include <iomanip>
18 #include <sstream>
19
20 namespace openpower::pels::user_data
21 {
22 namespace pv = openpower::pels::pel_values;
23 using orderedJSON = nlohmann::ordered_json;
24
pyDecRef(PyObject * pyObj)25 void pyDecRef(PyObject* pyObj)
26 {
27 Py_XDECREF(pyObj);
28 }
29
30 /**
31 * @brief Returns a JSON string for use by PEL::printSectionInJSON().
32 *
33 * The returning string will contain a JSON object, but without
34 * the outer {}. If the input JSON isn't a JSON object (dict), then
35 * one will be created with the input added to a 'Data' key.
36 *
37 * @param[in] creatorID - The creator ID for the PEL
38 *
39 * @param[in] json - The JSON to convert to a string
40 *
41 * @return std::string - The JSON string
42 */
prettyJSON(uint16_t componentID,uint8_t subType,uint8_t version,uint8_t creatorID,const orderedJSON & json)43 std::string prettyJSON(uint16_t componentID, uint8_t subType, uint8_t version,
44 uint8_t creatorID, const orderedJSON& json)
45 {
46 orderedJSON output;
47 output[pv::sectionVer] = std::to_string(version);
48 output[pv::subSection] = std::to_string(subType);
49 output[pv::createdBy] = getComponentName(componentID, creatorID);
50
51 if (!json.is_object())
52 {
53 output["Data"] = json;
54 }
55 else
56 {
57 for (const auto& [key, value] : json.items())
58 {
59 output[key] = value;
60 }
61 }
62
63 // Let nlohmann do the pretty printing.
64 std::stringstream stream;
65 stream << std::setw(4) << output;
66
67 auto jsonString = stream.str();
68
69 // Now it looks like:
70 // {
71 // "Section Version": ...
72 // ...
73 // }
74
75 // Since PEL::printSectionInJSON() will supply the outer { }s,
76 // remove the existing ones.
77
78 // Replace the { and the following newline, and the } and its
79 // preceeding newline.
80 jsonString.erase(0, 2);
81
82 auto pos = jsonString.find_last_of('}');
83 jsonString.erase(pos - 1);
84
85 return jsonString;
86 }
87
88 /**
89 * @brief Return a JSON string from the passed in CBOR data.
90 *
91 * @param[in] componentID - The comp ID from the UserData section header
92 * @param[in] subType - The subtype from the UserData section header
93 * @param[in] version - The version from the UserData section header
94 * @param[in] creatorID - The creator ID for the PEL
95 * @param[in] data - The CBOR data
96 *
97 * @return std::string - The JSON string
98 */
getCBORJSON(uint16_t componentID,uint8_t subType,uint8_t version,uint8_t creatorID,const std::vector<uint8_t> & data)99 std::string getCBORJSON(uint16_t componentID, uint8_t subType, uint8_t version,
100 uint8_t creatorID, const std::vector<uint8_t>& data)
101 {
102 // The CBOR parser needs the pad bytes added to 4 byte align
103 // removed. The number of bytes added to the pad is on the
104 // very end, so will remove both fields before parsing.
105
106 // If the data vector is too short, an exception will get
107 // thrown which will be handled up the call stack.
108
109 auto cborData = data;
110 uint32_t pad{};
111
112 Stream stream{cborData};
113 stream.offset(cborData.size() - 4);
114 stream >> pad;
115
116 if (cborData.size() > (pad + sizeof(pad)))
117 {
118 cborData.resize(data.size() - sizeof(pad) - pad);
119 }
120
121 orderedJSON json = orderedJSON::from_cbor(cborData);
122
123 return prettyJSON(componentID, subType, version, creatorID, json);
124 }
125
126 /**
127 * @brief Return a JSON string from the passed in text data.
128 *
129 * The function breaks up the input text into a vector of strings with
130 * newline as separator and converts that into JSON. It will convert any
131 * unprintable characters to periods.
132 *
133 * @param[in] componentID - The comp ID from the UserData section header
134 * @param[in] subType - The subtype from the UserData section header
135 * @param[in] version - The version from the UserData section header
136 * @param[in] creatorID - The creator ID for the PEL
137 * @param[in] data - The CBOR data
138 *
139 * @return std::string - The JSON string
140 */
getTextJSON(uint16_t componentID,uint8_t subType,uint8_t version,uint8_t creatorID,const std::vector<uint8_t> & data)141 std::string getTextJSON(uint16_t componentID, uint8_t subType, uint8_t version,
142 uint8_t creatorID, const std::vector<uint8_t>& data)
143 {
144 std::vector<std::string> text;
145 size_t startPos = 0;
146
147 // Converts any unprintable characters to periods
148 auto validate = [](char& ch) {
149 if ((ch < ' ') || (ch > '~'))
150 {
151 ch = '.';
152 }
153 };
154
155 // Break up the data into an array of strings with newline as separator
156 for (size_t pos = 0; pos < data.size(); ++pos)
157 {
158 if (data[pos] == '\n')
159 {
160 std::string line{reinterpret_cast<const char*>(&data[startPos]),
161 pos - startPos};
162 std::for_each(line.begin(), line.end(), validate);
163 text.push_back(std::move(line));
164 startPos = pos + 1;
165 }
166 }
167 if (startPos < data.size())
168 {
169 std::string line{reinterpret_cast<const char*>(&data[startPos]),
170 data.size() - startPos};
171 std::for_each(line.begin(), line.end(), validate);
172 text.push_back(std::move(line));
173 }
174
175 orderedJSON json = text;
176 return prettyJSON(componentID, subType, version, creatorID, json);
177 }
178
179 /**
180 * @brief Convert to an appropriate JSON string as the data is one of
181 * the formats that we natively support.
182 *
183 * @param[in] componentID - The comp ID from the UserData section header
184 * @param[in] subType - The subtype from the UserData section header
185 * @param[in] version - The version from the UserData section header
186 * @param[in] data - The data itself
187 *
188 * @return std::optional<std::string> - The JSON string if it could be created,
189 * else std::nullopt.
190 */
getBuiltinFormatJSON(uint16_t componentID,uint8_t subType,uint8_t version,const std::vector<uint8_t> & data,uint8_t creatorID)191 std::optional<std::string> getBuiltinFormatJSON(
192 uint16_t componentID, uint8_t subType, uint8_t version,
193 const std::vector<uint8_t>& data, uint8_t creatorID)
194 {
195 switch (subType)
196 {
197 case static_cast<uint8_t>(UserDataFormat::json):
198 {
199 std::string jsonString{data.begin(), data.begin() + data.size()};
200
201 orderedJSON json = orderedJSON::parse(jsonString);
202
203 return prettyJSON(componentID, subType, version, creatorID, json);
204 }
205 case static_cast<uint8_t>(UserDataFormat::cbor):
206 {
207 return getCBORJSON(componentID, subType, version, creatorID, data);
208 }
209 case static_cast<uint8_t>(UserDataFormat::text):
210 {
211 return getTextJSON(componentID, subType, version, creatorID, data);
212 }
213 default:
214 break;
215 }
216 return std::nullopt;
217 }
218
219 /**
220 * @brief Call Python modules to parse the data into a JSON string
221 *
222 * The module to call is based on the Creator Subsystem ID and the Component
223 * ID under the namespace "udparsers". For example: "udparsers.xyyyy.xyyyy"
224 * where "x" is the Creator Subsystem ID and "yyyy" is the Component ID.
225 *
226 * All modules must provide the following:
227 * Function: parseUDToJson
228 * Argument list:
229 * 1. (int) Sub-section type
230 * 2. (int) Section version
231 * 3. (memoryview): Data
232 *-Return data:
233 * 1. (str) JSON string
234 *
235 * @param[in] componentID - The comp ID from the UserData section header
236 * @param[in] subType - The subtype from the UserData section header
237 * @param[in] version - The version from the UserData section header
238 * @param[in] data - The data itself
239 * @param[in] creatorID - The creatorID from the PrivateHeader section
240 * @return std::optional<std::string> - The JSON string if it could be created,
241 * else std::nullopt
242 */
getPythonJSON(uint16_t componentID,uint8_t subType,uint8_t version,const std::vector<uint8_t> & data,uint8_t creatorID)243 std::optional<std::string> getPythonJSON(
244 uint16_t componentID, uint8_t subType, uint8_t version,
245 const std::vector<uint8_t>& data, uint8_t creatorID)
246 {
247 PyObject *pName, *pModule, *eType, *eValue, *eTraceback, *pKey;
248 std::string pErrStr;
249 std::string module = getNumberString("%c", tolower(creatorID)) +
250 getNumberString("%04x", componentID);
251 pName = PyUnicode_FromString(
252 std::string("udparsers." + module + "." + module).c_str());
253 std::unique_ptr<PyObject, decltype(&pyDecRef)> modNamePtr(pName, &pyDecRef);
254 pModule = PyImport_Import(pName);
255 if (pModule == NULL)
256 {
257 pErrStr = "No error string found";
258 PyErr_Fetch(&eType, &eValue, &eTraceback);
259 if (eType)
260 {
261 Py_XDECREF(eType);
262 }
263 if (eTraceback)
264 {
265 Py_XDECREF(eTraceback);
266 }
267 if (eValue)
268 {
269 PyObject* pStr = PyObject_Str(eValue);
270 Py_XDECREF(eValue);
271 if (pStr)
272 {
273 pErrStr = PyUnicode_AsUTF8(pStr);
274 Py_XDECREF(pStr);
275 }
276 }
277 }
278 else
279 {
280 std::unique_ptr<PyObject, decltype(&pyDecRef)> modPtr(
281 pModule, &pyDecRef);
282 std::string funcToCall = "parseUDToJson";
283 pKey = PyUnicode_FromString(funcToCall.c_str());
284 std::unique_ptr<PyObject, decltype(&pyDecRef)> keyPtr(pKey, &pyDecRef);
285 PyObject* pDict = PyModule_GetDict(pModule);
286 Py_INCREF(pDict);
287 if (!PyDict_Contains(pDict, pKey))
288 {
289 Py_DECREF(pDict);
290 lg2::error("Python module error. Function missing: {FUNC}, "
291 "module = {MODULE}, subtype = {SUBTYPE}, "
292 "version = {VERSION}, data length = {LEN}",
293 "FUNC", funcToCall, "MODULE", module, "SUBTYPE", subType,
294 "VERSION", version, "LEN", data.size());
295 return std::nullopt;
296 }
297 PyObject* pFunc = PyDict_GetItemString(pDict, funcToCall.c_str());
298 Py_DECREF(pDict);
299 Py_INCREF(pFunc);
300 if (PyCallable_Check(pFunc))
301 {
302 auto ud = data.data();
303 PyObject* pArgs = PyTuple_New(3);
304 std::unique_ptr<PyObject, decltype(&pyDecRef)> argPtr(
305 pArgs, &pyDecRef);
306 PyTuple_SetItem(pArgs, 0,
307 PyLong_FromUnsignedLong((unsigned long)subType));
308 PyTuple_SetItem(pArgs, 1,
309 PyLong_FromUnsignedLong((unsigned long)version));
310 PyObject* pData = PyMemoryView_FromMemory(
311 reinterpret_cast<char*>(const_cast<unsigned char*>(ud)),
312 data.size(), PyBUF_READ);
313 PyTuple_SetItem(pArgs, 2, pData);
314 PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
315 Py_DECREF(pFunc);
316 if (pResult)
317 {
318 std::unique_ptr<PyObject, decltype(&pyDecRef)> resPtr(
319 pResult, &pyDecRef);
320
321 if (pResult == Py_None)
322 {
323 // Just return a nullopt so it will hexdump the section
324 return std::nullopt;
325 }
326
327 PyObject* pBytes =
328 PyUnicode_AsEncodedString(pResult, "utf-8", "~E~");
329 std::unique_ptr<PyObject, decltype(&pyDecRef)> pyBytePtr(
330 pBytes, &pyDecRef);
331 const char* output = PyBytes_AS_STRING(pBytes);
332 try
333 {
334 orderedJSON json = orderedJSON::parse(output);
335 if ((json.is_object() && !json.empty()) ||
336 (json.is_array() && json.size() > 0) ||
337 (json.is_string() && json != ""))
338 {
339 return prettyJSON(componentID, subType, version,
340 creatorID, json);
341 }
342 }
343 catch (const std::exception& e)
344 {
345 lg2::error("Bad JSON from parser. Error = {ERROR}, "
346 "module = {MODULE}, subtype = {SUBTYPE}, "
347 "version = {VERSION}, data length = {LEN}",
348 "ERROR", e, "MODULE", module, "SUBTYPE", subType,
349 "VERSION", version, "LEN", data.size());
350 return std::nullopt;
351 }
352 }
353 else
354 {
355 pErrStr = "No error string found";
356 PyErr_Fetch(&eType, &eValue, &eTraceback);
357 if (eType)
358 {
359 Py_XDECREF(eType);
360 }
361 if (eTraceback)
362 {
363 Py_XDECREF(eTraceback);
364 }
365 if (eValue)
366 {
367 PyObject* pStr = PyObject_Str(eValue);
368 Py_XDECREF(eValue);
369 if (pStr)
370 {
371 pErrStr = PyUnicode_AsUTF8(pStr);
372 Py_XDECREF(pStr);
373 }
374 }
375 }
376 }
377 }
378 if (!pErrStr.empty())
379 {
380 lg2::debug("Python exception thrown by parser. Error = {ERROR}, "
381 "module = {MODULE}, subtype = {SUBTYPE}, "
382 "version = {VERSION}, data length = {LEN}",
383 "ERROR", pErrStr, "MODULE", module, "SUBTYPE", subType,
384 "VERSION", version, "LEN", data.size());
385 }
386 return std::nullopt;
387 }
388
getJSON(uint16_t componentID,uint8_t subType,uint8_t version,const std::vector<uint8_t> & data,uint8_t creatorID,const std::vector<std::string> & plugins)389 std::optional<std::string> getJSON(
390 uint16_t componentID, uint8_t subType, uint8_t version,
391 const std::vector<uint8_t>& data, uint8_t creatorID,
392 const std::vector<std::string>& plugins)
393 {
394 std::string subsystem = getNumberString("%c", tolower(creatorID));
395 std::string component = getNumberString("%04x", componentID);
396 try
397 {
398 if (pv::creatorIDs.at(getNumberString("%c", creatorID)) == "BMC" &&
399 componentID == static_cast<uint16_t>(ComponentID::phosphorLogging))
400 {
401 return getBuiltinFormatJSON(componentID, subType, version, data,
402 creatorID);
403 }
404 else if (std::find(plugins.begin(), plugins.end(),
405 subsystem + component) != plugins.end())
406 {
407 return getPythonJSON(componentID, subType, version, data,
408 creatorID);
409 }
410 }
411 catch (const std::exception& e)
412 {
413 lg2::error("Failed parsing UserData. Error = {ERROR}, "
414 "component ID = {COMP_ID}, subtype = {SUBTYPE}, "
415 "version = {VERSION}, data length = {LEN}",
416 "ERROR", e, "COMP_ID", componentID, "SUBTYPE", subType,
417 "VERSION", version, "LEN", data.size());
418 }
419
420 return std::nullopt;
421 }
422
423 } // namespace openpower::pels::user_data
424