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 <fifo_map.hpp> 26 #include <iomanip> 27 #include <nlohmann/json.hpp> 28 #include <phosphor-logging/log.hpp> 29 #include <sstream> 30 31 namespace openpower::pels::user_data 32 { 33 namespace pv = openpower::pels::pel_values; 34 using namespace phosphor::logging; 35 36 // Use fifo_map as nlohmann::json's map. We are just ignoring the 'less' 37 // compare. With this map the keys are kept in FIFO order. 38 template <class K, class V, class dummy_compare, class A> 39 using fifoMap = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>; 40 using fifoJSON = nlohmann::basic_json<fifoMap>; 41 42 /** 43 * @brief Returns a JSON string for use by PEL::printSectionInJSON(). 44 * 45 * The returning string will contain a JSON object, but without 46 * the outer {}. If the input JSON isn't a JSON object (dict), then 47 * one will be created with the input added to a 'Data' key. 48 * 49 * @param[in] json - The JSON to convert to a string 50 * 51 * @return std::string - The JSON string 52 */ 53 std::string prettyJSON(uint16_t componentID, uint8_t subType, uint8_t version, 54 const fifoJSON& json) 55 { 56 fifoJSON output; 57 output[pv::sectionVer] = std::to_string(version); 58 output[pv::subSection] = std::to_string(subType); 59 output[pv::createdBy] = getNumberString("0x%04X", componentID); 60 61 if (!json.is_object()) 62 { 63 output["Data"] = json; 64 } 65 else 66 { 67 for (const auto& [key, value] : json.items()) 68 { 69 output[key] = value; 70 } 71 } 72 73 // Let nlohmann do the pretty printing. 74 std::stringstream stream; 75 stream << std::setw(4) << output; 76 77 auto jsonString = stream.str(); 78 79 // Now it looks like: 80 // { 81 // "Section Version": ... 82 // ... 83 // } 84 85 // Since PEL::printSectionInJSON() will supply the outer { }s, 86 // remove the existing ones. 87 88 // Replace the { and the following newline, and the } and its 89 // preceeding newline. 90 jsonString.erase(0, 2); 91 92 auto pos = jsonString.find_last_of('}'); 93 jsonString.erase(pos - 1); 94 95 return jsonString; 96 } 97 98 /** 99 * @brief Return a JSON string from the passed in CBOR data. 100 * 101 * @param[in] componentID - The comp ID from the UserData section header 102 * @param[in] subType - The subtype from the UserData section header 103 * @param[in] version - The version from the UserData section header 104 * @param[in] data - The CBOR data 105 * 106 * @return std::string - The JSON string 107 */ 108 std::string getCBORJSON(uint16_t componentID, uint8_t subType, uint8_t version, 109 const std::vector<uint8_t>& data) 110 { 111 // The CBOR parser needs the pad bytes added to 4 byte align 112 // removed. The number of bytes added to the pad is on the 113 // very end, so will remove both fields before parsing. 114 115 // If the data vector is too short, an exception will get 116 // thrown which will be handled up the call stack. 117 118 auto cborData = data; 119 uint32_t pad{}; 120 121 Stream stream{cborData}; 122 stream.offset(cborData.size() - 4); 123 stream >> pad; 124 125 if (cborData.size() > (pad + sizeof(pad))) 126 { 127 cborData.resize(data.size() - sizeof(pad) - pad); 128 } 129 130 fifoJSON json = nlohmann::json::from_cbor(cborData); 131 132 return prettyJSON(componentID, subType, version, json); 133 } 134 135 /** 136 * @brief Return a JSON string from the passed in text data. 137 * 138 * The function breaks up the input text into a vector of 60 character 139 * strings and converts that into JSON. It will convert any unprintable 140 * characters to periods. 141 * 142 * @param[in] componentID - The comp ID from the UserData section header 143 * @param[in] subType - The subtype from the UserData section header 144 * @param[in] version - The version from the UserData section header 145 * @param[in] data - The CBOR data 146 * 147 * @return std::string - The JSON string 148 */ 149 std::string getTextJSON(uint16_t componentID, uint8_t subType, uint8_t version, 150 const std::vector<uint8_t>& data) 151 { 152 constexpr size_t maxLineLength = 60; 153 std::vector<std::string> text; 154 size_t startPos = 0; 155 bool done = false; 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 60 character strings 166 while (!done) 167 { 168 // 60 or less characters left 169 if (startPos + maxLineLength >= data.size()) 170 { 171 std::string line{reinterpret_cast<const char*>(&data[startPos]), 172 data.size() - startPos}; 173 std::for_each(line.begin(), line.end(), validate); 174 text.push_back(std::move(line)); 175 done = true; 176 } 177 else 178 { 179 std::string line{reinterpret_cast<const char*>(&data[startPos]), 180 maxLineLength}; 181 std::for_each(line.begin(), line.end(), validate); 182 text.push_back(std::move(line)); 183 startPos += maxLineLength; 184 } 185 } 186 187 fifoJSON json = text; 188 return prettyJSON(componentID, subType, version, json); 189 } 190 191 /** 192 * @brief Convert to an appropriate JSON string as the data is one of 193 * the formats that we natively support. 194 * 195 * @param[in] componentID - The comp ID from the UserData section header 196 * @param[in] subType - The subtype from the UserData section header 197 * @param[in] version - The version from the UserData section header 198 * @param[in] data - The data itself 199 * 200 * @return std::optional<std::string> - The JSON string if it could be created, 201 * else std::nullopt. 202 */ 203 std::optional<std::string> 204 getBuiltinFormatJSON(uint16_t componentID, uint8_t subType, uint8_t version, 205 const std::vector<uint8_t>& data) 206 { 207 switch (subType) 208 { 209 case static_cast<uint8_t>(UserDataFormat::json): 210 { 211 std::string jsonString{data.begin(), data.begin() + data.size()}; 212 213 fifoJSON json = nlohmann::json::parse(jsonString); 214 215 return prettyJSON(componentID, subType, version, json); 216 } 217 case static_cast<uint8_t>(UserDataFormat::cbor): 218 { 219 return getCBORJSON(componentID, subType, version, data); 220 } 221 case static_cast<uint8_t>(UserDataFormat::text): 222 { 223 return getTextJSON(componentID, subType, version, data); 224 } 225 default: 226 break; 227 } 228 return std::nullopt; 229 } 230 231 std::optional<std::string> getJSON(uint16_t componentID, uint8_t subType, 232 uint8_t version, 233 const std::vector<uint8_t>& data) 234 { 235 try 236 { 237 switch (componentID) 238 { 239 case static_cast<uint16_t>(ComponentID::phosphorLogging): 240 return getBuiltinFormatJSON(componentID, subType, version, 241 data); 242 default: 243 break; 244 } 245 } 246 catch (std::exception& e) 247 { 248 log<level::ERR>("Failed parsing UserData", entry("ERROR=%s", e.what()), 249 entry("COMP_ID=0x%X", componentID), 250 entry("SUBTYPE=0x%X", subType), 251 entry("VERSION=%d", version), 252 entry("DATA_LENGTH=%lu\n", data.size())); 253 } 254 255 return std::nullopt; 256 } 257 258 } // namespace openpower::pels::user_data 259