blob: 3769c0052236cd726e42f45b82904d6c954e9222 [file] [log] [blame]
/*
* 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;
};