blob: bf6152b5a1d42b244b88c7f0100f6f9e8de37943 [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 *
51 * @param[in] json - The JSON to convert to a string
52 *
53 * @return std::string - The JSON string
54 */
55std::string prettyJSON(uint16_t componentID, uint8_t subType, uint8_t version,
Sumit Kumar516935a2021-04-14 13:00:54 -050056 const orderedJSON& json)
Matt Spinleracb7c102020-01-10 13:49:22 -060057{
Sumit Kumar516935a2021-04-14 13:00:54 -050058 orderedJSON output;
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080059 output[pv::sectionVer] = std::to_string(version);
60 output[pv::subSection] = std::to_string(subType);
61 output[pv::createdBy] = getNumberString("0x%04X", componentID);
Matt Spinleracb7c102020-01-10 13:49:22 -060062
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/**
Matt Spinler18207142020-03-26 14:45:54 -0500101 * @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 */
110std::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
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600132 orderedJSON json = orderedJSON::from_cbor(cborData);
Matt Spinler18207142020-03-26 14:45:54 -0500133
134 return prettyJSON(componentID, subType, version, json);
135}
136
137/**
138 * @brief Return a JSON string from the passed in text data.
139 *
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800140 * 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.
Matt Spinler18207142020-03-26 14:45:54 -0500143 *
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 */
151std::string getTextJSON(uint16_t componentID, uint8_t subType, uint8_t version,
152 const std::vector<uint8_t>& data)
153{
Matt Spinler18207142020-03-26 14:45:54 -0500154 std::vector<std::string> text;
155 size_t startPos = 0;
Matt Spinler18207142020-03-26 14:45:54 -0500156
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800157 // Converts any unprintable characters to periods
Matt Spinler18207142020-03-26 14:45:54 -0500158 auto validate = [](char& ch) {
159 if ((ch < ' ') || (ch > '~'))
160 {
161 ch = '.';
162 }
163 };
164
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800165 // Break up the data into an array of strings with newline as separator
166 for (size_t pos = 0; pos < data.size(); ++pos)
Matt Spinler18207142020-03-26 14:45:54 -0500167 {
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800168 if (data[pos] == '\n')
Matt Spinler18207142020-03-26 14:45:54 -0500169 {
170 std::string line{reinterpret_cast<const char*>(&data[startPos]),
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800171 pos - startPos};
Matt Spinler18207142020-03-26 14:45:54 -0500172 std::for_each(line.begin(), line.end(), validate);
173 text.push_back(std::move(line));
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800174 startPos = pos + 1;
Matt Spinler18207142020-03-26 14:45:54 -0500175 }
Harisuddin Mohamed Isaaadf28f2020-09-29 22:10:52 +0800176 }
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));
Matt Spinler18207142020-03-26 14:45:54 -0500183 }
184
Sumit Kumar516935a2021-04-14 13:00:54 -0500185 orderedJSON json = text;
Matt Spinler18207142020-03-26 14:45:54 -0500186 return prettyJSON(componentID, subType, version, json);
187}
188
189/**
Matt Spinleracb7c102020-01-10 13:49:22 -0600190 * @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 */
201std::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
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600211 orderedJSON json = orderedJSON::parse(jsonString);
Matt Spinleracb7c102020-01-10 13:49:22 -0600212
213 return prettyJSON(componentID, subType, version, json);
214 }
Matt Spinler18207142020-03-26 14:45:54 -0500215 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 }
Matt Spinleracb7c102020-01-10 13:49:22 -0600223 default:
224 break;
225 }
226 return std::nullopt;
227}
228
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800229/**
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 */
253std::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{
Matt Spinlerbe952d22022-07-01 11:30:11 -0500258 PyObject *pName, *pModule, *eType, *eValue, *eTraceback, *pKey;
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800259 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);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800266 if (pModule == NULL)
267 {
268 pErrStr = "No error string found";
269 PyErr_Fetch(&eType, &eValue, &eTraceback);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800270 if (eType)
271 {
272 Py_XDECREF(eType);
273 }
274 if (eTraceback)
275 {
276 Py_XDECREF(eTraceback);
277 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800278 if (eValue)
279 {
280 PyObject* pStr = PyObject_Str(eValue);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800281 Py_XDECREF(eValue);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800282 if (pStr)
283 {
284 pErrStr = PyUnicode_AsUTF8(pStr);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800285 Py_XDECREF(pStr);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800286 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800287 }
288 }
289 else
290 {
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800291 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);
Matt Spinlerbe952d22022-07-01 11:30:11 -0500296 PyObject* pDict = PyModule_GetDict(pModule);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800297 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 }
Matt Spinlerbe952d22022-07-01 11:30:11 -0500310 PyObject* pFunc = PyDict_GetItemString(pDict, funcToCall.c_str());
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800311 Py_DECREF(pDict);
312 Py_INCREF(pFunc);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800313 if (PyCallable_Check(pFunc))
314 {
315 auto ud = data.data();
Matt Spinlerbe952d22022-07-01 11:30:11 -0500316 PyObject* pArgs = PyTuple_New(3);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800317 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));
Matt Spinlerbe952d22022-07-01 11:30:11 -0500323 PyObject* pData = PyMemoryView_FromMemory(
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800324 reinterpret_cast<char*>(const_cast<unsigned char*>(ud)),
325 data.size(), PyBUF_READ);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800326 PyTuple_SetItem(pArgs, 2, pData);
Matt Spinlerbe952d22022-07-01 11:30:11 -0500327 PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800328 Py_DECREF(pFunc);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800329 if (pResult)
330 {
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800331 std::unique_ptr<PyObject, decltype(&pyDecRef)> resPtr(
332 pResult, &pyDecRef);
Patrick Williams2544b412022-10-04 08:41:06 -0500333 PyObject* pBytes = PyUnicode_AsEncodedString(pResult, "utf-8",
334 "~E~");
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800335 std::unique_ptr<PyObject, decltype(&pyDecRef)> pyBytePtr(
336 pBytes, &pyDecRef);
337 const char* output = PyBytes_AS_STRING(pBytes);
338 try
339 {
Matt Spinlerbb1c1d52021-06-03 13:18:48 -0600340 orderedJSON json = orderedJSON::parse(output);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800341 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 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800347 }
Patrick Williams66491c62021-10-06 12:23:37 -0500348 catch (const std::exception& e)
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800349 {
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);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800363 if (eType)
364 {
365 Py_XDECREF(eType);
366 }
367 if (eTraceback)
368 {
369 Py_XDECREF(eTraceback);
370 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800371 if (eValue)
372 {
373 PyObject* pStr = PyObject_Str(eValue);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800374 Py_XDECREF(eValue);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800375 if (pStr)
376 {
377 pErrStr = PyUnicode_AsUTF8(pStr);
Harisuddin Mohamed Isad5c31362021-05-29 13:33:39 +0800378 Py_XDECREF(pStr);
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800379 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800380 }
381 }
382 }
383 }
384 if (!pErrStr.empty())
385 {
Matt Spinler3279cc52022-02-15 11:02:00 -0600386 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()));
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800392 }
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800393 return std::nullopt;
394}
395
Matt Spinleracb7c102020-01-10 13:49:22 -0600396std::optional<std::string> getJSON(uint16_t componentID, uint8_t subType,
397 uint8_t version,
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800398 const std::vector<uint8_t>& data,
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800399 uint8_t creatorID,
400 const std::vector<std::string>& plugins)
Matt Spinleracb7c102020-01-10 13:49:22 -0600401{
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800402 std::string subsystem = getNumberString("%c", tolower(creatorID));
403 std::string component = getNumberString("%04x", componentID);
Matt Spinleracb7c102020-01-10 13:49:22 -0600404 try
405 {
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800406 if (pv::creatorIDs.at(getNumberString("%c", creatorID)) == "BMC" &&
407 componentID == static_cast<uint16_t>(ComponentID::phosphorLogging))
Matt Spinleracb7c102020-01-10 13:49:22 -0600408 {
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800409 return getBuiltinFormatJSON(componentID, subType, version, data);
410 }
Harisuddin Mohamed Isa3fdcd4e2020-08-26 11:56:42 +0800411 else if (std::find(plugins.begin(), plugins.end(),
412 subsystem + component) != plugins.end())
Harisuddin Mohamed Isaf67bafd2020-07-06 17:51:21 +0800413 {
414 return getPythonJSON(componentID, subType, version, data,
415 creatorID);
Matt Spinleracb7c102020-01-10 13:49:22 -0600416 }
417 }
Patrick Williams66491c62021-10-06 12:23:37 -0500418 catch (const std::exception& e)
Matt Spinleracb7c102020-01-10 13:49:22 -0600419 {
Matt Spinler18207142020-03-26 14:45:54 -0500420 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()));
Matt Spinleracb7c102020-01-10 13:49:22 -0600425 }
426
427 return std::nullopt;
428}
429
430} // namespace openpower::pels::user_data