Skip to content

Commit

Permalink
[chip-tool] Support complex types (as command argument or as writeabl…
Browse files Browse the repository at this point in the history
…e attribute) (#13761)

* [chip-tool] Add complex types supports

* Update generated code
  • Loading branch information
vivien-apple authored Jan 26, 2022
1 parent 9cacae1 commit 750f976
Show file tree
Hide file tree
Showing 12 changed files with 2,910 additions and 73 deletions.
1 change: 1 addition & 0 deletions examples/chip-tool/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ static_library("chip-tool-utils") {
sources = [
"${chip_root}/src/app/tests/suites/include/ConstraintsChecker.h",
"${chip_root}/src/app/tests/suites/include/ValueChecker.h",
"${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp",
"${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp",
"commands/clusters/ModelCommand.cpp",
"commands/common/CHIPCommand.cpp",
Expand Down
295 changes: 295 additions & 0 deletions examples/chip-tool/commands/clusters/ComplexArgument.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
/*
* 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/SafeInt.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() || !chip::CanCastTo<T>(value.asLargestUInt()))
{
ChipLogError(chipTool, "Error while encoding %s as an unsigned integer.", label);
return CHIP_ERROR_INVALID_ARGUMENT;
}

request = static_cast<T>(value.asUInt());
return CHIP_NO_ERROR;
}

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.asInt());
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::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)));
strncpy(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, bool hasMember)
{
if (hasMember)
{
return CHIP_NO_ERROR;
}

ChipLogError(chipTool, "%s is required.", label);
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(T * request) : mRequest(request) {}
~TypedComplexArgument() { ComplexArgumentParser::Finalize(*mRequest); }

CHIP_ERROR Parse(const char * label, const char * json)
{
Json::Value value;
Json::Reader reader;
reader.parse(json, value);

return ComplexArgumentParser::Setup(label, *mRequest, value);
}

private:
T * mRequest;
};
16 changes: 16 additions & 0 deletions examples/chip-tool/commands/common/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ bool Command::InitArgument(size_t argIndex, char * argValue)
Argument arg = mArgs.at(argIndex);
switch (arg.type)
{
case ArgumentType::Complex: {
auto complexArgument = static_cast<ComplexArgument *>(arg.value);
return CHIP_NO_ERROR == complexArgument->Parse(arg.name, argValue);
}

case ArgumentType::Attribute: {
if (arg.isOptional() || arg.isNullable())
{
Expand Down Expand Up @@ -479,6 +484,17 @@ size_t Command::AddArgument(const char * name, AddressWithInterface * out, uint8
return AddArgumentToList(std::move(arg));
}

size_t Command::AddArgument(const char * name, ComplexArgument * value)
{
Argument arg;
arg.type = ArgumentType::Complex;
arg.name = name;
arg.value = static_cast<void *>(value);
arg.flags = 0;

return AddArgumentToList(std::move(arg));
}

size_t Command::AddArgument(const char * name, float min, float max, float * out, uint8_t flags)
{
Argument arg;
Expand Down
5 changes: 4 additions & 1 deletion examples/chip-tool/commands/common/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#pragma once

#include <app/data-model/Nullable.h>
#include <commands/clusters/ComplexArgument.h>
#include <controller/CHIPDeviceController.h>
#include <inet/InetInterface.h>
#include <lib/core/Optional.h>
Expand Down Expand Up @@ -65,7 +66,8 @@ enum ArgumentType
CharString,
OctetString,
Attribute,
Address
Address,
Complex
};

struct Argument
Expand Down Expand Up @@ -124,6 +126,7 @@ class Command
size_t AddArgument(const char * name, chip::ByteSpan * value, uint8_t flags = 0);
size_t AddArgument(const char * name, chip::Span<const char> * value, uint8_t flags = 0);
size_t AddArgument(const char * name, AddressWithInterface * out, uint8_t flags = 0);
size_t AddArgument(const char * name, ComplexArgument * value);
size_t AddArgument(const char * name, int64_t min, uint64_t max, bool * out, uint8_t flags = 0)
{
return AddArgument(name, min, max, reinterpret_cast<void *>(out), Boolean, flags);
Expand Down
39 changes: 39 additions & 0 deletions examples/chip-tool/templates/ComplexArgumentParser-src.zapt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{{> header}}

#include <commands/clusters/ComplexArgument.h>

{{#structs_with_cluster_name}}
CHIP_ERROR ComplexArgumentParser::Setup(const char * label, {{zapTypeToEncodableClusterObjectType name ns=clusterName}} & request, Json::Value & value)
{
VerifyOrReturnError(value.isObject(), CHIP_ERROR_INVALID_ARGUMENT);

{{#zcl_struct_items}}
{{#unless isOptional}}
ReturnErrorOnFailure(ComplexArgumentParser::EnsureMemberExist("{{parent.name}}.{{asLowerCamelCase label}}", value.isMember("{{asLowerCamelCase label}}")));
{{/unless}}
{{/zcl_struct_items}}

char labelWithMember[kMaxLabelLength];
{{#zcl_struct_items}}
{{#if isOptional}}
if (value.isMember("{{asLowerCamelCase label}}"))
{
{{/if}}
snprintf(labelWithMember, sizeof(labelWithMember), "%s.%s", label, "{{asLowerCamelCase label}}");
ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.{{asLowerCamelCase label}}, value["{{asLowerCamelCase label}}"]));
{{#if isOptional}}
}
{{/if}}

{{/zcl_struct_items}}
return CHIP_NO_ERROR;
}

void ComplexArgumentParser::Finalize({{zapTypeToEncodableClusterObjectType name ns=clusterName}} & request)
{
{{#zcl_struct_items}}
ComplexArgumentParser::Finalize(request.{{asLowerCamelCase label}});
{{/zcl_struct_items}}
}
{{/structs_with_cluster_name}}

Loading

0 comments on commit 750f976

Please sign in to comment.