Skip to content

Commit

Permalink
[tlv] Implemented Getter for Localized String Identifier (#24528)
Browse files Browse the repository at this point in the history
* [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]>
  • Loading branch information
2 people authored and pull[bot] committed Oct 18, 2023
1 parent 6bd1377 commit 64c7602
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 9 deletions.
3 changes: 2 additions & 1 deletion src/lib/core/DataModelTypes.h
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
Expand Down
51 changes: 49 additions & 2 deletions src/lib/core/TLVReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <lib/core/CHIPEncoding.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/core/TLV.h>
#include <lib/support/BytesToHex.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
Expand Down Expand Up @@ -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;
Expand All @@ -324,6 +328,49 @@ CHIP_ERROR TLVReader::Get(CharSpan & v)
return CHIP_NO_ERROR;
}

CHIP_ERROR TLVReader::Get(Optional<LocalizedStringIdentifier> & 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 = static_cast<const uint8_t *>(memchr(bytes, kUnicodeInformationSeparator1, len));
if (infoSeparator1 == nullptr)
{
return CHIP_NO_ERROR;
}

const uint8_t * lsidPtr = infoSeparator1 + 1;
len -= static_cast<uint32_t>(lsidPtr - bytes);

const uint8_t * infoSeparator2 = static_cast<const uint8_t *>(memchr(lsidPtr, kUnicodeInformationSeparator1, len));
if (infoSeparator2 != nullptr)
{
len = static_cast<uint32_t>(infoSeparator2 - lsidPtr);
}
if (len == 0)
{
return CHIP_NO_ERROR;
}
VerifyOrReturnError(len <= kMaxLocalizedStringIdentifierLen, CHIP_ERROR_INVALID_TLV_ELEMENT);
// Leading zeroes are not allowed.
VerifyOrReturnError(static_cast<char>(lsidPtr[0]) != '0', CHIP_ERROR_INVALID_TLV_ELEMENT);

char idStr[kMaxLocalizedStringIdentifierLen] = { '0', '0', '0', '0' };
memcpy(&idStr[kMaxLocalizedStringIdentifierLen - len], lsidPtr, len);

LocalizedStringIdentifier id;
VerifyOrReturnError(Encoding::UppercaseHexToUint16(idStr, sizeof(idStr), id) == sizeof(LocalizedStringIdentifier),
CHIP_ERROR_INVALID_TLV_ELEMENT);

lsid.SetValue(id);
return CHIP_NO_ERROR;
}

