Skip to content

Commit a30b439

Browse files
Some interface cleanup and make AttributeValueEncoder/Decoder more similar (#33028)
* Uniform API: provide subject descriptor only for both value encode and decode * Split out AttributeEncodeState and decouple things to use less friend classes * Fix tests * Slight move around based on pahole results, to try to make size differences a bit smaller * Fix typo ... amazingly enough tests still pass, so test read is maybe broken * Two more replacements for state compare logic * Restyle * Fix logic error in Descripto creation for test * Update argument name * Remove useless comments * move AttributeEncodeState to a separate file * Make the copy from a pointer logic cleaner * Fix value passing * Fix const correctness * Fix one more compile * Put back AccessingFabricIndex * Updated back the comments and file layout --------- Co-authored-by: Andrei Litvin <andreilitvin@google.com>
1 parent 745c17c commit a30b439

20 files changed

+201
-108
lines changed

src/app/AttributeEncodeState.h

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright (c) 2021-2024 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#pragma once
18+
19+
#include <lib/core/DataModelTypes.h>
20+
21+
namespace chip {
22+
namespace app {
23+
24+
/// Maintains the internal state of list encoding
25+
///
26+
/// List encoding is generally assumed incremental and chunkable (i.e.
27+
/// partial encoding is ok.). For this purpose the class maintains two
28+
/// pieces of data:
29+
/// - AllowPartialData tracks if partial encoding is acceptable in the
30+
/// current encoding state (to be used for atomic/non-atomic list item writes)
31+
/// - CurrentEncodingListIndex representing the list index that is next
32+
/// to be encoded in the output. kInvalidListIndex means that a new list
33+
/// encoding has been started.
34+
class AttributeEncodeState
35+
{
36+
public:
37+
AttributeEncodeState() = default;
38+
39+
/// Allows the encode state to be initialized from an OPTIONAL
40+
/// other encoding state
41+
///
42+
/// if other is nullptr, this is the same as the default initializer.
43+
AttributeEncodeState(const AttributeEncodeState * other)
44+
{
45+
if (other != nullptr)
46+
{
47+
*this = *other;
48+
}
49+
else
50+
{
51+
mCurrentEncodingListIndex = kInvalidListIndex;
52+
mAllowPartialData = false;
53+
}
54+
}
55+
56+
bool AllowPartialData() const { return mAllowPartialData; }
57+
ListIndex CurrentEncodingListIndex() const { return mCurrentEncodingListIndex; }
58+
59+
AttributeEncodeState & SetAllowPartialData(bool allow)
60+
{
61+
mAllowPartialData = allow;
62+
return *this;
63+
}
64+
65+
AttributeEncodeState & SetCurrentEncodingListIndex(ListIndex idx)
66+
{
67+
mCurrentEncodingListIndex = idx;
68+
return *this;
69+
}
70+
71+
void Reset()
72+
{
73+
mCurrentEncodingListIndex = kInvalidListIndex;
74+
mAllowPartialData = false;
75+
}
76+
77+
private:
78+
/**
79+
* If set to kInvalidListIndex, indicates that we have not encoded any data for the list yet and
80+
* need to start by encoding an empty list before we start encoding any list items.
81+
*
82+
* When set to a valid ListIndex value, indicates the index of the next list item that needs to be
83+
* encoded (i.e. the count of items encoded so far).
84+
*/
85+
ListIndex mCurrentEncodingListIndex = kInvalidListIndex;
86+
87+
/**
88+
* When an attempt to encode an attribute returns an error, the buffer may contain tailing dirty data
89+
* (since the put was aborted). The report engine normally rolls back the buffer to right before encoding
90+
* of the attribute started on errors.
91+
*
92+
* When chunking a list, EncodeListItem will atomically encode list items, ensuring that the
93+
* state of the buffer is valid to send (i.e. contains no trailing garbage), and return an error
94+
* if the list doesn't entirely fit. In this situation, mAllowPartialData is set to communicate to the
95+
* report engine that it should not roll back the list items.
96+
*
97+
* TODO: There might be a better name for this variable.
98+
*/
99+
bool mAllowPartialData = false;
100+
};
101+
102+
} // namespace app
103+
} // namespace chip

src/app/AttributeValueEncoder.cpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ CHIP_ERROR AttributeValueEncoder::EnsureListStarted()
3232
{
3333
VerifyOrDie(mCurrentEncodingListIndex == kInvalidListIndex);
3434

35-
mEncodingInitialList = (mEncodeState.mCurrentEncodingListIndex == kInvalidListIndex);
35+
mEncodingInitialList = (mEncodeState.CurrentEncodingListIndex() == kInvalidListIndex);
3636
if (mEncodingInitialList)
3737
{
3838
// Clear mAllowPartialData flag here since this encode procedure is not atomic.
3939
// The most common error in this function is CHIP_ERROR_NO_MEMORY / CHIP_ERROR_BUFFER_TOO_SMALL, just revert and try
4040
// next time is ok.
41-
mEncodeState.mAllowPartialData = false;
41+
mEncodeState.SetAllowPartialData(false);
4242

4343
AttributeReportBuilder builder;
4444

@@ -59,7 +59,7 @@ CHIP_ERROR AttributeValueEncoder::EnsureListStarted()
5959
ReturnErrorOnFailure(
6060
mAttributeReportIBsBuilder.GetWriter()->ReserveBuffer(kEndOfAttributeReportIBByteCount + kEndOfListByteCount));
6161

62-
mEncodeState.mCurrentEncodingListIndex = 0;
62+
mEncodeState.SetCurrentEncodingListIndex(0);
6363
}
6464
else
6565
{
@@ -72,7 +72,7 @@ CHIP_ERROR AttributeValueEncoder::EnsureListStarted()
7272

7373
// After encoding the initial list start, the remaining items are atomically encoded into the buffer. Tell report engine to not
7474
// revert partial data.
75-
mEncodeState.mAllowPartialData = true;
75+
mEncodeState.SetAllowPartialData(true);
7676

7777
return CHIP_NO_ERROR;
7878
}
@@ -105,7 +105,7 @@ void AttributeValueEncoder::EnsureListEnded()
105105
// If we succeeded at encoding the whole list (i.e. the list is in fact
106106
// empty and we fit in the packet), mAllowPartialData will be ignored,
107107
// so it's safe to set it to false even if encoding succeeded.
108-
mEncodeState.mAllowPartialData = false;
108+
mEncodeState.SetAllowPartialData(false);
109109
}
110110
}
111111

