xref: /openbmc/phosphor-logging/extensions/openpower-pels/user_data_json.cpp (revision 40fb54935ce7367636a7156039396ee91cc4d5e2)
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