CHIP_ERROR TLVReader::GetBytes(uint8_t * buf, size_t bufSize)
{
if (!TLVTypeIsString(ElementType()))
Expand Down
19 changes: 19 additions & 0 deletions src/lib/core/TLVReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

#pragma once

#include <lib/core/DataModelTypes.h>
#include <lib/core/Optional.h>

#include "TLVCommon.h"

#include "TLVWriter.h"
Expand Down Expand Up @@ -472,6 +475,22 @@ class DLL_EXPORT TLVReader
*/
CHIP_ERROR Get(CharSpan & v);

/**
* Get the Localized String Identifier contained in the current element..
*
* 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.
*
* @param[out] lsid Optional Localized String Identifier. Returns empty
* if the value is not found or it was 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.
* @retval #CHIP_ERROR_INVALID_TLV_ELEMENT If the Localized String Identifier is malformed.
*/
CHIP_ERROR Get(Optional<LocalizedStringIdentifier> & lsid);

/**
* Get the value of the current element as an enum value, if it's an integer
* value that fits in the enum type.
Expand Down
121 changes: 115 additions & 6 deletions src/lib/core/tests/TestTLV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2789,6 +2789,8 @@ void CheckTLVByteSpan(nlTestSuite * inSuite, void * inContext)
NL_TEST_ASSERT(inSuite, memcmp(readerSpan.data(), bytesBuffer, sizeof(bytesBuffer)) == 0);
}

#define IS1_CHAR "\x1F"

void CheckTLVCharSpan(nlTestSuite * inSuite, void * inContext)
{
struct CharSpanTestCase
Expand All @@ -2799,12 +2801,14 @@ void CheckTLVCharSpan(nlTestSuite * inSuite, void * inContext)

// clang-format off
static CharSpanTestCase sCharSpanTestCases[] = {
// Test String Expected String from Get()
// =========================================================================================
{ "This is a test case #0", "This is a test case #0" },
{ "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" },
// Test String Expected String from Get()
// ==================================================================================================
{ "This is a test case #0", "This is a test case #0" },
{ "This is a test case #1" IS1_CHAR "Test Localized String Identifier", "This is a test case #1" },
{ "This is a test case #2 " IS1_CHAR "abc" IS1_CHAR "def", "This is a test case #2 " },
{ "This is a test case #3" IS1_CHAR, "This is a test case #3" },
{ "Thé" IS1_CHAR, "Thé" },
{ IS1_CHAR " abc " IS1_CHAR " def", "" },
};
// clang-format on

Expand Down Expand Up @@ -2836,6 +2840,110 @@ void CheckTLVCharSpan(nlTestSuite * inSuite, void * inContext)
}
}

void CheckTLVGetLocalizedStringIdentifier(nlTestSuite * inSuite, void * inContext)
{
struct CharSpanTestCase
{
const char * testString;
Optional<LocalizedStringIdentifier> expectedLSID;
CHIP_ERROR expectedResult;
};

// clang-format off
static CharSpanTestCase sCharSpanTestCases[] = {
// Test String Expected LocalizedStringIdentifier from Get() Expected Return
// =============================================================================================================================================
{ "This is a test case #0", chip::Optional<LocalizedStringIdentifier>(), CHIP_NO_ERROR },
{ "This is a test case #1" IS1_CHAR "0123", chip::Optional<LocalizedStringIdentifier>(), CHIP_ERROR_INVALID_TLV_ELEMENT },
{ "This is a test case #2" IS1_CHAR "123" IS1_CHAR "3210", chip::Optional<LocalizedStringIdentifier>(0x123), CHIP_NO_ERROR },
{ "This is a test case #3" IS1_CHAR "012", chip::Optional<LocalizedStringIdentifier>(), CHIP_ERROR_INVALID_TLV_ELEMENT },
{ "This is a test case #3" IS1_CHAR "12", chip::Optional<LocalizedStringIdentifier>(0x12), CHIP_NO_ERROR },
{ "Thé" IS1_CHAR "", chip::Optional<LocalizedStringIdentifier>(), CHIP_NO_ERROR },
{ "Thé" IS1_CHAR "7", chip::Optional<LocalizedStringIdentifier>(0x7), CHIP_NO_ERROR },
{ "Thé" IS1_CHAR "1FA", chip::Optional<LocalizedStringIdentifier>(0x1FA), CHIP_NO_ERROR },
{ "" IS1_CHAR "1FA", chip::Optional<LocalizedStringIdentifier>(0x1FA), CHIP_NO_ERROR },
{ "Thé" IS1_CHAR "1FAB", chip::Optional<LocalizedStringIdentifier>(0x1FAB), CHIP_NO_ERROR },
{ "Thé" IS1_CHAR "1FAb", chip::Optional<LocalizedStringIdentifier>(), CHIP_ERROR_INVALID_TLV_ELEMENT },
{ "Thé" IS1_CHAR "1FABC", chip::Optional<LocalizedStringIdentifier>(), CHIP_ERROR_INVALID_TLV_ELEMENT },
{ "Thé" IS1_CHAR "1FA" IS1_CHAR "", chip::Optional<LocalizedStringIdentifier>(0x1FA), CHIP_NO_ERROR },
{ "Thé" IS1_CHAR "1FA" IS1_CHAR "F8sa===", chip::Optional<LocalizedStringIdentifier>(0x1FA), CHIP_NO_ERROR },
};
// 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<LocalizedStringIdentifier> readerLSID;
err = reader.Get(readerLSID);
NL_TEST_ASSERT(inSuite, testCase.expectedResult == err);
NL_TEST_ASSERT(inSuite, testCase.expectedLSID == readerLSID);
}

// Error case: A case of TLVReader buffer underrun.
// Expected error after Next() call is: CHIP_ERROR_TLV_UNDERRUN
{
uint8_t backingStore[100];
TLVWriter writer;
TLVReader reader;
CHIP_ERROR err = CHIP_NO_ERROR;

writer.Init(backingStore);

err = writer.PutString(ProfileTag(TestProfile_1, 1), sCharSpanTestCases[2].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() - 1);
err = reader.Next();
NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_TLV_UNDERRUN);
}

// Error case: the reader is on a bytestring, not utf-8 string.
// Expected error after Get(Optional<LocalizedStringIdentifier> &) call is: CHIP_ERROR_WRONG_TLV_TYPE
{
uint8_t backingStore[100];
TLVWriter writer;
TLVReader reader;
CHIP_ERROR err = CHIP_NO_ERROR;

writer.Init(backingStore);

err = writer.PutBytes(ProfileTag(TestProfile_1, 1), reinterpret_cast<const uint8_t *>(sCharSpanTestCases[2].testString),
static_cast<uint32_t>(strlen(sCharSpanTestCases[2].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<LocalizedStringIdentifier> readerLSID;
err = reader.Get(readerLSID);
NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_WRONG_TLV_TYPE);
NL_TEST_ASSERT(inSuite, readerLSID == Optional<LocalizedStringIdentifier>());
}
}

void CheckTLVSkipCircular(nlTestSuite * inSuite, void * inContext)
{
const size_t bufsize = 40; // large enough s.t. 2 elements fit, 3rd causes eviction
Expand Down Expand Up @@ -4514,6 +4622,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),
Expand Down

0 comments on commit 64c7602

Please sign in to comment.