Line data Source code
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 <access/SubjectDescriptor.h>
20 : #include <app/AttributeEncodeState.h>
21 : #include <app/AttributeReportBuilder.h>
22 : #include <app/ConcreteAttributePath.h>
23 : #include <app/MessageDef/AttributeReportIBs.h>
24 : #include <app/data-model/FabricScoped.h>
25 : #include <app/data-model/List.h>
26 :
27 : #include <type_traits>
28 :
29 : namespace chip {
30 : namespace app {
31 :
32 : /**
33 : * The AttributeValueEncoder is a helper class for filling report payloads into AttributeReportIBs.
34 : * The attribute value encoder can be initialized with a AttributeEncodeState for saving and recovering its state between encode
35 : * sessions (chunkings).
36 : *
37 : * When Encode returns recoverable errors (e.g. CHIP_ERROR_NO_MEMORY) the state can be used to initialize the AttributeValueEncoder
38 : * for future use on the same attribute path.
39 : */
40 : class AttributeValueEncoder
41 : {
42 : public:
43 : class ListEncodeHelper
44 : {
45 : public:
46 1307 : ListEncodeHelper(AttributeValueEncoder & encoder) : mAttributeValueEncoder(encoder) {}
47 :
48 : template <typename T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, bool> = true>
49 : CHIP_ERROR Encode(T && aArg) const
50 : {
51 : VerifyOrReturnError(aArg.GetFabricIndex() != kUndefinedFabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX);
52 :
53 : // If we are encoding for a fabric filtered attribute read and the fabric index does not match that present in the
54 : // request, skip encoding this list item.
55 : VerifyOrReturnError(!mAttributeValueEncoder.mIsFabricFiltered ||
56 : aArg.GetFabricIndex() == mAttributeValueEncoder.AccessingFabricIndex(),
57 : CHIP_NO_ERROR);
58 : return mAttributeValueEncoder.EncodeListItem(mAttributeValueEncoder.AccessingFabricIndex(), std::forward<T>(aArg));
59 : }
60 :
61 : template <typename T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, bool> = true>
62 2086 : CHIP_ERROR Encode(T && aArg) const
63 : {
64 2086 : return mAttributeValueEncoder.EncodeListItem(std::forward<T>(aArg));
65 : }
66 :
67 : private:
68 : AttributeValueEncoder & mAttributeValueEncoder;
69 : };
70 :
71 2471 : AttributeValueEncoder(AttributeReportIBs::Builder & aAttributeReportIBsBuilder, Access::SubjectDescriptor subjectDescriptor,
72 : const ConcreteAttributePath & aPath, DataVersion aDataVersion, bool aIsFabricFiltered = false,
73 2471 : const AttributeEncodeState & aState = AttributeEncodeState()) :
74 2471 : mAttributeReportIBsBuilder(aAttributeReportIBsBuilder),
75 2471 : mSubjectDescriptor(subjectDescriptor), mPath(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
76 2471 : mDataVersion(aDataVersion), mIsFabricFiltered(aIsFabricFiltered), mEncodeState(aState)
77 2471 : {}
78 :
79 : /**
80 : * Encode a single value. This value will not be chunked; it will either be
81 : * entirely encoded or fail to be encoded. Consumers are allowed to make
82 : * either one call to Encode or one call to EncodeList to handle a read.
83 : */
84 : template <typename... Ts>
85 36 : CHIP_ERROR Encode(Ts &&... aArgs)
86 : {
87 36 : mTriedEncode = true;
88 36 : return EncodeAttributeReportIB(std::forward<Ts>(aArgs)...);
89 : }
90 :
91 : /**
92 : * Encode an explicit null value.
93 : */
94 : CHIP_ERROR EncodeNull()
95 : {
96 : // Doesn't matter what type Nullable we use here.
97 : return Encode(DataModel::Nullable<uint8_t>());
98 : }
99 :
100 : /**
101 : * Encode an explicit empty list.
102 : */
103 : CHIP_ERROR EncodeEmptyList()
104 : {
105 : // Doesn't matter what type List we use here.
106 : return Encode(DataModel::List<uint8_t>());
107 : }
108 :
109 : /**
110 : * aCallback is expected to take a const auto & argument and Encode() on it as many times as needed to encode all the list
111 : * elements one by one. If any of those Encode() calls returns failure, aCallback must stop encoding and return failure. When
112 : * all items are encoded aCallback is expected to return success.
113 : *
114 : * aCallback may not be called. Consumers must not assume it will be called.
115 : *
116 : * When EncodeList returns an error, the consumers must abort the encoding, and return the exact error to the caller.
117 : *
118 : * TODO: Can we hold a error state in the AttributeValueEncoder itself?
119 : *
120 : * Consumers are allowed to make either one call to EncodeList or one call to Encode to handle a read.
121 : *
122 : */
123 : template <typename ListGenerator>
124 716 : CHIP_ERROR EncodeList(ListGenerator aCallback)
125 : {
126 716 : mTriedEncode = true;
127 : // Spec 10.5.4.3.1, 10.5.4.6 (Replace a list w/ Multiple IBs)
128 : // EmptyList acts as the beginning of the whole array type attribute report.
129 : // An empty list is encoded iff both mCurrentEncodingListIndex and mEncodeState.mCurrentEncodingListIndex are invalid
130 : // values. After encoding the empty list, mEncodeState.mCurrentEncodingListIndex and mCurrentEncodingListIndex are set to 0.
131 716 : ReturnErrorOnFailure(EnsureListStarted());
132 666 : CHIP_ERROR err = aCallback(ListEncodeHelper(*this));
133 :
134 : // Even if encoding list items failed, make sure we EnsureListEnded().
135 : // Since we encode list items atomically, in the case when we just
136 : // didn't fit the next item we want to make sure our list is properly
137 : // ended before the reporting engine starts chunking.
138 666 : EnsureListEnded();
139 666 : if (err == CHIP_NO_ERROR)
140 : {
141 : // The Encode procedure finished without any error, clear the state.
142 622 : mEncodeState.Reset();
143 : }
144 666 : return err;
145 : }
146 :
147 2247 : bool TriedEncode() const { return mTriedEncode; }
148 :
149 : const Access::SubjectDescriptor & GetSubjectDescriptor() const { return mSubjectDescriptor; }
150 :
151 : /**
152 : * The accessing fabric index for this read or subscribe interaction.
153 : */
154 : FabricIndex AccessingFabricIndex() const { return GetSubjectDescriptor().fabricIndex; }
155 :
156 : /**
157 : * AttributeValueEncoder is a short lived object, and the state is persisted by mEncodeState and restored by constructor.
158 : */
159 228 : const AttributeEncodeState & GetState() const { return mEncodeState; }
160 :
161 : private:
162 : // We made EncodeListItem() private, and ListEncoderHelper will expose it by Encode()
163 : friend class ListEncodeHelper;
164 : friend class TestOnlyAttributeValueEncoderAccessor;
165 :
166 : template <typename... Ts>
167 2086 : CHIP_ERROR EncodeListItem(Ts &&... aArgs)
168 : {
169 : // EncodeListItem must be called after EnsureListStarted(), thus mCurrentEncodingListIndex and
170 : // mEncodeState.mCurrentEncodingListIndex are not invalid values.
171 2086 : if (mCurrentEncodingListIndex < mEncodeState.CurrentEncodingListIndex())
172 : {
173 : // We have encoded this element in previous chunks, skip it.
174 196 : mCurrentEncodingListIndex++;
175 196 : return CHIP_NO_ERROR;
176 : }
177 :
178 1890 : TLV::TLVWriter backup;
179 1890 : mAttributeReportIBsBuilder.Checkpoint(backup);
180 :
181 : CHIP_ERROR err;
182 1890 : if (mEncodingInitialList)
183 : {
184 : // Just encode a single item, with an anonymous tag.
185 : AttributeReportBuilder builder;
186 1726 : err = builder.EncodeValue(mAttributeReportIBsBuilder, TLV::AnonymousTag(), std::forward<Ts>(aArgs)...);
187 : }
188 : else
189 : {
190 164 : err = EncodeAttributeReportIB(std::forward<Ts>(aArgs)...);
191 : }
192 1890 : if (err != CHIP_NO_ERROR)
193 : {
194 : // For list chunking, ReportEngine should not rollback the buffer when CHIP_ERROR_NO_MEMORY or similar error occurred.
195 : // However, the error might be raised in the middle of encoding procedure, then the buffer may contain partial data,
196 : // unclosed containers etc. This line clears all possible partial data and makes EncodeListItem is atomic.
197 44 : mAttributeReportIBsBuilder.Rollback(backup);
198 44 : return err;
199 : }
200 :
201 1846 : mCurrentEncodingListIndex++;
202 1846 : mEncodeState.SetCurrentEncodingListIndex(mCurrentEncodingListIndex);
203 1846 : mEncodedAtLeastOneListItem = true;
204 1846 : return CHIP_NO_ERROR;
205 : }
206 :
207 : /**
208 : * Builds a single AttributeReportIB in AttributeReportIBs. The caller is
209 : * responsible for setting up mPath correctly.
210 : *
211 : * In particular, when we are encoding a single element in the list, mPath
212 : * must indicate a null list index to represent an "append" operation.
213 : * operation.
214 : */
215 : template <typename... Ts>
216 200 : CHIP_ERROR EncodeAttributeReportIB(Ts &&... aArgs)
217 : {
218 : AttributeReportBuilder builder;
219 200 : ReturnErrorOnFailure(builder.PrepareAttribute(mAttributeReportIBsBuilder, mPath, mDataVersion));
220 200 : ReturnErrorOnFailure(builder.EncodeValue(mAttributeReportIBsBuilder, TLV::ContextTag(AttributeDataIB::Tag::kData),
221 : std::forward<Ts>(aArgs)...));
222 :
223 200 : return builder.FinishAttribute(mAttributeReportIBsBuilder);
224 : }
225 :
226 : /**
227 : * EnsureListStarted sets our mCurrentEncodingListIndex to 0, and:
228 : *
229 : * * If we are just starting the list, gets us ready to encode list items.
230 : *
231 : * * If we are continuing a chunked list, guarantees that mPath.mListOp is
232 : * AppendItem after it returns.
233 : */
234 : CHIP_ERROR EnsureListStarted();
235 :
236 : /**
237 : * EnsureListEnded writes out the end of the list and our attribute data IB,
238 : * if we were encoding our initial list
239 : */
240 : void EnsureListEnded();
241 :
242 : AttributeReportIBs::Builder & mAttributeReportIBsBuilder;
243 : const Access::SubjectDescriptor mSubjectDescriptor;
244 : ConcreteDataAttributePath mPath;
245 : DataVersion mDataVersion;
246 : bool mTriedEncode = false;
247 : bool mIsFabricFiltered = false;
248 : // mEncodingInitialList is true if we're encoding a list and we have not
249 : // started chunking it yet, so we're encoding a single attribute report IB
250 : // for the whole list, not one per item.
251 : bool mEncodingInitialList = false;
252 : // mEncodedAtLeastOneListItem becomes true once we successfully encode a list item.
253 : bool mEncodedAtLeastOneListItem = false;
254 : ListIndex mCurrentEncodingListIndex = kInvalidListIndex;
255 : AttributeEncodeState mEncodeState;
256 : };
257 :
258 : } // namespace app
259 : } // namespace chip
|