Skip to content

Commit 2740201

Browse files
emargolisksperling-apple
authored andcommitted
[tlv] Implemented Getter for Localized String Identifier (#24528)
* [tlv] Implemented Getter for Localized String Identifier New method TLVReader::Get(Optional<LocalizedStringIdentifier>&) returns Localized String Identifier if present. The method takes what's after the first Information Separator 1 <IS1>, and until end of string or second <IS1>, and return the hex-decoded string identifier, if one was there. * Added two error test cases: - buffer underrun error: happens when encoded tlv data buffer length is shorter than needed. - wrong tlv type error, happens when bytestring is encoded while utf-8 string was expected. * Update src/lib/core/TLVReader.h Co-authored-by: Karsten Sperling <[email protected]> * Update src/lib/core/TLVReader.cpp Co-authored-by: Karsten Sperling <[email protected]> * Update src/lib/core/TLVReader.cpp Co-authored-by: Karsten Sperling <[email protected]> * Added check for leading zeroes in the Localized String Identifier. Co-authored-by: Karsten Sperling <[email protected]>
1 parent 27ac79d commit 2740201

File tree

4 files changed

+185
-9
lines changed

4 files changed

+185
-9
lines changed

src/lib/core/DataModelTypes.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
*
3-
* Copyright (c) 2021 Project CHIP Authors
3+
* Copyright (c) 2021-2023 Project CHIP Authors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@ typedef uint64_t FabricId;
4141
typedef uint8_t FabricIndex;
4242
typedef uint32_t FieldId;
4343
typedef uint16_t ListIndex;
44+
typedef uint16_t LocalizedStringIdentifier;
4445
typedef uint32_t TransactionId;
4546
typedef uint16_t KeysetId;
4647
typedef uint8_t InteractionModelRevision;

src/lib/core/TLVReader.cpp

+49-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <lib/core/CHIPEncoding.h>
2929
#include <lib/core/CHIPSafeCasts.h>
3030
#include <lib/core/TLV.h>
31+
#include <lib/support/BytesToHex.h>
3132
#include <lib/support/CHIPMem.h>
3233
#include <lib/support/CodeUtils.h>
3334
#include <lib/support/SafeInt.h>
@@ -298,10 +299,13 @@ CHIP_ERROR TLVReader::Get(ByteSpan & v)
298299
return CHIP_NO_ERROR;
299300
}
300301

