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 <app/BufferedReadCallback.h>
20 : #include <app/InteractionModelEngine.h>
21 : #include <lib/core/TLV.h>
22 : #include <lib/core/TLVTags.h>
23 : #include <lib/core/TLVTypes.h>
24 : #include <lib/support/ScopedMemoryBuffer.h>
25 : #include <protocols/interaction_model/Constants.h>
26 : #include <system/SystemPacketBuffer.h>
27 : #include <system/TLVPacketBufferBackingStore.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 1646 : 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 : TEMPORARY_RETURN_IGNORED 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 : const size_t bufSize = mAllowLargePayload ? chip::app::kMaxLargeSecureSduLengthBytes : chip::app::kMaxSecureSduLengthBytes;
121 8012 : handle = System::PacketBufferHandle::New(bufSize);
122 8012 : VerifyOrReturnError(!handle.IsNull(), CHIP_ERROR_NO_MEMORY);
123 :
124 8012 : writer.Init(std::move(handle), false);
125 :
126 8012 : ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag(), reader));
127 8012 : ReturnErrorOnFailure(writer.Finalize(&handle));
128 :
129 : // Compact the buffer down to a more reasonably sized packet buffer
130 : // if we can.
131 : //
132 8012 : handle.RightSize();
133 :
134 8012 : mBufferedList.push_back(std::move(handle));
135 :
136 8012 : return CHIP_NO_ERROR;
137 8012 : }
138 :
139 3562 : CHIP_ERROR BufferedReadCallback::BufferData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData)
140 : {
141 :
142 3562 : if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll)
143 : {
144 1272 : VerifyOrReturnError(apData != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
145 : TLV::TLVType outerContainer;
146 :
147 1271 : VerifyOrReturnError(apData->GetType() == TLV::kTLVType_Array, CHIP_ERROR_INVALID_TLV_ELEMENT);
148 1271 : mBufferedList.clear();
149 :
150 1271 : ReturnErrorOnFailure(apData->EnterContainer(outerContainer));
151 :
152 : CHIP_ERROR err;
153 :
154 13992 : while ((err = apData->Next()) == CHIP_NO_ERROR)
155 : {
156 5725 : ReturnErrorOnFailure(BufferListItem(*apData));
157 : }
158 :
159 2542 : if (err == CHIP_END_OF_TLV)
160 : {
161 1271 : err = CHIP_NO_ERROR;
162 : }
163 :
164 1271 : ReturnErrorOnFailure(err);
165 1271 : ReturnErrorOnFailure(apData->ExitContainer(outerContainer));
166 : }
167 2290 : else if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
168 : {
169 2288 : VerifyOrReturnError(apData != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
170 2287 : ReturnErrorOnFailure(BufferListItem(*apData));
171 : }
172 :
173 3560 : return CHIP_NO_ERROR;
174 : }
175 :
176 5773 : CHIP_ERROR BufferedReadCallback::DispatchBufferedData(const ConcreteAttributePath & aPath, const StatusIB & aStatusIB,
177 : bool aEndOfReport)
178 : {
179 5773 : if (aPath == mBufferedPath)
180 : {
181 : //
182 : // If we encountered the same list again and it's not the last DataIB, then
183 : // we need to continue to buffer up this list's data, so return immediately without dispatching
184 : // the existing buffered up contents.
185 : //
186 3269 : if (!aEndOfReport)
187 : {
188 2446 : return CHIP_NO_ERROR;
189 : }
190 :
191 : //
192 : // If we had previously buffered up data for this list and now we have encountered
193 : // an error for this list, that error takes precedence and the buffered data is now
194 : // rendered invalid. Return immediately without dispatching the existing buffered up contents.
195 : //
196 823 : if (aStatusIB.mStatus != Protocols::InteractionModel::Status::Success)
197 : {
198 0 : return CHIP_NO_ERROR;
199 : }
200 : }
201 :
202 3327 : if (!mBufferedPath.IsListOperation())
203 : {
204 2201 : return CHIP_NO_ERROR;
205 : }
206 :
207 1126 : StatusIB statusIB;
208 1126 : TLV::ScopedBufferTLVReader reader;
209 :
210 1126 : ReturnErrorOnFailure(GenerateListTLV(reader));
211 :
212 : //
213 : // Update the list operation to now reflect the delivery of the entire list
214 : // i.e a replace all operation.
215 : //
216 1126 : mBufferedPath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
217 :
218 : //
219 : // Advance the reader forward to the list itself
220 : //
221 1126 : ReturnErrorOnFailure(reader.Next());
222 :
223 1126 : mCallback.OnAttributeData(mBufferedPath, &reader, statusIB);
224 :
225 : //
226 : // Clear out our buffered contents to free up allocated buffers, and reset the buffered path.
227 : //
228 1126 : mBufferedList.clear();
229 1126 : mBufferedPath = ConcreteDataAttributePath();
230 1126 : return CHIP_NO_ERROR;
231 1126 : }
232 :
233 4950 : void BufferedReadCallback::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
234 : const StatusIB & aStatus)
235 : {
236 : CHIP_ERROR err;
237 :
238 : //
239 : // First, let's dispatch to our registered callback any buffered up list data from previous calls.
240 : //
241 4950 : err = DispatchBufferedData(aPath, aStatus);
242 4950 : SuccessOrExit(err);
243 :
244 : //
245 : // We buffer up list data (only if the status was successful)
246 : //
247 4950 : if (aPath.IsListOperation() && aStatus.mStatus == Protocols::InteractionModel::Status::Success)
248 : {
249 3562 : err = BufferData(aPath, apData);
250 3562 : SuccessOrExit(err);
251 : }
252 : else
253 : {
254 1388 : mCallback.OnAttributeData(aPath, apData, aStatus);
255 : }
256 :
257 : //
258 : // Update our latched buffered path.
259 : //
260 4948 : mBufferedPath = aPath;
261 :
262 4950 : exit:
263 9900 : if (err != CHIP_NO_ERROR)
264 : {
265 2 : mCallback.OnError(err);
266 : }
267 4950 : }
268 :
269 : } // namespace app
270 : } // namespace chip
|