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)