302+
namespace {
303+
constexpr int kUnicodeInformationSeparator1 = 0x1F;
304+
constexpr size_t kMaxLocalizedStringIdentifierLen = 2 * sizeof(LocalizedStringIdentifier);
305+
} // namespace
306+
301307
CHIP_ERROR TLVReader::Get(CharSpan & v)
302308
{
303-
constexpr int kUnicodeInformationSeparator1 = 0x1F;
304-
305309
if (!TLVTypeIsUTF8String(ElementType()))
306310
{
307311
return CHIP_ERROR_WRONG_TLV_TYPE;
@@ -324,6 +328,49 @@ CHIP_ERROR TLVReader::Get(CharSpan & v)
324328
return CHIP_NO_ERROR;
325329
}
326330

331+
CHIP_ERROR TLVReader::Get(Optional<LocalizedStringIdentifier> & lsid)
332+
{
333+
lsid.ClearValue();
334+
VerifyOrReturnError(TLVTypeIsUTF8String(ElementType()), CHIP_ERROR_WRONG_TLV_TYPE);
335+
336+
const uint8_t * bytes;
337+
ReturnErrorOnFailure(GetDataPtr(bytes)); // Does length sanity checks
338+
339+
uint32_t len = GetLength();
340+
341+
const uint8_t * infoSeparator1 = static_cast<const uint8_t *>(memchr(bytes, kUnicodeInformationSeparator1, len));
342+
if (infoSeparator1 == nullptr)
343+
{
344+
return CHIP_NO_ERROR;
345+
}
346+
347+
const uint8_t * lsidPtr = infoSeparator1 + 1;
348+
len -= static_cast<uint32_t>(lsidPtr - bytes);
349+
350+
const uint8_t * infoSeparator2 = static_cast<const uint8_t *>(memchr(lsidPtr, kUnicodeInformationSeparator1, len));
351+
if (infoSeparator2 != nullptr)
352+
{
353+
len = static_cast<uint32_t>(infoSeparator2 - lsidPtr);
354+
}
355+
if (len == 0)
356+
{
357+
return CHIP_NO_ERROR;
358+
}
359+
VerifyOrReturnError(len <= kMaxLocalizedStringIdentifierLen, CHIP_ERROR_INVALID_TLV_ELEMENT);
360+
// Leading zeroes are not allowed.
361+
VerifyOrReturnError(static_cast<char>(lsidPtr[0]) != '0', CHIP_ERROR_INVALID_TLV_ELEMENT);
362+
363+
char idStr[kMaxLocalizedStringIdentifierLen] = { '0', '0', '0', '0' };
364+
memcpy(&idStr[kMaxLocalizedStringIdentifierLen - len], lsidPtr, len);
365+
366+
LocalizedStringIdentifier id;
367+
VerifyOrReturnError(Encoding::UppercaseHexToUint16(idStr, sizeof(idStr), id) == sizeof(LocalizedStringIdentifier),
368+
CHIP_ERROR_INVALID_TLV_ELEMENT);
369+
370+
lsid.SetValue(id);
371+
return CHIP_NO_ERROR;
372+
}
373+
327374
CHIP_ERROR TLVReader::GetBytes(uint8_t * buf, size_t bufSize)
328375
{
329376
if (!TLVTypeIsString(ElementType()))

src/lib/core/TLVReader.h

+19
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727

2828
#pragma once
2929

30+
#include <lib/core/DataModelTypes.h>
31+
#include <lib/core/Optional.h>
32+
3033
#include "TLVCommon.h"
3134

3235
#include "TLVWriter.h"
@@ -472,6 +475,22 @@ class DLL_EXPORT TLVReader
472475
*/
473476
CHIP_ERROR Get(CharSpan & v);
474477

478+
/**
479+
* Get the Localized String Identifier contained in the current element..
480+
*
481+
* The method takes what's after the first Information Separator 1 <IS1>, and until end of string
482+
* or second <IS1>, and return the hex-decoded string identifier, if one was there.
483+
*
484+
* @param[out] lsid Optional Localized String Identifier. Returns empty
485+
* if the value is not found or it was invalidly encoded.
486+
*
487+
* @retval #CHIP_NO_ERROR If the method succeeded.
488+
* @retval #CHIP_ERROR_WRONG_TLV_TYPE If the current element is not a TLV character string, or
489+
* the reader is not positioned on an element.
490+
* @retval #CHIP_ERROR_INVALID_TLV_ELEMENT If the Localized String Identifier is malformed.
491+
*/
492+
CHIP_ERROR Get(Optional<LocalizedStringIdentifier> & lsid);
493+
475494
/**
476495
* Get the value of the current element as an enum value, if it's an integer
477496
* value that fits in the enum type.

src/lib/core/tests/TestTLV.cpp

+115-6
Original file line numberDiff line numberDiff line change
@@ -2789,6 +2789,8 @@ void CheckTLVByteSpan(nlTestSuite * inSuite, void * inContext)
27892789
NL_TEST_ASSERT(inSuite, memcmp(readerSpan.data(), bytesBuffer, sizeof(bytesBuffer)) == 0);
27902790
}
27912791

2792+
#define IS1_CHAR "\x1F"
2793+
27922794
void CheckTLVCharSpan(nlTestSuite * inSuite, void * inContext)
27932795
{
27942796
struct CharSpanTestCase
@@ -2799,12 +2801,14 @@ void CheckTLVCharSpan(nlTestSuite * inSuite, void * inContext)
27992801

28002802
// clang-format off
28012803
static CharSpanTestCase sCharSpanTestCases[] = {
2802-
// Test String Expected String from Get()
2803-
// =========================================================================================
2804-
{ "This is a test case #0", "This is a test case #0" },
2805-
{ "This is a test case #1\x1fTest Localized String Identifier", "This is a test case #1" },
2806-
{ "This is a test case #2 \x1f abc \x1f def", "This is a test case #2 " },
2807-
{ "This is a test case #3\x1f", "This is a test case #3" },
2804+
// Test String Expected String from Get()
2805+
// ==================================================================================================
2806+
{ "This is a test case #0", "This is a test case #0" },
2807+
{ "This is a test case #1" IS1_CHAR "Test Localized String Identifier", "This is a test case #1" },
2808+
{ "This is a test case #2 " IS1_CHAR "abc" IS1_CHAR "def", "This is a test case #2 " },
2809+
{ "This is a test case #3" IS1_CHAR, "This is a test case #3" },
2810+
{ "Thé" IS1_CHAR, "Thé" },
2811+
{ IS1_CHAR " abc " IS1_CHAR " def", "" },
28082812
};
28092813
// clang-format on
28102814

@@ -2836,6 +2840,110 @@ void CheckTLVCharSpan(nlTestSuite * inSuite, void * inContext)
28362840
}
28372841
}
28382842

2843+
void CheckTLVGetLocalizedStringIdentifier(nlTestSuite * inSuite, void * inContext)
2844+
{
2845+
struct CharSpanTestCase
2846+
{
2847+
const char * testString;
2848+
Optional<LocalizedStringIdentifier> expectedLSID;
2849+
CHIP_ERROR expectedResult;
2850+
};
2851+
2852+
// clang-format off
2853+
static CharSpanTestCase sCharSpanTestCases[] = {
2854+
// Test String Expected LocalizedStringIdentifier from Get() Expected Return
2855+
// =============================================================================================================================================
2856+
{ "This is a test case #0", chip::Optional<LocalizedStringIdentifier>(), CHIP_NO_ERROR },
2857+
{ "This is a test case #1" IS1_CHAR "0123", chip::Optional<LocalizedStringIdentifier>(), CHIP_ERROR_INVALID_TLV_ELEMENT },
2858+
{ "This is a test case #2" IS1_CHAR "123" IS1_CHAR "3210", chip::Optional<LocalizedStringIdentifier>(0x123), CHIP_NO_ERROR },
2859+
{ "This is a test case #3" IS1_CHAR "012", chip::Optional<LocalizedStringIdentifier>(), CHIP_ERROR_INVALID_TLV_ELEMENT },
2860+
{ "This is a test case #3" IS1_CHAR "12", chip::Optional<LocalizedStringIdentifier>(0x12), CHIP_NO_ERROR },
2861+
{ "Thé" IS1_CHAR "", chip::Optional<LocalizedStringIdentifier>(), CHIP_NO_ERROR },
2862+
{ "Thé" IS1_CHAR "7", chip::Optional<LocalizedStringIdentifier>(0x7), CHIP_NO_ERROR },
2863+
{ "Thé" IS1_CHAR "1FA", chip::Optional<LocalizedStringIdentifier>(0x1FA), CHIP_NO_ERROR },
2864+
{ "" IS1_CHAR "1FA", chip::Optional<LocalizedStringIdentifier>(0x1FA), CHIP_NO_ERROR },
2865+
{ "Thé" IS1_CHAR "1FAB", chip::Optional<LocalizedStringIdentifier>(0x1FAB), CHIP_NO_ERROR },
2866+
{ "Thé" IS1_CHAR "1FAb", chip::Optional<LocalizedStringIdentifier>(), CHIP_ERROR_INVALID_TLV_ELEMENT },
2867+
{ "Thé" IS1_CHAR "1FABC", chip::Optional<LocalizedStringIdentifier>(), CHIP_ERROR_INVALID_TLV_ELEMENT },
2868+
{ "Thé" IS1_CHAR "1FA" IS1_CHAR "", chip::Optional<LocalizedStringIdentifier>(0x1FA), CHIP_NO_ERROR },
2869+
{ "Thé" IS1_CHAR "1FA" IS1_CHAR "F8sa===", chip::Optional<LocalizedStringIdentifier>(0x1FA), CHIP_NO_ERROR },
2870+
};
2871+
// clang-format on
2872+
2873+
for (auto & testCase : sCharSpanTestCases)
2874+
{
2875+
uint8_t backingStore[100];
2876+
TLVWriter writer;
2877+
TLVReader reader;
2878+
CHIP_ERROR err = CHIP_NO_ERROR;
2879+
2880+
writer.Init(backingStore);
2881+
2882+
err = writer.PutString(ProfileTag(TestProfile_1, 1), testCase.testString);
2883+
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
2884+
2885+
err = writer.Finalize();
2886+
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
2887+
2888+
reader.Init(backingStore, writer.GetLengthWritten());
2889+
err = reader.Next();
2890+
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
2891+
2892+
Optional<LocalizedStringIdentifier> readerLSID;
2893+
err = reader.Get(readerLSID);
2894+
NL_TEST_ASSERT(inSuite, testCase.expectedResult == err);
2895+
NL_TEST_ASSERT(inSuite, testCase.expectedLSID == readerLSID);
2896+
}
2897+
2898+
// Error case: A case of TLVReader buffer underrun.
2899+
// Expected error after Next() call is: CHIP_ERROR_TLV_UNDERRUN
2900+
{
2901+
uint8_t backingStore[100];
2902+
TLVWriter writer;
2903+
TLVReader reader;
2904+
CHIP_ERROR err = CHIP_NO_ERROR;
2905+
2906+
writer.Init(backingStore);
2907+
2908+
err = writer.PutString(ProfileTag(TestProfile_1, 1), sCharSpanTestCases[2].testString);
2909+
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
2910+
2911+
err = writer.Finalize();
2912+
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
2913+
2914+
reader.Init(backingStore, writer.GetLengthWritten() - 1);
2915+
err = reader.Next();
2916+
NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_TLV_UNDERRUN);
2917+
}
2918+
2919+
// Error case: the reader is on a bytestring, not utf-8 string.
2920+
// Expected error after Get(Optional<LocalizedStringIdentifier> &) call is: CHIP_ERROR_WRONG_TLV_TYPE
2921+
{
2922+
uint8_t backingStore[100];
2923+
TLVWriter writer;
2924+
TLVReader reader;
2925+
CHIP_ERROR err = CHIP_NO_ERROR;
2926+
2927+
writer.Init(backingStore);
2928+
2929+
err = writer.PutBytes(ProfileTag(TestProfile_1, 1), reinterpret_cast<const uint8_t *>(sCharSpanTestCases[2].testString),
2930+
static_cast<uint32_t>(strlen(sCharSpanTestCases[2].testString)));
2931+
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
2932+
2933+
err = writer.Finalize();
2934+
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
2935+
2936+
reader.Init(backingStore, writer.GetLengthWritten());
2937+
err = reader.Next();
2938+
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
2939+
2940+
Optional<LocalizedStringIdentifier> readerLSID;
2941+
err = reader.Get(readerLSID);
2942+
NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_WRONG_TLV_TYPE);
2943+
NL_TEST_ASSERT(inSuite, readerLSID == Optional<LocalizedStringIdentifier>());
2944+
}
2945+
}
2946+
28392947
void CheckTLVSkipCircular(nlTestSuite * inSuite, void * inContext)
28402948
{
28412949
const size_t bufsize = 40; // large enough s.t. 2 elements fit, 3rd causes eviction
@@ -4514,6 +4622,7 @@ static const nlTest sTests[] =
45144622
NL_TEST_DEF("CHIP TLV Skip non-contiguous", CheckTLVSkipCircular),
45154623
NL_TEST_DEF("CHIP TLV ByteSpan", CheckTLVByteSpan),
45164624
NL_TEST_DEF("CHIP TLV CharSpan", CheckTLVCharSpan),
4625+
NL_TEST_DEF("CHIP TLV Get LocalizedStringIdentifier", CheckTLVGetLocalizedStringIdentifier),
45174626
NL_TEST_DEF("CHIP TLV Scoped Buffer", CheckTLVScopedBuffer),
45184627
NL_TEST_DEF("CHIP TLV Check reserve", CheckCloseContainerReserve),
45194628
NL_TEST_DEF("CHIP TLV Reader Fuzz Test", TLVReaderFuzzTest),

0 commit comments

Comments
 (0)