src/app/AttributeValueEncoder.h

+16-43
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
#pragma once
1818

19+
#include <access/SubjectDescriptor.h>
20+
#include <app/AttributeEncodeState.h>
1921
#include <app/AttributeReportBuilder.h>
2022
#include <app/ConcreteAttributePath.h>
2123
#include <app/MessageDef/AttributeReportIBs.h>
@@ -51,9 +53,9 @@ class AttributeValueEncoder
5153
// If we are encoding for a fabric filtered attribute read and the fabric index does not match that present in the
5254
// request, skip encoding this list item.
5355
VerifyOrReturnError(!mAttributeValueEncoder.mIsFabricFiltered ||
54-
aArg.GetFabricIndex() == mAttributeValueEncoder.mAccessingFabricIndex,
56+
aArg.GetFabricIndex() == mAttributeValueEncoder.AccessingFabricIndex(),
5557
CHIP_NO_ERROR);
56-
return mAttributeValueEncoder.EncodeListItem(mAttributeValueEncoder.mAccessingFabricIndex, std::forward<T>(aArg));
58+
return mAttributeValueEncoder.EncodeListItem(mAttributeValueEncoder.AccessingFabricIndex(), std::forward<T>(aArg));
5759
}
5860

5961
template <typename T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, bool> = true>
@@ -66,42 +68,11 @@ class AttributeValueEncoder
6668
AttributeValueEncoder & mAttributeValueEncoder;
6769
};
6870

