blob: 7380b6c7525c6b518faed7436721299766c0a8f7 [file]
/**
* Copyright 2023 Google Inc.
*
* 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 "rde_update_utils.hpp"
#include "libbej/bej_common.h"
#include "libbej/bej_dictionary.h"
#include "libbej/bej_encoder_json.hpp"
#include "nlohmann/json.hpp"
#include <helper/common.hpp>
#include <cstring>
#include <iostream>
#include <sstream>
#include <unordered_map>
#include <variant>
using json = nlohmann::json;
using BejChildNodesVariant =
std::variant<struct RedfishPropertyParent, struct RedfishPropertyLeafNull,
struct RedfishPropertyLeafInt, struct RedfishPropertyLeafEnum,
struct RedfishPropertyLeafString,
struct RedfishPropertyLeafReal,
struct RedfishPropertyLeafBool>;
static int addLeafToBejTree(
const struct BejDictionaryProperty* property,
const std::string& propertyName, json& propertyValue,
struct RedfishPropertyParent* root,
std::unordered_map<std::string, BejChildNodesVariant>& childNodesStorage)
{
if (propertyValue.is_null())
{
struct RedfishPropertyLeafNull child{};
childNodesStorage[propertyName] = child;
bejTreeAddNull(root,
&std::get<struct RedfishPropertyLeafNull>(
childNodesStorage[propertyName]),
propertyName.c_str());
return 0;
}
switch (property->format.principalDataType)
{
case bejNull:
{
struct RedfishPropertyLeafNull child{};
childNodesStorage[propertyName] = child;
bejTreeAddNull(root,
&std::get<struct RedfishPropertyLeafNull>(
childNodesStorage[propertyName]),
propertyName.c_str());
break;
}
case bejInteger:
{
try
{
int64_t data = propertyValue.get<int64_t>();
if (DEBUG)
{
std::cerr << "Converted integer value: " << data
<< std::endl;
}
struct RedfishPropertyLeafInt child{};
childNodesStorage[propertyName] = child;
bejTreeAddInteger(root,
&std::get<struct RedfishPropertyLeafInt>(
childNodesStorage[propertyName]),
propertyName.c_str(), data);
}
catch (const json::exception& e)
{
std::cerr << "Integer Conversion error: " << e.what()
<< std::endl;
return -1;
}
break;
}
case bejEnum:
{
try
{
// Libbej APIs accept char* pointer for strings. So we need to
// grab the json string value by reference as a local string
// variable can get deallocated.
std::string& data = propertyValue.get_ref<std::string&>();
std::cerr << "Converted enum value: " << data << std::endl;
struct RedfishPropertyLeafEnum child{};
childNodesStorage[propertyName] = child;
bejTreeAddEnum(root,
&std::get<struct RedfishPropertyLeafEnum>(
childNodesStorage[propertyName]),
propertyName.c_str(), data.c_str());
}
catch (const json::exception& e)
{
std::cerr << "Enum Conversion error: " << e.what() << std::endl;
return -1;
}
break;
}
case bejString:
{
try
{
// Libbej APIs accept char* pointer for strings. So we need to
// grab the json string value by reference as a local string
// variable can get deallocated
std::string& data = propertyValue.get_ref<std::string&>();
std::cerr << "Converted string value: " << data << std::endl;
struct RedfishPropertyLeafString child{};
childNodesStorage[propertyName] = child;
bejTreeAddString(root,
&std::get<struct RedfishPropertyLeafString>(
childNodesStorage[propertyName]),
propertyName.c_str(), data.c_str());
}
catch (const json::exception& e)
{
std::cerr << "String Conversion error: " << e.what()
<< std::endl;
return -1;
}
break;
}
case bejReal:
{
try
{
double data = propertyValue.get<double>();
if (DEBUG)
{
std::cerr << "Converted real/double value: " << data
<< std::endl;
}
struct RedfishPropertyLeafReal child{};
childNodesStorage[propertyName] = child;
bejTreeAddReal(root,
&std::get<struct RedfishPropertyLeafReal>(
childNodesStorage[propertyName]),
propertyName.c_str(), data);
}
catch (const json::exception& e)
{
std::cerr << "Real/Double Conversion error: " << e.what()
<< std::endl;
return -1;
}
break;
}
case bejBoolean:
{
try
{
bool data = propertyValue.get<bool>();
std::cerr << "Converted boolean value: " << data << std::endl;
struct RedfishPropertyLeafBool child{};
childNodesStorage[propertyName] = child;
bejTreeAddBool(root,
&std::get<struct RedfishPropertyLeafBool>(
childNodesStorage[propertyName]),
propertyName.c_str(), data);
}
catch (const json::exception& e)
{
std::cerr << "Boolean Conversion error: " << e.what()
<< std::endl;
return -1;
}
break;
}
default:
std::cerr << "Unknown Bej Data type " << std::endl;
return -1;
}
return 0;
}
static int addBranchToBejTree(
const uint8_t* schemaDict, uint16_t startingPropertyOffset,
const std::string& propertyName, json& propertyValue,
struct RedfishPropertyParent* root,
std::unordered_map<std::string, BejChildNodesVariant>& childNodesStorage)
{
uint16_t currentOffset;
const struct BejDictionaryProperty* property = nullptr;
// Get the property details from the schema dictionary to identify the
// property type
int rc = bejDictGetPropertyByName(schemaDict, startingPropertyOffset,
propertyName.c_str(), &property,
&currentOffset);
if (rc != 0)
{
std::cerr << "Failed to find dictionary sequence for property "
<< propertyName << " for the resource, rc : " << rc
<< std::endl;
return rc;
}
uint16_t childPointerOffset = property->childPointerOffset;
if (property->format.principalDataType == bejPropertyAnnotation)
{
// As per Redfish specifiction, patching is not allowed on the Odata
// annotation properties. So return error in this case.
std::cerr
<< "It is an annotation property. Patching is not allowed on annotated properties"
<< std::endl;
return -1;
}
else if (property->format.principalDataType == bejSet)
{
// If it is a set then link it with the parent and continue encoding the
// rest of the BEJ tree
struct RedfishPropertyParent child{};
childNodesStorage[propertyName] = child;
bejTreeInitSet(&std::get<struct RedfishPropertyParent>(
childNodesStorage[propertyName]),
propertyName.c_str());
// Since it is a set, link the child to the parent and continue
// recursively fetching the leaf property
bejTreeLinkChildToParent(root, &std::get<struct RedfishPropertyParent>(
childNodesStorage[propertyName]));
// Iterate through the JSON object.
// There could be multiple properties that are being patched at a time
// and they can be present on different branches of the BEJ tree. So
// construct the BEJ tree branches at each level
for (const auto& it : propertyValue.items())
{
const std::string& childMemberPropertyName = it.key();
if (DEBUG)
{
std::cerr << "Property name: " << childMemberPropertyName
<< std::endl;
}
json& childMemberPropertyValue = it.value();
rc = addBranchToBejTree(schemaDict, childPointerOffset,
childMemberPropertyName,
childMemberPropertyValue,
&std::get<struct RedfishPropertyParent>(
childNodesStorage[propertyName]),
childNodesStorage);
if (rc != 0)
{
return rc;
}
}
}
else
{
// If it is a leaf property then this is a property that has to be
// updated/patched. The recursion ends when we find a leaf property
rc = addLeafToBejTree(property, propertyName, propertyValue, root,
childNodesStorage);
if (rc != 0)
{
return rc;
}
}
return 0;
}
int encodePayloadForUpdateOp(const std::string& redfishReqPayload,
uint8_t** schemaDict, uint8_t** annotationDict,
std::vector<uint8_t>& encodedPayload)
{
if (redfishReqPayload.empty())
{
return -1;
}
if ((schemaDict == nullptr) || (*schemaDict == nullptr))
{
std::cerr << "Schema dictionary cannot be NULL" << std::endl;
return -1;
}
if ((annotationDict == nullptr) || (*annotationDict == nullptr))
{
std::cerr << "Annotation dictionary cannot be NULL" << std::endl;
return -1;
}
BejDictionaries bejDictionaries;
bejDictionaries.schemaDictionary = *schemaDict;
bejDictionaries.annotationDictionary = *annotationDict;
bejDictionaries.errorDictionary = nullptr;
int rc;
struct RedfishPropertyParent parent;
bejTreeInitSet(&parent, nullptr);
json redfishReqJsonPayload;
// Get the Patch Data in JSON format
try
{
redfishReqJsonPayload = json::parse(redfishReqPayload);
}
catch (const json::parse_error& e)
{
std::cerr << "JSON parsing error: " << e.what()
<< " while parsing sensor collection response "
<< redfishReqPayload << std::endl;
return -1;
}
uint16_t rootOffset = bejDictGetPropertyHeadOffset();
// Create a storage for the bej child nodes so that their memory is not
// deallocated by the time we use the libbej encoder to encode the tree in
// to a binary.
std::unordered_map<std::string, BejChildNodesVariant> childNodesStorage;
// Iterate through the JSON object
// There could be multiple properties that are being patched at a time
// and they can be present on different branches of the BEJ tree. So
// construct the BEJ tree branches at each level
for (const auto& it : redfishReqJsonPayload.items())
{
const std::string& memberPropertyName = it.key();
if (DEBUG)
{
std::cerr << "Property name: " << memberPropertyName << std::endl;
}
json& memberPropertyValue = it.value();
// Create a BEJ tree for encoding
rc = addBranchToBejTree(bejDictionaries.schemaDictionary, rootOffset,
memberPropertyName, memberPropertyValue,
&parent, childNodesStorage);
if (rc != 0)
{
return rc;
}
}
libbej::BejEncoderJson encoder;
rc = encoder.encode(&bejDictionaries, bejMajorSchemaClass, &parent);
if (rc != 0)
{
std::cerr << "Encoding failure with rc: " << rc << std::endl;
return rc;
}
encodedPayload = encoder.getOutput();
return 0;
}
int encodePayloadForActionOp(const std::string& redfishReqPayload,
const std::string& action, uint16_t offset,
uint8_t** schemaDict, uint8_t** annotationDict,
std::vector<uint8_t>& encodedPayload)
{
if (redfishReqPayload.empty())
{
return 0;
}
if ((schemaDict == nullptr) || (*schemaDict == nullptr))
{
std::cerr << "Schema dictionary cannot be NULL" << std::endl;
return -1;
}
if ((annotationDict == nullptr) || (*annotationDict == nullptr))
{
std::cerr << "Annotation dictionary cannot be NULL" << std::endl;
return -1;
}
BejDictionaries bejDictionaries;
bejDictionaries.schemaDictionary = *schemaDict;
bejDictionaries.annotationDictionary = *annotationDict;
bejDictionaries.errorDictionary = nullptr;
int rc;
// Extract the action name (last component of action path, prepended with
// '#')
std::string actionName = action;
size_t lastSlash = actionName.rfind('/');
if (lastSlash != std::string::npos)
{
actionName = actionName.substr(lastSlash + 1);
}
if (actionName != "Oem" && actionName != "Actions")
{
if (actionName.empty() || actionName[0] != '#')
{
actionName = "#" + actionName;
}
}
struct RedfishPropertyParent parent;
bejTreeInitSet(&parent, actionName.c_str());
json redfishReqJsonPayload;
try
{
redfishReqJsonPayload = json::parse(redfishReqPayload);
}
catch (const json::parse_error& e)
{
std::cerr << "JSON parsing error: " << e.what()
<< " while parsing Action request payload "
<< redfishReqPayload << std::endl;
return -1;
}
// Get the child pointer offset of the Action property using the passed
// offset
const struct BejDictionaryProperty* actionProperty =
reinterpret_cast<const struct BejDictionaryProperty*>(*schemaDict +
offset);
uint16_t startingPropertyOffset = actionProperty->childPointerOffset;
std::unordered_map<std::string, BejChildNodesVariant> childNodesStorage;
// Iterate through the JSON object and build the BEJ tree
for (const auto& it : redfishReqJsonPayload.items())
{
const std::string& memberPropertyName = it.key();
if (DEBUG)
{
std::cerr << "Property name: " << memberPropertyName << std::endl;
}
json& memberPropertyValue = it.value();
rc = addBranchToBejTree(bejDictionaries.schemaDictionary,
startingPropertyOffset, memberPropertyName,
memberPropertyValue, &parent,
childNodesStorage);
if (rc != 0)
{
return rc;
}
}
libbej::BejEncoderJson encoder;
rc = encoder.encode(&bejDictionaries, bejMajorSchemaClass, &parent, offset);
if (rc != 0)
{
std::cerr << "Encoding failure with rc: " << rc << std::endl;
return rc;
}
encodedPayload = encoder.getOutput();
return 0;
}