Simplify TLV <-> JSON parsing (#32342)

* Simplify TLV <-> JSON parsing

- Remove error-prone/repeated `strtol` usage, replace with std::from_chars
- Remove needless validation logic done de-facto by other parts

Fixes #32341

Testing done:

- Added test cases for edges of octet strings.
- All existing test cases pass otherwise.

* Restyled by clang-format

* Fix build where CHIP_ERROR doesn't format as string

* Fix CI again

* Simplify further

* Restyled by clang-format

* Fix lint

---------

Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py
index 6943c26..f471588 100644
--- a/scripts/tools/check_includes_config.py
+++ b/scripts/tools/check_includes_config.py
@@ -164,7 +164,7 @@
     'src/app/PendingResponseTrackerImpl.h': {'unordered_set'},
 
     # Not intended for embedded clients
-    'src/lib/support/jsontlv/JsonToTlv.cpp': {'sstream'},
+    'src/lib/support/jsontlv/JsonToTlv.cpp': {'sstream', 'string', 'vector'},
     'src/lib/support/jsontlv/JsonToTlv.h': {'string'},
     'src/lib/support/jsontlv/TlvToJson.h': {'string'},
     'src/lib/support/jsontlv/TextFormat.h': {'string'},
diff --git a/src/lib/support/jsontlv/JsonToTlv.cpp b/src/lib/support/jsontlv/JsonToTlv.cpp
index 0cc8161..11bfbca 100644
--- a/src/lib/support/jsontlv/JsonToTlv.cpp
+++ b/src/lib/support/jsontlv/JsonToTlv.cpp
@@ -15,8 +15,14 @@
  *    limitations under the License.
  */
 
+#include <stdint.h>
+
 #include <algorithm>
-#include <errno.h>
+#include <charconv>
+#include <sstream>
+#include <string>
+#include <vector>
+
 #include <json/json.h>
 #include <lib/support/Base64.h>
 #include <lib/support/SafeInt.h>
@@ -98,69 +104,6 @@
     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;
@@ -205,7 +148,17 @@
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR ParseJsonName(const std::string name, ElementContext & elementCtx, uint32_t implicitProfileId)
+template <typename T>
+CHIP_ERROR ParseNumericalField(const std::string & decimalString, T & outValue)
+{
+    const char * start_ptr       = decimalString.data();
+    const char * end_ptr         = decimalString.data() + decimalString.size();
+    auto [last_converted_ptr, _] = std::from_chars(start_ptr, end_ptr, outValue, 10);
+    VerifyOrReturnError(last_converted_ptr == end_ptr, CHIP_ERROR_INVALID_ARGUMENT);
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ParseJsonName(const std::string & name, ElementContext & elementCtx, uint32_t implicitProfileId)
 {
     uint32_t tagNumber                  = 0;
     const char * elementType            = nullptr;
@@ -216,28 +169,12 @@
 
     if (nameFields.size() == 2)
     {
-        VerifyOrReturnError(IsUnsignedInteger(nameFields[0]), CHIP_ERROR_INVALID_ARGUMENT);
-
-        char * endPtr;
-        errno                = 0;
-        unsigned long result = strtoul(nameFields[0].c_str(), &endPtr, 10);
-        VerifyOrReturnError(nameFields[0].c_str() != endPtr, CHIP_ERROR_INVALID_ARGUMENT);
-        VerifyOrReturnError((errno != ERANGE && result <= UINT32_MAX), CHIP_ERROR_INVALID_ARGUMENT);
-
-        tagNumber   = static_cast<uint32_t>(result);
+        ReturnErrorOnFailure(ParseNumericalField(nameFields[0], tagNumber));
         elementType = nameFields[1].c_str();
     }
     else if (nameFields.size() == 3)
     {
-        VerifyOrReturnError(IsUnsignedInteger(nameFields[1]), CHIP_ERROR_INVALID_ARGUMENT);
-
-        char * endPtr;
-        errno                = 0;
-        unsigned long result = strtoul(nameFields[1].c_str(), &endPtr, 10);
-        VerifyOrReturnError(nameFields[1].c_str() != endPtr, CHIP_ERROR_INVALID_ARGUMENT);
-        VerifyOrReturnError((errno != ERANGE && result <= UINT32_MAX), CHIP_ERROR_INVALID_ARGUMENT);
-
-        tagNumber   = static_cast<uint32_t>(result);
+        ReturnErrorOnFailure(ParseNumericalField(nameFields[1], tagNumber));
         elementType = nameFields[2].c_str();
     }
     else
@@ -278,16 +215,14 @@
     switch (elementCtx.type.tlvType)
     {
     case TLV::kTLVType_UnsignedInteger: {
-        uint64_t v;
+        uint64_t v = 0;
         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);
+            ReturnErrorOnFailure(ParseNumericalField(val.asString(), v));
         }
         else
         {
@@ -298,16 +233,14 @@
     }
 
     case TLV::kTLVType_SignedInteger: {
-        int64_t v;
+        int64_t v = 0;
         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);
+            ReturnErrorOnFailure(ParseNumericalField(val.asString(), v));
         }
         else
         {
@@ -377,20 +310,23 @@
         size_t encodedLen             = valAsString.length();
         VerifyOrReturnError(CanCastTo<uint16_t>(encodedLen), CHIP_ERROR_INVALID_ARGUMENT);
 
-        VerifyOrReturnError(IsValidBase64String(valAsString), CHIP_ERROR_INVALID_ARGUMENT);
+        // Check if the length is a multiple of 4 as strict padding is required.
+        VerifyOrReturnError(encodedLen % 4 == 0, CHIP_ERROR_INVALID_ARGUMENT);
 
         Platform::ScopedMemoryBuffer<uint8_t> byteString;
         byteString.Alloc(BASE64_MAX_DECODED_LEN(static_cast<uint16_t>(encodedLen)));
         VerifyOrReturnError(byteString.Get() != nullptr, CHIP_ERROR_NO_MEMORY);
 
         auto decodedLen = Base64Decode(valAsString.c_str(), static_cast<uint16_t>(encodedLen), byteString.Get());
+        VerifyOrReturnError(decodedLen < UINT16_MAX, CHIP_ERROR_INVALID_ARGUMENT);
         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()));
+        const std::string valAsString = val.asString();
+        ReturnErrorOnFailure(writer.PutString(tag, valAsString.data(), static_cast<uint32_t>(valAsString.size())));
         break;
     }
 
