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