Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restyle [chip-tool] Support complex types (as command argument or as writeable attribute) #14312

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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