Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tlv] Implemented Getter for Localized String Identifier #24528

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);

emargolis marked this conversation as resolved.
Show resolved Hide resolved
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.
*/
emargolis marked this conversation as resolved.
Show resolved Hide resolved
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)
emargolis marked this conversation as resolved.
Show resolved Hide resolved
{
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