/** * Copyright © 2020 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "user_data_json.hpp" #include "json_utils.hpp" #include "pel_types.hpp" #include "pel_values.hpp" #include "stream.hpp" #include "user_data_formats.hpp" #include #include #include #include #include namespace openpower::pels::user_data { namespace pv = openpower::pels::pel_values; using namespace phosphor::logging; // Use fifo_map as nlohmann::json's map. We are just ignoring the 'less' // compare. With this map the keys are kept in FIFO order. template using fifoMap = nlohmann::fifo_map, A>; using fifoJSON = nlohmann::basic_json; /** * @brief Returns a JSON string for use by PEL::printSectionInJSON(). * * The returning string will contain a JSON object, but without * the outer {}. If the input JSON isn't a JSON object (dict), then * one will be created with the input added to a 'Data' key. * * @param[in] json - The JSON to convert to a string * * @return std::string - The JSON string */ std::string prettyJSON(uint16_t componentID, uint8_t subType, uint8_t version, const fifoJSON& json) { fifoJSON output; output[pv::sectionVer] = std::to_string(version); output[pv::subSection] = std::to_string(subType); output[pv::createdBy] = getNumberString("0x%04X", componentID); if (!json.is_object()) { output["Data"] = json; } else { for (const auto& [key, value] : json.items()) { output[key] = value; } } // Let nlohmann do the pretty printing. std::stringstream stream; stream << std::setw(4) << output; auto jsonString = stream.str(); // Now it looks like: // { // "Section Version": ... // ... // } // Since PEL::printSectionInJSON() will supply the outer { }s, // remove the existing ones. // Replace the { and the following newline, and the } and its // preceeding newline. jsonString.erase(0, 2); auto pos = jsonString.find_last_of('}'); jsonString.erase(pos - 1); return jsonString; } /** * @brief Return a JSON string from the passed in CBOR data. * * @param[in] componentID - The comp ID from the UserData section header * @param[in] subType - The subtype from the UserData section header * @param[in] version - The version from the UserData section header * @param[in] data - The CBOR data * * @return std::string - The JSON string */ std::string getCBORJSON(uint16_t componentID, uint8_t subType, uint8_t version, const std::vector& data) { // The CBOR parser needs the pad bytes added to 4 byte align // removed. The number of bytes added to the pad is on the // very end, so will remove both fields before parsing. // If the data vector is too short, an exception will get // thrown which will be handled up the call stack. auto cborData = data; uint32_t pad{}; Stream stream{cborData}; stream.offset(cborData.size() - 4); stream >> pad; if (cborData.size() > (pad + sizeof(pad))) { cborData.resize(data.size() - sizeof(pad) - pad); } fifoJSON json = nlohmann::json::from_cbor(cborData); return prettyJSON(componentID, subType, version, json); } /** * @brief Return a JSON string from the passed in text data. * * The function breaks up the input text into a vector of 60 character * strings and converts that into JSON. It will convert any unprintable * characters to periods. * * @param[in] componentID - The comp ID from the UserData section header * @param[in] subType - The subtype from the UserData section header * @param[in] version - The version from the UserData section header * @param[in] data - The CBOR data * * @return std::string - The JSON string */ std::string getTextJSON(uint16_t componentID, uint8_t subType, uint8_t version, const std::vector& data) { constexpr size_t maxLineLength = 60; std::vector text; size_t startPos = 0; bool done = false; // Converts any unprintable characters to periods. auto validate = [](char& ch) { if ((ch < ' ') || (ch > '~')) { ch = '.'; } }; // Break up the data into an array of 60 character strings while (!done) { // 60 or less characters left if (startPos + maxLineLength >= data.size()) { std::string line{reinterpret_cast(&data[startPos]), data.size() - startPos}; std::for_each(line.begin(), line.end(), validate); text.push_back(std::move(line)); done = true; } else { std::string line{reinterpret_cast(&data[startPos]), maxLineLength}; std::for_each(line.begin(), line.end(), validate); text.push_back(std::move(line)); startPos += maxLineLength; } } fifoJSON json = text; return prettyJSON(componentID, subType, version, json); } /** * @brief Convert to an appropriate JSON string as the data is one of * the formats that we natively support. * * @param[in] componentID - The comp ID from the UserData section header * @param[in] subType - The subtype from the UserData section header * @param[in] version - The version from the UserData section header * @param[in] data - The data itself * * @return std::optional - The JSON string if it could be created, * else std::nullopt. */ std::optional getBuiltinFormatJSON(uint16_t componentID, uint8_t subType, uint8_t version, const std::vector& data) { switch (subType) { case static_cast(UserDataFormat::json): { std::string jsonString{data.begin(), data.begin() + data.size()}; fifoJSON json = nlohmann::json::parse(jsonString); return prettyJSON(componentID, subType, version, json); } case static_cast(UserDataFormat::cbor): { return getCBORJSON(componentID, subType, version, data); } case static_cast(UserDataFormat::text): { return getTextJSON(componentID, subType, version, data); } default: break; } return std::nullopt; } std::optional getJSON(uint16_t componentID, uint8_t subType, uint8_t version, const std::vector& data) { try { switch (componentID) { case static_cast(ComponentID::phosphorLogging): return getBuiltinFormatJSON(componentID, subType, version, data); default: break; } } catch (std::exception& e) { log("Failed parsing UserData", entry("ERROR=%s", e.what()), entry("COMP_ID=0x%X", componentID), entry("SUBTYPE=0x%X", subType), entry("VERSION=%d", version), entry("DATA_LENGTH=%lu\n", data.size())); } return std::nullopt; } } // namespace openpower::pels::user_data