/* * * Copyright (c) 2020-2021 Project CHIP Authors * Copyright (c) 2016-2017 Nest Labs, Inc. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file * This file implements the circular buffer for TLV * elements. When used as the backing store for the TLVReader and * TLVWriter, those classes will work with the wraparound of data * within the buffer. Additionally, the TLVWriter will be able * to continually add top-level TLV elements by evicting * pre-existing elements. */ #include #include #include #include #include #include #include #include namespace chip { namespace TLV { using namespace chip::Encoding; /** * @brief * TLVCircularBuffer constructor * * @param[in] inBuffer A pointer to the backing store for the queue * * @param[in] inBufferLength Length, in bytes, of the backing store * * @param[in] inHead Initial point for the head. The @a inHead pointer is must fall within the backing store for the * circular buffer, i.e. within @a inBuffer and &(@a inBuffer[@a inBufferLength]) */ TLVCircularBuffer::TLVCircularBuffer(uint8_t * inBuffer, uint32_t inBufferLength, uint8_t * inHead) { mQueue = inBuffer; mQueueSize = inBufferLength; mQueueLength = 0; mQueueHead = inHead; mProcessEvictedElement = nullptr; mAppData = nullptr; // use common as opposed to unspecified, s.t. the reader that // skips over the elements does not complain about implicit // profile tags. mImplicitProfileId = kCommonProfileId; } /** * @brief * TLVCircularBuffer constructor * * @param[in] inBuffer A pointer to the backing store for the queue * * @param[in] inBufferLength Length, in bytes, of the backing store */ TLVCircularBuffer::TLVCircularBuffer(uint8_t * inBuffer, uint32_t inBufferLength) { Init(inBuffer, inBufferLength); } /** * @brief * TLVCircularBuffer Init function * * @param[in] inBuffer A pointer to the backing store for the queue * * @param[in] inBufferLength Length, in bytes, of the backing store */ void TLVCircularBuffer::Init(uint8_t * inBuffer, uint32_t inBufferLength) { mQueue = inBuffer; mQueueSize = inBufferLength; mQueueLength = 0; mQueueHead = mQueue; mProcessEvictedElement = nullptr; mAppData = nullptr; // use common as opposed to unspecified, s.t. the reader that // skips over the elements does not complain about implicit // profile tags. mImplicitProfileId = kCommonProfileId; } /** * @brief * Evicts the oldest top-level TLV element in the TLVCircularBuffer * * This function removes the oldest top level TLV element in the * buffer. The function will call the callback registered at * #mProcessEvictedElement to process the element prior to removal. * If the callback returns anything but #CHIP_NO_ERROR, the element * is not removed. Similarly, if any other error occurs -- no * elements within the buffer, etc -- the underlying * #TLVCircularBuffer remains unchanged. * * @retval #CHIP_NO_ERROR On success. * * @retval other On any other error returned either by the callback * or by the TLVReader. * */ CHIP_ERROR TLVCircularBuffer::EvictHead() { CircularTLVReader reader; uint8_t * newHead; uint32_t newLen; // find the boundaries of an event to throw away reader.Init(*this); reader.ImplicitProfileId = mImplicitProfileId; // position the reader on the first element ReturnErrorOnFailure(reader.Next()); // skip to the next element ReturnErrorOnFailure(reader.Skip()); // record the state of the queue post-call newLen = mQueueLength - (reader.GetLengthRead()); newHead = const_cast(reader.GetReadPoint()); // if a custom handler is installed, give it a chance to // process the element before we evict it from the buffer. if (mProcessEvictedElement != nullptr) { // Reinitialize the reader reader.Init(*this); reader.ImplicitProfileId = mImplicitProfileId; ReturnErrorOnFailure(mProcessEvictedElement(*this, mAppData, reader)); } // update queue state mQueueLength = newLen; mQueueHead = newHead; return CHIP_NO_ERROR; } /** * @brief * Implements TLVBackingStore::OnInit(TLVWriter) for circular buffers. */ CHIP_ERROR TLVCircularBuffer::OnInit(TLVWriter & writer, uint8_t *& bufStart, uint32_t & bufLen) { return GetNewBuffer(writer, bufStart, bufLen); } /** * @brief * Get additional space for the TLVWriter. In actuality, the * function evicts an element from the circular buffer, and adjusts * the head of this buffer queue * * @param[in,out] ioWriter TLVWriter calling this function * * @param[out] outBufStart The pointer to the new buffer * * @param[out] outBufLen The available length for writing * * @retval #CHIP_NO_ERROR On success. * * @retval other If the function was unable to elide a complete * top-level TLV element. */ CHIP_ERROR TLVCircularBuffer::GetNewBuffer(TLVWriter & ioWriter, uint8_t *& outBufStart, uint32_t & outBufLen) { if (mQueueLength >= mQueueSize) { // Queue is out of space, need to evict an element ReturnErrorOnFailure(EvictHead()); } GetCurrentWritableBuffer(outBufStart, outBufLen); return CHIP_NO_ERROR; } void TLVCircularBuffer::GetCurrentWritableBuffer(uint8_t *& outBufStart, uint32_t & outBufLen) const { uint8_t * tail = QueueTail(); // set the output values, returned buffer must be contiguous outBufStart = tail; if (tail >= mQueueHead) { outBufLen = mQueueSize - static_cast(tail - mQueue); } else { outBufLen = static_cast(mQueueHead - tail); } } /** * @brief * FinalizeBuffer adjust the `TLVCircularBuffer` state on * completion of output from the TLVWriter. This function affects * the position of the queue tail. * * @param[in,out] ioWriter TLVWriter calling this function * * @param[in] inBufStart pointer to the start of data (from `TLVWriter` * perspective) * * @param[in] inBufLen length of data in the buffer pointed to by * `inbufStart` * * @retval #CHIP_NO_ERROR Unconditionally. */ CHIP_ERROR TLVCircularBuffer::FinalizeBuffer(TLVWriter & ioWriter, uint8_t * inBufStart, uint32_t inBufLen) { CHIP_ERROR err = CHIP_NO_ERROR; uint8_t * tail = inBufStart + inBufLen; if (inBufLen) { if (tail <= mQueueHead) { mQueueLength = mQueueSize - static_cast(mQueueHead - tail); } else { mQueueLength = static_cast(tail - mQueueHead); } } return err; } /** * @brief * Implements TLVBackingStore::OnInit(TVLReader) for circular buffers. */ CHIP_ERROR TLVCircularBuffer::OnInit(TLVReader & reader, const uint8_t *& bufStart, uint32_t & bufLen) { return GetNextBuffer(reader, bufStart, bufLen); } /** * @brief * Get additional space for the TLVReader. * * The storage provided by the TLVCircularBuffer may be * wraparound within the buffer. This function provides us with an * ability to match the buffering of the circular buffer to the * TLVReader constraints. The reader will read at most `mQueueSize` * bytes from the buffer. * * @param[in] ioReader TLVReader calling this function. * * @param[in,out] outBufStart The reference to the data buffer. On * return, it is set to a value within this * buffer. * * @param[out] outBufLen On return, set to the number of continuous * bytes that could be read out of the buffer. * * @retval #CHIP_NO_ERROR Succeeds unconditionally. */ CHIP_ERROR TLVCircularBuffer::GetNextBuffer(TLVReader & ioReader, const uint8_t *& outBufStart, uint32_t & outBufLen) { CHIP_ERROR err = CHIP_NO_ERROR; uint8_t * tail = QueueTail(); const uint8_t * readerStart = outBufStart; if (readerStart == nullptr) { outBufStart = mQueueHead; if (outBufStart == mQueue + mQueueSize) { outBufStart = mQueue; } } else if (readerStart >= (mQueue + mQueueSize)) { outBufStart = mQueue; } else { outBufLen = 0; return err; } if ((mQueueLength != 0) && (tail <= outBufStart)) { // the buffer is non-empty and data wraps around the end // point. The returned buffer conceptually spans from // outBufStart until the end of the underlying storage buffer // (i.e. mQueue+mQueueSize). This case tail == outBufStart // indicates that the buffer is completely full outBufLen = mQueueSize - static_cast(outBufStart - mQueue); if ((tail == outBufStart) && (readerStart != nullptr)) outBufLen = 0; } else { // the buffer length is the distance between head and tail; // tail is either strictly larger or the buffer is empty outBufLen = static_cast(tail - outBufStart); } return err; } } // namespace TLV } // namespace chip