-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[chip-tool] Support complex types (as command argument or as writeabl…
…e attribute) (#13761) * [chip-tool] Add complex types supports * Update generated code
- Loading branch information
1 parent
9cacae1
commit 750f976
Showing
12 changed files
with
2,910 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
examples/chip-tool/templates/ComplexArgumentParser-src.zapt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}} | ||
|
Oops, something went wrong.