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