blob: 0e3ac24156730187ac1c5bb42278641670a44ef3 [file] [log] [blame]
Matt Spinleracb7c102020-01-10 13:49:22 -06001/**
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
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080019#include "json_utils.hpp"
Matt Spinleracb7c102020-01-10 13:49:22 -060020#include "pel_types.hpp"
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080021#include "pel_values.hpp"
Matt Spinler18207142020-03-26 14:45:54 -050022#include "stream.hpp"
Matt Spinleracb7c102020-01-10 13:49:22 -060023#include "user_data_formats.hpp"
24
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +080025#include <Python.h>
26
Matt Spinleracb7c102020-01-10 13:49:22 -060027#include <nlohmann/json.hpp>
28#include <phosphor-logging/log.hpp>
Patrick Williams2544b412022-10-04 08:41:06 -050029
30#include <iomanip>
Matt Spinleracb7c102020-01-10 13:49:22 -060031#include <sstream>
32
33namespace openpower::pels::user_data
34{
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080035namespace pv = openpower::pels::pel_values;
Matt Spinleracb7c102020-01-10 13:49:22 -060036using namespace phosphor::logging;
Sumit Kumar516935a2021-04-14 13:00:54 -050037using orderedJSON = nlohmann::ordered_json;
Matt Spinleracb7c102020-01-10 13:49:22 -060038
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +080039void pyDecRef(PyObject* pyObj)
40{
41 Py_XDECREF(pyObj);
42}
43
Matt Spinleracb7c102020-01-10 13:49:22 -060044/**
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 *
Matt Spinlerb832aa52023-03-21 15:32:34 -050051 * @param[in] creatorID - The creator ID for the PEL
52 *
Matt Spinleracb7c102020-01-10 13:49:22 -060053 * @param[in] json - The JSON to convert to a string
54 *
55 * @return std::string - The JSON string
56 */
57std::string prettyJSON(uint16_t componentID, uint8_t subType, uint8_t version,
Matt Spinlerb832aa52023-03-21 15:32:34 -050058 uint8_t creatorID, const orderedJSON& json)
Matt Spinleracb7c102020-01-10 13:49:22 -060059{
Sumit Kumar516935a2021-04-14 13:00:54 -050060 orderedJSON output;
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080061 output[pv::sectionVer] = std::to_string(version);
62 output[pv::subSection] = std::to_string(subType);
Matt Spinlerb832aa52023-03-21 15:32:34 -050063 output[pv::createdBy] = getComponentName(componentID, creatorID);
Matt Spinleracb7c102020-01-10 13:49:22 -060064
65 if (!json.is_object())
66 {
67 output["Data"] = json;
68 }
69 else
70 {
71 for (const auto& [key, value] : json.items())
72 {
73 output[key] = value;
74 }
75 }
76
77 // Let nlohmann do the pretty printing.
78 std::stringstream stream;
79 stream << std::setw(4) << output;
80
81 auto jsonString = stream.str();
82
83 // Now it looks like:
84 // {
85 // "Section Version": ...
86 // ...
87 // }
88
89 // Since PEL::printSectionInJSON() will supply the outer { }s,
90 // remove the existing ones.
91
92 // Replace the { and the following newline, and the } and its
93 // preceeding newline.
94 jsonString.erase(0, 2);
95
96 auto pos = jsonString.find_last_of('}');
97 jsonString.erase(pos - 1);
98
99 return jsonString;
100}
101
102/**
Matt Spinler18207142020-03-26 14:45:54 -0500103 * @brief Return a JSON string from the passed in CBOR data.
104 *
105 * @param[in] componentID - The comp ID from the UserData section header
106 * @param[in] subType - The subtype from the UserData section header
107 * @param[in] version - The version from the UserData section header
Matt Spinlerb832aa52023-03-21 15:32:34 -0500108 * @param[in] creatorID - The creator ID for the PEL
Matt Spinler18207142020-03-26 14:45:54 -0500109 * @param[in] data - The CBOR data
110 *
111 * @return std::string - The JSON string
112 */
113std::string getCBORJSON(uint16_t componentID, uint8_t subType, uint8_t version,
Matt Spinlerb832aa52023-03-21 15:32:34 -0500114 uint8_t creatorID, const std::vector<uint8_t>& data)
Matt Spinler18207142020-03-26 14:45:54 -0500115{
116 // The CBOR parser needs the pad bytes added to 4 byte align
117 // removed. The number of bytes added to the pad is on the
118 // very end, so will remove both fields before parsing.
119
120 // If the data vector is too short, an exception will get
121 // thrown which will be handled up the call stack.
122
123 auto cborData = data;
124 uint32_t pad{};
125
126 Stream stream{cborData};
127 stream.offset(cborData.size() - 4);
128 stream >> pad;
129
130 if (cborData.size() > (pad + sizeof(pad)))
131 {
132 cborData.resize(data.size() - sizeof(pad) - pad);
133 }
134
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600135 orderedJSON json = orderedJSON::from_cbor(cborData);
Matt Spinler18207142020-03-26 14:45:54 -0500136
Matt Spinlerb832aa52023-03-21 15:32:34 -0500137 return prettyJSON(componentID, subType, version, creatorID, json);
Matt Spinler18207142020-03-26 14:45:54 -0500138}
139
140/**
141 * @brief Return a JSON string from the passed in text data.
142 *
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800143 * The function breaks up the input text into a vector of strings with
144 * newline as separator and converts that into JSON. It will convert any
145 * unprintable characters to periods.
Matt Spinler18207142020-03-26 14:45:54 -0500146 *
147 * @param[in] componentID - The comp ID from the UserData section header
148 * @param[in] subType - The subtype from the UserData section header
149 * @param[in] version - The version from the UserData section header
Matt Spinlerb832aa52023-03-21 15:32:34 -0500150 * @param[in] creatorID - The creator ID for the PEL
Matt Spinler18207142020-03-26 14:45:54 -0500151 * @param[in] data - The CBOR data
152 *
153 * @return std::string - The JSON string
154 */
155std::string getTextJSON(uint16_t componentID, uint8_t subType, uint8_t version,
Matt Spinlerb832aa52023-03-21 15:32:34 -0500156 uint8_t creatorID, const std::vector<uint8_t>& data)
Matt Spinler18207142020-03-26 14:45:54 -0500157{
Matt Spinler18207142020-03-26 14:45:54 -0500158 std::vector<std::string> text;
159 size_t startPos = 0;
Matt Spinler18207142020-03-26 14:45:54 -0500160
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800161 // Converts any unprintable characters to periods
Matt Spinler18207142020-03-26 14:45:54 -0500162 auto validate = [](char& ch) {
163 if ((ch < ' ') || (ch > '~'))
164 {
165 ch = '.';
166 }
167 };
168
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800169 // Break up the data into an array of strings with newline as separator
170 for (size_t pos = 0; pos < data.size(); ++pos)
Matt Spinler18207142020-03-26 14:45:54 -0500171 {
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800172 if (data[pos] == '\n')
Matt Spinler18207142020-03-26 14:45:54 -0500173 {
174 std::string line{reinterpret_cast<const char*>(&data[startPos]),
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800175 pos - startPos};
Matt Spinler18207142020-03-26 14:45:54 -0500176 std::for_each(line.begin(), line.end(), validate);
177 text.push_back(std::move(line));
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800178 startPos = pos + 1;
Matt Spinler18207142020-03-26 14:45:54 -0500179 }
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800180 }
181 if (startPos < data.size())
182 {
183 std::string line{reinterpret_cast<const char*>(&data[startPos]),
184 data.size() - startPos};
185 std::for_each(line.begin(), line.end(), validate);
186 text.push_back(std::move(line));
Matt Spinler18207142020-03-26 14:45:54 -0500187 }
188
Sumit Kumar516935a2021-04-14 13:00:54 -0500189 orderedJSON json = text;
Matt Spinlerb832aa52023-03-21 15:32:34 -0500190 return prettyJSON(componentID, subType, version, creatorID, json);
Matt Spinler18207142020-03-26 14:45:54 -0500191}
192
193/**
Matt Spinleracb7c102020-01-10 13:49:22 -0600194 * @brief Convert to an appropriate JSON string as the data is one of
195 * the formats that we natively support.
196 *
197 * @param[in] componentID - The comp ID from the UserData section header
198 * @param[in] subType - The subtype from the UserData section header
199 * @param[in] version - The version from the UserData section header
200 * @param[in] data - The data itself
201 *
202 * @return std::optional<std::string> - The JSON string if it could be created,
203 * else std::nullopt.
204 */
205std::optional<std::string>
206 getBuiltinFormatJSON(uint16_t componentID, uint8_t subType, uint8_t version,
Matt Spinlerb832aa52023-03-21 15:32:34 -0500207 const std::vector<uint8_t>& data, uint8_t creatorID)
Matt Spinleracb7c102020-01-10 13:49:22 -0600208{
209 switch (subType)
210 {
211 case static_cast<uint8_t>(UserDataFormat::json):
212 {
213 std::string jsonString{data.begin(), data.begin() + data.size()};
214
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600215 orderedJSON json = orderedJSON::parse(jsonString);
Matt Spinleracb7c102020-01-10 13:49:22 -0600216
Matt Spinlerb832aa52023-03-21 15:32:34 -0500217 return prettyJSON(componentID, subType, version, creatorID, json);
Matt Spinleracb7c102020-01-10 13:49:22 -0600218 }
Matt Spinler18207142020-03-26 14:45:54 -0500219 case static_cast<uint8_t>(UserDataFormat::cbor):
220 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500221 return getCBORJSON(componentID, subType, version, creatorID, data);
Matt Spinler18207142020-03-26 14:45:54 -0500222 }
223 case static_cast<uint8_t>(UserDataFormat::text):
224 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500225 return getTextJSON(componentID, subType, version, creatorID, data);
Matt Spinler18207142020-03-26 14:45:54 -0500226 }
Matt Spinleracb7c102020-01-10 13:49:22 -0600227 default:
228 break;
229 }
230 return std::nullopt;
231}
232
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800233/**
234 * @brief Call Python modules to parse the data into a JSON string
235 *
236 * The module to call is based on the Creator Subsystem ID and the Component
237 * ID under the namespace "udparsers". For example: "udparsers.xyyyy.xyyyy"
238 * where "x" is the Creator Subsystem ID and "yyyy" is the Component ID.
239 *
240 * All modules must provide the following:
241 * Function: parseUDToJson
242 * Argument list:
243 * 1. (int) Sub-section type
244 * 2. (int) Section version
245 * 3. (memoryview): Data
246 *-Return data:
247 * 1. (str) JSON string
248 *
249 * @param[in] componentID - The comp ID from the UserData section header
250 * @param[in] subType - The subtype from the UserData section header
251 * @param[in] version - The version from the UserData section header
252 * @param[in] data - The data itself
253 * @param[in] creatorID - The creatorID from the PrivateHeader section
254 * @return std::optional<std::string> - The JSON string if it could be created,
255 * else std::nullopt
256 */
257std::optional<std::string> getPythonJSON(uint16_t componentID, uint8_t subType,
258 uint8_t version,
259 const std::vector<uint8_t>& data,
260 uint8_t creatorID)
261{
Matt Spinlerbe952d22022-07-01 11:30:11 -0500262 PyObject *pName, *pModule, *eType, *eValue, *eTraceback, *pKey;
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800263 std::string pErrStr;
264 std::string module = getNumberString("%c", tolower(creatorID)) +
265 getNumberString("%04x", componentID);
266 pName = PyUnicode_FromString(
267 std::string("udparsers." + module + "." + module).c_str());
268 std::unique_ptr<PyObject, decltype(&pyDecRef)> modNamePtr(pName, &pyDecRef);
269 pModule = PyImport_Import(pName);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800270 if (pModule == NULL)
271 {
272 pErrStr = "No error string found";
273 PyErr_Fetch(&eType, &eValue, &eTraceback);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800274 if (eType)
275 {
276 Py_XDECREF(eType);
277 }
278 if (eTraceback)
279 {
280 Py_XDECREF(eTraceback);
281 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800282 if (eValue)
283 {
284 PyObject* pStr = PyObject_Str(eValue);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800285 Py_XDECREF(eValue);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800286 if (pStr)
287 {
288 pErrStr = PyUnicode_AsUTF8(pStr);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800289 Py_XDECREF(pStr);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800290 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800291 }
292 }
293 else
294 {
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800295 std::unique_ptr<PyObject, decltype(&pyDecRef)> modPtr(pModule,
296 &pyDecRef);
297 std::string funcToCall = "parseUDToJson";
298 pKey = PyUnicode_FromString(funcToCall.c_str());
299 std::unique_ptr<PyObject, decltype(&pyDecRef)> keyPtr(pKey, &pyDecRef);
Matt Spinlerbe952d22022-07-01 11:30:11 -0500300 PyObject* pDict = PyModule_GetDict(pModule);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800301 Py_INCREF(pDict);
302 if (!PyDict_Contains(pDict, pKey))
303 {
304 Py_DECREF(pDict);
305 log<level::ERR>(
306 "Python module error",
307 entry("ERROR=%s",
308 std::string(funcToCall + " function missing").c_str()),
309 entry("PARSER_MODULE=%s", module.c_str()),
310 entry("SUBTYPE=0x%X", subType), entry("VERSION=%d", version),
311 entry("DATA_LENGTH=%lu\n", data.size()));
312 return std::nullopt;
313 }
Matt Spinlerbe952d22022-07-01 11:30:11 -0500314 PyObject* pFunc = PyDict_GetItemString(pDict, funcToCall.c_str());
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800315 Py_DECREF(pDict);
316 Py_INCREF(pFunc);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800317 if (PyCallable_Check(pFunc))
318 {
319 auto ud = data.data();
Matt Spinlerbe952d22022-07-01 11:30:11 -0500320 PyObject* pArgs = PyTuple_New(3);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800321 std::unique_ptr<PyObject, decltype(&pyDecRef)> argPtr(pArgs,
322 &pyDecRef);
323 PyTuple_SetItem(pArgs, 0,
324 PyLong_FromUnsignedLong((unsigned long)subType));
325 PyTuple_SetItem(pArgs, 1,
326 PyLong_FromUnsignedLong((unsigned long)version));
Matt Spinlerbe952d22022-07-01 11:30:11 -0500327 PyObject* pData = PyMemoryView_FromMemory(
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800328 reinterpret_cast<char*>(const_cast<unsigned char*>(ud)),
329 data.size(), PyBUF_READ);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800330 PyTuple_SetItem(pArgs, 2, pData);
Matt Spinlerbe952d22022-07-01 11:30:11 -0500331 PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800332 Py_DECREF(pFunc);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800333 if (pResult)
334 {
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800335 std::unique_ptr<PyObject, decltype(&pyDecRef)> resPtr(
336 pResult, &pyDecRef);
Patrick Williams2544b412022-10-04 08:41:06 -0500337 PyObject* pBytes = PyUnicode_AsEncodedString(pResult, "utf-8",
338 "~E~");
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800339 std::unique_ptr<PyObject, decltype(&pyDecRef)> pyBytePtr(
340 pBytes, &pyDecRef);
341 const char* output = PyBytes_AS_STRING(pBytes);
342 try
343 {
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600344 orderedJSON json = orderedJSON::parse(output);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800345 if ((json.is_object() && !json.empty()) ||
346 (json.is_array() && json.size() > 0) ||
347 (json.is_string() && json != ""))
348 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500349 return prettyJSON(componentID, subType, version,
350 creatorID, json);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800351 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800352 }
Patrick Williams66491c62021-10-06 12:23:37 -0500353 catch (const std::exception& e)
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800354 {
355 log<level::ERR>("Bad JSON from parser",
356 entry("ERROR=%s", e.what()),
357 entry("PARSER_MODULE=%s", module.c_str()),
358 entry("SUBTYPE=0x%X", subType),
359 entry("VERSION=%d", version),
360 entry("DATA_LENGTH=%lu\n", data.size()));
361 return std::nullopt;
362 }
363 }
364 else
365 {
366 pErrStr = "No error string found";
367 PyErr_Fetch(&eType, &eValue, &eTraceback);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800368 if (eType)
369 {
370 Py_XDECREF(eType);
371 }
372 if (eTraceback)
373 {
374 Py_XDECREF(eTraceback);
375 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800376 if (eValue)
377 {
378 PyObject* pStr = PyObject_Str(eValue);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800379 Py_XDECREF(eValue);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800380 if (pStr)
381 {
382 pErrStr = PyUnicode_AsUTF8(pStr);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800383 Py_XDECREF(pStr);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800384 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800385 }
386 }
387 }
388 }
389 if (!pErrStr.empty())
390 {
Matt Spinler3279cc52022-02-15 11:02:00 -0600391 log<level::DEBUG>("Python exception thrown by parser",
392 entry("ERROR=%s", pErrStr.c_str()),
393 entry("PARSER_MODULE=%s", module.c_str()),
394 entry("SUBTYPE=0x%X", subType),
395 entry("VERSION=%d", version),
396 entry("DATA_LENGTH=%lu\n", data.size()));
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800397 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800398 return std::nullopt;
399}
400
Matt Spinleracb7c102020-01-10 13:49:22 -0600401std::optional<std::string> getJSON(uint16_t componentID, uint8_t subType,
402 uint8_t version,
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800403 const std::vector<uint8_t>& data,
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800404 uint8_t creatorID,
405 const std::vector<std::string>& plugins)
Matt Spinleracb7c102020-01-10 13:49:22 -0600406{
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800407 std::string subsystem = getNumberString("%c", tolower(creatorID));
408 std::string component = getNumberString("%04x", componentID);
Matt Spinleracb7c102020-01-10 13:49:22 -0600409 try
410 {
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800411 if (pv::creatorIDs.at(getNumberString("%c", creatorID)) == "BMC" &&
412 componentID == static_cast<uint16_t>(ComponentID::phosphorLogging))
Matt Spinleracb7c102020-01-10 13:49:22 -0600413 {
Matt Spinlerb832aa52023-03-21 15:32:34 -0500414 return getBuiltinFormatJSON(componentID, subType, version, data,
415 creatorID);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800416 }
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800417 else if (std::find(plugins.begin(), plugins.end(),
418 subsystem + component) != plugins.end())
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800419 {
420 return getPythonJSON(componentID, subType, version, data,
421 creatorID);
Matt Spinleracb7c102020-01-10 13:49:22 -0600422 }
423 }
Patrick Williams66491c62021-10-06 12:23:37 -0500424 catch (const std::exception& e)
Matt Spinleracb7c102020-01-10 13:49:22 -0600425 {
Matt Spinler18207142020-03-26 14:45:54 -0500426 log<level::ERR>("Failed parsing UserData", entry("ERROR=%s", e.what()),
427 entry("COMP_ID=0x%X", componentID),
428 entry("SUBTYPE=0x%X", subType),
429 entry("VERSION=%d", version),
430 entry("DATA_LENGTH=%lu\n", data.size()));
Matt Spinleracb7c102020-01-10 13:49:22 -0600431 }
432
433 return std::nullopt;
434}
435
436} // namespace openpower::pels::user_data