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 823 : void BufferedReadCallback::OnReportBegin()
33 : {
34 823 : mCallback.OnReportBegin();
35 823 : }
36 :
37 823 : void BufferedReadCallback::OnReportEnd()
38 : {
39 823 : CHIP_ERROR err = DispatchBufferedData(mBufferedPath, StatusIB(), true);
40 823 : if (err != CHIP_NO_ERROR)
41 : {
42 0 : mCallback.OnError(err);
43 0 : return;
44 : }
45 :
46 823 : mCallback.OnReportEnd();
47 : }
48 :
49 1126 : CHIP_ERROR BufferedReadCallback::GenerateListTLV(TLV::ScopedBufferTLVReader & aReader)
50 : {
51 : TLV::TLVType outerType;
52 1126 : 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 1126 : size_t totalBufSize = 0;
69 7524 : for (const auto & packetBuffer : mBufferedList)
70 : {
71 6398 : 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 1126 : totalBufSize += 4;
78 :
79 1126 : backingBuffer.Calloc(totalBufSize);
80 1126 : VerifyOrReturnError(backingBuffer.Get() != nullptr, CHIP_ERROR_NO_MEMORY);
81 :
82 1126 : TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), totalBufSize);
83 1126 : ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, outerType));
84 :
85 7524 : for (auto & bufHandle : mBufferedList)
86 : {
87 6398 : System::PacketBufferTLVReader reader;
88 :
89 6398 : reader.Init(std::move(bufHandle));
90 :
91 6398 : ReturnErrorOnFailure(reader.Next());
92 6398 : ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag(), reader));
93 6398 : }
94 :
95 1126 : ReturnErrorOnFailure(writer.EndContainer(outerType));
96 :
97 1126 : writer.Finalize(backingBuffer);
98 :
99 1126 : aReader.Init(std::move(backingBuffer), totalBufSize);
100 :
101 1126 : return CHIP_NO_ERROR;
102 1126 : }
103 :
104 8012 : CHIP_ERROR BufferedReadCallback::BufferListItem(TLV::TLVReader & reader)
105 : {
106 8012 : System::PacketBufferTLVWriter writer;
107 8012 : 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 8012 : handle = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
121 8012 : VerifyOrReturnError(!handle.IsNull(), CHIP_ERROR_NO_MEMORY);
122 :
123 8012 : writer.Init(std::move(handle), false);
124 :
125 8012 : ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag(), reader));
126 8012 : ReturnErrorOnFailure(writer.Finalize(&handle));
127 :
128 : // Compact the buffer down to a more reasonably sized packet buffer
129 : // if we can.
130 : //
131 8012 : handle.RightSize();
132 :
133 8012 : mBufferedList.push_back(std::move(handle));
134 :
135 8012 : return CHIP_NO_ERROR;
136 8012 : }
137 :
138 3558 : CHIP_ERROR BufferedReadCallback::BufferData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData)
139 : {
140 :
141 3558 : if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll)
142 : {
143 : TLV::TLVType outerContainer;
144 :
145 1271 : VerifyOrReturnError(apData->GetType() == TLV::kTLVType_Array, CHIP_ERROR_INVALID_TLV_ELEMENT);
146 1271 : mBufferedList.clear();
147 :
148 1271 : ReturnErrorOnFailure(apData->EnterContainer(outerContainer));
149 :
150 : CHIP_ERROR err;
151 :
152 6996 : while ((err = apData->Next()) == CHIP_NO_ERROR)
153 : {
154 5725 : ReturnErrorOnFailure(BufferListItem(*apData));
155 : }
156 :
157 1271 : if (err == CHIP_END_OF_TLV)
158 : {
159 1271 : err = CHIP_NO_ERROR;
160 : }
161 :
162 1271 : ReturnErrorOnFailure(err);
163 1271 : ReturnErrorOnFailure(apData->ExitContainer(outerContainer));
164 : }
165 2287 : else if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
166 : {
167 2287 : ReturnErrorOnFailure(BufferListItem(*apData));
168 : }
169 :
170 3558 : return CHIP_NO_ERROR;
171 : }
172 :
173 5731 : CHIP_ERROR BufferedReadCallback::DispatchBufferedData(const ConcreteAttributePath & aPath, const StatusIB & aStatusIB,
174 : bool aEndOfReport)
175 : {
176 5731 : 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 3268 : 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 823 : if (aStatusIB.mStatus != Protocols::InteractionModel::Status::Success)
194 : {
195 0 : return CHIP_NO_ERROR;
196 : }
197 : }
198 :
199 3286 : if (!mBufferedPath.IsListOperation())
200 : {
201 2160 : return CHIP_NO_ERROR;
202 : }
203 :
204 1126 : StatusIB statusIB;
205 1126 : TLV::ScopedBufferTLVReader reader;
206 :
207 1126 : 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 1126 : mBufferedPath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
214 :
215 : //
216 : // Advance the reader forward to the list itself
217 : //
218 1126 : ReturnErrorOnFailure(reader.Next());
219 :
220 1126 : 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 1126 : mBufferedList.clear();
226 1126 : mBufferedPath = ConcreteDataAttributePath();
227 1126 : return CHIP_NO_ERROR;
228 1126 : }
229 :
230 4908 : 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 4908 : err = DispatchBufferedData(aPath, aStatus);
239 4908 : SuccessOrExit(err);
240 :
241 : //
242 : // We buffer up list data (only if the status was successful)
243 : //
244 4908 : if (aPath.IsListOperation() && aStatus.mStatus == Protocols::InteractionModel::Status::Success)
245 : {
246 3558 : err = BufferData(aPath, apData);
247 3558 : SuccessOrExit(err);
248 : }
249 : else
250 : {
251 1350 : mCallback.OnAttributeData(aPath, apData, aStatus);
252 : }
253 :
254 : //
255 : // Update our latched buffered path.
256 : //
257 4908 : mBufferedPath = aPath;
258 :
259 4908 : exit:
260 4908 : if (err != CHIP_NO_ERROR)
261 : {
262 0 : mCallback.OnError(err);
263 : }
264 4908 : }
265 :
266 : } // namespace app
267 : } // namespace chip
|