| /** |
| * 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, |
| ¤tOffset); |
| 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; |
| } |