Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020-2021 Project CHIP Authors
4 : * Copyright (c) 2016-2017 Nest Labs, Inc.
5 : * All rights reserved.
6 : *
7 : * Licensed under the Apache License, Version 2.0 (the "License");
8 : * you may not use this file except in compliance with the License.
9 : * You may obtain a copy of the License at
10 : *
11 : * http://www.apache.org/licenses/LICENSE-2.0
12 : *
13 : * Unless required by applicable law or agreed to in writing, software
14 : * distributed under the License is distributed on an "AS IS" BASIS,
15 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 : * See the License for the specific language governing permissions and
17 : * limitations under the License.
18 : */
19 :
20 : /**
21 : * @file
22 : * This file implements the circular buffer for TLV
23 : * elements. When used as the backing store for the TLVReader and
24 : * TLVWriter, those classes will work with the wraparound of data
25 : * within the buffer. Additionally, the TLVWriter will be able
26 : * to continually add top-level TLV elements by evicting
27 : * pre-existing elements.
28 : */
29 : #include <lib/core/TLVCircularBuffer.h>
30 :
31 : #include <lib/core/CHIPError.h>
32 : #include <lib/core/TLVReader.h>
33 : #include <lib/core/TLVTags.h>
34 : #include <lib/core/TLVWriter.h>
35 : #include <lib/support/BufferWriter.h>
36 : #include <lib/support/CodeUtils.h>
37 :
38 : #include <stdint.h>
39 :
40 : namespace chip {
41 : namespace TLV {
42 :
43 : using namespace chip::Encoding;
44 :
45 : /**
46 : * @brief
47 : * TLVCircularBuffer constructor
48 : *
49 : * @param[in] inBuffer A pointer to the backing store for the queue
50 : *
51 : * @param[in] inBufferLength Length, in bytes, of the backing store
52 : *
53 : * @param[in] inHead Initial point for the head. The @a inHead pointer is must fall within the backing store for the
54 : * circular buffer, i.e. within @a inBuffer and &(@a inBuffer[@a inBufferLength])
55 : */
56 1 : TLVCircularBuffer::TLVCircularBuffer(uint8_t * inBuffer, uint32_t inBufferLength, uint8_t * inHead)
57 : {
58 1 : mQueue = inBuffer;
59 1 : mQueueSize = inBufferLength;
60 1 : mQueueLength = 0;
61 1 : mQueueHead = inHead;
62 :
63 1 : mProcessEvictedElement = nullptr;
64 1 : mAppData = nullptr;
65 :
66 : // use common as opposed to unspecified, s.t. the reader that
67 : // skips over the elements does not complain about implicit
68 : // profile tags.
69 1 : mImplicitProfileId = kCommonProfileId;
70 1 : }
71 :
72 : /**
73 : * @brief
74 : * TLVCircularBuffer constructor
75 : *
76 : * @param[in] inBuffer A pointer to the backing store for the queue
77 : *
78 : * @param[in] inBufferLength Length, in bytes, of the backing store
79 : */
80 1027 : TLVCircularBuffer::TLVCircularBuffer(uint8_t * inBuffer, uint32_t inBufferLength)
81 : {
82 1027 : Init(inBuffer, inBufferLength);
83 1027 : }
84 :
85 : /**
86 : * @brief
87 : * TLVCircularBuffer Init function
88 : *
89 : * @param[in] inBuffer A pointer to the backing store for the queue
90 : *
91 : * @param[in] inBufferLength Length, in bytes, of the backing store
92 : */
93 1402 : void TLVCircularBuffer::Init(uint8_t * inBuffer, uint32_t inBufferLength)
94 : {
95 1402 : mQueue = inBuffer;
96 1402 : mQueueSize = inBufferLength;
97 1402 : mQueueLength = 0;
98 1402 : mQueueHead = mQueue;
99 :
100 1402 : mProcessEvictedElement = nullptr;
101 1402 : mAppData = nullptr;
102 :
103 : // use common as opposed to unspecified, s.t. the reader that
104 : // skips over the elements does not complain about implicit
105 : // profile tags.
106 1402 : mImplicitProfileId = kCommonProfileId;
107 1402 : }
108 :
109 : /**
110 : * @brief
111 : * Evicts the oldest top-level TLV element in the TLVCircularBuffer
112 : *
113 : * This function removes the oldest top level TLV element in the
114 : * buffer. The function will call the callback registered at
115 : * #mProcessEvictedElement to process the element prior to removal.
116 : * If the callback returns anything but #CHIP_NO_ERROR, the element
117 : * is not removed. Similarly, if any other error occurs -- no
118 : * elements within the buffer, etc -- the underlying
119 : * #TLVCircularBuffer remains unchanged.
120 : *
121 : * @retval #CHIP_NO_ERROR On success.
122 : *
123 : * @retval other On any other error returned either by the callback
124 : * or by the TLVReader.
125 : *
126 : */
127 1545 : CHIP_ERROR TLVCircularBuffer::EvictHead()
128 : {
129 1545 : CircularTLVReader reader;
130 : uint8_t * newHead;
131 : uint32_t newLen;
132 :
133 : // find the boundaries of an event to throw away
134 1545 : reader.Init(*this);
135 1545 : reader.ImplicitProfileId = mImplicitProfileId;
136 :
137 : // position the reader on the first element
138 1545 : ReturnErrorOnFailure(reader.Next());
139 :
140 : // skip to the next element
141 1545 : ReturnErrorOnFailure(reader.Skip());
142 :
143 : // record the state of the queue post-call
144 1544 : newLen = mQueueLength - (reader.GetLengthRead());
145 1544 : newHead = const_cast<uint8_t *>(reader.GetReadPoint());
146 :
147 : // if a custom handler is installed, give it a chance to
148 : // process the element before we evict it from the buffer.
149 1544 : if (mProcessEvictedElement != nullptr)
150 : {
151 : // Reinitialize the reader
152 1116 : reader.Init(*this);
153 1116 : reader.ImplicitProfileId = mImplicitProfileId;
154 :
155 1116 : ReturnErrorOnFailure(mProcessEvictedElement(*this, mAppData, reader));
156 : }
157 :
158 : // update queue state
159 809 : mQueueLength = newLen;
160 809 : mQueueHead = newHead;
161 :
162 809 : return CHIP_NO_ERROR;
163 : }
164 :
165 : /**
166 : * @brief
167 : * Implements TLVBackingStore::OnInit(TLVWriter) for circular buffers.
168 : */
169 10 : CHIP_ERROR TLVCircularBuffer::OnInit(TLVWriter & writer, uint8_t *& bufStart, uint32_t & bufLen)
170 : {
171 10 : return GetNewBuffer(writer, bufStart, bufLen);
172 : }
173 :
174 : /**
175 : * @brief
176 : * Get additional space for the TLVWriter. In actuality, the
177 : * function evicts an element from the circular buffer, and adjusts
178 : * the head of this buffer queue
179 : *
180 : * @param[in,out] ioWriter TLVWriter calling this function
181 : *
182 : * @param[out] outBufStart The pointer to the new buffer
183 : *
184 : * @param[out] outBufLen The available length for writing
185 : *
186 : * @retval #CHIP_NO_ERROR On success.
187 : *
188 : * @retval other If the function was unable to elide a complete
189 : * top-level TLV element.
190 : */
191 :
192 508 : CHIP_ERROR TLVCircularBuffer::GetNewBuffer(TLVWriter & ioWriter, uint8_t *& outBufStart, uint32_t & outBufLen)
193 : {
194 508 : if (mQueueLength >= mQueueSize)
195 : {
196 : // Queue is out of space, need to evict an element
197 18 : ReturnErrorOnFailure(EvictHead());
198 : }
199 :
200 507 : GetCurrentWritableBuffer(outBufStart, outBufLen);
201 507 : return CHIP_NO_ERROR;
202 : }
203 :
204 1591 : void TLVCircularBuffer::GetCurrentWritableBuffer(uint8_t *& outBufStart, uint32_t & outBufLen) const
205 : {
206 1591 : uint8_t * tail = QueueTail();
207 :
208 : // set the output values, returned buffer must be contiguous
209 1591 : outBufStart = tail;
210 :
211 1591 : if (tail >= mQueueHead)
212 : {
213 351 : outBufLen = mQueueSize - static_cast<uint32_t>(tail - mQueue);
214 : }
215 : else
216 : {
217 1240 : outBufLen = static_cast<uint32_t>(mQueueHead - tail);
218 : }
219 1591 : }
220 :
221 : /**
222 : * @brief
223 : * FinalizeBuffer adjust the `TLVCircularBuffer` state on
224 : * completion of output from the TLVWriter. This function affects
225 : * the position of the queue tail.
226 : *
227 : * @param[in,out] ioWriter TLVWriter calling this function
228 : *
229 : * @param[in] inBufStart pointer to the start of data (from `TLVWriter`
230 : * perspective)
231 : *
232 : * @param[in] inBufLen length of data in the buffer pointed to by
233 : * `inbufStart`
234 : *
235 : * @retval #CHIP_NO_ERROR Unconditionally.
236 : */
237 :
238 1605 : CHIP_ERROR TLVCircularBuffer::FinalizeBuffer(TLVWriter & ioWriter, uint8_t * inBufStart, uint32_t inBufLen)
239 : {
240 1605 : CHIP_ERROR err = CHIP_NO_ERROR;
241 1605 : uint8_t * tail = inBufStart + inBufLen;
242 1605 : if (inBufLen)
243 : {
244 1605 : if (tail <= mQueueHead)
245 : {
246 1242 : mQueueLength = mQueueSize - static_cast<uint32_t>(mQueueHead - tail);
247 : }
248 : else
249 : {
250 363 : mQueueLength = static_cast<uint32_t>(tail - mQueueHead);
251 : }
252 : }
253 1605 : return err;
254 : }
255 :
256 : /**
257 : * @brief
258 : * Implements TLVBackingStore::OnInit(TVLReader) for circular buffers.
259 : */
260 4041 : CHIP_ERROR TLVCircularBuffer::OnInit(TLVReader & reader, const uint8_t *& bufStart, uint32_t & bufLen)
261 : {
262 4041 : return GetNextBuffer(reader, bufStart, bufLen);
263 : }
264 :
265 : /**
266 : * @brief
267 : * Get additional space for the TLVReader.
268 : *
269 : * The storage provided by the TLVCircularBuffer may be
270 : * wraparound within the buffer. This function provides us with an
271 : * ability to match the buffering of the circular buffer to the
272 : * TLVReader constraints. The reader will read at most `mQueueSize`
273 : * bytes from the buffer.
274 : *
275 : * @param[in] ioReader TLVReader calling this function.
276 : *
277 : * @param[in,out] outBufStart The reference to the data buffer. On
278 : * return, it is set to a value within this
279 : * buffer.
280 : *
281 : * @param[out] outBufLen On return, set to the number of continuous
282 : * bytes that could be read out of the buffer.
283 : *
284 : * @retval #CHIP_NO_ERROR Succeeds unconditionally.
285 : */
286 6141 : CHIP_ERROR TLVCircularBuffer::GetNextBuffer(TLVReader & ioReader, const uint8_t *& outBufStart, uint32_t & outBufLen)
287 : {
288 6141 : CHIP_ERROR err = CHIP_NO_ERROR;
289 6141 : uint8_t * tail = QueueTail();
290 6141 : const uint8_t * readerStart = outBufStart;
291 :
292 6141 : if (readerStart == nullptr)
293 : {
294 5855 : outBufStart = mQueueHead;
295 :
296 5855 : if (outBufStart == mQueue + mQueueSize)
297 : {
298 10 : outBufStart = mQueue;
299 : }
300 : }
301 286 : else if (readerStart >= (mQueue + mQueueSize))
302 : {
303 238 : outBufStart = mQueue;
304 : }
305 : else
306 : {
307 48 : outBufLen = 0;
308 48 : return err;
309 : }
310 :
311 6093 : if ((mQueueLength != 0) && (tail <= outBufStart))
312 : {
313 : // the buffer is non-empty and data wraps around the end
314 : // point. The returned buffer conceptually spans from
315 : // outBufStart until the end of the underlying storage buffer
316 : // (i.e. mQueue+mQueueSize). This case tail == outBufStart
317 : // indicates that the buffer is completely full
318 3077 : outBufLen = mQueueSize - static_cast<uint32_t>(outBufStart - mQueue);
319 3077 : if ((tail == outBufStart) && (readerStart != nullptr))
320 0 : outBufLen = 0;
321 : }
322 : else
323 : {
324 : // the buffer length is the distance between head and tail;
325 : // tail is either strictly larger or the buffer is empty
326 3016 : outBufLen = static_cast<uint32_t>(tail - outBufStart);
327 : }
328 6093 : return err;
329 : }
330 :
331 : } // namespace TLV
332 : } // namespace chip
|