Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020-2021 Project CHIP Authors
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 :
19 : #pragma once
20 :
21 : #include <app/data-model/Decode.h>
22 : #include <app/data-model/Encode.h>
23 : #include <app/data-model/FabricScoped.h>
24 : #include <lib/core/TLV.h>
25 :
26 : namespace chip {
27 : namespace app {
28 : namespace DataModel {
29 :
30 : namespace detail {
31 :
32 : /*
33 : * Base class of DecodableList to minimize template usage
34 : */
35 : class DecodableListBase
36 : {
37 : public:
38 0 : DecodableListBase() { ClearReader(); }
39 :
40 : /*
41 : * @brief
42 : *
43 : * This call stores a TLV reader positioned on the list this class is to manage.
44 : *
45 : * Specifically, the passed-in reader should be pointing into the list just after
46 : * having called `OpenContainer` on the list element.
47 : */
48 0 : void SetReader(const TLV::TLVReader & reader) { mReader = reader; }
49 :
50 : /*
51 : * @brief
52 : *
53 : * This call clears the TLV reader managed by this class, so it can be reused.
54 : */
55 0 : void ClearReader() { mReader.Init(nullptr, 0); }
56 :
57 : /*
58 : * Compute the size of the list. This can fail if the TLV is malformed. If
59 : * this succeeds, that does not guarantee that the individual items can be
60 : * successfully decoded; consumers should check Iterator::GetStatus() when
61 : * actually decoding them. If there is no list then the size is considered
62 : * to be zero.
63 : */
64 : CHIP_ERROR ComputeSize(size_t * size) const
65 : {
66 : if (mReader.GetContainerType() == TLV::kTLVType_NotSpecified)
67 : {
68 : *size = 0;
69 : return CHIP_NO_ERROR;
70 : }
71 :
72 : return mReader.CountRemainingInContainer(size);
73 : }
74 :
75 0 : CHIP_ERROR Decode(TLV::TLVReader & reader)
76 : {
77 0 : VerifyOrReturnError(reader.GetType() == TLV::kTLVType_Array, CHIP_ERROR_SCHEMA_MISMATCH);
78 : TLV::TLVType type;
79 0 : ReturnErrorOnFailure(reader.EnterContainer(type));
80 0 : SetReader(reader);
81 0 : ReturnErrorOnFailure(reader.ExitContainer(type));
82 0 : return CHIP_NO_ERROR;
83 : }
84 :
85 : protected:
86 : class Iterator
87 : {
88 : public:
89 0 : Iterator(const TLV::TLVReader & reader)
90 0 : {
91 0 : mStatus = CHIP_NO_ERROR;
92 0 : mReader.Init(reader);
93 0 : }
94 :
95 0 : bool Next()
96 : {
97 0 : if (mReader.GetContainerType() == TLV::kTLVType_NotSpecified)
98 : {
99 0 : return false;
100 : }
101 :
102 0 : if (mStatus == CHIP_NO_ERROR)
103 : {
104 0 : mStatus = mReader.Next();
105 : }
106 :
107 0 : return (mStatus == CHIP_NO_ERROR);
108 : }
109 :
110 : /*
111 : * Returns the result of all previous operations on this iterator.
112 : *
113 : * Notably, if the end-of-list was encountered in a previous call to Next,
114 : * the status returned shall be CHIP_NO_ERROR.
115 : */
116 0 : CHIP_ERROR GetStatus() const
117 : {
118 0 : if (mStatus == CHIP_END_OF_TLV)
119 : {
120 0 : return CHIP_NO_ERROR;
121 : }
122 :
123 0 : return mStatus;
124 : }
125 :
126 : protected:
127 : CHIP_ERROR mStatus;
128 : TLV::TLVReader mReader;
129 : };
130 :
131 : TLV::TLVReader mReader;
132 : };
133 :
134 : template <bool IsFabricScoped>
135 : class FabricIndexListMemberMixin
136 : {
137 : };
138 :
139 : template <>
140 : class FabricIndexListMemberMixin<true>
141 : {
142 : public:
143 : void SetFabricIndex(FabricIndex fabricIndex) { mFabricIndex.SetValue(fabricIndex); }
144 :
145 : protected:
146 : Optional<FabricIndex> mFabricIndex;
147 : };
148 :
149 : template <bool IsFabricScoped>
150 : class FabricIndexIteratorMemberMixin
151 : {
152 : };
153 :
154 : template <>
155 : class FabricIndexIteratorMemberMixin<true>
156 : {
157 : public:
158 0 : FabricIndexIteratorMemberMixin(const Optional<FabricIndex> & fabricindex) : mFabricIndex(fabricindex) {}
159 :
160 : protected:
161 : const Optional<FabricIndex> mFabricIndex;
162 : };
163 :
164 : template <bool IsFabricScoped>
165 : class DecodableMaybeFabricScopedList : public DecodableListBase, public FabricIndexListMemberMixin<IsFabricScoped>
166 : {
167 : public:
168 : static constexpr bool kIsFabricScoped = IsFabricScoped;
169 :
170 : protected:
171 : class Iterator : public DecodableListBase::Iterator, public FabricIndexIteratorMemberMixin<IsFabricScoped>
172 : {
173 : public:
174 : template <bool IsActuallyFabricScoped = IsFabricScoped, std::enable_if_t<IsActuallyFabricScoped, bool> = true>
175 0 : Iterator(const TLV::TLVReader & reader, const Optional<FabricIndex> & fabricIndex) :
176 0 : DecodableListBase::Iterator(reader), FabricIndexIteratorMemberMixin<IsFabricScoped>(fabricIndex)
177 0 : {}
178 :
179 : template <bool IsActuallyFabricScoped = IsFabricScoped, std::enable_if_t<!IsActuallyFabricScoped, bool> = true>
180 0 : Iterator(const TLV::TLVReader & reader) : DecodableListBase::Iterator(reader)
181 0 : {}
182 : };
183 : };
184 :
185 : } // namespace detail
186 :
187 : /*
188 : * @brief
189 : *
190 : * This class provides an iteratable decoder of list items within TLV payloads
191 : * such that no memory has to be provided ahead of time to store the entirety of the decoded
192 : * list contents.
193 : *
194 : * Typical use of a DecodableList looks like this:
195 : *
196 : * auto iter = list.begin();
197 : * while (iter.Next()) {
198 : * auto & entry = iter.GetValue();
199 : * // Do whatever with entry
200 : * }
201 : * CHIP_ERROR err = iter.GetStatus();
202 : * // If err is failure, decoding failed somewhere along the way. Some valid
203 : * // entries may have been processed already.
204 : *
205 : */
206 : template <typename T>
207 : class DecodableList : public detail::DecodableMaybeFabricScopedList<DataModel::IsFabricScoped<T>::value>
208 : {
209 : public:
210 0 : DecodableList() {}
211 :
212 : class Iterator : public detail::DecodableMaybeFabricScopedList<DataModel::IsFabricScoped<T>::value>::Iterator
213 : {
214 : using IteratorBase = typename detail::DecodableMaybeFabricScopedList<DataModel::IsFabricScoped<T>::value>::Iterator;
215 :
216 : public:
217 : /*
218 : * Initialize the iterator with a reference to a reader.
219 : *
220 : * This reader should be pointing into the list just after
221 : * having called `OpenContainer` on the list element, or should
222 : * have a `kTLVType_NotSpecified` container type if there is
223 : * no list.
224 : */
225 : template <typename T0 = T, std::enable_if_t<DataModel::IsFabricScoped<T0>::value, bool> = true>
226 0 : Iterator(const TLV::TLVReader & reader, Optional<FabricIndex> fabricIndex) : IteratorBase(reader, fabricIndex)
227 0 : {}
228 :
229 : template <typename T0 = T, std::enable_if_t<!DataModel::IsFabricScoped<T0>::value, bool> = true>
230 0 : Iterator(const TLV::TLVReader & reader) : IteratorBase(reader)
231 0 : {}
232 :
233 : /*
234 : * Increments the iterator to point to the next list element
235 : * if a valid one exists, and decodes the list element into
236 : * the internal value storage.
237 : *
238 : * If an element does exist and was successfully decoded, this
239 : * shall return true.
240 : *
241 : * Otherwise, if the end of list is reached, or there was no list,
242 : * this call shall return false.
243 : *
244 : * If an error was encountered at any point during the iteration or decode,
245 : * this shall return false as well. The caller is expected to invoke GetStatus()
246 : * to retrieve the status of the operation.
247 : */
248 : template <typename T0 = T, std::enable_if_t<!DataModel::IsFabricScoped<T0>::value, bool> = true>
249 0 : bool Next()
250 : {
251 0 : return IteratorBase::Next() && DecodeValue();
252 : }
253 :
254 : template <typename T0 = T, std::enable_if_t<DataModel::IsFabricScoped<T0>::value, bool> = true>
255 0 : bool Next()
256 : {
257 0 : bool hasNext = IteratorBase::Next() && DecodeValue();
258 :
259 0 : if (hasNext && this->mFabricIndex.HasValue())
260 : {
261 0 : mValue.SetFabricIndex(this->mFabricIndex.Value());
262 : }
263 :
264 0 : return hasNext;
265 : }
266 :
267 : /*
268 : * Retrieves a reference to the decoded value, if one
269 : * was decoded on a previous call to Next().
270 : */
271 0 : const T & GetValue() const { return mValue; }
272 :
273 : private:
274 0 : bool DecodeValue()
275 : {
276 0 : if (this->mStatus == CHIP_NO_ERROR)
277 : {
278 : //
279 : // Re-construct mValue to reset its state back to cluster object defaults.
280 : // This is especially important when decoding successive list elements
281 : // that do not contain all of the fields for a given struct because
282 : // they are marked optional/fabric-sensitive. Without this re-construction,
283 : // data from previous decode attempts will continue to linger and give
284 : // an incorrect view of the state as seen from a client.
285 : //
286 0 : mValue = T();
287 0 : this->mStatus = DataModel::Decode(this->mReader, mValue);
288 : }
289 :
290 0 : return (this->mStatus == CHIP_NO_ERROR);
291 : }
292 :
293 : T mValue;
294 : };
295 :
296 : // Need this->mReader and this->mFabricIndex for the name lookup to realize
297 : // those can be found in our superclasses.
298 : template <typename T0 = T, std::enable_if_t<DataModel::IsFabricScoped<T0>::value, bool> = true>
299 0 : Iterator begin() const
300 : {
301 0 : return Iterator(this->mReader, this->mFabricIndex);
302 : }
303 :
304 : template <typename T0 = T, std::enable_if_t<!DataModel::IsFabricScoped<T0>::value, bool> = true>
305 0 : Iterator begin() const
306 : {
307 0 : return Iterator(this->mReader);
308 : }
309 : };
310 :
311 : } // namespace DataModel
312 : } // namespace app
313 : } // namespace chip
|