blob: e133cdd72ae5de2d46d1cafe73fd8cfd94ddb60f [file] [log] [blame]
Matt Spinleracb7c102020-01-10 13:49:22 -06001/**
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
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080019#include "json_utils.hpp"
Matt Spinleracb7c102020-01-10 13:49:22 -060020#include "pel_types.hpp"
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080021#include "pel_values.hpp"
Matt Spinler18207142020-03-26 14:45:54 -050022#include "stream.hpp"
Matt Spinleracb7c102020-01-10 13:49:22 -060023#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
31namespace openpower::pels::user_data
32{
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080033namespace pv = openpower::pels::pel_values;
Matt Spinleracb7c102020-01-10 13:49:22 -060034using 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.
38template <class K, class V, class dummy_compare, class A>
39using fifoMap = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
40using 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 */
53std::string prettyJSON(uint16_t componentID, uint8_t subType, uint8_t version,
54 const fifoJSON& json)
55{
56 fifoJSON output;
Harisuddin Mohamed Isabebeb942020-03-12 17:12:24 +080057 output[pv::sectionVer] = std::to_string(version);
58 output[pv::subSection] = std::to_string(subType);
59 output[pv::createdBy] = getNumberString("0x%04X", componentID);
Matt Spinleracb7c102020-01-10 13:49:22 -060060
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/**
Matt Spinler18207142020-03-26 14:45:54 -050099 * @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 */
108std::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 */
149std::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/**
Matt Spinleracb7c102020-01-10 13:49:22 -0600192 * @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 */
203std::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 }
Matt Spinler18207142020-03-26 14:45:54 -0500217 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 }
Matt Spinleracb7c102020-01-10 13:49:22 -0600225 default:
226 break;
227 }
228 return std::nullopt;
229}
230
231std::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 {
Matt Spinler18207142020-03-26 14:45:54 -0500248 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()));
Matt Spinleracb7c102020-01-10 13:49:22 -0600253 }
254
255 return std::nullopt;
256}
257
258} // namespace openpower::pels::user_data