69-
class AttributeEncodeState
70-
{
71-
public:
72-
AttributeEncodeState() : mAllowPartialData(false), mCurrentEncodingListIndex(kInvalidListIndex) {}
73-
bool AllowPartialData() const { return mAllowPartialData; }
74-
75-
private:
76-
friend class AttributeValueEncoder;
77-
/**
78-
* When an attempt to encode an attribute returns an error, the buffer may contain tailing dirty data
79-
* (since the put was aborted). The report engine normally rolls back the buffer to right before encoding
80-
* of the attribute started on errors.
81-
*
82-
* When chunking a list, EncodeListItem will atomically encode list items, ensuring that the
83-
* state of the buffer is valid to send (i.e. contains no trailing garbage), and return an error
84-
* if the list doesn't entirely fit. In this situation, mAllowPartialData is set to communicate to the
85-
* report engine that it should not roll back the list items.
86-
*
87-
* TODO: There might be a better name for this variable.
88-
*/
89-
bool mAllowPartialData = false;
90-
/**
91-
* If set to kInvalidListIndex, indicates that we have not encoded any data for the list yet and
92-
* need to start by encoding an empty list before we start encoding any list items.
93-
*
94-
* When set to a valid ListIndex value, indicates the index of the next list item that needs to be
95-
* encoded (i.e. the count of items encoded so far).
96-
*/
97-
ListIndex mCurrentEncodingListIndex = kInvalidListIndex;
98-
};
99-
100-
AttributeValueEncoder(AttributeReportIBs::Builder & aAttributeReportIBsBuilder, FabricIndex aAccessingFabricIndex,
71+
AttributeValueEncoder(AttributeReportIBs::Builder & aAttributeReportIBsBuilder, Access::SubjectDescriptor subjectDescriptor,
10172
const ConcreteAttributePath & aPath, DataVersion aDataVersion, bool aIsFabricFiltered = false,
10273
const AttributeEncodeState & aState = AttributeEncodeState()) :
10374
mAttributeReportIBsBuilder(aAttributeReportIBsBuilder),
104-
mAccessingFabricIndex(aAccessingFabricIndex), mPath(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
75+
mSubjectDescriptor(subjectDescriptor), mPath(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
10576
mDataVersion(aDataVersion), mIsFabricFiltered(aIsFabricFiltered), mEncodeState(aState)
10677
{}
10778

@@ -169,17 +140,19 @@ class AttributeValueEncoder
169140
if (err == CHIP_NO_ERROR)
170141
{
171142
// The Encode procedure finished without any error, clear the state.
172-
mEncodeState = AttributeEncodeState();
143+
mEncodeState.Reset();
173144
}
174145
return err;
175146
}
176147

177148
bool TriedEncode() const { return mTriedEncode; }
178149

150+
const Access::SubjectDescriptor & GetSubjectDescriptor() const { return mSubjectDescriptor; }
151+
179152
/**
180153
* The accessing fabric index for this read or subscribe interaction.
181154
*/
182-
FabricIndex AccessingFabricIndex() const { return mAccessingFabricIndex; }
155+
FabricIndex AccessingFabricIndex() const { return GetSubjectDescriptor().fabricIndex; }
183156

184157
/**
185158
* AttributeValueEncoder is a short lived object, and the state is persisted by mEncodeState and restored by constructor.
@@ -195,7 +168,7 @@ class AttributeValueEncoder
195168
{
196169
// EncodeListItem must be called after EnsureListStarted(), thus mCurrentEncodingListIndex and
197170
// mEncodeState.mCurrentEncodingListIndex are not invalid values.
198-
if (mCurrentEncodingListIndex < mEncodeState.mCurrentEncodingListIndex)
171+
if (mCurrentEncodingListIndex < mEncodeState.CurrentEncodingListIndex())
199172
{
200173
// We have encoded this element in previous chunks, skip it.
201174
mCurrentEncodingListIndex++;
@@ -226,7 +199,7 @@ class AttributeValueEncoder
226199
}
227200

228201
mCurrentEncodingListIndex++;
229-
mEncodeState.mCurrentEncodingListIndex++;
202+
mEncodeState.SetCurrentEncodingListIndex(mCurrentEncodingListIndex);
230203
mEncodedAtLeastOneListItem = true;
231204
return CHIP_NO_ERROR;
232205
}
@@ -266,20 +239,20 @@ class AttributeValueEncoder
266239
*/
267240
void EnsureListEnded();
268241

269-
bool mTriedEncode = false;
270242
AttributeReportIBs::Builder & mAttributeReportIBsBuilder;
271-
const FabricIndex mAccessingFabricIndex;
243+
const Access::SubjectDescriptor mSubjectDescriptor;
272244
ConcreteDataAttributePath mPath;
273245
DataVersion mDataVersion;
246+
bool mTriedEncode = false;
274247
bool mIsFabricFiltered = false;
275248
// mEncodingInitialList is true if we're encoding a list and we have not
276249
// started chunking it yet, so we're encoding a single attribute report IB
277250
// for the whole list, not one per item.
278251
bool mEncodingInitialList = false;
279252
// mEncodedAtLeastOneListItem becomes true once we successfully encode a list item.
280-
bool mEncodedAtLeastOneListItem = false;
281-
AttributeEncodeState mEncodeState;
253+
bool mEncodedAtLeastOneListItem = false;
282254
ListIndex mCurrentEncodingListIndex = kInvalidListIndex;
255+
AttributeEncodeState mEncodeState;
283256
};
284257

285258
} // namespace app

