blob: 1435614a8d247ccaf483ae248ca662977dfdbc92 [file] [log] [blame]
Alexander Hansen40fb5492025-10-28 17:56:12 +01001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2020 IBM Corporation
Matt Spinleracb7c102020-01-10 13:49:22 -06003
4#include "user_data_json.hpp"
5
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +08006#include "json_utils.hpp"
Matt Spinleracb7c102020-01-10 13:49:22 -06007#include "pel_types.hpp"
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +08008#include "pel_values.hpp"
Matt Spinler18207142020-03-26 14:45:54 -05009#include "stream.hpp"
Matt Spinleracb7c102020-01-10 13:49:22 -060010#include "user_data_formats.hpp"
11
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +080012#include <Python.h>
13
Matt Spinleracb7c102020-01-10 13:49:22 -060014#include <nlohmann/json.hpp>
Matt Spinler7cc3aea2023-07-07 16:24:57 -050015#include <phosphor-logging/lg2.hpp>
Patrick Williams2544b412022-10-04 08:41:06 -050016
17#include <iomanip>
Matt Spinleracb7c102020-01-10 13:49:22 -060018#include <sstream>
19
20namespace openpower::pels::user_data
21{
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080022namespace pv = openpower::pels::pel_values;
Sumit Kumar516935a2021-04-14 13:00:54 -050023using orderedJSON = nlohmann::ordered_json;
Matt Spinleracb7c102020-01-10 13:49:22 -060024
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +080025void pyDecRef(PyObject* pyObj)
26{
27 Py_XDECREF(pyObj);
28}
29
Matt Spinleracb7c102020-01-10 13:49:22 -060030/**
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 *
Matt Spinlerb832aa52023-03-21 15:32:34 -050037 * @param[in] creatorID - The creator ID for the PEL
38 *
Matt Spinleracb7c102020-01-10 13:49:22 -060039 * @param[in] json - The JSON to convert to a string
40 *
41 * @return std::string - The JSON string
42 */
43std::string prettyJSON(uint16_t componentID, uint8_t subType, uint8_t version,
Matt Spinlerb832aa52023-03-21 15:32:34 -050044 uint8_t creatorID, const orderedJSON& json)
Matt Spinleracb7c102020-01-10 13:49:22 -060045{
Sumit Kumar516935a2021-04-14 13:00:54 -050046 orderedJSON output;
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080047 output[pv::sectionVer] = std::to_string(version);
48 output[pv::subSection] = std::to_string(subType);
Matt Spinlerb832aa52023-03-21 15:32:34 -050049 output[pv::createdBy] = getComponentName(componentID, creatorID);
Matt Spinleracb7c102020-01-10 13:49:22 -060050
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/**
Matt Spinler18207142020-03-26 14:45:54 -050089 * @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
Matt Spinlerb832aa52023-03-21 15:32:34 -050094 * @param[in] creatorID - The creator ID for the PEL
Matt Spinler18207142020-03-26 14:45:54 -050095 * @param[in] data - The CBOR data
96 *
97 * @return std::string - The JSON string
98 */
99std::string getCBORJSON(uint16_t componentID, uint8_t subType, uint8_t version,
Matt Spinlerb832aa52023-03-21 15:32:34 -0500100 uint8_t creatorID, const std::vector<uint8_t>& data)
Matt Spinler18207142020-03-26 14:45:54 -0500101{
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
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600121 orderedJSON json = orderedJSON::from_cbor(cborData);
Matt Spinler18207142020-03-26 14:45:54 -0500122
Matt Spinlerb832aa52023-03-21 15:32:34 -0500123 return prettyJSON(componentID, subType, version, creatorID, json);
Matt Spinler18207142020-03-26 14:45:54 -0500124}
125
126/**
127 * @brief Return a JSON string from the passed in text data.
128 *
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800129 * 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.
Matt Spinler18207142020-03-26 14:45:54 -0500132 *
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
Matt Spinlerb832aa52023-03-21 15:32:34 -0500136 * @param[in] creatorID - The creator ID for the PEL
Matt Spinler18207142020-03-26 14:45:54 -0500137 * @param[in] data - The CBOR data
138 *
139 * @return std::string - The JSON string
140 */
141std::string getTextJSON(uint16_t componentID, uint8_t subType, uint8_t version,
Matt Spinlerb832aa52023-03-21 15:32:34 -0500142 uint8_t creatorID, const std::vector<uint8_t>& data)
Matt Spinler18207142020-03-26 14:45:54 -0500143{
Matt Spinler18207142020-03-26 14:45:54 -0500144 std::vector<std::string> text;
145 size_t startPos = 0;
Matt Spinler18207142020-03-26 14:45:54 -0500146
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800147 // Converts any unprintable characters to periods
Matt Spinler18207142020-03-26 14:45:54 -0500148 auto validate = [](char& ch) {
149 if ((ch < ' ') || (ch > '~'))
150 {
151 ch = '.';
152 }
153 };
154
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800155 // Break up the data into an array of strings with newline as separator
156 for (size_t pos = 0; pos < data.size(); ++pos)
Matt Spinler18207142020-03-26 14:45:54 -0500157 {
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800158 if (data[pos] == '\n')
Matt Spinler18207142020-03-26 14:45:54 -0500159 {
160 std::string line{reinterpret_cast<const char*>(&data[startPos]),
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800161 pos - startPos};
Matt Spinler18207142020-03-26 14:45:54 -0500162 std::for_each(line.begin(), line.end(), validate);
163 text.push_back(std::move(line));
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800164 startPos = pos + 1;
Matt Spinler18207142020-03-26 14:45:54 -0500165 }
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800166 }
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));
Matt Spinler18207142020-03-26 14:45:54 -0500173 }
174
Sumit Kumar516935a2021-04-14 13:00:54 -0500175 orderedJSON json = text;
Matt Spinlerb832aa52023-03-21 15:32:34 -0500176 return prettyJSON(componentID, subType, version, creatorID, json);
Matt Spinler18207142020-03-26 14:45:54 -0500177}
178
179/**
Matt Spinleracb7c102020-01-10 13:49:22 -0600180 * @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 */
Patrick Williams25291152025-02-01 08:21:42 -0500191std::optional<std::string> getBuiltinFormatJSON(
192 uint16_t componentID, uint8_t subType, uint8_t version,
193 const std::vector<uint8_t>& data, uint8_t creatorID)
Matt Spinleracb7c102020-01-10 13:49:22 -0600194{
195 switch (subType)
196 {
197 case static_cast<uint8_t>(UserDataFormat::json):
198 {
199 std::string jsonString{data.begin(), data.begin() + data.size()};
200
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600201 orderedJSON json = orderedJSON::parse(jsonString);
Matt Spinleracb7c102020-01-10 13:49:22 -0600202
Matt Spinlerb832aa52023-03-21 15:32:34 -0500203 return prettyJSON(componentID, subType, version, creatorID, json);
Matt Spinleracb7c102020-01-10 13:49:22 -0600204 }
Matt Spinler18207142020-03-26 14:45:54 -0500205 case static_cast<uint8_t>(UserDataFormat::cbor):
206 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500207 return getCBORJSON(componentID, subType, version, creatorID, data);
Matt Spinler18207142020-03-26 14:45:54 -0500208 }
209 case static_cast<uint8_t>(UserDataFormat::text):
210 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500211 return getTextJSON(componentID, subType, version, creatorID, data);
Matt Spinler18207142020-03-26 14:45:54 -0500212 }
Matt Spinleracb7c102020-01-10 13:49:22 -0600213 default:
214 break;
215 }
216 return std::nullopt;
217}
218
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800219/**
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 */
Patrick Williams25291152025-02-01 08:21:42 -0500243std::optional<std::string> getPythonJSON(
244 uint16_t componentID, uint8_t subType, uint8_t version,
245 const std::vector<uint8_t>& data, uint8_t creatorID)
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800246{
Matt Spinlerbe952d22022-07-01 11:30:11 -0500247 PyObject *pName, *pModule, *eType, *eValue, *eTraceback, *pKey;
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800248 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);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800255 if (pModule == NULL)
256 {
257 pErrStr = "No error string found";
258 PyErr_Fetch(&eType, &eValue, &eTraceback);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800259 if (eType)
260 {
261 Py_XDECREF(eType);
262 }
263 if (eTraceback)
264 {
265 Py_XDECREF(eTraceback);
266 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800267 if (eValue)
268 {
269 PyObject* pStr = PyObject_Str(eValue);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800270 Py_XDECREF(eValue);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800271 if (pStr)
272 {
273 pErrStr = PyUnicode_AsUTF8(pStr);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800274 Py_XDECREF(pStr);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800275 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800276 }
277 }
278 else
279 {
Patrick Williams075c7922024-08-16 15:19:49 -0400280 std::unique_ptr<PyObject, decltype(&pyDecRef)> modPtr(
281 pModule, &pyDecRef);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800282 std::string funcToCall = "parseUDToJson";
283 pKey = PyUnicode_FromString(funcToCall.c_str());
284 std::unique_ptr<PyObject, decltype(&pyDecRef)> keyPtr(pKey, &pyDecRef);
Matt Spinlerbe952d22022-07-01 11:30:11 -0500285 PyObject* pDict = PyModule_GetDict(pModule);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800286 Py_INCREF(pDict);
287 if (!PyDict_Contains(pDict, pKey))
288 {
289 Py_DECREF(pDict);
Matt Spinler7cc3aea2023-07-07 16:24:57 -0500290 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());
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800295 return std::nullopt;
296 }
Matt Spinlerbe952d22022-07-01 11:30:11 -0500297 PyObject* pFunc = PyDict_GetItemString(pDict, funcToCall.c_str());
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800298 Py_DECREF(pDict);
299 Py_INCREF(pFunc);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800300 if (PyCallable_Check(pFunc))
301 {
302 auto ud = data.data();
Matt Spinlerbe952d22022-07-01 11:30:11 -0500303 PyObject* pArgs = PyTuple_New(3);
Patrick Williams075c7922024-08-16 15:19:49 -0400304 std::unique_ptr<PyObject, decltype(&pyDecRef)> argPtr(
305 pArgs, &pyDecRef);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800306 PyTuple_SetItem(pArgs, 0,
307 PyLong_FromUnsignedLong((unsigned long)subType));
308 PyTuple_SetItem(pArgs, 1,
309 PyLong_FromUnsignedLong((unsigned long)version));
Matt Spinlerbe952d22022-07-01 11:30:11 -0500310 PyObject* pData = PyMemoryView_FromMemory(
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800311 reinterpret_cast<char*>(const_cast<unsigned char*>(ud)),
312 data.size(), PyBUF_READ);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800313 PyTuple_SetItem(pArgs, 2, pData);
Matt Spinlerbe952d22022-07-01 11:30:11 -0500314 PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800315 Py_DECREF(pFunc);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800316 if (pResult)
317 {
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800318 std::unique_ptr<PyObject, decltype(&pyDecRef)> resPtr(
319 pResult, &pyDecRef);
Matt Spinler91f6d3a2025-05-22 08:32:49 -0500320
321 if (pResult == Py_None)
322 {
323 // Just return a nullopt so it will hexdump the section
324 return std::nullopt;
325 }
326
Patrick Williams075c7922024-08-16 15:19:49 -0400327 PyObject* pBytes =
328 PyUnicode_AsEncodedString(pResult, "utf-8", "~E~");
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800329 std::unique_ptr<PyObject, decltype(&pyDecRef)> pyBytePtr(
330 pBytes, &pyDecRef);
331 const char* output = PyBytes_AS_STRING(pBytes);
332 try
333 {
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600334 orderedJSON json = orderedJSON::parse(output);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800335 if ((json.is_object() && !json.empty()) ||
336 (json.is_array() && json.size() > 0) ||
337 (json.is_string() && json != ""))
338 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500339 return prettyJSON(componentID, subType, version,
340 creatorID, json);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800341 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800342 }
Patrick Williams66491c62021-10-06 12:23:37 -0500343 catch (const std::exception& e)
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800344 {
Matt Spinler7cc3aea2023-07-07 16:24:57 -0500345 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());
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800350 return std::nullopt;
351 }
352 }
353 else
354 {
355 pErrStr = "No error string found";
356 PyErr_Fetch(&eType, &eValue, &eTraceback);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800357 if (eType)
358 {
359 Py_XDECREF(eType);
360 }
361 if (eTraceback)
362 {
363 Py_XDECREF(eTraceback);
364 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800365 if (eValue)
366 {
367 PyObject* pStr = PyObject_Str(eValue);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800368 Py_XDECREF(eValue);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800369 if (pStr)
370 {
371 pErrStr = PyUnicode_AsUTF8(pStr);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800372 Py_XDECREF(pStr);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800373 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800374 }
375 }
376 }
377 }
378 if (!pErrStr.empty())
379 {
Matt Spinler7cc3aea2023-07-07 16:24:57 -0500380 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());
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800385 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800386 return std::nullopt;
387}
388
Patrick Williams25291152025-02-01 08:21:42 -0500389std::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)
Matt Spinleracb7c102020-01-10 13:49:22 -0600393{
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800394 std::string subsystem = getNumberString("%c", tolower(creatorID));
395 std::string component = getNumberString("%04x", componentID);
Matt Spinleracb7c102020-01-10 13:49:22 -0600396 try
397 {
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800398 if (pv::creatorIDs.at(getNumberString("%c", creatorID)) == "BMC" &&
399 componentID == static_cast<uint16_t>(ComponentID::phosphorLogging))
Matt Spinleracb7c102020-01-10 13:49:22 -0600400 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500401 return getBuiltinFormatJSON(componentID, subType, version, data,
402 creatorID);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800403 }
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800404 else if (std::find(plugins.begin(), plugins.end(),
405 subsystem + component) != plugins.end())
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800406 {
407 return getPythonJSON(componentID, subType, version, data,
408 creatorID);
Matt Spinleracb7c102020-01-10 13:49:22 -0600409 }
410 }
Patrick Williams66491c62021-10-06 12:23:37 -0500411 catch (const std::exception& e)
Matt Spinleracb7c102020-01-10 13:49:22 -0600412 {
Matt Spinler7cc3aea2023-07-07 16:24:57 -0500413 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());
Matt Spinleracb7c102020-01-10 13:49:22 -0600418 }
419
420 return std::nullopt;
421}
422
423} // namespace openpower::pels::user_data