blob: e133cdd72ae5de2d46d1cafe73fd8cfd94ddb60f [file] [log] [blame]
/**
* 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 <fifo_map.hpp>
#include <iomanip>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <sstream>
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 <class K, class V, class dummy_compare, class A>
using fifoMap = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
using fifoJSON = nlohmann::basic_json<fifoMap>;
/**
* @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<uint8_t>& 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<uint8_t>& data)
{
constexpr size_t maxLineLength = 60;
std::vector<std::string> 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<const char*>(&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<const char*>(&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<std::string> - The JSON string if it could be created,
* else std::nullopt.
*/
std::optional<std::string>
getBuiltinFormatJSON(uint16_t componentID, uint8_t subType, uint8_t version,
const std::vector<uint8_t>& data)
{
switch (subType)
{
case static_cast<uint8_t>(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<uint8_t>(UserDataFormat::cbor):
{
return getCBORJSON(componentID, subType, version, data);
}
case static_cast<uint8_t>(UserDataFormat::text):
{
return getTextJSON(componentID, subType, version, data);
}
default:
break;
}
return std::nullopt;
}
std::optional<std::string> getJSON(uint16_t componentID, uint8_t subType,
uint8_t version,
const std::vector<uint8_t>& data)
{
try
{
switch (componentID)
{
case static_cast<uint16_t>(ComponentID::phosphorLogging):
return getBuiltinFormatJSON(componentID, subType, version,
data);
default:
break;
}
}
catch (std::exception& e)
{
log<level::ERR>("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