Skip to content

Commit

Permalink
Add a helper for encoding lists via AttributeValueEncoder. (#10265)
Browse files Browse the repository at this point in the history
* Add a helper for encoding lists via AttributeValueEncoder.

The idea is to not need the whole list in memory at once.

* Addressing review comments
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Oct 15, 2021
1 parent d48d754 commit 4513748
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 4 deletions.
31 changes: 27 additions & 4 deletions src/app/AttributeAccessInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include <app/ClusterInfo.h>
#include <app/MessageDef/AttributeDataElement.h>
#include <app/data-model/Encode.h>
#include <app/data-model/List.h> // So we can encode lists
#include <app/data-model/TagBoundEncoder.h>
#include <app/util/basic-types.h>
#include <lib/core/CHIPTLV.h>
#include <lib/core/Optional.h>
Expand All @@ -38,10 +40,11 @@
namespace chip {
namespace app {

class AttributeValueEncoder
class AttributeValueEncoder : protected TagBoundEncoder
{
public:
AttributeValueEncoder(TLV::TLVWriter * aWriter) : mWriter(aWriter) {}
AttributeValueEncoder(TLV::TLVWriter * aWriter) : TagBoundEncoder(aWriter, TLV::ContextTag(AttributeDataElement::kCsTag_Data))
{}

template <typename... Ts>
CHIP_ERROR Encode(Ts... aArgs)
Expand All @@ -51,7 +54,28 @@ class AttributeValueEncoder
{
return CHIP_NO_ERROR;
}
return DataModel::Encode(*mWriter, TLV::ContextTag(AttributeDataElement::kCsTag_Data), std::forward<Ts>(aArgs)...);
return TagBoundEncoder::Encode(std::forward<Ts>(aArgs)...);
}

/**
* aCallback is expected to take a const TagBoundEncoder& argument and
* Encode() on it as many times as needed to encode all the list elements
* one by one. If any of those Encode() calls returns failure, aCallback
* must stop encoding and return failure. When all items are encoded
* aCallback is expected to return success.
*
* aCallback may not be called. Consumers must not assume it will be
* called.
*/
template <typename ListGenerator>
CHIP_ERROR EncodeList(ListGenerator aCallback)
{
mTriedEncode = true;
if (mWriter == nullptr)
{
return CHIP_NO_ERROR;
}
return TagBoundEncoder::EncodeList(aCallback);
}

bool TriedEncode() const { return mTriedEncode; }
Expand All @@ -66,7 +90,6 @@ class AttributeValueEncoder
}

private:
TLV::TLVWriter * mWriter;
bool mTriedEncode = false;
};

Expand Down
76 changes: 76 additions & 0 deletions src/app/data-model/TagBoundEncoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <app/data-model/Encode.h>
#include <app/data-model/List.h> // So we can encode lists
#include <lib/core/CHIPTLV.h>
#include <lib/support/CodeUtils.h>

/**
* Class that encapsulates a TLVWriter and tag that can be provided to a
* consumer so that the consumer can just call Encode and have the tagging
* handled for them.
*/
namespace chip {
namespace app {

class TagBoundEncoder
{
public:
// Initialization with a null TLVWriter is allowed, but attempts to Encode()
// will fail.
TagBoundEncoder(TLV::TLVWriter * aWriter, TLV::Tag aTag) : mWriter(aWriter), mTag(aTag) {}

template <typename... Ts>
CHIP_ERROR Encode(Ts... aArgs) const
{
VerifyOrReturnError(mWriter != nullptr, CHIP_ERROR_INCORRECT_STATE);
return DataModel::Encode(*mWriter, mTag, std::forward<Ts>(aArgs)...);
}

/**
* aCallback is expected to take a const TagBoundEncoder& argument and
* Encode() on it as many times as needed to encode all the list elements
* one by one. If any of those Encode() calls returns failure, aCallback
* must stop encoding and return failure. When all items are encoded
* aCallback is expected to return success.
*
* aCallback may not be called. Consumers must not assume it will be
* called.
*/
template <typename ListGenerator>
CHIP_ERROR EncodeList(ListGenerator aCallback)
{
VerifyOrReturnError(mWriter != nullptr, CHIP_ERROR_INCORRECT_STATE);
TLV::TLVType outerType;
ReturnErrorOnFailure(mWriter->StartContainer(mTag, TLV::kTLVType_Array, outerType));
ReturnErrorOnFailure(aCallback(TagBoundEncoder(mWriter, TLV::AnonymousTag)));
return mWriter->EndContainer(outerType);
}

protected:
TLV::TLVWriter * const mWriter;

private:
const TLV::Tag mTag;
};

} // namespace app
} // namespace chip
1 change: 1 addition & 0 deletions src/app/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ chip_test_suite("tests") {
output_name = "libAppTests"

test_sources = [
"TestAttributeValueEncoder.cpp",
"TestCHIPDeviceCallbacksMgr.cpp",
"TestClusterInfo.cpp",
"TestCommandPathParams.cpp",
Expand Down
138 changes: 138 additions & 0 deletions src/app/tests/TestAttributeValueEncoder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @file
* This file implements unit tests for CommandPathParams
*
*/

#include <app/AttributeAccessInterface.h>
#include <app/MessageDef/AttributeDataElement.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/UnitTestRegistration.h>
#include <nlunit-test.h>

using namespace chip;
using namespace chip::app;
using namespace chip::TLV;

namespace {

struct TestSetup
{
TestSetup(nlTestSuite * aSuite) : encoder(&writer)
{
writer.Init(buf);
TLVType ignored;
CHIP_ERROR err = writer.StartContainer(AnonymousTag, kTLVType_Structure, ignored);
NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR);
}

AttributeValueEncoder encoder;
uint8_t buf[1024];
TLVWriter writer;
};

// Macro so we get better error reporting in terms of which test failed, because
// the reporting uses __LINE__.
#define VERIFY_BUFFER_STATE(aSuite, aSetup, aExpected) \
do \
{ \
NL_TEST_ASSERT(aSuite, aSetup.writer.GetLengthWritten() == sizeof(aExpected)); \
NL_TEST_ASSERT(aSuite, memcmp(aSetup.buf, aExpected, sizeof(aExpected)) == 0); \
} while (0)

void TestEncodeNothing(nlTestSuite * aSuite, void * aContext)
{
TestSetup test(aSuite);
// Just have an anonymous struct marker.
const uint8_t expected[] = { 0x15 };
VERIFY_BUFFER_STATE(aSuite, test, expected);
}

void TestEncodeBool(nlTestSuite * aSuite, void * aContext)
{
TestSetup test(aSuite);
CHIP_ERROR err = test.encoder.Encode(true);
NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR);
// Anonymous tagged struct.
// Control tag for boolean true with context tag.
// Context tag with value AttributeDataElement::kCsTag_Data.
const uint8_t expected[] = { 0x15, 0x29, 0x02 };
VERIFY_BUFFER_STATE(aSuite, test, expected);
}

void TestEncodeListOfBools1(nlTestSuite * aSuite, void * aContext)
{
TestSetup test(aSuite);
bool list[] = { true, false };
CHIP_ERROR err = test.encoder.Encode(DataModel::List<bool>(list));
NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR);
// Anonymous tagged struct.
// Control tag for array with context tag.
// Context tag with value AttributeDataElement::kCsTag_Data.
// Control tag for boolean true with anonymous tag.
// Control tag for boolean false with anonymous tag.
// End of list marker.
const uint8_t expected[] = { 0x15, 0x36, 0x02, 0x09, 0x08, 0x18 };
VERIFY_BUFFER_STATE(aSuite, test, expected);
}

void TestEncodeListOfBools2(nlTestSuite * aSuite, void * aContext)
{
TestSetup test(aSuite);
bool list[] = { true, false };
CHIP_ERROR err = test.encoder.EncodeList([&list](const TagBoundEncoder & encoder) -> CHIP_ERROR {
for (auto & item : list)
{
ReturnErrorOnFailure(encoder.Encode(item));
}
return CHIP_NO_ERROR;
});
NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR);
// Anonymous tagged struct.
// Control tag for array with context tag.
// Context tag with value AttributeDataElement::kCsTag_Data.
// Control tag for boolean true with anonymous tag.
// Control tag for boolean false with anonymous tag.
// End of list marker.
const uint8_t expected[] = { 0x15, 0x36, 0x02, 0x09, 0x08, 0x18 };
VERIFY_BUFFER_STATE(aSuite, test, expected);
}

#undef VERIFY_STATE

} // anonymous namespace

namespace {
const nlTest sTests[] = { NL_TEST_DEF("TestEncodeNothing", TestEncodeNothing), NL_TEST_DEF("TestEncodeBool", TestEncodeBool),
NL_TEST_DEF("TestEncodeListOfBools1", TestEncodeListOfBools1),
NL_TEST_DEF("TestEncodeListOfBools2", TestEncodeListOfBools2), NL_TEST_SENTINEL() };
}

int TestAttributeValueEncoder()
{
nlTestSuite theSuite = { "AttributeValueEncoder", &sTests[0], nullptr, nullptr };

nlTestRunner(&theSuite, nullptr);

return (nlTestRunnerStats(&theSuite));
}

CHIP_REGISTER_TEST_SUITE(TestAttributeValueEncoder)

0 comments on commit 4513748

Please sign in to comment.