From 85f39a5cb1371c4fabd8f79f9a2d954bb7b9eb3f Mon Sep 17 00:00:00 2001 From: Evgeny Margolis Date: Fri, 4 Aug 2023 16:54:04 -0700 Subject: [PATCH] Json to Tlv and Tlv to Json Converters Implementation in CPP (#27635) * Json to Tlv and Tlv to Json Converters Implemented in CPP This implementation is equivalent to the Kotlin implementation in: src/controller/java/src/chip/jsontlv/ Note that NOT all TLV configurations are supported by the current implementation. Here is the list of limitations: - TLV Structure elements are expected to be sorted in a canonical tag order - TLV Lists are not supported - Multi-Dimensional TLV Arrays are not supported - All elements in an array MUST be of the same type - The top-level TLV element MUST be a single structure with AnonymousTag - The following tags are supported: - AnonymousTag are only used with TLV Array elements or a top-level structure. - ContextSpecificTag are used only with TLV Structure elements. - CommonProfileTag are used only with TLV Structure elements. - Infinity Float/Double values are not supported. Added README.md file that describing the format. Added unit tests for TLV to JSON, JSON to TLV, JSON to TLV back to JSON conversion cases. NOTE about the current implementation of the Tlv-to-Json converter in: src/lib/support/jsontlv/TlvJson.cpp I kept this implementation because it is currently used in a few places in the code for testing/logging purposes. As a follow up work item, this implementation should be replaced with the new one presented in this commit. * Update src/lib/support/jsontlv/JsonToTlv.cpp Co-authored-by: Robert Szewczyk * Update src/lib/support/jsontlv/TlvToJson.cpp Co-authored-by: Robert Szewczyk * Update src/lib/support/jsontlv/TlvToJson.cpp Co-authored-by: Boris Zbarsky * Update src/lib/support/jsontlv/TlvToJson.cpp Co-authored-by: Boris Zbarsky * Update src/lib/support/jsontlv/JsonToTlv.h Co-authored-by: Boris Zbarsky * Update src/lib/support/jsontlv/JsonToTlv.h Co-authored-by: Boris Zbarsky * Addressed Review Comments * Restyled by clang-format * Restyled by prettier-markdown * Removed Debug Prints * Added Support for Float/Double Infinity Values Those values should be encoded as "Infinity" and "-Infinity" strings. * Restyled by clang-format * Restyled by prettier-markdown * Update src/lib/support/jsontlv/JsonToTlv.cpp Co-authored-by: Boris Zbarsky * Addressed Review Comments * Switch from CommonTag to ProfileTag with an implicit profile id * Documentation update * Restyle * Fix type of variable in unit test * Update src/lib/support/jsontlv/README.md Co-authored-by: Boris Zbarsky * Update src/lib/support/jsontlv/TlvToJson.cpp Co-authored-by: Boris Zbarsky * Update src/lib/support/jsontlv/TlvToJson.cpp Co-authored-by: Boris Zbarsky * Update src/lib/support/jsontlv/TlvToJson.cpp Co-authored-by: Boris Zbarsky * Update src/lib/support/jsontlv/TlvJson.cpp Co-authored-by: Boris Zbarsky * Added more comments about kTemporaryImplicitProfileId not being actually used in stored values. Made the values consistent everywhere * Revert old code updates * Casing on json updated according to code review * Make the tlv element naming a bit more consistent, including invalid JSON in case element types are NOT as expected * Remove obsolete comment * Update src/lib/support/jsontlv/TlvToJson.cpp Co-authored-by: Boris Zbarsky * Update src/lib/support/jsontlv/TlvToJson.cpp Co-authored-by: Boris Zbarsky --------- Co-authored-by: Robert Szewczyk Co-authored-by: Boris Zbarsky Co-authored-by: Restyled.io Co-authored-by: Andrei Litvin Co-authored-by: Andrei Litvin Co-authored-by: yunhanw-google --- .github/.wordlist.txt | 1 + scripts/tools/check_includes_config.py | 5 + src/lib/core/TLVReader.h | 5 + src/lib/support/jsontlv/BUILD.gn | 15 +- src/lib/support/jsontlv/ElementTypes.h | 43 + src/lib/support/jsontlv/JsonToTlv.cpp | 460 ++++ src/lib/support/jsontlv/JsonToTlv.h | 35 + src/lib/support/jsontlv/README.md | 134 ++ src/lib/support/jsontlv/TlvToJson.cpp | 382 ++++ src/lib/support/jsontlv/TlvToJson.h | 51 + src/lib/support/tests/BUILD.gn | 5 +- src/lib/support/tests/TestJsonToTlv.cpp | 344 +++ src/lib/support/tests/TestJsonToTlvToJson.cpp | 1913 +++++++++++++++++ src/lib/support/tests/TestTlvJson.cpp | 259 +++ src/lib/support/tests/TestTlvToJson.cpp | 239 +- 15 files changed, 3769 insertions(+), 122 deletions(-) create mode 100644 src/lib/support/jsontlv/ElementTypes.h create mode 100644 src/lib/support/jsontlv/JsonToTlv.cpp create mode 100644 src/lib/support/jsontlv/JsonToTlv.h create mode 100644 src/lib/support/jsontlv/README.md create mode 100644 src/lib/support/jsontlv/TlvToJson.cpp create mode 100644 src/lib/support/jsontlv/TlvToJson.h create mode 100644 src/lib/support/tests/TestJsonToTlv.cpp create mode 100644 src/lib/support/tests/TestJsonToTlvToJson.cpp create mode 100644 src/lib/support/tests/TestTlvJson.cpp diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 0b195ba9a244e5..41e37196757c41 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -154,6 +154,7 @@ blocklist blockquote bluetoothd bluez +BOOL BooleanState bootable Bootloader diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index cb1f4e1062fb26..ae06b487cbaba8 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -158,4 +158,9 @@ # Library meant for non-embedded 'src/tracing/json/json_tracing.cpp': {'string', 'sstream'}, 'src/tracing/json/json_tracing.h': {'fstream'}, + + # Not intended for embedded clients + 'src/lib/support/jsontlv/JsonToTlv.cpp': {'sstream'}, + 'src/lib/support/jsontlv/JsonToTlv.h': {'string'}, + 'src/lib/support/jsontlv/TlvToJson.h': {'string'} } diff --git a/src/lib/core/TLVReader.h b/src/lib/core/TLVReader.h index caf56332762e79..0acc618bc86598 100644 --- a/src/lib/core/TLVReader.h +++ b/src/lib/core/TLVReader.h @@ -855,6 +855,11 @@ class DLL_EXPORT TLVReader */ TLVBackingStore * GetBackingStore() { return mBackingStore; } + /** + * Returns true if the current TLV element type is a double. + */ + bool IsElementDouble() { return ElementType() == TLVElementType::FloatingPointNumber64; } + /** * Gets the point in the underlying input buffer that corresponds to the reader's current position. * diff --git a/src/lib/support/jsontlv/BUILD.gn b/src/lib/support/jsontlv/BUILD.gn index 1e62202adc29a5..377b9be19d0656 100644 --- a/src/lib/support/jsontlv/BUILD.gn +++ b/src/lib/support/jsontlv/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2020-2023 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,7 +19,18 @@ config("jsontlv_config") { } static_library("jsontlv") { - sources = [ "TlvJson.cpp" ] + sources = [ + "ElementTypes.h", + "JsonToTlv.cpp", + "TlvJson.cpp", + "TlvToJson.cpp", + ] + + public = [ + "JsonToTlv.h", + "TlvJson.h", + "TlvToJson.h", + ] public_configs = [ ":jsontlv_config" ] diff --git a/src/lib/support/jsontlv/ElementTypes.h b/src/lib/support/jsontlv/ElementTypes.h new file mode 100644 index 00000000000000..0dd23bf9aaaa76 --- /dev/null +++ b/src/lib/support/jsontlv/ElementTypes.h @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +#include + +namespace { + +const char kElementTypeInt[] = "INT"; +const char kElementTypeUInt[] = "UINT"; +const char kElementTypeBool[] = "BOOL"; +const char kElementTypeFloat[] = "FLOAT"; +const char kElementTypeDouble[] = "DOUBLE"; +const char kElementTypeBytes[] = "BYTES"; +const char kElementTypeString[] = "STRING"; +const char kElementTypeNull[] = "NULL"; +const char kElementTypeStruct[] = "STRUCT"; +const char kElementTypeArray[] = "ARRAY"; +const char kElementTypeEmpty[] = "?"; + +const char kFloatingPointPositiveInfinity[] = "Infinity"; +const char kFloatingPointNegativeInfinity[] = "-Infinity"; + +struct ElementTypeContext +{ + chip::TLV::TLVType tlvType = chip::TLV::kTLVType_NotSpecified; + bool isDouble = false; +}; + +} // namespace diff --git a/src/lib/support/jsontlv/JsonToTlv.cpp b/src/lib/support/jsontlv/JsonToTlv.cpp new file mode 100644 index 00000000000000..327419e7273b57 --- /dev/null +++ b/src/lib/support/jsontlv/JsonToTlv.cpp @@ -0,0 +1,460 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +namespace chip { + +namespace { + +// Not directly used: TLV encoding will not encode this number and +// will just encode "Implicit profile tag" +// This profile, but will be used for deciding what binary values to encode. +constexpr uint32_t kTemporaryImplicitProfileId = 0xFF01; + +std::vector SplitIntoFieldsBySeparator(const std::string & input, char separator) +{ + std::vector substrings; + std::stringstream ss(input); + std::string substring; + + while (std::getline(ss, substring, separator)) + { + substrings.push_back(std::move(substring)); + } + + return substrings; +} + +CHIP_ERROR JsonTypeStrToTlvType(const char * elementType, ElementTypeContext & type) +{ + if (strcmp(elementType, kElementTypeInt) == 0) + { + type.tlvType = TLV::kTLVType_SignedInteger; + } + else if (strcmp(elementType, kElementTypeUInt) == 0) + { + type.tlvType = TLV::kTLVType_UnsignedInteger; + } + else if (strcmp(elementType, kElementTypeBool) == 0) + { + type.tlvType = TLV::kTLVType_Boolean; + } + else if (strcmp(elementType, kElementTypeFloat) == 0) + { + type.tlvType = TLV::kTLVType_FloatingPointNumber; + type.isDouble = false; + } + else if (strcmp(elementType, kElementTypeDouble) == 0) + { + type.tlvType = TLV::kTLVType_FloatingPointNumber; + type.isDouble = true; + } + else if (strcmp(elementType, kElementTypeBytes) == 0) + { + type.tlvType = TLV::kTLVType_ByteString; + } + else if (strcmp(elementType, kElementTypeString) == 0) + { + type.tlvType = TLV::kTLVType_UTF8String; + } + else if (strcmp(elementType, kElementTypeNull) == 0) + { + type.tlvType = TLV::kTLVType_Null; + } + else if (strcmp(elementType, kElementTypeStruct) == 0) + { + type.tlvType = TLV::kTLVType_Structure; + } + else if (strncmp(elementType, kElementTypeArray, strlen(kElementTypeArray)) == 0) + { + type.tlvType = TLV::kTLVType_Array; + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return CHIP_NO_ERROR; +} + +bool IsUnsignedInteger(const std::string & s) +{ + size_t len = s.length(); + if (len == 0) + { + return false; + } + for (size_t i = 0; i < len; i++) + { + if (!isdigit(s[i])) + { + return false; + } + } + return true; +} + +bool IsSignedInteger(const std::string & s) +{ + if (s.length() == 0) + { + return false; + } + if (s[0] == '-') + { + return IsUnsignedInteger(s.substr(1)); + } + return IsUnsignedInteger(s); +} + +bool IsValidBase64String(const std::string & s) +{ + const std::string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t len = s.length(); + + // Check if the length is a multiple of 4 + if (len % 4 != 0) + { + return false; + } + + size_t paddingLen = 0; + if (s[len - 1] == '=') + { + paddingLen++; + if (s[len - 2] == '=') + { + paddingLen++; + } + } + + // Check for invalid characters + for (char c : s.substr(0, len - paddingLen)) + { + if (base64Chars.find(c) == std::string::npos) + { + return false; + } + } + + return true; +} + +struct ElementContext +{ + std::string jsonName; + TLV::Tag tag = TLV::AnonymousTag(); + ElementTypeContext type; + ElementTypeContext subType; +}; + +bool CompareByTag(const ElementContext & a, const ElementContext & b) +{ + // If tags are of the same type compare by tag number + if (IsContextTag(a.tag) == IsContextTag(b.tag)) + { + return TLV::TagNumFromTag(a.tag) < TLV::TagNumFromTag(b.tag); + } + // Otherwise, compare by tag type: context tags first followed by common profile tags + return IsContextTag(a.tag); +} + +CHIP_ERROR ParseJsonName(const std::string name, ElementContext & elementCtx, uint32_t implicitProfileId) +{ + uint64_t tagNumber = 0; + const char * elementType = nullptr; + std::vector nameFields = SplitIntoFieldsBySeparator(name, ':'); + TLV::Tag tag = TLV::AnonymousTag(); + ElementTypeContext type; + ElementTypeContext subType; + + if (nameFields.size() == 2) + { + VerifyOrReturnError(IsUnsignedInteger(nameFields[0]), CHIP_ERROR_INVALID_ARGUMENT); + tagNumber = std::strtoull(nameFields[0].c_str(), nullptr, 10); + elementType = nameFields[1].c_str(); + } + else if (nameFields.size() == 3) + { + VerifyOrReturnError(IsUnsignedInteger(nameFields[1]), CHIP_ERROR_INVALID_ARGUMENT); + tagNumber = std::strtoull(nameFields[1].c_str(), nullptr, 10); + elementType = nameFields[2].c_str(); + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (tagNumber <= UINT8_MAX) + { + tag = TLV::ContextTag(static_cast(tagNumber)); + } + else if (tagNumber <= UINT32_MAX) + { + tag = TLV::ProfileTag(implicitProfileId, static_cast(tagNumber)); + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + ReturnErrorOnFailure(JsonTypeStrToTlvType(elementType, type)); + + if (type.tlvType == TLV::kTLVType_Array) + { + std::vector arrayFields = SplitIntoFieldsBySeparator(elementType, '-'); + VerifyOrReturnError(arrayFields.size() == 2, CHIP_ERROR_INVALID_ARGUMENT); + + if (strcmp(arrayFields[1].c_str(), kElementTypeEmpty) == 0) + { + subType.tlvType = TLV::kTLVType_NotSpecified; + } + else + { + ReturnErrorOnFailure(JsonTypeStrToTlvType(arrayFields[1].c_str(), subType)); + } + } + + elementCtx.jsonName = name; + elementCtx.tag = tag; + elementCtx.type = type; + elementCtx.subType = subType; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR EncodeTlvElement(const Json::Value & val, TLV::TLVWriter & writer, const ElementContext & elementCtx) +{ + TLV::Tag tag = elementCtx.tag; + + switch (elementCtx.type.tlvType) + { + case TLV::kTLVType_UnsignedInteger: { + uint64_t v; + if (val.isUInt64()) + { + v = val.asUInt64(); + } + else if (val.isString()) + { + const std::string valAsString = val.asString(); + VerifyOrReturnError(IsUnsignedInteger(valAsString), CHIP_ERROR_INVALID_ARGUMENT); + v = std::strtoull(valAsString.c_str(), nullptr, 10); + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + ReturnErrorOnFailure(writer.Put(tag, v)); + break; + } + + case TLV::kTLVType_SignedInteger: { + int64_t v; + if (val.isInt64()) + { + v = val.asInt64(); + } + else if (val.isString()) + { + const std::string valAsString = val.asString(); + VerifyOrReturnError(IsSignedInteger(valAsString), CHIP_ERROR_INVALID_ARGUMENT); + v = std::strtoll(valAsString.c_str(), nullptr, 10); + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + ReturnErrorOnFailure(writer.Put(tag, v)); + break; + } + + case TLV::kTLVType_Boolean: { + VerifyOrReturnError(val.isBool(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(writer.Put(tag, val.asBool())); + break; + } + + case TLV::kTLVType_FloatingPointNumber: { + if (val.isNumeric()) + { + if (elementCtx.type.isDouble) + { + ReturnErrorOnFailure(writer.Put(tag, val.asDouble())); + } + else + { + ReturnErrorOnFailure(writer.Put(tag, val.asFloat())); + } + } + else if (val.isString()) + { + const std::string valAsString = val.asString(); + bool isPositiveInfinity = (valAsString == kFloatingPointPositiveInfinity); + bool isNegativeInfinity = (valAsString == kFloatingPointNegativeInfinity); + VerifyOrReturnError(isPositiveInfinity || isNegativeInfinity, CHIP_ERROR_INVALID_ARGUMENT); + if (elementCtx.type.isDouble) + { + if (isPositiveInfinity) + { + ReturnErrorOnFailure(writer.Put(tag, std::numeric_limits::infinity())); + } + else + { + ReturnErrorOnFailure(writer.Put(tag, -std::numeric_limits::infinity())); + } + } + else + { + if (isPositiveInfinity) + { + ReturnErrorOnFailure(writer.Put(tag, std::numeric_limits::infinity())); + } + else + { + ReturnErrorOnFailure(writer.Put(tag, -std::numeric_limits::infinity())); + } + } + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + break; + } + + case TLV::kTLVType_ByteString: { + VerifyOrReturnError(val.isString(), CHIP_ERROR_INVALID_ARGUMENT); + const std::string valAsString = val.asString(); + size_t encodedLen = valAsString.length(); + VerifyOrReturnError(CanCastTo(encodedLen), CHIP_ERROR_INVALID_ARGUMENT); + + VerifyOrReturnError(IsValidBase64String(valAsString), CHIP_ERROR_INVALID_ARGUMENT); + + Platform::ScopedMemoryBuffer byteString; + byteString.Alloc(BASE64_MAX_DECODED_LEN(static_cast(encodedLen))); + VerifyOrReturnError(byteString.Get() != nullptr, CHIP_ERROR_NO_MEMORY); + + auto decodedLen = Base64Decode(valAsString.c_str(), static_cast(encodedLen), byteString.Get()); + ReturnErrorOnFailure(writer.PutBytes(tag, byteString.Get(), decodedLen)); + break; + } + + case TLV::kTLVType_UTF8String: { + VerifyOrReturnError(val.isString(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(writer.PutString(tag, val.asCString())); + break; + } + + case TLV::kTLVType_Null: { + VerifyOrReturnError(val.isNull(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(writer.PutNull(tag)); + break; + } + + case TLV::kTLVType_Structure: { + TLV::TLVType containerType; + VerifyOrReturnError(val.isObject(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, containerType)); + + std::vector jsonNames = val.getMemberNames(); + std::vector nestedElementsCtx; + + for (size_t i = 0; i < jsonNames.size(); i++) + { + ElementContext ctx; + ReturnErrorOnFailure(ParseJsonName(jsonNames[i], ctx, writer.ImplicitProfileId)); + nestedElementsCtx.push_back(ctx); + } + + // Sort Json object elements by Tag number (low to high). + // Note that all sorted Context Tags will appear first followed by all sorted Common Tags. + std::sort(nestedElementsCtx.begin(), nestedElementsCtx.end(), CompareByTag); + + for (auto & ctx : nestedElementsCtx) + { + ReturnErrorOnFailure(EncodeTlvElement(val[ctx.jsonName], writer, ctx)); + } + + ReturnErrorOnFailure(writer.EndContainer(containerType)); + break; + } + + case TLV::kTLVType_Array: { + TLV::TLVType containerType; + VerifyOrReturnError(val.isArray(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Array, containerType)); + + if (elementCtx.subType.tlvType == TLV::kTLVType_NotSpecified) + { + VerifyOrReturnError(val.size() == 0, CHIP_ERROR_INVALID_ARGUMENT); + } + else + { + ElementContext nestedElementCtx; + nestedElementCtx.tag = TLV::AnonymousTag(); + nestedElementCtx.type = elementCtx.subType; + for (Json::ArrayIndex i = 0; i < val.size(); i++) + { + ReturnErrorOnFailure(EncodeTlvElement(val[i], writer, nestedElementCtx)); + } + } + + ReturnErrorOnFailure(writer.EndContainer(containerType)); + break; + } + + default: + return CHIP_ERROR_INVALID_TLV_ELEMENT; + break; + } + + return CHIP_NO_ERROR; +} + +} // namespace + +CHIP_ERROR JsonToTlv(const std::string & jsonString, MutableByteSpan & tlv) +{ + TLV::TLVWriter writer; + writer.Init(tlv); + writer.ImplicitProfileId = kTemporaryImplicitProfileId; + ReturnErrorOnFailure(JsonToTlv(jsonString, writer)); + ReturnErrorOnFailure(writer.Finalize()); + tlv.reduce_size(writer.GetLengthWritten()); + return CHIP_NO_ERROR; +} + +CHIP_ERROR JsonToTlv(const std::string & jsonString, TLV::TLVWriter & writer) +{ + Json::Reader reader; + Json::Value json; + bool result = reader.parse(jsonString, json); + VerifyOrReturnError(result, CHIP_ERROR_INTERNAL); + + ElementContext elementCtx; + elementCtx.type = { TLV::kTLVType_Structure, false }; + return EncodeTlvElement(json, writer, elementCtx); +} + +} // namespace chip diff --git a/src/lib/support/jsontlv/JsonToTlv.h b/src/lib/support/jsontlv/JsonToTlv.h new file mode 100644 index 00000000000000..d4660465c4c3d9 --- /dev/null +++ b/src/lib/support/jsontlv/JsonToTlv.h @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +#include +#include + +namespace chip { + +/* + * Given a JSON object that represents TLV, this function writes the corresponding TLV bytes into the provided buffer. + * The size of tlv will be adjusted to the size of the actual data written to the buffer. + */ +CHIP_ERROR JsonToTlv(const std::string & jsonString, MutableByteSpan & tlv); + +/* + * Given a JSON object that represents TLV, this function makes encode calls on the given TLVWriter to encode the corresponding TLV + * bytes. + */ +CHIP_ERROR JsonToTlv(const std::string & jsonString, TLV::TLVWriter & writer); + +} // namespace chip diff --git a/src/lib/support/jsontlv/README.md b/src/lib/support/jsontlv/README.md new file mode 100644 index 00000000000000..06de13398af7b1 --- /dev/null +++ b/src/lib/support/jsontlv/README.md @@ -0,0 +1,134 @@ +# JSON to TLV and TLV to JSON Converter + +## Introduction + +Helper functions for converting TLV-encoded data to Json format and vice versa. + +### Supported payloads + +The library supports + +- full bi-directional conversion for matter data model payloads +- Additional support for generic 32-bit unsigned integer ids using "implicit + profile tags": + + - 8-bit IDs are encoded as `ContextTags`, which matches matter + specification for encoding field identifiers + - For IDs that are larger, they will be encoded as + `Implicit Profile Tags`. The reason for allowing such IDs is to support + json formats where keys contain ids typically found in paths, like + `{"1234:INT": 10}` meaning `"Attribute 1234 has value 10"`. + +### Format details + +In order for the Json format to represent the TLV format without loss of +information, the Json name of each element should contain the TLV element tag +and type information. + +The Json name schema is as follows: + +``` +{ [field_name:]field_id:element_type[-sub_element_type] } +``` + +Specific rules: + +- 'element_type' MUST map to the TLV Type as specified in the table below. +- For 'element_type' of type "ARRAY", the sub_element_type MUST always be + present and be equal to the 'element_type' of the elements in the array. +- Array elements MUST have the same 'element_type'. +- Array elements MUST have anonymous TLV tags. +- 'field_name' is always an optional field and can be provided if the semantic + name for the field is available. +- In the case of an empty array, 'sub_element_type' should be "?" (unknown + element type). +- The unknown element type “?” MUST never occur outside of "ARRAY-?". +- 'field_id' is equivalent to the Field ID for all cluster payloads (commands, + events, attributes), encoded as a decimal number. + +The table below summarizes all element types and their corresponding encoding in +the Json element name: + +| TLV Type | element_type | JSON Type | +| -------------------------------------- | ---------------------- | ------------------------------- | +| Unsigned Integer ( < 2^32 ) | UINT | Number | +| Unsigned Integer ( >= 2^32 ) | UINT | String | +| Signed Integer ( >= -2^31 and < 2^31 ) | INT | Number | +| Signed Integer ( < -2^31 or >= 2^31 ) | INT | String | +| Boolean | BOOL | Boolean | +| Float (positive/negative infinity) | FLOAT | String ("Infinity"/"-Infinity") | +| Double (positive/negative infinity) | DOUBLE | String ("Infinity"/"-Infinity") | +| Float (not infinity) | FLOAT | Number | +| Double (not infinity) | DOUBLE | Number | +| Octet String | BYTES | String (Base64 encoded) | +| UTF-8 String | STRING | String | +| Null Value | NULL | null | +| Struct | STRUCT | Dict | +| Array | ARRAY-sub_element_type | Array | +| Unknown | ? | Empty Array | + +Note that this is NOT a generalized JSON representation of TLV and does not +support arbitrary TLV conversions. The main goal of this Json format is to +support data model payloads for events/commands/attributes. Some of the +limitations of this format are: + +- TLV List types are not supported. +- TLV Array cannot contain another TLV Array. +- The top-level container MUST be an anonymous STRUCT. +- Elements of the TLV Structure MUST have Context or Implicit Profile Tags. +- Implicit Profile Tag number MUST be larger or equal to 256 and smaller than + 2^32 + 1. +- TLV Structure element MUST be sorted by tag numbers from low to high, where + sorted elements with Context Tags MUST appear first followed by sorted + elements with Implicit Profile Tags. + +## Format Example + +The following is an example of a Json string. It represents various TLV +elements, arrays, and structures. + +``` +{ // top-level anonymous structure + "0:ARRAY-STRUCT" : [ // array of structures + { + "0:INT" : 8, // Struct.elem0 + "1:BOOL" : true // Struct.elem1 + } + ], + "1:STRUCT" : { // structure + "0:INT" : 12, // Struct.elem0 + "1:BOOL" : false, // Struct.elem1 + "2:STRING" : "example" // Struct.elem2 + }, + "2:INT" : "40000000000", // int as string + "isQualified:3:BOOL" : true, // boolean with field_name in the Json name + "4:ARRAY-?" : [], // empty array + "5:ARRAY-DOUBLE" : [ // array of doubles + 1.1, + 134.2763, + -12345.87, + "Infinity", // positive infinity + 62534, // positive integer-valued double + -62534 // negative integer-valued double + ], + "6:ARRAY-BYTES" : [ // array of Octet Strings: [{00 01 02 03 04}, {FF}, {4A EF 88}] + "AAECAwQ=", // base64( {00 01 02 03 04} ) + "/w==", // base64( {FF} ) + "Su+I" // base64( {4A EF 88} ) + ], + "7:BYTES" : "VGVzdCBCeXRlcw==", // base64( "Test Bytes" ) + "8:DOUBLE" : 17.9, // 17.9 as double + "9:FLOAT" : 17.9, // 17.9 as float + "10:FLOAT" : "-Infinity", // Negative infinity float + "contact:11:STRUCT" : { // structure example with field_name in the Json name + "name:1:STRING" : "John", + "age:2:UINT" : 34, + "approved:3:BOOL" : true, + "kids:4:ARRAY-INT" : [ + 5, + 9, + 10 + ] + } +} +``` diff --git a/src/lib/support/jsontlv/TlvToJson.cpp b/src/lib/support/jsontlv/TlvToJson.cpp new file mode 100644 index 00000000000000..d9884f60af43fa --- /dev/null +++ b/src/lib/support/jsontlv/TlvToJson.cpp @@ -0,0 +1,382 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +#include "lib/support/CHIPMemString.h" +#include "lib/support/ScopedBuffer.h" +#include +#include +#include +#include +#include +#include + +namespace chip { + +namespace { + +// actual value of this does not actually matter, however we need +// a value to be able to read 32-bit implicit profile tags +// +// JSON format never has this and TLV payload contains "implicit profile" +// and this value is never stored. +constexpr uint32_t kTemporaryImplicitProfileId = 0xFF01; + +/// RAII to switch the implicit profile id for a reader +class ImplicitProfileIdChange +{ +public: + ImplicitProfileIdChange(TLV::TLVReader & reader, uint32_t id) : mReader(reader), mOldImplicitProfileId(reader.ImplicitProfileId) + { + reader.ImplicitProfileId = id; + } + ~ImplicitProfileIdChange() { mReader.ImplicitProfileId = mOldImplicitProfileId; } + +private: + TLV::TLVReader & mReader; + uint32_t mOldImplicitProfileId; +}; + +const char * GetJsonElementStrFromType(const ElementTypeContext & ctx) +{ + switch (ctx.tlvType) + { + case TLV::kTLVType_UnsignedInteger: + return kElementTypeUInt; + case TLV::kTLVType_SignedInteger: + return kElementTypeInt; + case TLV::kTLVType_Boolean: + return kElementTypeBool; + case TLV::kTLVType_FloatingPointNumber: + return ctx.isDouble ? kElementTypeDouble : kElementTypeFloat; + case TLV::kTLVType_ByteString: + return kElementTypeBytes; + case TLV::kTLVType_UTF8String: + return kElementTypeString; + case TLV::kTLVType_Null: + return kElementTypeNull; + case TLV::kTLVType_Structure: + return kElementTypeStruct; + case TLV::kTLVType_Array: + return kElementTypeArray; + default: + return kElementTypeEmpty; + } +}; + +/* + * Encapsulates the element information required to construct a JSON element name string in a JSON object. + * + * The generated JSON element name string is constructed as: + * 'TagNumber:ElementType-SubElementType'. + */ +struct JsonObjectElementContext +{ + JsonObjectElementContext(TLV::TLVReader & reader) + { + tag = reader.GetTag(); + implicitProfileId = reader.ImplicitProfileId; + type.tlvType = reader.GetType(); + if (type.tlvType == TLV::kTLVType_FloatingPointNumber) + { + type.isDouble = reader.IsElementDouble(); + } + } + + std::string GenerateJsonElementName() const + { + std::string str = "???"; + if (TLV::IsContextTag(tag)) + { + // common case for context tags: raw value + str = std::to_string(TLV::TagNumFromTag(tag)); + } + else if (TLV::IsProfileTag(tag)) + { + if (TLV::ProfileIdFromTag(tag) == implicitProfileId) + { + // Explicit assume implicit tags are just things we want + // 32-bit numbers for + str = std::to_string(TLV::TagNumFromTag(tag)); + } + else + { + // UNEXPECTED, create a full 64-bit number here + str = std::to_string(TLV::ProfileIdFromTag(tag)) + "/" + std::to_string(TLV::TagNumFromTag(tag)); + } + } + str = str + ":" + GetJsonElementStrFromType(type); + if (type.tlvType == TLV::kTLVType_Array) + { + str = str + "-" + GetJsonElementStrFromType(subType); + } + return str; + } + + TLV::Tag tag; + uint32_t implicitProfileId; + ElementTypeContext type; + ElementTypeContext subType; +}; + +/* + * This templated function inserts a name/value pair into the Json object. + * The value is templated to be of type T and accepts any of the following types: + * + * bool, uint*_t, int*_t, char *, float, double, std::string, Json::Value + * + * This method uses the provided element context to generate Json name string. + */ +template +void InsertJsonElement(Json::Value & json, const JsonObjectElementContext & ctx, T val) +{ + if (json.isArray()) + { + json.append(val); + } + else + { + json[ctx.GenerateJsonElementName()] = val; + } +} + +static CHIP_ERROR TlvToJson(TLV::TLVReader & reader, Json::Value & jsonObj); + +/* + * Given a TLVReader positioned at TLV structure this function: + * - enters structure + * - converts all elements of a structure into JSON object representation + * - exits structure + */ +CHIP_ERROR TlvStructToJson(TLV::TLVReader & reader, Json::Value & jsonObj) +{ + CHIP_ERROR err; + TLV::TLVType containerType; + + ReturnErrorOnFailure(reader.EnterContainer(containerType)); + + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + TLV::Tag tag = reader.GetTag(); + VerifyOrReturnError(TLV::IsContextTag(tag) || TLV::IsProfileTag(tag), CHIP_ERROR_INVALID_TLV_TAG); + + // Profile tags are expected to be implicit profile tags and they are + // used to encode > 8bit values from json + if (TLV::IsProfileTag(tag)) + { + VerifyOrReturnError(TLV::ProfileIdFromTag(tag) == reader.ImplicitProfileId, CHIP_ERROR_INVALID_TLV_TAG); + VerifyOrReturnError(TLV::TagNumFromTag(tag) > UINT8_MAX, CHIP_ERROR_INVALID_TLV_TAG); + } + + // Recursively convert to JSON the item within the struct. + ReturnErrorOnFailure(TlvToJson(reader, jsonObj)); + } + + VerifyOrReturnError(err == CHIP_END_OF_TLV, err); + return reader.ExitContainer(containerType); +} + +CHIP_ERROR TlvToJson(TLV::TLVReader & reader, Json::Value & jsonObj) +{ + JsonObjectElementContext context(reader); + + switch (reader.GetType()) + { + case TLV::kTLVType_UnsignedInteger: { + uint64_t v; + ReturnErrorOnFailure(reader.Get(v)); + if (CanCastTo(v)) + { + InsertJsonElement(jsonObj, context, v); + } + else + { + InsertJsonElement(jsonObj, context, std::to_string(v)); + } + break; + } + + case TLV::kTLVType_SignedInteger: { + int64_t v; + ReturnErrorOnFailure(reader.Get(v)); + if (CanCastTo(v)) + { + InsertJsonElement(jsonObj, context, v); + } + else + { + InsertJsonElement(jsonObj, context, std::to_string(v)); + } + break; + } + + case TLV::kTLVType_Boolean: { + bool v; + ReturnErrorOnFailure(reader.Get(v)); + InsertJsonElement(jsonObj, context, v); + break; + } + + case TLV::kTLVType_FloatingPointNumber: { + double v; + ReturnErrorOnFailure(reader.Get(v)); + if (v == std::numeric_limits::infinity()) + { + InsertJsonElement(jsonObj, context, kFloatingPointPositiveInfinity); + } + else if (v == -std::numeric_limits::infinity()) + { + InsertJsonElement(jsonObj, context, kFloatingPointNegativeInfinity); + } + else + { + InsertJsonElement(jsonObj, context, v); + } + break; + } + + case TLV::kTLVType_ByteString: { + ByteSpan span; + ReturnErrorOnFailure(reader.Get(span)); + + Platform::ScopedMemoryBuffer byteString; + byteString.Alloc(BASE64_ENCODED_LEN(span.size()) + 1); + VerifyOrReturnError(byteString.Get() != nullptr, CHIP_ERROR_NO_MEMORY); + + auto encodedLen = Base64Encode(span.data(), static_cast(span.size()), byteString.Get()); + byteString.Get()[encodedLen] = '\0'; + + InsertJsonElement(jsonObj, context, byteString.Get()); + break; + } + + case TLV::kTLVType_UTF8String: { + CharSpan span; + ReturnErrorOnFailure(reader.Get(span)); + + std::string str(span.data(), span.size()); + InsertJsonElement(jsonObj, context, str); + break; + } + + case TLV::kTLVType_Null: { + InsertJsonElement(jsonObj, context, Json::Value()); + break; + } + + case TLV::kTLVType_Structure: { + Json::Value jsonStruct(Json::objectValue); + ReturnErrorOnFailure(TlvStructToJson(reader, jsonStruct)); + InsertJsonElement(jsonObj, context, jsonStruct); + break; + } + + case TLV::kTLVType_Array: { + CHIP_ERROR err; + Json::Value jsonArray(Json::arrayValue); + ElementTypeContext prevSubType; + ElementTypeContext nextSubType; + TLV::TLVType containerType; + + ReturnErrorOnFailure(reader.EnterContainer(containerType)); + + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + VerifyOrReturnError(reader.GetTag() == TLV::AnonymousTag(), CHIP_ERROR_INVALID_TLV_TAG); + VerifyOrReturnError(reader.GetType() != TLV::kTLVType_Array, CHIP_ERROR_INVALID_TLV_ELEMENT); + + nextSubType.tlvType = reader.GetType(); + if (nextSubType.tlvType == TLV::kTLVType_FloatingPointNumber) + { + nextSubType.isDouble = reader.IsElementDouble(); + } + + if (jsonArray.empty()) + { + prevSubType = nextSubType; + } + else + { + VerifyOrReturnError(prevSubType.tlvType == nextSubType.tlvType && prevSubType.isDouble == nextSubType.isDouble, + CHIP_ERROR_INVALID_TLV_ELEMENT); + } + + // Recursively convert to JSON the encompassing item within the array. + ReturnErrorOnFailure(TlvToJson(reader, jsonArray)); + } + + VerifyOrReturnError(err == CHIP_END_OF_TLV, err); + ReturnErrorOnFailure(reader.ExitContainer(containerType)); + + context.subType = prevSubType; + InsertJsonElement(jsonObj, context, jsonArray); + break; + } + + default: + return CHIP_ERROR_INVALID_TLV_ELEMENT; + break; + } + + return CHIP_NO_ERROR; +} + +} // namespace + +CHIP_ERROR TlvToJson(const ByteSpan & tlv, std::string & jsonString) +{ + TLV::TLVReader reader; + reader.Init(tlv); + reader.ImplicitProfileId = kTemporaryImplicitProfileId; + + ReturnErrorOnFailure(reader.Next()); + return TlvToJson(reader, jsonString); +} + +CHIP_ERROR TlvToJson(TLV::TLVReader & reader, std::string & jsonString) +{ + // The top level element must be a TLV Structure of Anonymous type. + VerifyOrReturnError(reader.GetType() == TLV::kTLVType_Structure, CHIP_ERROR_WRONG_TLV_TYPE); + VerifyOrReturnError(reader.GetTag() == TLV::AnonymousTag(), CHIP_ERROR_INVALID_TLV_TAG); + + // During json conversion, a implicit profile ID is required + ImplicitProfileIdChange implicitProfileIdChange(reader, kTemporaryImplicitProfileId); + + Json::Value jsonObject(Json::objectValue); + ReturnErrorOnFailure(TlvStructToJson(reader, jsonObject)); + + Json::StyledWriter writer; + jsonString = writer.write(jsonObject); + return CHIP_NO_ERROR; +} + +std::string PrettyPrintJsonString(const std::string & jsonString) +{ + Json::Reader reader; + Json::Value jsonObject; + reader.parse(jsonString, jsonObject); + Json::StyledWriter writer; + return writer.write(jsonObject); +} + +std::string MakeJsonSingleLine(const std::string & jsonString) +{ + std::string str = PrettyPrintJsonString(jsonString); + str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end()); + return str; +} + +} // namespace chip diff --git a/src/lib/support/jsontlv/TlvToJson.h b/src/lib/support/jsontlv/TlvToJson.h new file mode 100644 index 00000000000000..ea7589271e8d2e --- /dev/null +++ b/src/lib/support/jsontlv/TlvToJson.h @@ -0,0 +1,51 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +#include +#include + +namespace chip { + +/* + * Given a TLVReader positioned at a particular cluster data payload, this function converts + * the TLV data into a JSON object representation. + * + * NOTE: This only accepts data model payloads for events/commands/attributes. It does not support + * arbitrary TLV conversion to JSON. + */ +CHIP_ERROR TlvToJson(TLV::TLVReader & reader, std::string & jsonString); + +/* + * Given a TLV encoded byte array, this function converts it into JSON object. + */ +CHIP_ERROR TlvToJson(const ByteSpan & tlv, std::string & jsonString); + +/* + * Pretty-prints the input Json string using standard library pretty-printer. + * This pretty-printer generates a Json string in a human friendly format with 3 space indentation + * and nice representation of arrays and objects. + * The input can be any string, as long as it's valid Json. + */ +std::string PrettyPrintJsonString(const std::string & jsonString); + +/* + * Reformats the input Json string as a single-line string with no spaces or newlines. + * The input can be any string, as long as it's valid Json. + */ +std::string MakeJsonSingleLine(const std::string & jsonString); + +} // namespace chip diff --git a/src/lib/support/tests/BUILD.gn b/src/lib/support/tests/BUILD.gn index a42ecec423a382..1ef6db03710146 100644 --- a/src/lib/support/tests/BUILD.gn +++ b/src/lib/support/tests/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2020-2023 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ chip_test_suite("tests") { "TestFold.cpp", "TestIniEscaping.cpp", "TestIntrusiveList.cpp", + "TestJsonToTlv.cpp", + "TestJsonToTlvToJson.cpp", "TestOwnerOf.cpp", "TestPersistedCounter.cpp", "TestPool.cpp", @@ -51,6 +53,7 @@ chip_test_suite("tests") { "TestTestPersistentStorageDelegate.cpp", "TestThreadOperationalDataset.cpp", "TestTimeUtils.cpp", + "TestTlvJson.cpp", "TestTlvToJson.cpp", "TestVariant.cpp", "TestZclString.cpp", diff --git a/src/lib/support/tests/TestJsonToTlv.cpp b/src/lib/support/tests/TestJsonToTlv.cpp new file mode 100644 index 00000000000000..c08ba978360173 --- /dev/null +++ b/src/lib/support/tests/TestJsonToTlv.cpp @@ -0,0 +1,344 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace chip::Encoding; +using namespace chip; +using namespace chip::app; + +constexpr uint32_t kImplicitProfileId = 0x1234; + +uint8_t gBuf1[1024]; +uint8_t gBuf2[1024]; +TLV::TLVWriter gWriter1; +TLV::TLVWriter gWriter2; +nlTestSuite * gSuite; + +void SetupWriters() +{ + gWriter1.Init(gBuf1); + gWriter1.ImplicitProfileId = kImplicitProfileId; + + gWriter2.Init(gBuf2); + gWriter2.ImplicitProfileId = kImplicitProfileId; +} + +void PrintBytes(const uint8_t * buf, uint32_t len) +{ + for (uint32_t i = 0; i < len; i++) + { + printf("%02X ", buf[i]); + } + printf("\n"); +} + +bool MatchWriter1and2() +{ + auto matches = + (gWriter1.GetLengthWritten() == gWriter2.GetLengthWritten()) && (memcmp(gBuf1, gBuf2, gWriter1.GetLengthWritten()) == 0); + + if (!matches) + { + printf("Didn't match!\n"); + printf("Reference:\n"); + PrintBytes(gBuf1, gWriter1.GetLengthWritten()); + + printf("Generated:\n"); + PrintBytes(gBuf2, gWriter2.GetLengthWritten()); + } + + return matches; +} + +template +void ConvertJsonToTlvAndValidate(T val, const std::string & jsonString) +{ + CHIP_ERROR err; + TLV::TLVType container; + + SetupWriters(); + + err = gWriter1.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, container); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + err = DataModel::Encode(gWriter1, TLV::ContextTag(1), val); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + err = gWriter1.EndContainer(container); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + err = gWriter1.Finalize(); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + err = JsonToTlv(jsonString, gWriter2); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(gSuite, MatchWriter1and2()); +} + +void TestConverter(nlTestSuite * inSuite, void * inContext) +{ + std::string jsonString; + + gSuite = inSuite; + + jsonString = "{\n" + " \"1:UINT\" : 30\n" + "}\n"; + ConvertJsonToTlvAndValidate(static_cast(30), jsonString); + + jsonString = "{\n" + " \"1:INT\" : -30\n" + "}\n"; + ConvertJsonToTlvAndValidate(static_cast(-30), jsonString); + + jsonString = "{\n" + " \"1:BOOL\" : false\n" + "}\n"; + ConvertJsonToTlvAndValidate(false, jsonString); + + jsonString = "{\n" + " \"1:BOOL\" : true\n" + "}\n"; + ConvertJsonToTlvAndValidate(true, jsonString); + + jsonString = "{\n" + " \"1:FLOAT\" : 1.0\n" + "}\n"; + ConvertJsonToTlvAndValidate(static_cast(1.0), jsonString); + + jsonString = "{\n" + " \"1:DOUBLE\" : 1.0\n" + "}\n"; + ConvertJsonToTlvAndValidate(static_cast(1.0), jsonString); + + jsonString = "{\n" + " \"1:STRING\" : \"hello\"\n" + "}\n"; + ConvertJsonToTlvAndValidate(CharSpan::fromCharString("hello"), jsonString); + + // Validated using https://base64.guru/converter/encode/hex + const uint8_t byteBuf[] = { 0x01, 0x02, 0x03, 0x04, 0xff, 0xfe, 0x99, 0x88, 0xdd, 0xcd }; + ByteSpan byteSpan(byteBuf); + jsonString = "{\n" + " \"1:BYTES\" : \"AQIDBP/+mYjdzQ==\"\n" + "}\n"; + ConvertJsonToTlvAndValidate(byteSpan, jsonString); + + DataModel::Nullable nullValue; + jsonString = "{\n" + " \"1:NULL\" : null\n" + "}\n"; + ConvertJsonToTlvAndValidate(nullValue, jsonString); + + Clusters::UnitTesting::Structs::SimpleStruct::Type structVal; + structVal.a = 20; + structVal.b = true; + structVal.d = byteBuf; + structVal.e = CharSpan::fromCharString("hello"); + structVal.g = static_cast(1.0); + structVal.h = static_cast(1.0); + + jsonString = "{\n" + " \"1:STRUCT\" : {\n" + " \"0:UINT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"AQIDBP/+mYjdzQ==\",\n" + " \"4:STRING\" : \"hello\",\n" + " \"5:UINT\" : 0,\n" + " \"6:FLOAT\" : 1.0,\n" + " \"7:DOUBLE\" : 1.0\n" + " }\n" + "}\n"; + ConvertJsonToTlvAndValidate(structVal, jsonString); + + uint8_t int8uListData[] = { 1, 2, 3, 4 }; + DataModel::List int8uList; + + int8uList = int8uListData; + jsonString = "{\n" + " \"1:ARRAY-UINT\" : [ 1, 2, 3, 4 ]\n" + "}\n"; + ConvertJsonToTlvAndValidate(int8uList, jsonString); + + int8uList = {}; + jsonString = "{\n" + " \"1:ARRAY-?\" : [ ]\n" + "}\n"; + ConvertJsonToTlvAndValidate(int8uList, jsonString); + + DataModel::Nullable> nullValueList; + jsonString = "{\n" + " \"1:NULL\" : null\n" + "}\n"; + ConvertJsonToTlvAndValidate(nullValueList, jsonString); + + Clusters::UnitTesting::Structs::SimpleStruct::Type structListData[2] = { structVal, structVal }; + DataModel::List structList; + + structList = structListData; + jsonString = "{\n" + " \"1:ARRAY-STRUCT\" : [\n" + " {\n" + " \"0:UINT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"AQIDBP/+mYjdzQ==\",\n" + " \"4:STRING\" : \"hello\",\n" + " \"5:UINT\" : 0,\n" + " \"6:FLOAT\" : 1.0,\n" + " \"7:DOUBLE\" : 1.0\n" + " },\n" + " {\n" + " \"0:UINT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"AQIDBP/+mYjdzQ==\",\n" + " \"4:STRING\" : \"hello\",\n" + " \"5:UINT\" : 0,\n" + " \"6:FLOAT\" : 1.0,\n" + " \"7:DOUBLE\" : 1.0\n" + " }\n" + " ]\n" + "}\n"; + ConvertJsonToTlvAndValidate(structList, jsonString); +} + +void Test32BitConvert(nlTestSuite * inSuite, void * inContext) +{ + // JSON TLV format explicitly wants to support 32-bit integer preservation. + // + // This is to support encode/decode of a format like: + // { "123456:BOOL" : true } to be a compact way of encoding + // "attribute id 123456 has value true" + // + // Such an encoding is NOT part of the matter spec, so best-effort is done here: + // - low ids are encoded as context tags (this is in the spec for any structure encoding) + // - large ids are encoded as implicit tags (NOT used in spec as spec never has such high ids) + TLV::TLVReader reader; + TLV::TLVType tlvType; + int32_t value = 0; + + // convert a simple single value + { + SetupWriters(); + JsonToTlv("{\"1:INT\": 321}", gWriter1); + NL_TEST_ASSERT(inSuite, gWriter1.Finalize() == CHIP_NO_ERROR); + + reader.Init(gBuf1, gWriter1.GetLengthWritten()); + reader.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(inSuite, reader.Next(TLV::AnonymousTag()) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.GetType() == TLV::kTLVType_Structure); + NL_TEST_ASSERT(inSuite, reader.EnterContainer(tlvType) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.Next(TLV::ContextTag(1)) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.GetType() == TLV::kTLVType_SignedInteger); + NL_TEST_ASSERT(inSuite, reader.Get(value) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, value == 321); + NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_END_OF_TLV); + NL_TEST_ASSERT(inSuite, reader.ExitContainer(tlvType) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_END_OF_TLV); + } + + // convert a single value that is larger than 8 bit + { + SetupWriters(); + JsonToTlv("{\"1234:INT\": 321}", gWriter1); + NL_TEST_ASSERT(inSuite, gWriter1.Finalize() == CHIP_NO_ERROR); + + reader.Init(gBuf1, gWriter1.GetLengthWritten()); + reader.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(inSuite, reader.Next(TLV::AnonymousTag()) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.GetType() == TLV::kTLVType_Structure); + NL_TEST_ASSERT(inSuite, reader.EnterContainer(tlvType) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.Next(TLV::ProfileTag(kImplicitProfileId, 1234)) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.GetType() == TLV::kTLVType_SignedInteger); + NL_TEST_ASSERT(inSuite, reader.Get(value) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, value == 321); + NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_END_OF_TLV); + NL_TEST_ASSERT(inSuite, reader.ExitContainer(tlvType) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_END_OF_TLV); + } + + // Convert to a full 32-bit value, unsigned + { + SetupWriters(); + JsonToTlv("{\"4275878552:INT\": 321}", gWriter1); + NL_TEST_ASSERT(inSuite, gWriter1.Finalize() == CHIP_NO_ERROR); + + reader.Init(gBuf1, gWriter1.GetLengthWritten()); + reader.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(inSuite, reader.Next(TLV::AnonymousTag()) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.GetType() == TLV::kTLVType_Structure); + NL_TEST_ASSERT(inSuite, reader.EnterContainer(tlvType) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.Next(TLV::ProfileTag(kImplicitProfileId, 0xFEDCBA98u)) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.GetType() == TLV::kTLVType_SignedInteger); + NL_TEST_ASSERT(inSuite, reader.Get(value) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, value == 321); + NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_END_OF_TLV); + NL_TEST_ASSERT(inSuite, reader.ExitContainer(tlvType) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, reader.Next() == CHIP_END_OF_TLV); + } + + // FIXME: implement +} + +int Initialize(void * apSuite) +{ + VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE); + return SUCCESS; +} + +int Finalize(void * aContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +// clang-format off +const nlTest sTests[] = +{ + NL_TEST_DEF("TestConverter", TestConverter), + NL_TEST_DEF("Test32BitConvert", Test32BitConvert), + NL_TEST_SENTINEL() +}; +// clang-format on + +} // namespace + +int TestJsonToTlv() +{ + nlTestSuite theSuite = { "JsonToTlv", sTests, Initialize, Finalize }; + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestJsonToTlv) diff --git a/src/lib/support/tests/TestJsonToTlvToJson.cpp b/src/lib/support/tests/TestJsonToTlvToJson.cpp new file mode 100644 index 00000000000000..d494521b62537e --- /dev/null +++ b/src/lib/support/tests/TestJsonToTlvToJson.cpp @@ -0,0 +1,1913 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace chip::Encoding; +using namespace chip; +using namespace chip::app; + +constexpr uint32_t kImplicitProfileId = 0x1122; + +nlTestSuite * gSuite; + +void PrintSpan(const char * prefix, const ByteSpan & span) +{ + printf("%s", prefix); + for (size_t i = 0; i < span.size(); i++) + { + printf("%02X ", span.data()[i]); + } + printf("\n"); +} + +void CheckValidConversion(const std::string & jsonOriginal, const ByteSpan & tlvEncoding, const std::string & jsonExpected) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + bool match = false; + + uint8_t buf[256]; + MutableByteSpan tlvEncodingLocal(buf); + err = JsonToTlv(jsonOriginal, tlvEncodingLocal); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + match = tlvEncodingLocal.data_equal(tlvEncoding); + NL_TEST_ASSERT(gSuite, match); + if (!match) + { + printf("ERROR: TLV Encoding Doesn't Match!\n"); + PrintSpan("TLV Encoding Provided as Input for Reference: ", tlvEncoding); + PrintSpan("TLV Encoding Generated from Json Input String: ", tlvEncodingLocal); + } + + std::string generatedJsonString; + err = TlvToJson(tlvEncoding, generatedJsonString); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + auto compactExpectedString = PrettyPrintJsonString(jsonExpected); + auto compactGeneratedString = PrettyPrintJsonString(generatedJsonString); + match = (compactGeneratedString == compactExpectedString); + NL_TEST_ASSERT(gSuite, match); + if (!match) + { + printf("ERROR: Json String Doesn't Match!\n"); + printf("Expected Json String:\n%s\n", compactExpectedString.c_str()); + printf("Generated Json String:\n%s\n", compactGeneratedString.c_str()); + } + + // Verify that Expected Json String Converts to the Same TLV Encoding + tlvEncodingLocal = MutableByteSpan(buf); + err = JsonToTlv(jsonOriginal, tlvEncodingLocal); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + match = tlvEncodingLocal.data_equal(tlvEncoding); + NL_TEST_ASSERT(gSuite, match); + if (!match) + { + printf("ERROR: TLV Encoding Doesn't Match!\n"); + PrintSpan("TLV Encoding Provided as Input for Reference: ", tlvEncoding); + PrintSpan("TLV Encoding Generated from Json Expected String: ", tlvEncodingLocal); + } +} + +// Boolean true +void TestConverter_Boolean_True(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1:BOOL\" : true\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Signed Integer 42, 1-octet +void TestConverter_SignedInt_1BytePositive(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(2), static_cast(42))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"2:INT\" : 42\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Signed Integer -17, 1-octet +void TestConverter_SignedInt_1ByteNegative(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(3), static_cast(-17))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"3:INT\" : -17\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Unsigned Integer 42, 1-octet +void TestConverter_UnsignedInt_1Byte(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(4), static_cast(42))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"value:4:UINT\" : 42\n" + "}\n"; + + std::string jsonExpected = "{\n" + " \"4:UINT\" : 42\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonExpected); +} + +// Signed Integer 4242, 2-octet +void TestConverter_SignedInt_2Bytes(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(7), static_cast(4242))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"7:INT\" : 4242\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Signed Integer -170000, 4-octet +void TestConverter_SignedInt_4Bytes(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(80), static_cast(-170000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"80:INT\" : -170000\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Signed Long Integer (int64_t) 40000000000, 8-octet +void TestConverter_SignedInt_8Bytes(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(202), static_cast(40000000000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"202:INT\" : \"40000000000\"\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Unsigned Long Integer (uint64_t) 40000000000, 8-octet +void TestConverter_UnsignedInt_8Bytes(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(222), static_cast(40000000000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"222:UINT\" : \"40000000000\"\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// UTF-8 String, 1-octet length, "Hello!" +void TestConverter_UTF8String_Hello(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::ContextTag(0), "Hello!")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:STRING\" : \"Hello!\"\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Octet String, 1-octet length, octets { 00 01 02 03 04 } +void TestConverter_OctetString(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t v[] = { 0, 1, 2, 3, 4 }; + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutBytes(TLV::ContextTag(1), v, sizeof(v))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1:BYTES\" : \"AAECAwQ=\"\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Null +void TestConverter_Null(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutNull(TLV::ContextTag(1))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1:NULL\" : null\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Single precision floating point 0.0 +void TestConverter_Float_0(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), static_cast(0.0))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1:FLOAT\" : 0.0\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Single precision floating point (1.0 / 3.0) +void TestConverter_Float_1third(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(100), static_cast(1.0 / 3.0))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"100:FLOAT\" : 0.33333334\n" + "}\n"; + std::string jsonExpected = "{\n" + " \"100:FLOAT\" : 0.3333333432674408\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonExpected); +} + +// Single precision floating point 17.9 +void TestConverter_Float_17_9(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(101), static_cast(17.9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"101:FLOAT\" : 17.9\n" + "}\n"; + std::string jsonExpected = "{\n" + " \"101:FLOAT\" : 17.899999618530273\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonExpected); +} + +// Single precision floating point positive infinity +void TestConverter_Float_PositiveInfinity(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(101), std::numeric_limits::infinity())); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"101:FLOAT\" : \"Infinity\"\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Single precision floating point negative infinity +void TestConverter_Float_NegativeInfinity(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(101), -std::numeric_limits::infinity())); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"101:FLOAT\" : \"-Infinity\"\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Double precision floating point 0.0 +void TestConverter_Double_0(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), static_cast(0.0))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1:DOUBLE\" : 0.0\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Double precision floating point (1.0 / 3.0) +void TestConverter_Double_1third(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(100), static_cast(1.0 / 3.0))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"100:DOUBLE\" : 0.33333333333333331\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Double precision floating point 17.9 +void TestConverter_Double_17_9(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(101), static_cast(17.9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"101:DOUBLE\" : 17.899999999999999\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Double precision floating point positive infinity +void TestConverter_Double_PositiveInfinity(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(101), std::numeric_limits::infinity())); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"101:DOUBLE\" : \"Infinity\"\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Double precision floating point negative infinity +void TestConverter_Double_NegativeInfinity(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(101), -std::numeric_limits::infinity())); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"101:DOUBLE\" : \"-Infinity\"\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Empty Top-Level Structure, {} +void TestConverter_Structure_TopLevelEmpty(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{ }"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Empty Nested Structure, { {} } +void TestConverter_Structure_NestedEmpty(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1:STRUCT\" : { }\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Empty Array, { [] } +void TestConverter_Array_Empty(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1:ARRAY-?\" : []\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +void TestConverter_Array_Empty_ImplicitProfileTag2(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + writer.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, + CHIP_NO_ERROR == + writer.StartContainer(TLV::ProfileTag(kImplicitProfileId, 10000), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"emptyarray:10000:ARRAY-?\" : []\n" + "}\n"; + std::string jsonExpected = "{\n" + " \"10000:ARRAY-?\" : []\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonExpected); +} + +void TestConverter_Array_Empty_ImplicitProfileTag4(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + writer.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, + CHIP_NO_ERROR == + writer.StartContainer(TLV::ProfileTag(kImplicitProfileId, 1000000), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1000000:ARRAY-?\" : []\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Two Signed Integers with context specific tags: {0 = 42, 1 = -17} +void TestConverter_IntsWithContextTags(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(0), static_cast(42))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), static_cast(-17))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:INT\" : 42,\n" + " \"1:INT\" : -17\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Structure with Two Signed Integers with context specific tags: { {0 = 42, 1 = -17} } +void TestConverter_Struct_IntsWithContextTags(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Structure, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(0), static_cast(42))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), static_cast(-17))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:STRUCT\" : {\n" + " \"0:INT\" : 42,\n" + " \"1:INT\" : -17\n" + " }\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Array of Signed Integers: { [0, 1, 2, 3, 4] } +void TestConverter_Array_Ints(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(0))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(1))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(2))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(3))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(4))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:ARRAY-INT\" : [\n" + " 0,\n" + " 1,\n" + " 2,\n" + " 3,\n" + " 4\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Array of Long Signed Integers: { [42, -17, -170000, 40000000000] } +void TestConverter_Array_Ints2(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(42))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(-17))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(-170000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(40000000000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:ARRAY-INT\" : [\n" + " 42,\n" + " -17,\n" + " -170000,\n" + " \"40000000000\"\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Array of Signed Integers with MIN/MAX values for each type int8_t/int16_t/int32_t/int64_t +void TestConverter_Array_IntsMinMax(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(INT8_MIN))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(INT8_MAX))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(INT16_MIN))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(INT16_MAX))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(INT32_MIN))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(INT32_MAX))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(INT64_MIN))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(INT64_MAX))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:ARRAY-INT\" : [\n" + " -128,\n" + " 127,\n" + " -32768,\n" + " 32767,\n" + " -2147483648,\n" + " 2147483647,\n" + " -9223372036854775808,\n" + " 9223372036854775807\n" + " ]\n" + "}\n"; + std::string expectedString = "{\n" + " \"0:ARRAY-INT\" : [\n" + " -128,\n" + " 127,\n" + " -32768,\n" + " 32767,\n" + " -2147483648,\n" + " 2147483647,\n" + " \"-9223372036854775808\",\n" + " \"9223372036854775807\"\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, expectedString); +} + +// Array of Long Unsigned Integers: { [42, 170000, 40000000000] } +void TestConverter_Array_UInts(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(42))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(170000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(40000000000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:ARRAY-UINT\" : [\n" + " 42,\n" + " 170000,\n" + " \"40000000000\"\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Array of Unsigned Integers, where each element represents MAX possible value for unsigned +// integere types uint8_t, uint16_t, uint32_t, uint64_t: [0xFF, 0xFFFF, 0xFFFFFFFF, 0xFFFFFFFF_FFFFFFFF] +void TestConverter_Array_UIntsMax(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(UINT8_MAX))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(UINT16_MAX))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(UINT32_MAX))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(UINT64_MAX))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:ARRAY-UINT\" : [\n" + " 255,\n" + " 65535,\n" + " 4294967295,\n" + " 18446744073709551615\n" + " ]\n" + "}\n"; + std::string expectedString = "{\n" + " \"0:ARRAY-UINT\" : [\n" + " 255,\n" + " 65535,\n" + " 4294967295,\n" + " \"18446744073709551615\"\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, expectedString); +} + +// Array of Doubles: { [1.1, 134.2763, -12345.87] } +void TestConverter_Array_Doubles(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(1.1))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(134.2763))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(-12345.87))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:ARRAY-DOUBLE\" : [\n" + " 1.1,\n" + " 134.2763,\n" + " -12345.87\n" + " ]\n" + "}\n"; + std::string expectedString = "{\n" + " \"0:ARRAY-DOUBLE\" : [\n" + " 1.1000000000000001,\n" + " 134.27629999999999,\n" + " -12345.870000000001\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, expectedString); +} + +// Array of Floats: { [1.1, 134.2763, -12345.87] } +void TestConverter_Array_Floats(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + writer.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, + CHIP_NO_ERROR == + writer.StartContainer(TLV::ProfileTag(kImplicitProfileId, 1000), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(1.1))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(134.2763))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(-12345.87))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1000:ARRAY-FLOAT\" : [\n" + " 1.1,\n" + " 134.2763,\n" + " -12345.87\n" + " ]\n" + "}\n"; + std::string expectedString = "{\n" + " \"1000:ARRAY-FLOAT\" : [\n" + " 1.1000000238418579,\n" + " 134.27630615234375,\n" + " -12345.8701171875\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, expectedString); +} + +// Array of Strings: ["ABC", "Options", "more"] +void TestConverter_Array_Strings(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + writer.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, + CHIP_NO_ERROR == + writer.StartContainer(TLV::ProfileTag(kImplicitProfileId, 100000), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::AnonymousTag(), "ABC")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::AnonymousTag(), "Options")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::AnonymousTag(), "more")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"100000:ARRAY-STRING\" : [\n" + " \"ABC\",\n" + " \"Options\",\n" + " \"more\"\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Array of Booleans: [true, false, false] +void TestConverter_Array_Booleans(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(255), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), false)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), false)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"255:ARRAY-BOOL\" : [\n" + " true,\n" + " false,\n" + " false\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Array of Nulls: [null, null] +void TestConverter_Array_Nulls(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutNull(TLV::AnonymousTag())); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutNull(TLV::AnonymousTag())); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1:ARRAY-NULL\" : [\n" + " null,\n" + " null\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Context tag 255 (max), Unsigned Integer, 1-octet value: {255 = 42U} +void TestConverter_Struct_UInt(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Structure, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(255), static_cast(42))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"value:0:STRUCT\" : {\n" + " \"name:255:UINT\" : 42\n" + " }\n" + "}\n"; + std::string jsonExpected = "{\n" + " \"0:STRUCT\" : {\n" + " \"255:UINT\" : 42\n" + " }\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonExpected); +} + +// Context and Common Profile tags, Unsigned Integer structure: {255 = 42, 256 = 17000, 65535 = +// 1, 65536 = 345678, 4294967295 = 500000000000} +void TestConverter_Struct_MixedTags(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + writer.Init(buf); + writer.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Structure, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(255), static_cast(42))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ProfileTag(kImplicitProfileId, 256), static_cast(17000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ProfileTag(kImplicitProfileId, 65535), static_cast(1))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ProfileTag(kImplicitProfileId, 65536), static_cast(345678))); + NL_TEST_ASSERT( + gSuite, CHIP_NO_ERROR == writer.Put(TLV::ProfileTag(kImplicitProfileId, 4294967295), static_cast(500000000000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"0:STRUCT\" : {\n" + " \"255:UINT\" : 42,\n" + " \"256:UINT\" : 17000,\n" + " \"65535:UINT\" : 1,\n" + " \"65536:UINT\" : 345678,\n" + " \"4294967295:UINT\" : \"500000000000\"\n" + " }\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonString); +} + +// Structure with mixed elements +void TestConverter_Struct_MixedElements(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + char bytes[] = "Test ByteString Value"; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Structure, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(0), static_cast(20))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(2), static_cast(0))); + NL_TEST_ASSERT( + gSuite, + CHIP_NO_ERROR == + writer.PutBytes(TLV::ContextTag(3), reinterpret_cast(bytes), static_cast(strlen(bytes)))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::ContextTag(4), "hello")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(5), static_cast(-500000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(6), static_cast(17.9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(7), static_cast(17.9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"c:0:STRUCT\" : {\n" + " \"z:0:INT\" : 20,\n" + " \"b:1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"abc:3:BYTES\" : \"VGVzdCBCeXRlU3RyaW5nIFZhbHVl\",\n" + " \"cc:4:STRING\" : \"hello\",\n" + " \"tt:5:INT\" : -500000,\n" + " \"AA:6:DOUBLE\" : 17.9,\n" + " \"B:7:FLOAT\" : 17.9\n" + " }\n" + "}\n"; + std::string expectedString = "{\n" + " \"0:STRUCT\" : {\n" + " \"0:INT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"VGVzdCBCeXRlU3RyaW5nIFZhbHVl\",\n" + " \"4:STRING\" : \"hello\",\n" + " \"5:INT\" : -500000,\n" + " \"6:DOUBLE\" : 17.899999999999999,\n" + " \"7:FLOAT\" : 17.899999618530273\n" + " }\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, expectedString); +} + +// Array of structures with mixed elements +void TestConverter_Array_Structures(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + TLV::TLVType containerType3; + char bytes1[] = "Test ByteString Value 1"; + char bytes2[] = "Test ByteString Value 2"; + + writer.Init(buf); + writer.ImplicitProfileId = kImplicitProfileId; + + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, + CHIP_NO_ERROR == + writer.StartContainer(TLV::ProfileTag(kImplicitProfileId, 1000), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(0), static_cast(20))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(2), static_cast(0))); + NL_TEST_ASSERT( + gSuite, + CHIP_NO_ERROR == + writer.PutBytes(TLV::ContextTag(3), reinterpret_cast(bytes1), static_cast(strlen(bytes1)))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::ContextTag(4), "hello1")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(5), static_cast(-500000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(6), static_cast(17.9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(7), static_cast(17.9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(0), static_cast(-10))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), false)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(2), static_cast(128))); + NL_TEST_ASSERT( + gSuite, + CHIP_NO_ERROR == + writer.PutBytes(TLV::ContextTag(3), reinterpret_cast(bytes2), static_cast(strlen(bytes2)))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::ContextTag(4), "hello2")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(5), static_cast(40000000000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(6), static_cast(-1754.923))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(7), static_cast(97.945))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"1000:ARRAY-STRUCT\": [\n" + " {\n" + " \"0:INT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"VGVzdCBCeXRlU3RyaW5nIFZhbHVlIDE=\",\n" + " \"4:STRING\" : \"hello1\",\n" + " \"5:INT\" : -500000,\n" + " \"6:DOUBLE\" : 17.9,\n" + " \"7:FLOAT\" : 17.9\n" + " },\n" + " {\n" + " \"0:INT\" : -10,\n" + " \"1:BOOL\" : false,\n" + " \"2:UINT\" : 128,\n" + " \"3:BYTES\" : \"VGVzdCBCeXRlU3RyaW5nIFZhbHVlIDI=\",\n" + " \"4:STRING\" : \"hello2\",\n" + " \"5:INT\" : \"40000000000\",\n" + " \"6:DOUBLE\" : -1754.923,\n" + " \"7:FLOAT\" : 97.945\n" + " }\n" + " ]\n" + "}\n"; + std::string expectedString = "{\n" + " \"1000:ARRAY-STRUCT\": [\n" + " {\n" + " \"0:INT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"VGVzdCBCeXRlU3RyaW5nIFZhbHVlIDE=\",\n" + " \"4:STRING\" : \"hello1\",\n" + " \"5:INT\" : -500000,\n" + " \"6:DOUBLE\" : 17.899999999999999,\n" + " \"7:FLOAT\" : 17.899999618530273\n" + " },\n" + " {\n" + " \"0:INT\" : -10,\n" + " \"1:BOOL\" : false,\n" + " \"2:UINT\" : 128,\n" + " \"3:BYTES\" : \"VGVzdCBCeXRlU3RyaW5nIFZhbHVlIDI=\",\n" + " \"4:STRING\" : \"hello2\",\n" + " \"5:INT\" : \"40000000000\",\n" + " \"6:DOUBLE\" : -1754.923,\n" + " \"7:FLOAT\" : 97.944999694824219\n" + " }\n" + " ]\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, expectedString); +} + +// Top level with mixed elements +void TestConverter_TopLevel_MixedElements(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + TLV::TLVType containerType3; + char bytes[] = "Test array member 0"; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(0), static_cast(42))); + NL_TEST_ASSERT( + gSuite, + CHIP_NO_ERROR == + writer.PutBytes(TLV::ContextTag(1), reinterpret_cast(bytes), static_cast(strlen(bytes)))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(2), static_cast(156.398))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(3), static_cast(73709551615))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(4), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutNull(TLV::ContextTag(5))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(6), TLV::kTLVType_Structure, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::ContextTag(1), "John")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(2), static_cast(34))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(3), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(4), TLV::kTLVType_Array, containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(5))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(10))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(5), TLV::kTLVType_Array, containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::AnonymousTag(), "Ammy")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::AnonymousTag(), "David")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::AnonymousTag(), "Larry")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(6), TLV::kTLVType_Array, containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), false)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(7), static_cast(0.0))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Finalize()); + + std::string jsonString = "{\n" + " \"value:0:INT\": 42,\n" + " \"value:1:BYTES\": \"VGVzdCBhcnJheSBtZW1iZXIgMA==\",\n" + " \"value:2:DOUBLE\": 156.398,\n" + " \"value:3:UINT\": \"73709551615\",\n" + " \"value:4:BOOL\": true,\n" + " \"value:5:NULL\": null,\n" + " \"value:6:STRUCT\": {\n" + " \"name:1:STRING\": \"John\",\n" + " \"age:2:UINT\": 34,\n" + " \"approved:3:BOOL\": true,\n" + " \"kids:4:ARRAY-INT\": [\n" + " 5,\n" + " 9,\n" + " 10\n" + " ],\n" + " \"names:5:ARRAY-STRING\": [\n" + " \"Ammy\",\n" + " \"David\",\n" + " \"Larry\"\n" + " ],\n" + " \"6:ARRAY-BOOL\": [\n" + " true,\n" + " false,\n" + " true\n" + " ]\n" + " },\n" + " \"value:7:FLOAT\": 0.0\n" + "}\n"; + std::string jsonExpected = "{\n" + " \"0:INT\": 42,\n" + " \"1:BYTES\": \"VGVzdCBhcnJheSBtZW1iZXIgMA==\",\n" + " \"2:DOUBLE\": 156.398,\n" + " \"3:UINT\": \"73709551615\",\n" + " \"4:BOOL\": true,\n" + " \"5:NULL\": null,\n" + " \"6:STRUCT\": {\n" + " \"1:STRING\": \"John\",\n" + " \"2:UINT\": 34,\n" + " \"3:BOOL\": true,\n" + " \"4:ARRAY-INT\": [\n" + " 5,\n" + " 9,\n" + " 10\n" + " ],\n" + " \"5:ARRAY-STRING\": [\n" + " \"Ammy\",\n" + " \"David\",\n" + " \"Larry\"\n" + " ],\n" + " \"6:ARRAY-BOOL\": [\n" + " true,\n" + " false,\n" + " true\n" + " ]\n" + " },\n" + " \"7:FLOAT\": 0.0\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonExpected); +} + +// Complex Structure from README +void TestConverter_Structure_FromReadme(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + uint8_t buf[256]; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + TLV::TLVType containerType3; + uint8_t bytes1[] = { 0x00, 0x01, 0x02, 0x03, 0x04 }; + uint8_t bytes2[] = { 0xFF }; + uint8_t bytes3[] = { 0x4A, 0xEF, 0x88 }; + char bytes4[] = "Test Bytes"; + + writer.Init(buf); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(0), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(0), static_cast(8))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Structure, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(0), static_cast(12))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), false)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::ContextTag(2), "example")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(2), static_cast(40000000000))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(3), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(4), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(5), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(1.1))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(134.2763))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(-12345.87))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), std::numeric_limits::infinity())); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(62534))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(-62534))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(6), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutBytes(TLV::AnonymousTag(), bytes1, static_cast(sizeof(bytes1)))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutBytes(TLV::AnonymousTag(), bytes2, static_cast(sizeof(bytes2)))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutBytes(TLV::AnonymousTag(), bytes3, static_cast(sizeof(bytes3)))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT( + gSuite, + CHIP_NO_ERROR == + writer.PutBytes(TLV::ContextTag(7), reinterpret_cast(bytes4), static_cast(strlen(bytes4)))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(8), static_cast(17.9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(9), static_cast(17.9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(10), -std::numeric_limits::infinity())); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(11), TLV::kTLVType_Structure, containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.PutString(TLV::ContextTag(1), "John")); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(2), static_cast(34))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(3), true)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(4), TLV::kTLVType_Array, containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(5))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(9))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(10))); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType3)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(gSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + + std::string jsonString = "{\n" + " \"0:ARRAY-STRUCT\" : [\n" + " {\n" + " \"0:INT\" : 8,\n" + " \"1:BOOL\" : true\n" + " }\n" + " ],\n" + " \"1:STRUCT\" : {\n" + " \"0:INT\" : 12,\n" + " \"1:BOOL\" : false,\n" + " \"2:STRING\" : \"example\"\n" + " },\n" + " \"2:INT\" : \"40000000000\",\n" + " \"isQualified:3:BOOL\" : true,\n" + " \"4:ARRAY-?\" : [],\n" + " \"5:ARRAY-DOUBLE\" : [\n" + " 1.1000000000000001,\n" + " 134.27629999999999,\n" + " -12345.870000000001,\n" + " \"Infinity\",\n" + " 62534.0,\n" + " -62534.0\n" + " ],\n" + " \"6:ARRAY-BYTES\" : [\n" + " \"AAECAwQ=\",\n" + " \"/w==\",\n" + " \"Su+I\"\n" + " ],\n" + " \"7:BYTES\" : \"VGVzdCBCeXRlcw==\",\n" + " \"8:DOUBLE\" : 17.899999999999999,\n" + " \"9:FLOAT\" : 17.899999618530273,\n" + " \"10:FLOAT\" : \"-Infinity\",\n" + " \"contact:11:STRUCT\" : {\n" + " \"name:1:STRING\" : \"John\",\n" + " \"age:2:UINT\" : 34,\n" + " \"approved:3:BOOL\" : true,\n" + " \"kids:4:ARRAY-INT\" : [\n" + " 5,\n" + " 9,\n" + " 10\n" + " ]\n" + " }\n" + "}\n"; + std::string jsonExpected = "{\n" + " \"0:ARRAY-STRUCT\" : [\n" + " {\n" + " \"0:INT\" : 8,\n" + " \"1:BOOL\" : true\n" + " }\n" + " ],\n" + " \"1:STRUCT\" : {\n" + " \"0:INT\" : 12,\n" + " \"1:BOOL\" : false,\n" + " \"2:STRING\" : \"example\"\n" + " },\n" + " \"2:INT\" : \"40000000000\",\n" + " \"3:BOOL\" : true,\n" + " \"4:ARRAY-?\" : [],\n" + " \"5:ARRAY-DOUBLE\" : [\n" + " 1.1000000000000001,\n" + " 134.27629999999999,\n" + " -12345.870000000001,\n" + " \"Infinity\",\n" + " 62534.0,\n" + " -62534.0\n" + " ],\n" + " \"6:ARRAY-BYTES\" : [\n" + " \"AAECAwQ=\",\n" + " \"/w==\",\n" + " \"Su+I\"\n" + " ],\n" + " \"7:BYTES\" : \"VGVzdCBCeXRlcw==\",\n" + " \"8:DOUBLE\" : 17.899999999999999,\n" + " \"9:FLOAT\" : 17.899999618530273,\n" + " \"10:FLOAT\" : \"-Infinity\",\n" + " \"11:STRUCT\" : {\n" + " \"1:STRING\" : \"John\",\n" + " \"2:UINT\" : 34,\n" + " \"3:BOOL\" : true,\n" + " \"4:ARRAY-INT\" : [\n" + " 5,\n" + " 9,\n" + " 10\n" + " ]\n" + " }\n" + "}\n"; + + ByteSpan tlvSpan(buf, writer.GetLengthWritten()); + CheckValidConversion(jsonString, tlvSpan, jsonExpected); +} + +void TestConverter_TlvToJson_ErrorCases(nlTestSuite * inSuite, void * inContext) +{ + CHIP_ERROR err; + TLV::TLVWriter writer; + TLV::TLVType containerType; + TLV::TLVType containerType2; + + struct TestCase + { + const ByteSpan nEncodedTlv; + CHIP_ERROR mExpectedResult; + const char * mNameString; + }; + + uint8_t buf1[32]; + writer.Init(buf1); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::CommonTag(1), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), true)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Finalize()); + ByteSpan topLevelStructWithTag(buf1, writer.GetLengthWritten()); + + uint8_t buf2[32]; + writer.Init(buf2); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), true)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Finalize()); + ByteSpan topLevelIsArray(buf2, writer.GetLengthWritten()); + + uint8_t buf3[32]; + writer.Init(buf3); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_List, containerType2)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Put(TLV::ContextTag(1), true)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Finalize()); + ByteSpan usingList(buf3, writer.GetLengthWritten()); + + uint8_t buf8[32]; + writer.Init(buf8); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::ContextTag(1), TLV::kTLVType_Array, containerType2)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(42))); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(-170000))); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Put(TLV::AnonymousTag(), static_cast(42456))); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.EndContainer(containerType2)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Finalize()); + ByteSpan arrayWithMixedElements(buf8, writer.GetLengthWritten()); + + uint8_t buf9[32]; + writer.Init(buf9); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Put(TLV::ProfileTag(0xAA55FEED, 234), static_cast(42))); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.EndContainer(containerType)); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == writer.Finalize()); + ByteSpan useFullyQualifiedTag(buf9, writer.GetLengthWritten()); + + // clang-format off + static const TestCase sTestCases[] = { + // TLV Encoded Input Expected Error Test Case String + // ================================================================================================== + { topLevelStructWithTag, CHIP_ERROR_INVALID_TLV_TAG, "Top-Level Struct is Not Anonymous" }, + { topLevelIsArray, CHIP_ERROR_WRONG_TLV_TYPE, "Top-Level is an Array" }, + { usingList, CHIP_ERROR_INVALID_TLV_ELEMENT, "Using Unsupported List Type" }, + { arrayWithMixedElements, CHIP_ERROR_INVALID_TLV_ELEMENT, "Array with Mixed Elements" }, + { useFullyQualifiedTag, CHIP_ERROR_INVALID_TLV_TAG, "Using Unsupported Fully Qualified Tag" }, + }; + // clang-format on + + for (const auto & testCase : sTestCases) + { + std::string jsonString; + err = TlvToJson(testCase.nEncodedTlv, jsonString); + NL_TEST_ASSERT(inSuite, err == testCase.mExpectedResult); + } +} + +void TestConverter_JsonToTlv_ErrorCases(nlTestSuite * inSuite, void * inContext) +{ + CHIP_ERROR err; + + struct TestCase + { + const std::string mJsonString; + CHIP_ERROR mExpectedResult; + const char * mNameString; + }; + + std::string arrayTypeStringExpectedBool = "{\n" + " \"value:1:ARRAY-BOOL\" : [\n" + " \"yes\",\n" + " \"no\"\n" + " ]\n" + "}\n"; + + std::string arrayTypeIntExpectedUInt = "{\n" + " \"value:1:ARRAY-UINT\" : [\n" + " 45,\n" + " -367\n" + " ]\n" + "}\n"; + + std::string arrayElementsWithName = "{\n" + " \"value:1:ARRAY-INT\" : [\n" + " \"1:INT\" : 45,\n" + " \"2:INT\" : -367\n" + " ]\n" + "}\n"; + + std::string invalidNameWithoutTagField = "{\n" + " \"value:ARRAY-BYTES\" : [\n" + " \"AAECAwQ=\"\n" + " ]\n" + "}\n"; + + std::string invalidNameWithoutTagField2 = "{\n" + " \"UINT\" : 42\n" + "}\n"; + + std::string invalidNameTagValueTooBig = "{\n" + " \"invalid:4294967296:UINT\" : 42\n" + "}\n"; + + std::string invalidNameWithNegativeTag = "{\n" + " \"-1:UINT\" : 42\n" + "}\n"; + + std::string invalidNameWithInvalidTypeField = "{\n" + " \"1:INTEGER\" : 42\n" + "}\n"; + + std::string invalidBytesBase64Value1 = "{\n" + " \"1:BYTES\" : \"SGVsbG8!\"\n" + "}\n"; + + std::string invalidBytesBase64Value2 = "{\n" + " \"1:BYTES\" : \"AAECw=Q\"\n" + "}\n"; + + std::string invalidBytesBase64Value3 = "{\n" + " \"1:BYTES\" : \"AAECwQ=\"\n" + "}\n"; + + std::string invalidPositiveInfinityValue = "{\n" + " \"1:DOUBLE\" : \"+Infinity\"\n" + "}\n"; + + std::string invalidFloatValueAsString = "{\n" + " \"1:FLOAT\" : \"1.1\"\n" + "}\n"; + + // clang-format off + static const TestCase sTestCases[] = { + // Json String Expected Error Test Case String + // ========================================================================================================================== + { arrayTypeStringExpectedBool, CHIP_ERROR_INVALID_ARGUMENT, "Array Type Is String While Bool Is Expected" }, + { arrayTypeIntExpectedUInt, CHIP_ERROR_INVALID_ARGUMENT, "Array Type Is Signed Integer While Unsigned Is Expected" }, + { arrayElementsWithName, CHIP_ERROR_INTERNAL, "Array Elements With Json Name" }, + { invalidNameWithoutTagField, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Name String Without Tag Field" }, + { invalidNameWithoutTagField2, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Name String Without Tag Field 2" }, + { invalidNameTagValueTooBig, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Name String Tag Value Larger than UINT32_MAX" }, + { invalidNameWithNegativeTag, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Name With Negative Tag Value" }, + { invalidNameWithInvalidTypeField, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Name With Invalid Type Field" }, + { invalidBytesBase64Value1, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Base64 Encoding: Invalid Character" }, + { invalidBytesBase64Value2, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Base64 Encoding: Invalid Character" }, + { invalidBytesBase64Value3, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Base64 Encoding: Invalid length" }, + { invalidPositiveInfinityValue, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Double Positive Infinity Encoding" }, + { invalidFloatValueAsString, CHIP_ERROR_INVALID_ARGUMENT, "Invalid Float Value Encoding as a String" }, + }; + // clang-format on + + for (const auto & testCase : sTestCases) + { + uint8_t buf[256]; + MutableByteSpan tlvSpan(buf); + err = JsonToTlv(testCase.mJsonString, tlvSpan); + NL_TEST_ASSERT(inSuite, err == testCase.mExpectedResult); + } +} + +int Initialize(void * apSuite) +{ + VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE); + return SUCCESS; +} + +int Finalize(void * aContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +const nlTest sTests[] = { + NL_TEST_DEF("Test Json Tlv Converter - Boolean True", TestConverter_Boolean_True), + NL_TEST_DEF("Test Json Tlv Converter - Signed Integer 1-Byte Positive", TestConverter_SignedInt_1BytePositive), + NL_TEST_DEF("Test Json Tlv Converter - Signed Integer 1-Byte Negative", TestConverter_SignedInt_1ByteNegative), + NL_TEST_DEF("Test Json Tlv Converter - Unsigned Integer 1-Byte", TestConverter_UnsignedInt_1Byte), + NL_TEST_DEF("Test Json Tlv Converter - Signed Integer 2-Bytes", TestConverter_SignedInt_2Bytes), + NL_TEST_DEF("Test Json Tlv Converter - Signed Integer 4-Bytes", TestConverter_SignedInt_4Bytes), + NL_TEST_DEF("Test Json Tlv Converter - Signed Integer 8-Bytes", TestConverter_SignedInt_8Bytes), + NL_TEST_DEF("Test Json Tlv Converter - Unsigned Integer 8-Bytes", TestConverter_UnsignedInt_8Bytes), + NL_TEST_DEF("Test Json Tlv Converter - UTF-8 String Hello!", TestConverter_UTF8String_Hello), + NL_TEST_DEF("Test Json Tlv Converter - Octet String", TestConverter_OctetString), + NL_TEST_DEF("Test Json Tlv Converter - Null", TestConverter_Null), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Single Precision 0.0", TestConverter_Float_0), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Single Precision 1/3", TestConverter_Float_1third), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Single Precision 17.9", TestConverter_Float_17_9), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Single Precision Positive Infinity", + TestConverter_Float_PositiveInfinity), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Single Precision Negative Infinity", + TestConverter_Float_NegativeInfinity), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Double Precision 0.0", TestConverter_Double_0), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Double Precision 1/3", TestConverter_Double_1third), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Double Precision 17.9", TestConverter_Double_17_9), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Double Precision Positive Infinity", + TestConverter_Double_PositiveInfinity), + NL_TEST_DEF("Test Json Tlv Converter - Floating Point Double Precision Negative Infinity", + TestConverter_Double_NegativeInfinity), + NL_TEST_DEF("Test Json Tlv Converter - Structure Top-Level Empty", TestConverter_Structure_TopLevelEmpty), + NL_TEST_DEF("Test Json Tlv Converter - Structure Nested Empty", TestConverter_Structure_NestedEmpty), + NL_TEST_DEF("Test Json Tlv Converter - Array Empty", TestConverter_Array_Empty), + NL_TEST_DEF("Test Json Tlv Converter - Array Empty with Implicit Profile Tag (length 2)", + TestConverter_Array_Empty_ImplicitProfileTag2), + NL_TEST_DEF("Test Json Tlv Converter - Array Empty with Implicit Profile Tag (length 4)", + TestConverter_Array_Empty_ImplicitProfileTag4), + NL_TEST_DEF("Test Json Tlv Converter - Two Signed Integers", TestConverter_IntsWithContextTags), + NL_TEST_DEF("Test Json Tlv Converter - Structure With Two Signed Integers", TestConverter_Struct_IntsWithContextTags), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Signed Integers", TestConverter_Array_Ints), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Long Signed Integers", TestConverter_Array_Ints2), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Min/Max Signed Integers", TestConverter_Array_IntsMinMax), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Long Unsigned Integers", TestConverter_Array_UInts), + NL_TEST_DEF("Test Json Tlv Converter - Array of Unsigned Integers with Max values", TestConverter_Array_UIntsMax), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Doubles", TestConverter_Array_Doubles), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Floats", TestConverter_Array_Floats), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Strings", TestConverter_Array_Strings), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Booleans", TestConverter_Array_Booleans), + NL_TEST_DEF("Test Json Tlv Converter - Array Of Nulls", TestConverter_Array_Nulls), + NL_TEST_DEF("Test Json Tlv Converter - Structure with Unsigned Integer", TestConverter_Struct_UInt), + NL_TEST_DEF("Test Json Tlv Converter - Structure Elements with Mixed Tags", TestConverter_Struct_MixedTags), + NL_TEST_DEF("Test Json Tlv Converter - Structure with Mixed Elements", TestConverter_Struct_MixedElements), + NL_TEST_DEF("Test Json Tlv Converter - Array of Structures with Mixed Elements", TestConverter_Array_Structures), + NL_TEST_DEF("Test Json Tlv Converter - Top-Level Structure with Mixed Elements", TestConverter_TopLevel_MixedElements), + NL_TEST_DEF("Test Json Tlv Converter - Complex Structure from the README File", TestConverter_Structure_FromReadme), + NL_TEST_DEF("Test Json Tlv Converter - Tlv to Json Error Cases", TestConverter_TlvToJson_ErrorCases), + NL_TEST_DEF("Test Json Tlv Converter - Json To Tlv Error Cases", TestConverter_JsonToTlv_ErrorCases), + NL_TEST_SENTINEL() +}; + +} // namespace + +int TestJsonToTlvToJson() +{ + nlTestSuite theSuite = { "JsonToTlvToJson", sTests, Initialize, Finalize }; + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestJsonToTlvToJson) diff --git a/src/lib/support/tests/TestTlvJson.cpp b/src/lib/support/tests/TestTlvJson.cpp new file mode 100644 index 00000000000000..95a8967820317e --- /dev/null +++ b/src/lib/support/tests/TestTlvJson.cpp @@ -0,0 +1,259 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace chip::Encoding; +using namespace chip; +using namespace chip::app; + +System::TLVPacketBufferBackingStore gStore; +TLV::TLVWriter gWriter; +TLV::TLVReader gReader; +nlTestSuite * gSuite; + +void SetupBuf() +{ + System::PacketBufferHandle buf; + + buf = System::PacketBufferHandle::New(1024); + gStore.Init(std::move(buf)); + + gWriter.Init(gStore); + gReader.Init(gStore); +} + +CHIP_ERROR SetupReader() +{ + gReader.Init(gStore); + return gReader.Next(); +} + +bool Matches(const char * referenceString, Json::Value & generatedValue) +{ + auto generatedStr = JsonToString(generatedValue); + + // Normalize the reference string to the expected compact value. + Json::Reader reader; + Json::Value referenceValue; + reader.parse(referenceString, referenceValue); + + Json::FastWriter writer; + writer.omitEndingLineFeed(); + auto compactReferenceString = writer.write(referenceValue); + + auto matches = (generatedStr == compactReferenceString); + + if (!matches) + { + printf("Didn't match!\n"); + printf("Reference:\n"); + printf("%s\n", compactReferenceString.c_str()); + + printf("Generated:\n"); + printf("%s\n", generatedStr.c_str()); + } + + return matches; +} + +template +void EncodeAndValidate(T val, const char * expectedJsonString) +{ + CHIP_ERROR err; + + SetupBuf(); + + err = DataModel::Encode(gWriter, TLV::AnonymousTag(), val); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + err = gWriter.Finalize(); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + err = SetupReader(); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + Json::Value d; + err = TlvToJson(gReader, d); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + bool matches = Matches(expectedJsonString, d); + NL_TEST_ASSERT(gSuite, matches); +} + +void TestConverter(nlTestSuite * inSuite, void * inContext) +{ + gSuite = inSuite; + + EncodeAndValidate(static_cast(30), + "{\n" + " \"value\" : 30\n" + "}\n"); + + EncodeAndValidate(static_cast(-30), + "{\n" + " \"value\" : -30\n" + "}\n"); + + EncodeAndValidate(false, + "{\n" + " \"value\" : false\n" + "}\n"); + + EncodeAndValidate(true, + "{\n" + " \"value\" : true\n" + "}\n"); + + EncodeAndValidate(1.0, + "{\n" + " \"value\" : 1.0\n" + "}\n"); + + const char charBuf[] = "hello"; + CharSpan charSpan(charBuf); + EncodeAndValidate(charSpan, + "{\n" + " \"value\" : \"hello\"\n" + "}\n"); + + // + // Validated using https://base64.guru/converter/encode/hex + // + const uint8_t byteBuf[] = { 0x01, 0x02, 0x03, 0x04, 0xff, 0xfe, 0x99, 0x88, 0xdd, 0xcd }; + ByteSpan byteSpan(byteBuf); + EncodeAndValidate(byteSpan, + "{\n" + " \"value\" : \"base64:AQIDBP/+mYjdzQ==\"\n" + "}\n"); + + DataModel::Nullable nullValue; + EncodeAndValidate(nullValue, + "{\n" + " \"value\" : null\n" + "}\n"); + + Clusters::UnitTesting::Structs::SimpleStruct::Type structVal; + structVal.a = 20; + structVal.b = true; + structVal.d = byteBuf; + structVal.e = charSpan; + structVal.g = 1.0; + structVal.h = 1.0; + + EncodeAndValidate(structVal, + "{\n" + " \"value\" : {\n" + " \"0\" : 20,\n" + " \"1\" : true,\n" + " \"2\" : 0,\n" + " \"3\" : \"base64:AQIDBP/+mYjdzQ==\",\n" + " \"4\" : \"hello\",\n" + " \"5\" : 0,\n" + " \"6\" : 1.0,\n" + " \"7\" : 1.0\n" + " }\n" + "}\n"); + + uint8_t int8uListData[] = { 1, 2, 3, 4 }; + DataModel::List int8uList; + + int8uList = int8uListData; + + EncodeAndValidate(int8uList, + "{\n" + " \"value\" : [ 1, 2, 3, 4 ]\n" + "}\n"); + + int8uList = {}; + EncodeAndValidate(int8uList, + "{\n" + " \"value\" : []\n" + "}\n"); + + DataModel::Nullable> nullValueList; + EncodeAndValidate(nullValueList, + "{\n" + " \"value\" : null\n" + "}\n"); + + Clusters::UnitTesting::Structs::SimpleStruct::Type structListData[2] = { structVal, structVal }; + DataModel::List structList; + + structList = structListData; + + EncodeAndValidate(structList, + "{\n" + " \"value\" : [\n" + " {\n" + " \"0\" : 20,\n" + " \"1\" : true,\n" + " \"2\" : 0,\n" + " \"3\" : \"base64:AQIDBP/+mYjdzQ==\",\n" + " \"4\" : \"hello\",\n" + " \"5\" : 0,\n" + " \"6\" : 1.0,\n" + " \"7\" : 1.0\n" + " },\n" + " {\n" + " \"0\" : 20,\n" + " \"1\" : true,\n" + " \"2\" : 0,\n" + " \"3\" : \"base64:AQIDBP/+mYjdzQ==\",\n" + " \"4\" : \"hello\",\n" + " \"5\" : 0,\n" + " \"6\" : 1.0,\n" + " \"7\" : 1.0\n" + " }\n" + " ]\n" + "}\n"); +} + +int Initialize(void * apSuite) +{ + VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE); + return SUCCESS; +} + +int Finalize(void * aContext) +{ + (void) gStore.Release(); + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +const nlTest sTests[] = { NL_TEST_DEF("TestConverter", TestConverter), NL_TEST_SENTINEL() }; + +} // namespace + +int TestTlvJson() +{ + nlTestSuite theSuite = { "TlvJson", sTests, Initialize, Finalize }; + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestTlvJson) diff --git a/src/lib/support/tests/TestTlvToJson.cpp b/src/lib/support/tests/TestTlvToJson.cpp index 95a8967820317e..5e4bb77cb9ef13 100644 --- a/src/lib/support/tests/TestTlvToJson.cpp +++ b/src/lib/support/tests/TestTlvToJson.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2021-2023 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -52,20 +52,12 @@ CHIP_ERROR SetupReader() return gReader.Next(); } -bool Matches(const char * referenceString, Json::Value & generatedValue) +bool Matches(const std::string & referenceString, const std::string & generatedString) { - auto generatedStr = JsonToString(generatedValue); + auto compactReferenceString = PrettyPrintJsonString(referenceString); + auto compactGeneratedString = PrettyPrintJsonString(generatedString); - // Normalize the reference string to the expected compact value. - Json::Reader reader; - Json::Value referenceValue; - reader.parse(referenceString, referenceValue); - - Json::FastWriter writer; - writer.omitEndingLineFeed(); - auto compactReferenceString = writer.write(referenceValue); - - auto matches = (generatedStr == compactReferenceString); + auto matches = (compactGeneratedString == compactReferenceString); if (!matches) { @@ -74,20 +66,27 @@ bool Matches(const char * referenceString, Json::Value & generatedValue) printf("%s\n", compactReferenceString.c_str()); printf("Generated:\n"); - printf("%s\n", generatedStr.c_str()); + printf("%s\n", compactGeneratedString.c_str()); } return matches; } template -void EncodeAndValidate(T val, const char * expectedJsonString) +void EncodeAndValidate(T val, const std::string & expectedJsonString) { CHIP_ERROR err; + TLV::TLVType container; SetupBuf(); - err = DataModel::Encode(gWriter, TLV::AnonymousTag(), val); + err = gWriter.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, container); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + err = DataModel::Encode(gWriter, TLV::ContextTag(1), val); + NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); + + err = gWriter.EndContainer(container); NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); err = gWriter.Finalize(); @@ -96,65 +95,69 @@ void EncodeAndValidate(T val, const char * expectedJsonString) err = SetupReader(); NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); - Json::Value d; - err = TlvToJson(gReader, d); + std::string jsonString; + err = TlvToJson(gReader, jsonString); NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR); - bool matches = Matches(expectedJsonString, d); + bool matches = Matches(expectedJsonString, jsonString); NL_TEST_ASSERT(gSuite, matches); } void TestConverter(nlTestSuite * inSuite, void * inContext) { + std::string jsonString; + gSuite = inSuite; - EncodeAndValidate(static_cast(30), - "{\n" - " \"value\" : 30\n" - "}\n"); - - EncodeAndValidate(static_cast(-30), - "{\n" - " \"value\" : -30\n" - "}\n"); - - EncodeAndValidate(false, - "{\n" - " \"value\" : false\n" - "}\n"); - - EncodeAndValidate(true, - "{\n" - " \"value\" : true\n" - "}\n"); - - EncodeAndValidate(1.0, - "{\n" - " \"value\" : 1.0\n" - "}\n"); - - const char charBuf[] = "hello"; - CharSpan charSpan(charBuf); - EncodeAndValidate(charSpan, - "{\n" - " \"value\" : \"hello\"\n" - "}\n"); - - // + jsonString = "{\n" + " \"1:UINT\" : 30\n" + "}\n"; + EncodeAndValidate(static_cast(30), jsonString); + + jsonString = "{\n" + " \"1:INT\" : -30\n" + "}\n"; + EncodeAndValidate(static_cast(-30), jsonString); + + jsonString = "{\n" + " \"1:BOOL\" : false\n" + "}\n"; + EncodeAndValidate(false, jsonString); + + jsonString = "{\n" + " \"1:BOOL\" : true\n" + "}\n"; + EncodeAndValidate(true, jsonString); + + jsonString = "{\n" + " \"1:FLOAT\" : 1.0\n" + "}\n"; + EncodeAndValidate(static_cast(1.0), jsonString); + + jsonString = "{\n" + " \"1:DOUBLE\" : 1.0\n" + "}\n"; + EncodeAndValidate(static_cast(1.0), jsonString); + + CharSpan charSpan = CharSpan::fromCharString("hello"); + jsonString = "{\n" + " \"1:STRING\" : \"hello\"\n" + "}\n"; + EncodeAndValidate(charSpan, jsonString); + // Validated using https://base64.guru/converter/encode/hex - // const uint8_t byteBuf[] = { 0x01, 0x02, 0x03, 0x04, 0xff, 0xfe, 0x99, 0x88, 0xdd, 0xcd }; ByteSpan byteSpan(byteBuf); - EncodeAndValidate(byteSpan, - "{\n" - " \"value\" : \"base64:AQIDBP/+mYjdzQ==\"\n" - "}\n"); + jsonString = "{\n" + " \"1:BYTES\" : \"AQIDBP/+mYjdzQ==\"\n" + "}\n"; + EncodeAndValidate(byteSpan, jsonString); DataModel::Nullable nullValue; - EncodeAndValidate(nullValue, - "{\n" - " \"value\" : null\n" - "}\n"); + jsonString = "{\n" + " \"1:NULL\" : null\n" + "}\n"; + EncodeAndValidate(nullValue, jsonString); Clusters::UnitTesting::Structs::SimpleStruct::Type structVal; structVal.a = 20; @@ -164,72 +167,70 @@ void TestConverter(nlTestSuite * inSuite, void * inContext) structVal.g = 1.0; structVal.h = 1.0; - EncodeAndValidate(structVal, - "{\n" - " \"value\" : {\n" - " \"0\" : 20,\n" - " \"1\" : true,\n" - " \"2\" : 0,\n" - " \"3\" : \"base64:AQIDBP/+mYjdzQ==\",\n" - " \"4\" : \"hello\",\n" - " \"5\" : 0,\n" - " \"6\" : 1.0,\n" - " \"7\" : 1.0\n" - " }\n" - "}\n"); + jsonString = "{\n" + " \"1:STRUCT\" : {\n" + " \"0:UINT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"AQIDBP/+mYjdzQ==\",\n" + " \"4:STRING\" : \"hello\",\n" + " \"5:UINT\" : 0,\n" + " \"6:FLOAT\" : 1.0,\n" + " \"7:DOUBLE\" : 1.0\n" + " }\n" + "}\n"; + EncodeAndValidate(structVal, jsonString); uint8_t int8uListData[] = { 1, 2, 3, 4 }; DataModel::List int8uList; - int8uList = int8uListData; + int8uList = int8uListData; + jsonString = "{\n" + " \"1:ARRAY-UINT\" : [ 1, 2, 3, 4 ]\n" + "}\n"; + EncodeAndValidate(int8uList, jsonString); - EncodeAndValidate(int8uList, - "{\n" - " \"value\" : [ 1, 2, 3, 4 ]\n" - "}\n"); - - int8uList = {}; - EncodeAndValidate(int8uList, - "{\n" - " \"value\" : []\n" - "}\n"); + int8uList = {}; + jsonString = "{\n" + " \"1:ARRAY-?\" : [ ]\n" + "}\n"; + EncodeAndValidate(int8uList, jsonString); DataModel::Nullable> nullValueList; - EncodeAndValidate(nullValueList, - "{\n" - " \"value\" : null\n" - "}\n"); + jsonString = "{\n" + " \"1:NULL\" : null\n" + "}\n"; + EncodeAndValidate(nullValueList, jsonString); Clusters::UnitTesting::Structs::SimpleStruct::Type structListData[2] = { structVal, structVal }; DataModel::List structList; structList = structListData; - - EncodeAndValidate(structList, - "{\n" - " \"value\" : [\n" - " {\n" - " \"0\" : 20,\n" - " \"1\" : true,\n" - " \"2\" : 0,\n" - " \"3\" : \"base64:AQIDBP/+mYjdzQ==\",\n" - " \"4\" : \"hello\",\n" - " \"5\" : 0,\n" - " \"6\" : 1.0,\n" - " \"7\" : 1.0\n" - " },\n" - " {\n" - " \"0\" : 20,\n" - " \"1\" : true,\n" - " \"2\" : 0,\n" - " \"3\" : \"base64:AQIDBP/+mYjdzQ==\",\n" - " \"4\" : \"hello\",\n" - " \"5\" : 0,\n" - " \"6\" : 1.0,\n" - " \"7\" : 1.0\n" - " }\n" - " ]\n" - "}\n"); + jsonString = "{\n" + " \"1:ARRAY-STRUCT\" : [\n" + " {\n" + " \"0:UINT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"AQIDBP/+mYjdzQ==\",\n" + " \"4:STRING\" : \"hello\",\n" + " \"5:UINT\" : 0,\n" + " \"6:FLOAT\" : 1.0,\n" + " \"7:DOUBLE\" : 1.0\n" + " },\n" + " {\n" + " \"0:UINT\" : 20,\n" + " \"1:BOOL\" : true,\n" + " \"2:UINT\" : 0,\n" + " \"3:BYTES\" : \"AQIDBP/+mYjdzQ==\",\n" + " \"4:STRING\" : \"hello\",\n" + " \"5:UINT\" : 0,\n" + " \"6:FLOAT\" : 1.0,\n" + " \"7:DOUBLE\" : 1.0\n" + " }\n" + " ]\n" + "}\n"; + EncodeAndValidate(structList, jsonString); } int Initialize(void * apSuite) @@ -249,11 +250,11 @@ const nlTest sTests[] = { NL_TEST_DEF("TestConverter", TestConverter), NL_TEST_S } // namespace -int TestTlvJson() +int TestTlvToJson() { - nlTestSuite theSuite = { "TlvJson", sTests, Initialize, Finalize }; + nlTestSuite theSuite = { "TlvToJson", sTests, Initialize, Finalize }; nlTestRunner(&theSuite, nullptr); return nlTestRunnerStats(&theSuite); } -CHIP_REGISTER_TEST_SUITE(TestTlvJson) +CHIP_REGISTER_TEST_SUITE(TestTlvToJson)