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