blob: 5d89e9464d347a10548e78062baf1fa37e9a1b97 [file] [log] [blame]
/*
*
* 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.
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <lib/core/CHIPCircularTLVBuffer.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPEncoding.h>
#include <lib/core/CHIPTLV.h>
#include <lib/support/CodeUtils.h>
#include <stdint.h>
namespace chip {
namespace TLV {
using namespace chip::Encoding;
/**
* @brief
* CHIPCircularTLVBuffer 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])
*/
CHIPCircularTLVBuffer::CHIPCircularTLVBuffer(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
* CHIPCircularTLVBuffer constructor
*
* @param[in] inBuffer A pointer to the backing store for the queue
*
* @param[in] inBufferLength Length, in bytes, of the backing store
*/
CHIPCircularTLVBuffer::CHIPCircularTLVBuffer(uint8_t * inBuffer, uint32_t inBufferLength)
{
Init(inBuffer, inBufferLength);
}
/**
* @brief
* CHIPCircularTLVBuffer 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 CHIPCircularTLVBuffer::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 CHIPCircularTLVBuffer
*
* 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
* #CHIPCircularTLVBuffer 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 CHIPCircularTLVBuffer::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<uint8_t *>(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 CHIPCircularTLVBuffer::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 CHIPCircularTLVBuffer::GetNewBuffer(TLVWriter & ioWriter, uint8_t *& outBufStart, uint32_t & outBufLen)
{
uint8_t * tail = QueueTail();
if (mQueueLength >= mQueueSize)
{
// Queue is out of space, need to evict an element
ReturnErrorOnFailure(EvictHead());
}
// set the output values, returned buffer must be contiguous
outBufStart = tail;
if (tail >= mQueueHead)
{
outBufLen = mQueueSize - static_cast<uint32_t>(tail - mQueue);
}
else
{
outBufLen = static_cast<uint32_t>(mQueueHead - tail);
}
return CHIP_NO_ERROR;
}
/**
* @brief
* FinalizeBuffer adjust the `CHIPCircularTLVBuffer` 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 CHIPCircularTLVBuffer::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<uint32_t>(mQueueHead - tail);
}
else
{
mQueueLength = static_cast<uint32_t>(tail - mQueueHead);
}
}
return err;
}
/**
* @brief
* Implements TLVBackingStore::OnInit(TVLReader) for circular buffers.
*/
CHIP_ERROR CHIPCircularTLVBuffer::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 CHIPCircularTLVBuffer 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 CHIPCircularTLVBuffer::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<uint32_t>(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<uint32_t>(tail - outBufStart);
}
return err;
}
} // namespace TLV
} // namespace chip