src/app/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ static_library("attribute-access") {
281281
"AttributeAccessInterfaceCache.h",
282282
"AttributeAccessInterfaceRegistry.cpp",
283283
"AttributeAccessInterfaceRegistry.h",
284+
"AttributeEncodeState.h",
284285
"AttributeReportBuilder.cpp",
285286
"AttributeReportBuilder.h",
286287
"AttributeValueDecoder.h",

src/app/ConcreteAttributePath.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ struct ConcreteReadAttributePath : public ConcreteAttributePath
101101
*/
102102
struct ConcreteDataAttributePath : public ConcreteAttributePath
103103
{
104-
enum class ListOperation
104+
enum class ListOperation : uint8_t
105105
{
106106
NotList, // Path points to an attribute that isn't a list.
107107
ReplaceAll, // Path points to an attribute that is a list, indicating that the contents of the list should be replaced in

src/app/ReadHandler.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ void ReadHandler::PersistSubscription()
841841
void ReadHandler::ResetPathIterator()
842842
{
843843
mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributePathList);
844-
mAttributeEncoderState = AttributeValueEncoder::AttributeEncodeState();
844+
mAttributeEncoderState.Reset();
845845
}
846846

847847
void ReadHandler::AttributePathIsDirty(const AttributePathParams & aAttributeChanged)
@@ -870,7 +870,7 @@ void ReadHandler::AttributePathIsDirty(const AttributePathParams & aAttributeCha
870870
// our iterator to point back to the beginning of that cluster. This ensures that the receiver will get a coherent view of
871871
// the state of the cluster as present on the server
872872
mAttributePathExpandIterator.ResetCurrentCluster();
873-
mAttributeEncoderState = AttributeValueEncoder::AttributeEncodeState();
873+
mAttributeEncoderState.Reset();
874874
}
875875

876876
// ReportScheduler will take care of verifying the reportability of the handler and schedule the run

src/app/ReadHandler.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,8 @@ class ReadHandler : public Messaging::ExchangeDelegate
412412
/// or after the min interval is reached if it has not yet been reached.
413413
void ForceDirtyState();
414414

415-
const AttributeValueEncoder::AttributeEncodeState & GetAttributeEncodeState() const { return mAttributeEncoderState; }
416-
void SetAttributeEncodeState(const AttributeValueEncoder::AttributeEncodeState & aState) { mAttributeEncoderState = aState; }
415+
const AttributeEncodeState & GetAttributeEncodeState() const { return mAttributeEncoderState; }
416+
void SetAttributeEncodeState(const AttributeEncodeState & aState) { mAttributeEncoderState = aState; }
417417
uint32_t GetLastWrittenEventsBytes() const { return mLastWrittenEventsBytes; }
418418

419419
// Returns the number of interested paths, including wildcard and concrete paths.
@@ -562,7 +562,7 @@ class ReadHandler : public Messaging::ExchangeDelegate
562562

563563
// The detailed encoding state for a single attribute, used by list chunking feature.
564564
// The size of AttributeEncoderState is 2 bytes for now.
565-
AttributeValueEncoder::AttributeEncodeState mAttributeEncoderState;
565+
AttributeEncodeState mAttributeEncoderState;
566566

567567
// Current Handler state
568568
HandlerState mState = HandlerState::Idle;

src/app/dynamic_server/DynamicDispatcher.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Status DetermineAttributeStatus(const ConcreteAttributePath & aPath, bool aIsWri
123123

124124
CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
125125
const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports,
126-
AttributeValueEncoder::AttributeEncodeState * aEncoderState)
126+
AttributeEncodeState * aEncoderState)
127127
{
128128
Status status = DetermineAttributeStatus(aPath, /* aIsWrite = */ false);
129129
return aAttributeReports.EncodeAttributeStatus(aPath, StatusIB(status));

src/app/reporting/Engine.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ bool Engine::IsClusterDataVersionMatch(const SingleLinkedListNode<DataVersionFil
8282
CHIP_ERROR
8383
Engine::RetrieveClusterData(const SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
8484
AttributeReportIBs::Builder & aAttributeReportIBs, const ConcreteReadAttributePath & aPath,
85-
AttributeValueEncoder::AttributeEncodeState * aEncoderState)
85+
AttributeEncodeState * aEncoderState)
8686
{
8787
ChipLogDetail(DataManagement, "<RE:Run> Cluster %" PRIx32 ", Attribute %" PRIx32 " is dirty", aPath.mClusterId,
8888
aPath.mAttributeId);
@@ -199,7 +199,7 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu
199199
attributeReportIBs.Checkpoint(attributeBackup);
200200
ConcreteReadAttributePath pathForRetrieval(readPath);
201201
// Load the saved state from previous encoding session for chunking of one single attribute (list chunking).
202-
AttributeValueEncoder::AttributeEncodeState encodeState = apReadHandler->GetAttributeEncodeState();
202+
AttributeEncodeState encodeState = apReadHandler->GetAttributeEncodeState();
203203
err = RetrieveClusterData(apReadHandler->GetSubjectDescriptor(), apReadHandler->IsFabricFiltered(), attributeReportIBs,
204204
pathForRetrieval, &encodeState);
205205
if (err != CHIP_NO_ERROR)
@@ -226,7 +226,7 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu
226226
// We met a error during writing reports, one common case is we are running out of buffer, rollback the
227227
// attributeReportIB to avoid any partial data.
228228
attributeReportIBs.Rollback(attributeBackup);
229-
apReadHandler->SetAttributeEncodeState(AttributeValueEncoder::AttributeEncodeState());
229+
apReadHandler->SetAttributeEncodeState(AttributeEncodeState());
230230

231231
if (!IsOutOfWriterSpaceError(err))
232232
{
@@ -256,7 +256,7 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu
256256
}
257257
SuccessOrExit(err);
258258
// Successfully encoded the attribute, clear the internal state.
259-
apReadHandler->SetAttributeEncodeState(AttributeValueEncoder::AttributeEncodeState());
259+
apReadHandler->SetAttributeEncodeState(AttributeEncodeState());
260260
}
261261
// We just visited all paths interested by this read handler and did not abort in the middle of iteration, there are no more
262262
// chunks for this report.

src/app/reporting/Engine.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ class Engine
170170
bool aBufferIsUsed, bool * apHasMoreChunks, bool * apHasEncodedData);
171171
CHIP_ERROR RetrieveClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
172172
AttributeReportIBs::Builder & aAttributeReportIBs,
173-
const ConcreteReadAttributePath & aClusterInfo,
174-
AttributeValueEncoder::AttributeEncodeState * apEncoderState);
173+
const ConcreteReadAttributePath & aClusterInfo, AttributeEncodeState * apEncoderState);
175174
CHIP_ERROR CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & aHasEncodedData, ReadHandler * apReadHandler);
176175

177176
// If version match, it means don't send, if version mismatch, it means send.

0 commit comments

Comments
 (0)