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