From a86ea4b07320e10a39571ea358c0aa57def13924 Mon Sep 17 00:00:00 2001 From: Evgeni Margolis Date: Mon, 16 Jan 2023 21:55:43 -0800 Subject: [PATCH] [tlv] Implemented Getter for Localized String Identifier New method TLVReader::Get(Optional&) returns Localized String Identifier if present. The method takes what's after the first Information Separator 1 , and until end of string or second , and return the hex-decoded string identifier, if one was there. --- src/lib/core/DataModelTypes.h | 3 +- src/lib/core/TLVReader.cpp | 51 ++++++++++++++++++++++++++++-- src/lib/core/TLVReader.h | 18 +++++++++++ src/lib/core/tests/TestTLV.cpp | 57 ++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/lib/core/DataModelTypes.h b/src/lib/core/DataModelTypes.h index a25eb3e53ebd3b..fb79584c5c6988 100644 --- a/src/lib/core/DataModelTypes.h +++ b/src/lib/core/DataModelTypes.h @@ -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. @@ -41,6 +41,7 @@ typedef uint64_t FabricId; typedef uint8_t FabricIndex; typedef uint32_t FieldId; typedef uint16_t ListIndex; +typedef uint16_t LocalizedStringIdentifier; typedef uint32_t TransactionId; typedef uint16_t KeysetId; typedef uint8_t InteractionModelRevision; diff --git a/src/lib/core/TLVReader.cpp b/src/lib/core/TLVReader.cpp index 335e44b457f6f1..75f2269fe634b6 100644 --- a/src/lib/core/TLVReader.cpp +++ b/src/lib/core/TLVReader.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -298,10 +299,13 @@ CHIP_ERROR TLVReader::Get(ByteSpan & v) return CHIP_NO_ERROR; } +namespace { +constexpr int kUnicodeInformationSeparator1 = 0x1F; +constexpr size_t kMaxLocalizedStringIdentifierLen = 2 * sizeof(LocalizedStringIdentifier); +} // namespace + CHIP_ERROR TLVReader::Get(CharSpan & v) { - constexpr int kUnicodeInformationSeparator1 = 0x1F; - if (!TLVTypeIsUTF8String(ElementType())) { return CHIP_ERROR_WRONG_TLV_TYPE; @@ -324,6 +328,49 @@ CHIP_ERROR TLVReader::Get(CharSpan & v) return CHIP_NO_ERROR; } +CHIP_ERROR TLVReader::Get(Optional & lsid) +{ + lsid.ClearValue(); + VerifyOrReturnError(TLVTypeIsUTF8String(ElementType()), CHIP_ERROR_WRONG_TLV_TYPE); + + const uint8_t * bytes; + ReturnErrorOnFailure(GetDataPtr(bytes)); // Does length sanity checks + + uint32_t len = GetLength(); + + const uint8_t * infoSeparator1 = reinterpret_cast(memchr(bytes, kUnicodeInformationSeparator1, len)); + if (infoSeparator1 == nullptr) + { + return CHIP_NO_ERROR; + } + + const uint8_t * lsidPtr = infoSeparator1 + 1; + len -= static_cast(lsidPtr - bytes); + + const uint8_t * infoSeparator2 = reinterpret_cast(memchr(lsidPtr, kUnicodeInformationSeparator1, len)); + if (infoSeparator2 != nullptr) + { + len = static_cast(infoSeparator2 - lsidPtr); + } + + if (len == 0 || len > kMaxLocalizedStringIdentifierLen) + { + return CHIP_NO_ERROR; + } + + char idStr[kMaxLocalizedStringIdentifierLen] = { '0', '0', '0', '0' }; + memcpy(&idStr[kMaxLocalizedStringIdentifierLen - len], lsidPtr, len); + + LocalizedStringIdentifier id; + if (Encoding::UppercaseHexToUint16(idStr, sizeof(idStr), id) != sizeof(LocalizedStringIdentifier)) + { + return CHIP_NO_ERROR; + } + + lsid.SetValue(id); + return CHIP_NO_ERROR; +} + CHIP_ERROR TLVReader::GetBytes(uint8_t * buf, size_t bufSize) { if (!TLVTypeIsString(ElementType())) diff --git a/src/lib/core/TLVReader.h b/src/lib/core/TLVReader.h index f41b8c30ca6773..c20fbbadaaafc4 100644 --- a/src/lib/core/TLVReader.h +++ b/src/lib/core/TLVReader.h @@ -27,6 +27,9 @@ #pragma once +#include +#include + #include "TLVCommon.h" #include "TLVWriter.h" @@ -472,6 +475,21 @@ class DLL_EXPORT TLVReader */ CHIP_ERROR Get(CharSpan & v); + /** + * Get the value of the Localized String Identifier. + * + * The method takes what's after the first Information Separator 1 , and until end of string + * or second , and return the hex-decoded string identifier, if one was there. + * + * @param[out] lsid Optional Localized String Identifier. Returns empty + * if the value is not found or it has invalidly encoded. + * + * @retval #CHIP_NO_ERROR If the method succeeded. + * @retval #CHIP_ERROR_WRONG_TLV_TYPE If the current element is not a TLV character string, or + * the reader is not positioned on an element. + */ + CHIP_ERROR Get(Optional & lsid); + /** * Get the value of the current element as an enum value, if it's an integer * value that fits in the enum type. diff --git a/src/lib/core/tests/TestTLV.cpp b/src/lib/core/tests/TestTLV.cpp index ec38b9463ecda7..e9366b0b7323d5 100644 --- a/src/lib/core/tests/TestTLV.cpp +++ b/src/lib/core/tests/TestTLV.cpp @@ -2805,6 +2805,8 @@ void CheckTLVCharSpan(nlTestSuite * inSuite, void * inContext) { "This is a test case #1\x1fTest Localized String Identifier", "This is a test case #1" }, { "This is a test case #2 \x1f abc \x1f def", "This is a test case #2 " }, { "This is a test case #3\x1f", "This is a test case #3" }, + { "Thé\037", "Thé" }, + { "\x1f abc \x1f def", "" }, }; // clang-format on @@ -2836,6 +2838,60 @@ void CheckTLVCharSpan(nlTestSuite * inSuite, void * inContext) } } +void CheckTLVGetLocalizedStringIdentifier(nlTestSuite * inSuite, void * inContext) +{ + struct CharSpanTestCase + { + const char * testString; + Optional expectedLSID; + }; + + // clang-format off + static CharSpanTestCase sCharSpanTestCases[] = { + // Test String Expected LocalizedStringIdentifier from Get() + // ============================================================================================== + { "This is a test case #0", chip::Optional() }, + { "This is a test case #1\0370123", chip::Optional(0x0123) }, + { "This is a test case #2\0370123\0373210", chip::Optional(0x0123) }, + { "This is a test case #3\037012", chip::Optional(0x012) }, + { "Thé\037", chip::Optional() }, + { "Thé\0377", chip::Optional(0x7) }, + { "Thé\0371FA", chip::Optional(0x1FA) }, + { "\0371FA", chip::Optional(0x1FA) }, + { "Thé\0371FAB", chip::Optional(0x1FAB) }, + { "Thé\0371FAb", chip::Optional() }, + { "Thé\0371FABC", chip::Optional() }, + { "Thé\0371FA\037", chip::Optional(0x1FA) }, + { "Thé\0371FA\037F8sa===", chip::Optional(0x1FA) }, + }; + // clang-format on + + for (auto & testCase : sCharSpanTestCases) + { + uint8_t backingStore[100]; + TLVWriter writer; + TLVReader reader; + CHIP_ERROR err = CHIP_NO_ERROR; + + writer.Init(backingStore); + + err = writer.PutString(ProfileTag(TestProfile_1, 1), testCase.testString); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = writer.Finalize(); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + reader.Init(backingStore, writer.GetLengthWritten()); + err = reader.Next(); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + Optional readerLSID; + err = reader.Get(readerLSID); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, testCase.expectedLSID == readerLSID); + } +} + void CheckTLVSkipCircular(nlTestSuite * inSuite, void * inContext) { const size_t bufsize = 40; // large enough s.t. 2 elements fit, 3rd causes eviction @@ -4514,6 +4570,7 @@ static const nlTest sTests[] = NL_TEST_DEF("CHIP TLV Skip non-contiguous", CheckTLVSkipCircular), NL_TEST_DEF("CHIP TLV ByteSpan", CheckTLVByteSpan), NL_TEST_DEF("CHIP TLV CharSpan", CheckTLVCharSpan), + NL_TEST_DEF("CHIP TLV Get LocalizedStringIdentifier", CheckTLVGetLocalizedStringIdentifier), NL_TEST_DEF("CHIP TLV Scoped Buffer", CheckTLVScopedBuffer), NL_TEST_DEF("CHIP TLV Check reserve", CheckCloseContainerReserve), NL_TEST_DEF("CHIP TLV Reader Fuzz Test", TLVReaderFuzzTest),