diff --git a/src/lib/support/tests/TestJsonToTlv.cpp b/src/lib/support/tests/TestJsonToTlv.cpp
index 539d272..3649764 100644
--- a/src/lib/support/tests/TestJsonToTlv.cpp
+++ b/src/lib/support/tests/TestJsonToTlv.cpp
@@ -151,6 +151,12 @@
                  "}\n";
     ConvertJsonToTlvAndValidate(byteSpan, jsonString);
 
+    // Empty bytes.
+    jsonString = "{\n"
+                 "   \"1:BYTES\" : \"\"\n"
+                 "}\n";
+    ConvertJsonToTlvAndValidate(ByteSpan{}, jsonString);
+
     DataModel::Nullable<uint8_t> nullValue;
     jsonString = "{\n"
                  "   \"1:NULL\" : null\n"
diff --git a/src/lib/support/tests/TestJsonToTlvToJson.cpp b/src/lib/support/tests/TestJsonToTlvToJson.cpp
index 5762034..b4f0a13 100644
--- a/src/lib/support/tests/TestJsonToTlvToJson.cpp
+++ b/src/lib/support/tests/TestJsonToTlvToJson.cpp
@@ -15,6 +15,8 @@
  *    limitations under the License.
  */
 
+#include <stdio.h>
+
 #include <app-common/zap-generated/cluster-objects.h>
 #include <app/data-model/Decode.h>
 #include <app/data-model/Encode.h>
