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 6 : ListEncodeHelper(AttributeValueEncoder & encoder) : mAttributeValueEncoder(encoder) {}
47 :
48 : template <typename T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, bool> = true>
49 : CHIP_ERROR Encode(const 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(mCheckpoint, aArg, mAttributeValueEncoder.AccessingFabricIndex());
59 : }
60 :
61 : template <typename T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, bool> = true>
62 1986 : CHIP_ERROR Encode(const T & aArg) const
63 : {
64 1986 : return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg);
65 : }
66 :
67 : // overrides that save flash: no need to care about the extra const
68 : // Without this, we have a usage of:
69 : // chip::ChipError chip::app::AttributeValueEncoder::EncodeListItem<unsigned long const&>
70 : // Overall we tend to have very similar code expand (from nm):
71 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char>(unsigned char&&)
72 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char&>(unsigned char&)
73 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char const&>(unsigned char const&)
74 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short const&>(unsigned short const&)
75 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long&>(unsigned long&)
76 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short&>(unsigned short&)
77 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long long&>(unsigned long long&)
78 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short>(unsigned short&&)
79 : // chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long long>(unsigned long long&&)
80 : // that we try to reduce
81 : //
82 : // TODO:
83 : // - we should figure where the extra const override is used
84 : // - we should try to avoid having such footguns. This list template-explosion seems
85 : // dangerous for flash.
86 : //
87 : // This relies on TLV numbers always being encoded as 64-bit value
88 : inline CHIP_ERROR Encode(uint32_t const & aArg) const { return Encode<uint64_t>(aArg); }
89 : inline CHIP_ERROR Encode(uint32_t & aArg) const { return Encode<uint64_t>(aArg); }
90 : inline CHIP_ERROR Encode(uint16_t const & aArg) const { return Encode<uint64_t>(aArg); }
91 : inline CHIP_ERROR Encode(uint16_t & aArg) const { return Encode<uint64_t>(aArg); }
92 : inline CHIP_ERROR Encode(uint8_t const & aArg) const { return Encode<uint64_t>(aArg); }
93 : inline CHIP_ERROR Encode(uint8_t & aArg) const { return Encode<uint64_t>(aArg); }
94 :
95 : private:
96 : AttributeValueEncoder & mAttributeValueEncoder;
97 : // Avoid calling the TLVWriter constructor for every instantiation of
98 : // EncodeListItem. We treat encoding as a const operation, so either
99 : // have to put this on the stack (in which case it's per-instantiation),
100 : // or have it as mutable state.
101 : mutable TLV::TLVWriter mCheckpoint;
102 : };
103 :
104 2471 : AttributeValueEncoder(AttributeReportIBs::Builder & aAttributeReportIBsBuilder, Access::SubjectDescriptor subjectDescriptor,
105 : const ConcreteAttributePath & aPath, DataVersion aDataVersion, bool aIsFabricFiltered = false,
106 2471 : const AttributeEncodeState & aState = AttributeEncodeState()) :
107 2471 : mAttributeReportIBsBuilder(aAttributeReportIBsBuilder),
108 2471 : mSubjectDescriptor(subjectDescriptor), mPath(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
109 2471 : mDataVersion(aDataVersion), mIsFabricFiltered(aIsFabricFiltered), mEncodeState(aState)
110 2471 : {}
111 :
112 : /**
113 : * Encode a single value. This value will not be chunked; it will either be
114 : * entirely encoded or fail to be encoded. Consumers are allowed to make
115 : * either one call to Encode or one call to EncodeList to handle a read.
116 : */
117 : template <typename... Ts>
118 36 : CHIP_ERROR Encode(Ts &&... aArgs)
119 : {
120 36 : mTriedEncode = true;
121 36 : return EncodeAttributeReportIB(std::forward<Ts>(aArgs)...);
122 : }
123 :
124 : /**
125 : * Encode an explicit null value.
126 : */
127 : CHIP_ERROR EncodeNull()
128 : {
129 : // Doesn't matter what type Nullable we use here.
130 : return Encode(DataModel::Nullable<uint8_t>());
131 : }
132 :
133 : /**
134 : * Encode an explicit empty list.
135 : */
136 : CHIP_ERROR EncodeEmptyList()
137 : {
138 : // Doesn't matter what type List we use here.
139 : return Encode(DataModel::List<uint8_t>());
140 : }
141 :
142 : /**
143 : * aCallback is expected to take a const auto & argument and Encode() on it as many times as needed to encode all the list
144 : * elements one by one. If any of those Encode() calls returns failure, aCallback must stop encoding and return failure. When
145 : * all items are encoded aCallback is expected to return success.
146 : *
147 : * aCallback may not be called. Consumers must not assume it will be called.
148 : *
149 : * When EncodeList returns an error, the consumers must abort the encoding, and return the exact error to the caller.
150 : *
151 : * TODO: Can we hold a error state in the AttributeValueEncoder itself?
152 : *
153 : * Consumers are allowed to make either one call to EncodeList or one call to Encode to handle a read.
154 : *
155 : */
156 : template <typename ListGenerator>
157 1190 : CHIP_ERROR EncodeList(ListGenerator aCallback)
158 : {
159 1190 : mTriedEncode = true;
160 : // Spec 10.5.4.3.1, 10.5.4.6 (Replace a list w/ Multiple IBs)
161 : // EmptyList acts as the beginning of the whole array type attribute report.
162 : // An empty list is encoded iff both mCurrentEncodingListIndex and mEncodeState.mCurrentEncodingListIndex are invalid
163 : // values. After encoding the empty list, mEncodeState.mCurrentEncodingListIndex and mCurrentEncodingListIndex are set to 0.
164 1190 : ReturnErrorOnFailure(EnsureListStarted());
165 1134 : CHIP_ERROR err = aCallback(ListEncodeHelper(*this));
166 :
167 : // Even if encoding list items failed, make sure we EnsureListEnded().
168 : // Since we encode list items atomically, in the case when we just
169 : // didn't fit the next item we want to make sure our list is properly
170 : // ended before the reporting engine starts chunking.
171 1134 : EnsureListEnded();
172 1134 : if (err == CHIP_NO_ERROR)
173 : {
174 : // The Encode procedure finished without any error, clear the state.
175 1089 : mEncodeState.Reset();
176 : }
177 1134 : return err;
178 : }
179 :
180 1625 : bool TriedEncode() const { return mTriedEncode; }
181 :
182 : const Access::SubjectDescriptor & GetSubjectDescriptor() const { return mSubjectDescriptor; }
183 :
184 : /**
185 : * The accessing fabric index for this read or subscribe interaction.
186 : */
187 : FabricIndex AccessingFabricIndex() const { return GetSubjectDescriptor().fabricIndex; }
188 :
189 : /**
190 : * AttributeValueEncoder is a short lived object, and the state is persisted by mEncodeState and restored by constructor.
191 : */
192 228 : const AttributeEncodeState & GetState() const { return mEncodeState; }
193 :
194 : private:
195 : // We made EncodeListItem() private, and ListEncoderHelper will expose it by Encode()
196 : friend class ListEncodeHelper;
197 : friend class TestOnlyAttributeValueEncoderAccessor;
198 :
199 : // Returns true if the list item should be encoded. If it should, the
200 : // passed-in TLVWriter will be used to checkpoint the current state of our
201 : // attribute report list builder.
202 : bool ShouldEncodeListItem(TLV::TLVWriter & aCheckpoint);
203 :
204 : // Does any cleanup work needed after attempting to encode a list item.
205 : void PostEncodeListItem(CHIP_ERROR aEncodeStatus, const TLV::TLVWriter & aCheckpoint);
206 :
207 : // EncodeListItem may be given an extra FabricIndex argument as a second
208 : // arg, or not. Represent that via a parameter pack (which might be
209 : // empty). In practice, for any given ItemType the extra arg is either there
210 : // or not, so we don't get more template explosion due to aExtraArgs.
211 : template <typename ItemType, typename... ExtraArgTypes>
212 1986 : CHIP_ERROR EncodeListItem(TLV::TLVWriter & aCheckpoint, const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
213 : {
214 1986 : if (!ShouldEncodeListItem(aCheckpoint))
215 : {
216 101 : return CHIP_NO_ERROR;
217 : }
218 :
219 : CHIP_ERROR err;
220 1885 : if (mEncodingInitialList)
221 : {
222 : // Just encode a single item, with an anonymous tag.
223 : AttributeReportBuilder builder;
224 1801 : err = builder.EncodeValue(mAttributeReportIBsBuilder, TLV::AnonymousTag(), aItem,
225 : std::forward<ExtraArgTypes>(aExtraArgs)...);
226 : }
227 : else
228 : {
229 84 : err = EncodeAttributeReportIB(aItem, std::forward<ExtraArgTypes>(aExtraArgs)...);
230 : }
231 :
232 1885 : PostEncodeListItem(err, aCheckpoint);
233 1885 : return err;
234 : }
235 :
236 : /**
237 : * Builds a single AttributeReportIB in AttributeReportIBs. The caller is
238 : * responsible for setting up mPath correctly.
239 : *
240 : * In particular, when we are encoding a single element in the list, mPath
241 : * must indicate a null list index to represent an "append" operation.
242 : * operation.
243 : *
244 : * EncodeAttributeReportIB may be given an extra FabricIndex argument as a second
245 : * arg, or not. Represent that via a parameter pack (which might be
246 : * empty). In practice, for any given ItemType the extra arg is either
247 : * there or not, so we don't get more template explosion due to aExtraArgs.
248 : */
249 : template <typename ItemType, typename... ExtraArgTypes>
250 120 : CHIP_ERROR EncodeAttributeReportIB(const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
251 : {
252 : AttributeReportBuilder builder;
253 120 : ReturnErrorOnFailure(builder.PrepareAttribute(mAttributeReportIBsBuilder, mPath, mDataVersion));
254 120 : ReturnErrorOnFailure(builder.EncodeValue(mAttributeReportIBsBuilder, TLV::ContextTag(AttributeDataIB::Tag::kData), aItem,
255 : std::forward<ExtraArgTypes>(aExtraArgs)...));
256 :
257 120 : return builder.FinishAttribute(mAttributeReportIBsBuilder);
258 : }
259 :
260 : /**
261 : * EnsureListStarted sets our mCurrentEncodingListIndex to 0, and:
262 : *
263 : * * If we are just starting the list, gets us ready to encode list items.
264 : *
265 : * * If we are continuing a chunked list, guarantees that mPath.mListOp is
266 : * AppendItem after it returns.
267 : */
268 : CHIP_ERROR EnsureListStarted();
269 :
270 : /**
271 : * EnsureListEnded writes out the end of the list and our attribute data IB,
272 : * if we were encoding our initial list
273 : */
274 : void EnsureListEnded();
275 :
276 : AttributeReportIBs::Builder & mAttributeReportIBsBuilder;
277 : const Access::SubjectDescriptor mSubjectDescriptor;
278 : ConcreteDataAttributePath mPath;
279 : DataVersion mDataVersion;
280 : bool mTriedEncode = false;
281 : bool mIsFabricFiltered = false;
282 : // mEncodingInitialList is true if we're encoding a list and we have not
283 : // started chunking it yet, so we're encoding a single attribute report IB
284 : // for the whole list, not one per item.
285 : bool mEncodingInitialList = false;
286 : // mEncodedAtLeastOneListItem becomes true once we successfully encode a list item.
287 : bool mEncodedAtLeastOneListItem = false;
288 : ListIndex mCurrentEncodingListIndex = kInvalidListIndex;
289 : AttributeEncodeState mEncodeState;
290 : };
291 :
292 : } // namespace app
293 : } // namespace chip
|