xref: /openbmc/phosphor-logging/extensions/openpower-pels/user_data_json.cpp (revision 81a91e3ee4bf962111cf555ab9d3c3c51000fa3b)
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