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 : /* 31 : * @brief 32 : * 33 : * This class provides an iteratable decoder of list items within TLV payloads 34 : * such that no memory has to be provided ahead of time to store the entirety of the decoded 35 : * list contents. 36 : * 37 : * Typical use of a DecodableList looks like this: 38 : * 39 : * auto iter = list.begin(); 40 : * while (iter.Next()) { 41 : * auto & entry = iter.GetValue(); 42 : * // Do whatever with entry 43 : * } 44 : * CHIP_ERROR err = iter.GetStatus(); 45 : * // If err is failure, decoding failed somewhere along the way. Some valid 46 : * // entries may have been processed already. 47 : * 48 : */ 49 : template <typename T> 50 : class DecodableList 51 : { 52 : public: 53 0 : DecodableList() { ClearReader(); } 54 : 55 : static constexpr bool kIsFabricScoped = DataModel::IsFabricScoped<T>::value; 56 : 57 : /* 58 : * @brief 59 : * 60 : * This call stores a TLV reader positioned on the list this class is to manage. 61 : * 62 : * Specifically, the passed-in reader should be pointing into the list just after 63 : * having called `OpenContainer` on the list element. 64 : */ 65 0 : void SetReader(const TLV::TLVReader & reader) { mReader = reader; } 66 : 67 : /* 68 : * @brief 69 : * 70 : * This call clears the TLV reader managed by this class, so it can be reused. 71 : */ 72 0 : void ClearReader() { mReader.Init(nullptr, 0); } 73 : 74 : template <typename T0 = T, std::enable_if_t<DataModel::IsFabricScoped<T0>::value, bool> = true> 75 : void SetFabricIndex(FabricIndex fabricIndex) 76 : { 77 : mFabricIndex.SetValue(fabricIndex); 78 : } 79 : 80 : class Iterator 81 : { 82 : public: 83 : /* 84 : * Initialize the iterator with a reference to a reader. 85 : * 86 : * This reader should be pointing into the list just after 87 : * having called `OpenContainer` on the list element, or should 88 : * have a `kTLVType_NotSpecified` container type if there is 89 : * no list. 90 : */ 91 0 : Iterator(const TLV::TLVReader & reader, Optional<FabricIndex> fabricIndex) : mFabricIndex(fabricIndex) 92 : { 93 0 : mStatus = CHIP_NO_ERROR; 94 0 : mReader.Init(reader); 95 0 : } 96 : 97 : /* 98 : * Increments the iterator to point to the next list element 99 : * if a valid one exists, and decodes the list element into 100 : * the internal value storage. 101 : * 102 : * If an element does exist and was successfully decoded, this 103 : * shall return true. 104 : * 105 : * Otherwise, if the end of list is reached, or there was no list, 106 : * this call shall return false. 107 : * 108 : * If an error was encountered at any point during the iteration or decode, 109 : * this shall return false as well. The caller is expected to invoke GetStatus() 110 : * to retrieve the status of the operation. 111 : */ 112 : template <typename T0 = T, std::enable_if_t<!DataModel::IsFabricScoped<T0>::value, bool> = true> 113 0 : bool Next() 114 : { 115 0 : return DoNext(); 116 : } 117 : 118 : template <typename T0 = T, std::enable_if_t<DataModel::IsFabricScoped<T0>::value, bool> = true> 119 0 : bool Next() 120 : { 121 0 : bool hasNext = DoNext(); 122 : 123 0 : if (hasNext && mFabricIndex.HasValue()) 124 : { 125 0 : mValue.SetFabricIndex(mFabricIndex.Value()); 126 : } 127 : 128 0 : return hasNext; 129 : } 130 : 131 : /* 132 : * Retrieves a reference to the decoded value, if one 133 : * was decoded on a previous call to Next(). 134 : */ 135 0 : const T & GetValue() const { return mValue; } 136 : 137 : /* 138 : * Returns the result of all previous operations on this iterator. 139 : * 140 : * Notably, if the end-of-list was encountered in a previous call to Next, 141 : * the status returned shall be CHIP_NO_ERROR. 142 : */ 143 0 : CHIP_ERROR GetStatus() const 144 : { 145 0 : if (mStatus == CHIP_END_OF_TLV) 146 : { 147 0 : return CHIP_NO_ERROR; 148 : } 149 : 150 0 : return mStatus; 151 : } 152 : 153 : private: 154 0 : bool DoNext() 155 : { 156 0 : if (mReader.GetContainerType() == TLV::kTLVType_NotSpecified) 157 : { 158 0 : return false; 159 : } 160 : 161 0 : if (mStatus == CHIP_NO_ERROR) 162 : { 163 0 : mStatus = mReader.Next(); 164 : } 165 : 166 0 : if (mStatus == CHIP_NO_ERROR) 167 : { 168 : // 169 : // Re-construct mValue to reset its state back to cluster object defaults. 170 : // This is especially important when decoding successive list elements 171 : // that do not contain all of the fields for a given struct because 172 : // they are marked optional/fabric-sensitive. Without this re-construction, 173 : // data from previous decode attempts will continue to linger and give 174 : // an incorrect view of the state as seen from a client. 175 : // 176 0 : mValue = T(); 177 0 : mStatus = DataModel::Decode(mReader, mValue); 178 : } 179 : 180 0 : return (mStatus == CHIP_NO_ERROR); 181 : } 182 : 183 : T mValue; 184 : CHIP_ERROR mStatus; 185 : TLV::TLVReader mReader; 186 : // TODO: Consider some setup where this field does not exist when T 187 : // is not a fabric scoped struct. 188 : const Optional<FabricIndex> mFabricIndex; 189 : }; 190 : 191 0 : Iterator begin() const { return Iterator(mReader, mFabricIndex); } 192 : 193 : /* 194 : * Compute the size of the list. This can fail if the TLV is malformed. If 195 : * this succeeds, that does not guarantee that the individual items can be 196 : * successfully decoded; consumers should check Iterator::GetStatus() when 197 : * actually decoding them. If there is no list then the size is considered 198 : * to be zero. 199 : */ 200 : CHIP_ERROR ComputeSize(size_t * size) const 201 : { 202 : if (mReader.GetContainerType() == TLV::kTLVType_NotSpecified) 203 : { 204 : *size = 0; 205 : return CHIP_NO_ERROR; 206 : } 207 : 208 : return mReader.CountRemainingInContainer(size); 209 : } 210 : 211 0 : CHIP_ERROR Decode(TLV::TLVReader & reader) 212 : { 213 0 : VerifyOrReturnError(reader.GetType() == TLV::kTLVType_Array, CHIP_ERROR_SCHEMA_MISMATCH); 214 : TLV::TLVType type; 215 0 : ReturnErrorOnFailure(reader.EnterContainer(type)); 216 0 : SetReader(reader); 217 0 : ReturnErrorOnFailure(reader.ExitContainer(type)); 218 0 : return CHIP_NO_ERROR; 219 : } 220 : 221 : private: 222 : TLV::TLVReader mReader; 223 : chip::Optional<FabricIndex> mFabricIndex; 224 : }; 225 : 226 : } // namespace DataModel 227 : } // namespace app 228 : } // namespace chip