| /* |
| * Copyright (c) 2024 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 <commands/common/HexConversion.h> |
| #include <lib/support/BytesToHex.h> |
| #include <lib/support/CHIPMemString.h> |
| #include <lib/support/SafeInt.h> |
| |
| #include <string> |
| |
| #include "JsonParser.h" |
| |
| namespace { |
| static constexpr char kPayloadHexPrefix[] = "hex:"; |
| static constexpr char kPayloadSignedPrefix[] = "s:"; |
| static constexpr char kPayloadUnsignedPrefix[] = "u:"; |
| static constexpr char kPayloadFloatPrefix[] = "f:"; |
| static constexpr char kPayloadDoublePrefix[] = "d:"; |
| static constexpr size_t kPayloadHexPrefixLen = ArraySize(kPayloadHexPrefix) - 1; // ignore null character |
| static constexpr size_t kPayloadSignedPrefixLen = ArraySize(kPayloadSignedPrefix) - 1; // ignore null character |
| static constexpr size_t kPayloadUnsignedPrefixLen = ArraySize(kPayloadUnsignedPrefix) - 1; // ignore null character |
| static constexpr size_t kPayloadFloatPrefixLen = ArraySize(kPayloadFloatPrefix) - 1; // ignore null character |
| static constexpr size_t kPayloadDoublePrefixLen = ArraySize(kPayloadDoublePrefix) - 1; // ignore null character |
| } // namespace |
| |
| class CustomArgumentParser |
| { |
| public: |
| static CHIP_ERROR Put(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| if (value.isObject()) |
| { |
| return CustomArgumentParser::PutObject(writer, tag, value); |
| } |
| |
| if (value.isArray()) |
| { |
| return CustomArgumentParser::PutArray(writer, tag, value); |
| } |
| |
| if (value.isString()) |
| { |
| if (IsOctetString(value)) |
| { |
| return CustomArgumentParser::PutOctetString(writer, tag, value); |
| } |
| if (IsUnsignedNumberPrefix(value)) |
| { |
| return CustomArgumentParser::PutUnsignedFromString(writer, tag, value); |
| } |
| if (IsSignedNumberPrefix(value)) |
| { |
| return CustomArgumentParser::PutSignedFromString(writer, tag, value); |
| } |
| if (IsFloatNumberPrefix(value)) |
| { |
| return CustomArgumentParser::PutFloatFromString(writer, tag, value); |
| } |
| if (IsDoubleNumberPrefix(value)) |
| { |
| return CustomArgumentParser::PutDoubleFromString(writer, tag, value); |
| } |
| |
| return CustomArgumentParser::PutCharString(writer, tag, value); |
| } |
| |
| if (value.isNull()) |
| { |
| return chip::app::DataModel::Encode(*writer, tag, chip::app::DataModel::Nullable<uint8_t>()); |
| } |
| |
| if (value.isBool()) |
| { |
| return chip::app::DataModel::Encode(*writer, tag, value.asBool()); |
| } |
| |
| if (value.isUInt()) |
| { |
| return chip::app::DataModel::Encode(*writer, tag, value.asLargestUInt()); |
| } |
| |
| if (value.isInt()) |
| { |
| return chip::app::DataModel::Encode(*writer, tag, value.asLargestInt()); |
| } |
| |
| if (value.isNumeric()) |
| { |
| return chip::app::DataModel::Encode(*writer, tag, value.asDouble()); |
| } |
| |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| private: |
| static CHIP_ERROR PutArray(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| chip::TLV::TLVType outer; |
| ReturnErrorOnFailure(writer->StartContainer(tag, chip::TLV::kTLVType_Array, outer)); |
| |
| Json::ArrayIndex size = value.size(); |
| |
| for (Json::ArrayIndex i = 0; i < size; i++) |
| { |
| ReturnErrorOnFailure(CustomArgumentParser::Put(writer, chip::TLV::AnonymousTag(), value[i])); |
| } |
| |
| return writer->EndContainer(outer); |
| } |
| |
| static CHIP_ERROR PutObject(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| chip::TLV::TLVType outer; |
| ReturnErrorOnFailure(writer->StartContainer(tag, chip::TLV::kTLVType_Structure, outer)); |
| |
| for (auto const & id : value.getMemberNames()) |
| { |
| auto index = std::stoul(id, nullptr, 0); |
| VerifyOrReturnError(chip::CanCastTo<uint8_t>(index), CHIP_ERROR_INVALID_ARGUMENT); |
| ReturnErrorOnFailure(CustomArgumentParser::Put(writer, chip::TLV::ContextTag(static_cast<uint8_t>(index)), value[id])); |
| } |
| |
| return writer->EndContainer(outer); |
| } |
| |
| static CHIP_ERROR PutOctetString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| const char * hexData = value.asCString() + kPayloadHexPrefixLen; |
| size_t hexDataLen = strlen(hexData); |
| chip::Platform::ScopedMemoryBuffer<uint8_t> buffer; |
| |
| size_t octetCount; |
| ReturnErrorOnFailure(HexToBytes( |
| chip::CharSpan(hexData, hexDataLen), |
| [&buffer](size_t allocSize) { |
| buffer.Calloc(allocSize); |
| return buffer.Get(); |
| }, |
| &octetCount)); |
| |
| return chip::app::DataModel::Encode(*writer, tag, chip::ByteSpan(buffer.Get(), octetCount)); |
| } |
| |
| static CHIP_ERROR PutCharString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| size_t size = strlen(value.asCString()); |
| return chip::app::DataModel::Encode(*writer, tag, chip::CharSpan(value.asCString(), size)); |
| } |
| |
| static CHIP_ERROR PutUnsignedFromString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| char numberAsString[21]; |
| chip::Platform::CopyString(numberAsString, value.asCString() + kPayloadUnsignedPrefixLen); |
| |
| auto number = std::stoull(numberAsString, nullptr, 0); |
| return chip::app::DataModel::Encode(*writer, tag, static_cast<uint64_t>(number)); |
| } |
| |
| static CHIP_ERROR PutSignedFromString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| char numberAsString[21]; |
| chip::Platform::CopyString(numberAsString, value.asCString() + kPayloadSignedPrefixLen); |
| |
| auto number = std::stoll(numberAsString, nullptr, 0); |
| return chip::app::DataModel::Encode(*writer, tag, static_cast<int64_t>(number)); |
| } |
| |
| static CHIP_ERROR PutFloatFromString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| char numberAsString[21]; |
| chip::Platform::CopyString(numberAsString, value.asCString() + kPayloadFloatPrefixLen); |
| |
| auto number = std::stof(numberAsString); |
| return chip::app::DataModel::Encode(*writer, tag, number); |
| } |
| |
| static CHIP_ERROR PutDoubleFromString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) |
| { |
| char numberAsString[21]; |
| chip::Platform::CopyString(numberAsString, value.asCString() + kPayloadDoublePrefixLen); |
| |
| auto number = std::stod(numberAsString); |
| return chip::app::DataModel::Encode(*writer, tag, number); |
| } |
| |
| static bool IsOctetString(Json::Value & value) |
| { |
| return (strncmp(value.asCString(), kPayloadHexPrefix, kPayloadHexPrefixLen) == 0); |
| } |
| |
| static bool IsUnsignedNumberPrefix(Json::Value & value) |
| { |
| return (strncmp(value.asCString(), kPayloadUnsignedPrefix, kPayloadUnsignedPrefixLen) == 0); |
| } |
| |
| static bool IsSignedNumberPrefix(Json::Value & value) |
| { |
| return (strncmp(value.asCString(), kPayloadSignedPrefix, kPayloadSignedPrefixLen) == 0); |
| } |
| |
| static bool IsFloatNumberPrefix(Json::Value & value) |
| { |
| return (strncmp(value.asCString(), kPayloadFloatPrefix, kPayloadFloatPrefixLen) == 0); |
| } |
| |
| static bool IsDoubleNumberPrefix(Json::Value & value) |
| { |
| return (strncmp(value.asCString(), kPayloadDoublePrefix, kPayloadDoublePrefixLen) == 0); |
| } |
| }; |
| |
| class CustomArgument |
| { |
| public: |
| ~CustomArgument() |
| { |
| if (mData != nullptr) |
| { |
| chip::Platform::MemoryFree(mData); |
| } |
| } |
| |
| CHIP_ERROR Parse(const char * label, const char * json) |
| { |
| Json::Value value; |
| static constexpr char kHexNumPrefix[] = "0x"; |
| constexpr size_t kHexNumPrefixLen = ArraySize(kHexNumPrefix) - 1; |
| if (strncmp(json, kPayloadHexPrefix, kPayloadHexPrefixLen) == 0 || |
| strncmp(json, kPayloadSignedPrefix, kPayloadSignedPrefixLen) == 0 || |
| strncmp(json, kPayloadUnsignedPrefix, kPayloadUnsignedPrefixLen) == 0 || |
| strncmp(json, kPayloadFloatPrefix, kPayloadFloatPrefixLen) == 0 || |
| strncmp(json, kPayloadDoublePrefix, kPayloadDoublePrefixLen) == 0) |
| { |
| value = Json::Value(json); |
| } |
| else if (strncmp(json, kHexNumPrefix, kHexNumPrefixLen) == 0) |
| { |
| // Assume that hex numbers are unsigned. Prepend |
| // kPayloadUnsignedPrefix and then let the rest of the logic handle |
| // things. |
| std::string str(kPayloadUnsignedPrefix); |
| str += json; |
| value = Json::Value(str); |
| } |
| else if (!JsonParser::ParseCustomArgument(label, json, value)) |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| mData = static_cast<uint8_t *>(chip::Platform::MemoryCalloc(sizeof(uint8_t), mDataMaxLen)); |
| VerifyOrReturnError(mData != nullptr, CHIP_ERROR_NO_MEMORY); |
| |
| chip::TLV::TLVWriter writer; |
| writer.Init(mData, mDataMaxLen); |
| |
| ReturnErrorOnFailure(CustomArgumentParser::Put(&writer, chip::TLV::AnonymousTag(), value)); |
| |
| mDataLen = writer.GetLengthWritten(); |
| return writer.Finalize(); |
| } |
| |
| CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const |
| { |
| chip::TLV::TLVReader reader; |
| reader.Init(mData, mDataLen); |
| ReturnErrorOnFailure(reader.Next()); |
| |
| return writer.CopyElement(tag, reader); |
| } |
| |
| // We trust our consumers to do the encoding of our data correctly, so don't |
| // need to know whether we are being encoded for a write. |
| static constexpr bool kIsFabricScoped = false; |
| |
| private: |
| uint8_t * mData = nullptr; |
| uint32_t mDataLen = 0; |
| static constexpr uint32_t mDataMaxLen = 4096; |
| }; |