|  | /* | 
|  | *   Copyright (c) 2022 Project CHIP Authors | 
|  | *   All rights reserved. | 
|  | * | 
|  | *   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. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #pragma once | 
|  |  | 
|  | #include <app-common/zap-generated/cluster-objects.h> | 
|  | #include <app/data-model/List.h> | 
|  | #include <app/data-model/Nullable.h> | 
|  | #include <json/json.h> | 
|  | #include <lib/core/Optional.h> | 
|  | #include <lib/support/BytesToHex.h> | 
|  | #include <lib/support/CHIPMemString.h> | 
|  | #include <lib/support/SafeInt.h> | 
|  |  | 
|  | #include "JsonParser.h" | 
|  |  | 
|  | constexpr uint8_t kMaxLabelLength = 100; | 
|  |  | 
|  | class ComplexArgumentParser | 
|  | { | 
|  | public: | 
|  | ComplexArgumentParser() {} | 
|  |  | 
|  | template <typename T, | 
|  | typename std::enable_if_t<std::is_integral<T>::value && !std::is_signed<T>::value && | 
|  | !std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, bool>::value, | 
|  | int> = 0> | 
|  | static CHIP_ERROR Setup(const char * label, T & request, Json::Value value) | 
|  | { | 
|  | if (value.isNumeric()) | 
|  | { | 
|  | if (chip::CanCastTo<T>(value.asLargestUInt())) | 
|  | { | 
|  | request = static_cast<T>(value.asLargestUInt()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  | } | 
|  | else if (value.isString()) | 
|  | { | 
|  | // Check for a hex number; JSON does not support those as numbers, | 
|  | // so they have to be done as strings.  And we might as well support | 
|  | // string-encoded unsigned numbers in general if we're doing that. | 
|  | bool isHexNotation = strncmp(value.asCString(), "0x", 2) == 0 || strncmp(value.asCString(), "0X", 2) == 0; | 
|  |  | 
|  | std::stringstream str; | 
|  | isHexNotation ? str << std::hex << value.asCString() : str << value.asCString(); | 
|  | uint64_t val; | 
|  | str >> val; | 
|  | if (!str.fail() && str.eof() && chip::CanCastTo<T>(val)) | 
|  | { | 
|  | request = static_cast<T>(val); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | ChipLogError(chipTool, "Error while encoding %s as an unsigned integer.", label); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | template <typename T, std::enable_if_t<std::is_signed<T>::value, bool> = true> | 
|  | static CHIP_ERROR Setup(const char * label, T & request, Json::Value value) | 
|  | { | 
|  | if (!value.isNumeric() || !chip::CanCastTo<T>(value.asLargestInt())) | 
|  | { | 
|  | ChipLogError(chipTool, "Error while encoding %s as an unsigned integer.", label); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | request = static_cast<T>(value.asLargestInt()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | template <typename T, typename std::enable_if_t<std::is_enum<T>::value, int> = 0> | 
|  | static CHIP_ERROR Setup(const char * label, T & request, Json::Value value) | 
|  | { | 
|  | std::underlying_type_t<T> requestValue; | 
|  | ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); | 
|  |  | 
|  | request = static_cast<T>(requestValue); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static CHIP_ERROR Setup(const char * label, chip::BitFlags<T> & request, Json::Value & value) | 
|  | { | 
|  | T requestValue; | 
|  | ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); | 
|  |  | 
|  | request = chip::BitFlags<T>(requestValue); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static CHIP_ERROR Setup(const char * label, chip::BitMask<T> & request, Json::Value & value) | 
|  | { | 
|  | T requestValue; | 
|  | ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); | 
|  |  | 
|  | request = chip::BitMask<T>(requestValue); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static CHIP_ERROR Setup(const char * label, chip::Optional<T> & request, Json::Value & value) | 
|  | { | 
|  | T requestValue; | 
|  | ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); | 
|  |  | 
|  | request = chip::Optional<T>(requestValue); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static CHIP_ERROR Setup(const char * label, chip::app::DataModel::Nullable<T> & request, Json::Value & value) | 
|  | { | 
|  | if (value.isNull()) | 
|  | { | 
|  | request.SetNull(); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | T requestValue; | 
|  | ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); | 
|  |  | 
|  | request = chip::app::DataModel::Nullable<T>(requestValue); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static CHIP_ERROR Setup(const char * label, chip::app::DataModel::List<T> & request, Json::Value & value) | 
|  | { | 
|  | if (!value.isArray()) | 
|  | { | 
|  | ChipLogError(chipTool, "Error while encoding %s as an array.", label); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | auto content = static_cast<typename std::remove_const<T>::type *>(chip::Platform::MemoryCalloc(value.size(), sizeof(T))); | 
|  |  | 
|  | Json::ArrayIndex size = value.size(); | 
|  | for (Json::ArrayIndex i = 0; i < size; i++) | 
|  | { | 
|  | char labelWithIndex[kMaxLabelLength]; | 
|  | snprintf(labelWithIndex, sizeof(labelWithIndex), "%s[%d]", label, i); | 
|  | ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithIndex, content[i], value[i])); | 
|  | } | 
|  |  | 
|  | request = chip::app::DataModel::List<T>(content, value.size()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR Setup(const char * label, chip::ByteSpan & request, Json::Value & value) | 
|  | { | 
|  | if (!value.isString()) | 
|  | { | 
|  | ChipLogError(chipTool, "Error while encoding %s as an octet string: Not a string.", label); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | if (strlen(value.asCString()) % 2 != 0) | 
|  | { | 
|  | ChipLogError(chipTool, "Error while encoding %s as an octet string: Odd number of characters.", label); | 
|  | return CHIP_ERROR_INVALID_STRING_LENGTH; | 
|  | } | 
|  |  | 
|  | size_t size       = strlen(value.asCString()); | 
|  | auto buffer       = static_cast<uint8_t *>(chip::Platform::MemoryCalloc(size / 2, sizeof(uint8_t))); | 
|  | size_t octetCount = chip::Encoding::HexToBytes(value.asCString(), size, buffer, size / 2); | 
|  |  | 
|  | request = chip::ByteSpan(buffer, octetCount); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR Setup(const char * label, chip::CharSpan & request, Json::Value & value) | 
|  | { | 
|  | if (!value.isString()) | 
|  | { | 
|  | ChipLogError(chipTool, "Error while encoding %s as a string: Not a string.", label); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | size_t size = strlen(value.asCString()); | 
|  | auto buffer = static_cast<char *>(chip::Platform::MemoryCalloc(size, sizeof(char))); | 
|  | memcpy(buffer, value.asCString(), size); | 
|  |  | 
|  | request = chip::CharSpan(buffer, size); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR Setup(const char * label, float & request, Json::Value & value) | 
|  | { | 
|  | if (!value.isNumeric()) | 
|  | { | 
|  | ChipLogError(chipTool, "Error while encoding %s as a float: Not a number.", label); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | request = static_cast<float>(value.asFloat()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR Setup(const char * label, double & request, Json::Value & value) | 
|  | { | 
|  | if (!value.isNumeric()) | 
|  | { | 
|  | ChipLogError(chipTool, "Error while encoding %s as a double: Not a number.", label); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | request = static_cast<double>(value.asDouble()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR Setup(const char * label, bool & request, Json::Value & value) | 
|  | { | 
|  | if (!value.isBool()) | 
|  | { | 
|  | ChipLogError(chipTool, "Error while encoding %s as a boolean: Not a boolean.", label); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | request = value.asBool(); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | static CHIP_ERROR EnsureMemberExist(const char * label, const char * memberName, bool hasMember) | 
|  | { | 
|  | if (hasMember) | 
|  | { | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | ChipLogError(chipTool, "%s is required.  Should be provided as {\"%s\": value}", label, memberName); | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static void Finalize(T & request) | 
|  | { | 
|  | // Nothing to do | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static void Finalize(chip::Optional<T> & request) | 
|  | { | 
|  | VerifyOrReturn(request.HasValue()); | 
|  | ComplexArgumentParser::Finalize(request.Value()); | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static void Finalize(chip::app::DataModel::Nullable<T> & request) | 
|  | { | 
|  | VerifyOrReturn(!request.IsNull()); | 
|  | ComplexArgumentParser::Finalize(request.Value()); | 
|  | } | 
|  |  | 
|  | static void Finalize(chip::ByteSpan & request) | 
|  | { | 
|  | VerifyOrReturn(request.data() != nullptr); | 
|  | chip::Platform::MemoryFree(reinterpret_cast<void *>(const_cast<uint8_t *>(request.data()))); | 
|  | } | 
|  |  | 
|  | static void Finalize(chip::CharSpan & request) | 
|  | { | 
|  | VerifyOrReturn(request.data() != nullptr); | 
|  | chip::Platform::MemoryFree(reinterpret_cast<void *>(const_cast<char *>(request.data()))); | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | static void Finalize(chip::app::DataModel::List<T> & request) | 
|  | { | 
|  | VerifyOrReturn(request.data() != nullptr); | 
|  |  | 
|  | size_t size = request.size(); | 
|  | auto data   = const_cast<typename std::remove_const<T>::type *>(request.data()); | 
|  | for (size_t i = 0; i < size; i++) | 
|  | { | 
|  | Finalize(data[i]); | 
|  | } | 
|  |  | 
|  | chip::Platform::MemoryFree(reinterpret_cast<void *>(data)); | 
|  | } | 
|  |  | 
|  | #include <zap-generated/cluster/ComplexArgumentParser.h> | 
|  | }; | 
|  |  | 
|  | class ComplexArgument | 
|  | { | 
|  | public: | 
|  | virtual ~ComplexArgument() {} | 
|  |  | 
|  | virtual CHIP_ERROR Parse(const char * label, const char * json) = 0; | 
|  | }; | 
|  |  | 
|  | template <typename T> | 
|  | class TypedComplexArgument : public ComplexArgument | 
|  | { | 
|  | public: | 
|  | TypedComplexArgument() {} | 
|  | TypedComplexArgument(T * request) : mRequest(request) {} | 
|  | ~TypedComplexArgument() | 
|  | { | 
|  | if (mRequest != nullptr) | 
|  | { | 
|  | ComplexArgumentParser::Finalize(*mRequest); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SetArgument(T * request) { mRequest = request; }; | 
|  |  | 
|  | CHIP_ERROR Parse(const char * label, const char * json) | 
|  | { | 
|  | Json::Value value; | 
|  | if (!JsonParser::ParseComplexArgument(label, json, value)) | 
|  | { | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | return ComplexArgumentParser::Setup(label, *mRequest, value); | 
|  | } | 
|  |  | 
|  | private: | 
|  | T * mRequest; | 
|  | }; |