@@ -328,6 +330,29 @@
     CheckValidConversion(jsonString, tlvSpan, jsonString);
 }
 
+// Octet String, empty
+void TestConverter_OctetString_Empty(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.PutBytes(TLV::ContextTag(1), nullptr, 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:BYTES\" : \"\"\n"
+                             "}\n";
+
+    ByteSpan tlvSpan(buf, writer.GetLengthWritten());
+    CheckValidConversion(jsonString, tlvSpan, jsonString);
+}
+
 // Null
 void TestConverter_Null(nlTestSuite * inSuite, void * inContext)
 {
@@ -1794,6 +1819,22 @@
                                            "   \"1:BYTES\" : \"AAECwQ=\"\n"
                                            "}\n";
 
+    std::string invalidBytesBase64Value4 = "{\n"
+                                           "   \"1:BYTES\" : \"AAECwQ\"\n"
+                                           "}\n";
+
+    std::string invalidBytesBase64Padding1 = "{\n"
+                                             "   \"1:BYTES\" : \"=\"\n"
+                                             "}\n";
+
+    std::string invalidBytesBase64Padding2 = "{\n"
+                                             "   \"1:BYTES\" : \"==\"\n"
+                                             "}\n";
+
+    std::string invalidBytesBase64Padding3 = "{\n"
+                                             "   \"1:BYTES\" : \"===\"\n"
+                                             "}\n";
+
     std::string invalidPositiveInfinityValue = "{\n"
                                                "   \"1:DOUBLE\" : \"+Infinity\"\n"
                                                "}\n";
@@ -1815,7 +1856,11 @@
         {  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"                 },
+        {  invalidBytesBase64Value3,        CHIP_ERROR_INVALID_ARGUMENT,  "Invalid Base64 Encoding: Invalid padding (missing 1)"    },
+        {  invalidBytesBase64Value4,        CHIP_ERROR_INVALID_ARGUMENT,  "Invalid Base64 Encoding: Invalid padding (missing 2)"    },
+        {  invalidBytesBase64Padding1,      CHIP_ERROR_INVALID_ARGUMENT,  "Invalid Base64 Encoding: Invalid padding (start 1)"      },
+        {  invalidBytesBase64Padding2,      CHIP_ERROR_INVALID_ARGUMENT,  "Invalid Base64 Encoding: Invalid padding (start 2)"      },
+        {  invalidBytesBase64Padding3,      CHIP_ERROR_INVALID_ARGUMENT,  "Invalid Base64 Encoding: Invalid padding (start 3)"      },
         {  invalidPositiveInfinityValue,    CHIP_ERROR_INVALID_ARGUMENT,  "Invalid Double Positive Infinity Encoding"               },
         {  invalidFloatValueAsString,       CHIP_ERROR_INVALID_ARGUMENT,  "Invalid Float Value Encoding as a String"                },
     };
@@ -1827,6 +1872,16 @@
         MutableByteSpan tlvSpan(buf);
         err = JsonToTlv(testCase.mJsonString, tlvSpan);
         NL_TEST_ASSERT(inSuite, err == testCase.mExpectedResult);
+#if CHIP_CONFIG_ERROR_FORMAT_AS_STRING
+        if (err != testCase.mExpectedResult)
+        {
+            std::string errStr{ err.Format() };
+            std::string expectedErrStr{ testCase.mExpectedResult.Format() };
+
+            printf("Case: %s, Error: %" CHIP_ERROR_FORMAT ", Expected: %" CHIP_ERROR_FORMAT ", Data: %s\n", testCase.mNameString,
+                   errStr.c_str(), expectedErrStr.c_str(), testCase.mJsonString.c_str());
+        }
+#endif // CHIP_CONFIG_ERROR_FORMAT_AS_STRING
     }
 }
 
@@ -1887,6 +1942,7 @@
     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 - Empty Octet String", TestConverter_OctetString_Empty),
     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),