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