Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 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 : #include "lib/core/TLV.h"
20 : #include "lib/core/TLVTags.h"
21 : #include "lib/core/TLVTypes.h"
22 : #include "protocols/interaction_model/Constants.h"
23 : #include "system/SystemPacketBuffer.h"
24 : #include "system/TLVPacketBufferBackingStore.h"
25 : #include <app/BufferedReadCallback.h>
26 : #include <app/InteractionModelEngine.h>
27 : #include <lib/support/ScopedBuffer.h>
28 :
29 : namespace chip {
30 : namespace app {
31 :
32 758 : void BufferedReadCallback::OnReportBegin()
33 : {
34 758 : mCallback.OnReportBegin();
35 758 : }
36 :
37 758 : void BufferedReadCallback::OnReportEnd()
38 : {
39 758 : CHIP_ERROR err = DispatchBufferedData(mBufferedPath, StatusIB(), true);
40 758 : if (err != CHIP_NO_ERROR)
41 : {
42 0 : mCallback.OnError(err);
43 0 : return;
44 : }
45 :
46 758 : mCallback.OnReportEnd();
47 : }
48 :
49 1028 : CHIP_ERROR BufferedReadCallback::GenerateListTLV(TLV::ScopedBufferTLVReader & aReader)
50 : {
51 : TLV::TLVType outerType;
52 1028 : Platform::ScopedMemoryBuffer<uint8_t> backingBuffer;
53 :
54 : //
55 : // To generate the final reconstituted list, we need to allocate a contiguous
56 : // buffer than can hold the entirety of its contents. To do so, we need to figure out
57 : // how big a buffer to allocate. This requires walking the buffered list items and computing their TLV sizes,
58 : // summing them all up and adding a bit of slop to account for the TLV array the list elements will go into.
59 : //
60 : // The alternative was to use a PacketBufferTLVWriter backed by chained packet buffers to
61 : // write out the list - this would have removed the need for this first pass. However,
62 : // we cannot actually back a TLVReader with a chained buffer since that violates the ability
63 : // for us to create readers off-of readers. Each reader would assume exclusive ownership of the chained
64 : // buffer and mutate the state within TLVPacketBufferBackingStore, preventing shared use.
65 : //
66 : // To avoid that, a single contiguous buffer is the best likely approach for now.
67 : //
68 1028 : uint32_t totalBufSize = 0;
69 7083 : for (const auto & packetBuffer : mBufferedList)
70 : {
71 6055 : totalBufSize += packetBuffer->TotalLength();
72 : }
73 :
74 : //
75 : // Size of the start container and end container are just 1 byte each, but, let's just be safe.
76 : //
77 1028 : totalBufSize += 4;
78 :
79 1028 : backingBuffer.Calloc(totalBufSize);
80 1028 : VerifyOrReturnError(backingBuffer.Get() != nullptr, CHIP_ERROR_NO_MEMORY);
81 :
82 1028 : TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), totalBufSize);
83 1028 : ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, outerType));
84 :
85 7083 : for (auto & bufHandle : mBufferedList)
86 : {
87 6055 : System::PacketBufferTLVReader reader;
88 :
89 6055 : reader.Init(std::move(bufHandle));
90 :
91 6055 : ReturnErrorOnFailure(reader.Next());
92 6055 : ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag(), reader));
93 6055 : }
94 :
95 1028 : ReturnErrorOnFailure(writer.EndContainer(outerType));
96 :
97 1028 : writer.Finalize(backingBuffer);
98 :
99 1028 : aReader.Init(std::move(backingBuffer), totalBufSize);
100 :
101 1028 : return CHIP_NO_ERROR;
102 1028 : }
103 :
104 7669 : CHIP_ERROR BufferedReadCallback::BufferListItem(TLV::TLVReader & reader)
105 : {
106 7669 : System::PacketBufferTLVWriter writer;
107 7669 : System::PacketBufferHandle handle;
108 :
109 : //
110 : // We conservatively allocate a packet buffer as big as an IPv6 MTU (since we're buffering
111 : // data received over the wire, which should always fit within that).
112 : //
113 : // We could have snapshotted the reader at its current position, advanced it past the current element
114 : // and computed the delta in its read point to figure out the size of the element before allocating
115 : // our target buffer. However, the reader's current position is already set past the control octet
116 : // and the tag. Consequently, the computed size is always going to omit the sizes of these two parts of the
117 : // TLV element. Since the tag can vary in size, for now, let's just do the safe thing. In the future, if this is a problem,
118 : // we can improve this.
119 : //
120 7669 : handle = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
121 7669 : VerifyOrReturnError(!handle.IsNull(), CHIP_ERROR_NO_MEMORY);
122 :
123 7669 : writer.Init(std::move(handle), false);
124 :
125 7669 : ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag(), reader));
126 7669 : ReturnErrorOnFailure(writer.Finalize(&handle));
127 :
128 : // Compact the buffer down to a more reasonably sized packet buffer
129 : // if we can.
130 : //
131 7669 : handle.RightSize();
132 :
133 7669 : mBufferedList.push_back(std::move(handle));
134 :
135 7669 : return CHIP_NO_ERROR;
136 7669 : }
137 :
138 3460 : CHIP_ERROR BufferedReadCallback::BufferData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData)
139 : {
140 :
141 3460 : if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll)
142 : {
143 : TLV::TLVType outerContainer;
144 :
145 1173 : VerifyOrReturnError(apData->GetType() == TLV::kTLVType_Array, CHIP_ERROR_INVALID_TLV_ELEMENT);
146 1173 : mBufferedList.clear();
147 :
148 1173 : ReturnErrorOnFailure(apData->EnterContainer(outerContainer));
149 :
150 : CHIP_ERROR err;
151 :
152 6555 : while ((err = apData->Next()) == CHIP_NO_ERROR)
153 : {
154 5382 : ReturnErrorOnFailure(BufferListItem(*apData));
155 : }
156 :
157 1173 : if (err == CHIP_END_OF_TLV)
158 : {
159 1173 : err = CHIP_NO_ERROR;
160 : }
161 :
162 1173 : ReturnErrorOnFailure(err);
163 1173 : ReturnErrorOnFailure(apData->ExitContainer(outerContainer));
164 : }
165 2287 : else if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
166 : {
167 2287 : ReturnErrorOnFailure(BufferListItem(*apData));
168 : }
169 :
170 3460 : return CHIP_NO_ERROR;
171 : }
172 :
173 5601 : CHIP_ERROR BufferedReadCallback::DispatchBufferedData(const ConcreteAttributePath & aPath, const StatusIB & aStatusIB,
174 : bool aEndOfReport)
175 : {
176 5601 : if (aPath == mBufferedPath)
177 : {
178 : //
179 : // If we encountered the same list again and it's not the last DataIB, then
180 : // we need to continue to buffer up this list's data, so return immediately without dispatching
181 : // the existing buffered up contents.
182 : //
183 3203 : if (!aEndOfReport)
184 : {
185 2445 : return CHIP_NO_ERROR;
186 : }
187 :
188 : //
189 : // If we had previously buffered up data for this list and now we have encountered
190 : // an error for this list, that error takes precedence and the buffered data is now
191 : // rendered invalid. Return immediately without dispatching the existing buffered up contents.
192 : //
193 758 : if (aStatusIB.mStatus != Protocols::InteractionModel::Status::Success)
194 : {
195 0 : return CHIP_NO_ERROR;
196 : }
197 : }
198 :
199 3156 : if (!mBufferedPath.IsListOperation())
200 : {
201 2128 : return CHIP_NO_ERROR;
202 : }
203 :
204 1028 : StatusIB statusIB;
205 1028 : TLV::ScopedBufferTLVReader reader;
206 :
207 1028 : ReturnErrorOnFailure(GenerateListTLV(reader));
208 :
209 : //
210 : // Update the list operation to now reflect the delivery of the entire list
211 : // i.e a replace all operation.
212 : //
213 1028 : mBufferedPath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
214 :
215 : //
216 : // Advance the reader forward to the list itself
217 : //
218 1028 : ReturnErrorOnFailure(reader.Next());
219 :
220 1028 : mCallback.OnAttributeData(mBufferedPath, &reader, statusIB);
221 :
222 : //
223 : // Clear out our buffered contents to free up allocated buffers, and reset the buffered path.
224 : //
225 1028 : mBufferedList.clear();
226 1028 : mBufferedPath = ConcreteDataAttributePath();
227 1028 : return CHIP_NO_ERROR;
228 1028 : }
229 :
230 4843 : void BufferedReadCallback::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
231 : const StatusIB & aStatus)
232 : {
233 : CHIP_ERROR err;
234 :
235 : //
236 : // First, let's dispatch to our registered callback any buffered up list data from previous calls.
237 : //
238 4843 : err = DispatchBufferedData(aPath, aStatus);
239 4843 : SuccessOrExit(err);
240 :
241 : //
242 : // We buffer up list data (only if the status was successful)
243 : //
244 4843 : if (aPath.IsListOperation() && aStatus.mStatus == Protocols::InteractionModel::Status::Success)
245 : {
246 3460 : err = BufferData(aPath, apData);
247 3460 : SuccessOrExit(err);
248 : }
249 : else
250 : {
251 1383 : mCallback.OnAttributeData(aPath, apData, aStatus);
252 : }
253 :
254 : //
255 : // Update our latched buffered path.
256 : //
257 4843 : mBufferedPath = aPath;
258 :
259 4843 : exit:
260 4843 : if (err != CHIP_NO_ERROR)
261 : {
262 0 : mCallback.OnError(err);
263 : }
264 4843 : }
265 :
266 : } // namespace app
267 : } // namespace chip
|