blob: 38d80092680919449593dc0f73c4ec494d06093a [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements NCP frame buffer class.
*/
#include <openthread/config.h>
#include "ncp_buffer.hpp"
#include "utils/wrap_string.h"
#include "common/code_utils.hpp"
namespace ot {
const NcpFrameBuffer::FrameTag NcpFrameBuffer::kInvalidTag = NULL;
NcpFrameBuffer::NcpFrameBuffer(uint8_t *aBuffer, uint16_t aBufferLen) :
mBuffer(aBuffer),
mBufferEnd(aBuffer + aBufferLen),
mBufferLength(aBufferLen)
{
otMessageQueueInit(&mMessageQueue);
otMessageQueueInit(&mWriteFrameMessageQueue);
SetFrameAddedCallback(NULL, NULL);
SetFrameRemovedCallback(NULL, NULL);
Clear();
}
void NcpFrameBuffer::Clear(void)
{
otMessage *message;
// Write (InFrame) related variables
mWriteFrameStart = mBuffer;
mWriteSegmentHead = mBuffer;
mWriteSegmentTail = mBuffer;
mWriteFrameTag = kInvalidTag;
// Read (OutFrame) related variables
mReadState = kReadStateDone;
mReadFrameLength = kUnknownFrameLength;
mReadFrameStart = mBuffer;
mReadSegmentHead = mBuffer;
mReadSegmentTail = mBuffer;
mReadPointer = mBuffer;
mReadMessage = NULL;
mReadMessageOffset = 0;
mReadMessageTail = mMessageBuffer;
// Free all messages in the queues.
while ((message = otMessageQueueGetHead(&mWriteFrameMessageQueue)) != NULL)
{
otMessageQueueDequeue(&mWriteFrameMessageQueue, message);
otMessageFree(message);
}
while ((message = otMessageQueueGetHead(&mMessageQueue)) != NULL)
{
otMessageQueueDequeue(&mMessageQueue, message);
otMessageFree(message);
}
}
void NcpFrameBuffer::SetFrameAddedCallback(BufferCallback aFrameAddedCallback, void *aFrameAddedContext)
{
mFrameAddedCallback = aFrameAddedCallback;
mFrameAddedContext = aFrameAddedContext;
}
void NcpFrameBuffer::SetFrameRemovedCallback(BufferCallback aFrameRemovedCallback, void *aFrameRemovedContext)
{
mFrameRemovedCallback = aFrameRemovedCallback;
mFrameRemovedContext = aFrameRemovedContext;
}
// Increments the buffer pointer by one byte while handling the the wrap-around at the end of buffer.
uint8_t *NcpFrameBuffer::Next(uint8_t *aBufPtr) const
{
aBufPtr++;
return (aBufPtr == mBufferEnd) ? mBuffer : aBufPtr;
}
// Returns an advanced (moved forward) version of the given buffer pointer by the given offset.
uint8_t *NcpFrameBuffer::Advance(uint8_t *aBufPtr, uint16_t aOffset) const
{
aBufPtr += aOffset;
while (aBufPtr >= mBufferEnd)
{
aBufPtr -= mBufferLength;
}
return aBufPtr;
}
// Gets the distance between two buffer pointers (adjusts for the wrap-around).
uint16_t NcpFrameBuffer::GetDistance(uint8_t *aStartPtr, uint8_t *aEndPtr) const
{
size_t distance;
if (aEndPtr >= aStartPtr)
{
distance = static_cast<size_t>(aEndPtr - aStartPtr);
}
else
{
distance = static_cast<size_t>(mBufferEnd - aStartPtr);
distance += static_cast<size_t>(aEndPtr - mBuffer);
}
return static_cast<uint16_t>(distance);
}
// Writes a uint16 value at the given buffer pointer (big-endian style).
void NcpFrameBuffer::WriteUint16At(uint8_t *aBufPtr, uint16_t aValue)
{
*aBufPtr = (aValue >> 8);
*Next(aBufPtr) = (aValue & 0xff);
}
// Reads a uint16 value at the given buffer pointer (big-endian style).
uint16_t NcpFrameBuffer::ReadUint16At(uint8_t *aBufPtr)
{
uint16_t value;
value = static_cast<uint16_t>((*aBufPtr) << 8);
value += *Next(aBufPtr);
return value;
}
// Writes a bytes at the write tail, discards the frame if buffer gets full.
otError NcpFrameBuffer::InFrameFeedByte(uint8_t aByte)
{
otError error = OT_ERROR_NONE;
uint8_t *newTail = Next(mWriteSegmentTail);
VerifyOrExit(newTail != mReadFrameStart, error = OT_ERROR_NO_BUFS);
*mWriteSegmentTail = aByte;
mWriteSegmentTail = newTail;
exit:
if (error != OT_ERROR_NONE)
{
InFrameDiscard();
}
return error;
}
// This method begins a new segment (if one is not already open)
otError NcpFrameBuffer::InFrameBeginSegment(void)
{
otError error = OT_ERROR_NONE;
uint16_t headerFlags = kSegmentHeaderNoFlag;
// Verify that segment is not yet started (i.e., head and tail are the same).
VerifyOrExit(mWriteSegmentHead == mWriteSegmentTail);
// If this is the start of a new frame (i.e., frame start is same as segment head)
if (mWriteFrameStart == mWriteSegmentHead)
{
headerFlags |= kSegmentHeaderNewFrameFlag;
}
// Reserve space for the segment header.
for (uint16_t i = kSegmentHeaderSize; i; i--)
{
SuccessOrExit(error = InFrameFeedByte(0));
}
// Write the flags at the segment head
WriteUint16At(mWriteSegmentHead, headerFlags);
exit:
return error;
}
// This function closes/ends the current segment.
void NcpFrameBuffer::InFrameEndSegment(uint16_t aHeaderFlags)
{
uint16_t segmentLength;
uint16_t header;
segmentLength = GetDistance(mWriteSegmentHead, mWriteSegmentTail);
if (segmentLength >= kSegmentHeaderSize)
{
// Reduce the header size.
segmentLength -= kSegmentHeaderSize;
// Update the length and the flags in segment header (at segment head pointer).
header = ReadUint16At(mWriteSegmentHead);
header |= (segmentLength & kSegmentHeaderLengthMask);
header |= aHeaderFlags;
WriteUint16At(mWriteSegmentHead, header);
// Move the segment head to current tail (to be ready for a possible next segment).
mWriteSegmentHead = mWriteSegmentTail;
}
else
{
// Remove the current segment (move the tail back to head).
mWriteSegmentTail = mWriteSegmentHead;
}
}
// This method discards the current frame.
void NcpFrameBuffer::InFrameDiscard(void)
{
otMessage *message;
// Move the write segment head and tail pointers back to frame start.
mWriteSegmentHead = mWriteSegmentTail = mWriteFrameStart;
// Free any messages associated with current frame.
while ((message = otMessageQueueGetHead(&mWriteFrameMessageQueue)) != NULL)
{
otMessageQueueDequeue(&mWriteFrameMessageQueue, message);
otMessageFree(message);
}
}
otError NcpFrameBuffer::InFrameBegin(void)
{
// Discard any previous frame.
InFrameDiscard();
return OT_ERROR_NONE;
}
otError NcpFrameBuffer::InFrameFeedData(const uint8_t *aDataBuffer, uint16_t aDataBufferLength)
{
otError error = OT_ERROR_NONE;
// Begin a new segment (if we are not in middle of segment already).
SuccessOrExit(error = InFrameBeginSegment());
// Write the data buffer
while (aDataBufferLength--)
{
SuccessOrExit(error = InFrameFeedByte(*aDataBuffer++));
}
exit:
return error;
}
otError NcpFrameBuffer::InFrameFeedMessage(otMessage *aMessage)
{
otError error = OT_ERROR_NONE;
// Begin a new segment (if we are not in middle of segment already).
SuccessOrExit(error = InFrameBeginSegment());
// Enqueue the message in the current write frame queue.
SuccessOrExit(error = otMessageQueueEnqueue(&mWriteFrameMessageQueue, aMessage));
// End/Close the current segment marking the flag that it contains an associated message.
InFrameEndSegment(kSegmentHeaderMessageIndicatorFlag);
exit:
return error;
}
otError NcpFrameBuffer::InFrameEnd(void)
{
otMessage *message;
// End/Close the current segment (if any).
InFrameEndSegment(kSegmentHeaderNoFlag);
// Save and use the frame start pointer as the tag associated with the frame.
mWriteFrameTag = mWriteFrameStart;
// Update the frame start pointer to current segment head to be ready for next frame.
mWriteFrameStart = mWriteSegmentHead;
// Move all the messages from the frame queue to the main queue.
while ((message = otMessageQueueGetHead(&mWriteFrameMessageQueue)) != NULL)
{
otMessageQueueDequeue(&mWriteFrameMessageQueue, message);
otMessageQueueEnqueue(&mMessageQueue, message);
}
if (mFrameAddedCallback != NULL)
{
mFrameAddedCallback(mFrameAddedContext, mWriteFrameTag, this);
}
return OT_ERROR_NONE;
}
NcpFrameBuffer::FrameTag NcpFrameBuffer::InFrameGetLastTag(void) const
{
return mWriteFrameTag;
}
bool NcpFrameBuffer::IsEmpty(void) const
{
return (mReadFrameStart == mWriteFrameStart);
}
// Start/Prepare a new segment for reading.
otError NcpFrameBuffer::OutFramePrepareSegment(void)
{
otError error = OT_ERROR_NONE;
uint16_t header;
while (true)
{
// Go to the next segment (set the segment head to current segment's end/tail).
mReadSegmentHead = mReadSegmentTail;
// Ensure there is something to read (i.e. segment head is not at start of frame being written).
VerifyOrExit(mReadSegmentHead != mWriteFrameStart, error = OT_ERROR_NOT_FOUND);
// Read the segment header.
header = ReadUint16At(mReadSegmentHead);
// Check if this segment is the start of a frame.
if (header & kSegmentHeaderNewFrameFlag)
{
// Ensure that this segment is start of current frame, otherwise the current frame is finished.
VerifyOrExit(mReadSegmentHead == mReadFrameStart, error = OT_ERROR_NOT_FOUND);
}
// Find tail/end of current segment.
mReadSegmentTail = Advance(mReadSegmentHead,
kSegmentHeaderSize + (header & kSegmentHeaderLengthMask));
// Update the current read pointer to skip the segment header.
mReadPointer = Advance(mReadSegmentHead, kSegmentHeaderSize);
// Check if there are data bytes to be read in this segment (i.e. read pointer not at the tail).
if (mReadPointer != mReadSegmentTail)
{
// Update the state to `InSegment` and return.
mReadState = kReadStateInSegment;
ExitNow();
}
// No data in this segment, prepare any appended/associated message of this segment.
if (OutFramePrepareMessage() == OT_ERROR_NONE)
{
ExitNow();
}
// If there is no message (`PrepareMessage()` returned an error), loop back to prepare the next segment.
}
exit:
if (error != OT_ERROR_NONE)
{
mReadState = kReadStateDone;
}
return error;
}
// This method prepares an associated message in current segment and fills the message buffer. It returns
// ThreadError_NotFound if there is no message or if the message has no content.
otError NcpFrameBuffer::OutFramePrepareMessage(void)
{
otError error = OT_ERROR_NONE;
uint16_t header;
// Read the segment header
header = ReadUint16At(mReadSegmentHead);
// Ensure that the segment header indicates that there is an associated message or return `NotFound` error.
VerifyOrExit((header & kSegmentHeaderMessageIndicatorFlag) != 0, error = OT_ERROR_NOT_FOUND);
// Update the current message from the queue.
mReadMessage = (mReadMessage == NULL) ?
otMessageQueueGetHead(&mMessageQueue) :
otMessageQueueGetNext(&mMessageQueue, mReadMessage);
VerifyOrExit(mReadMessage != NULL, error = OT_ERROR_NOT_FOUND);
// Reset the offset for reading the message.
mReadMessageOffset = 0;
// Fill the content from current message into the message buffer.
SuccessOrExit(error = OutFrameFillMessageBuffer());
// If all successful, set the state to `InMessage`.
mReadState = kReadStateInMessage;
exit:
return error;
}
// This method fills content from current message into the message buffer. It returns OT_ERROR_NOT_FOUND if no more
// content in the current message.
otError NcpFrameBuffer::OutFrameFillMessageBuffer(void)
{
otError error = OT_ERROR_NONE;
int readLength;
VerifyOrExit(mReadMessage != NULL, error = OT_ERROR_NOT_FOUND);
VerifyOrExit(mReadMessageOffset < otMessageGetLength(mReadMessage), error = OT_ERROR_NOT_FOUND);
// Read portion of current message from the offset into message buffer.
readLength = otMessageRead(mReadMessage, mReadMessageOffset, mMessageBuffer, sizeof(mMessageBuffer));
VerifyOrExit(readLength > 0, error = OT_ERROR_NOT_FOUND);
// Update the message offset, set up the message tail, and set read pointer to start of message buffer.
mReadMessageOffset += readLength;
mReadMessageTail = mMessageBuffer + readLength;
mReadPointer = mMessageBuffer;
exit:
return error;
}
otError NcpFrameBuffer::OutFrameBegin(void)
{
otError error = OT_ERROR_NONE;
mReadMessage = NULL;
// Move the segment head and tail to start of frame.
mReadSegmentHead = mReadSegmentTail = mReadFrameStart;
// Prepare the current segment for reading.
error = OutFramePrepareSegment();
return error;
}
bool NcpFrameBuffer::OutFrameHasEnded(void)
{
return (mReadState == kReadStateDone);
}
uint8_t NcpFrameBuffer::OutFrameReadByte(void)
{
otError error;
uint8_t retval = kReadByteAfterFrameHasEnded;
switch (mReadState)
{
case kReadStateDone:
retval = kReadByteAfterFrameHasEnded;
break;
case kReadStateInSegment:
// Read a byte from current read pointer and move the read pointer forward.
retval = *mReadPointer;
mReadPointer = Next(mReadPointer);
// Check if at end of current segment.
if (mReadPointer == mReadSegmentTail)
{
// Prepare any message associated with this segment.
error = OutFramePrepareMessage();
// If there is no message, move to next segment (if any).
if (error != OT_ERROR_NONE)
{
OutFramePrepareSegment();
}
}
break;
case kReadStateInMessage:
// Read a byte from current read pointer and move the read pointer forward.
retval = *mReadPointer;
mReadPointer++;
// Check if at the end of content in message buffer.
if (mReadPointer == mReadMessageTail)
{
// Fill more bytes from current message into message buffer.
error = OutFrameFillMessageBuffer();
// If no more bytes in the message, move to next segment (if any).
if (error != OT_ERROR_NONE)
{
OutFramePrepareSegment();
}
}
break;
}
return retval;
}
uint16_t NcpFrameBuffer::OutFrameRead(uint16_t aReadLength, uint8_t *aDataBuffer)
{
uint16_t bytesRead = 0;
for (bytesRead = 0; (bytesRead < aReadLength) && !OutFrameHasEnded(); bytesRead++)
{
*aDataBuffer++ = OutFrameReadByte();
}
return bytesRead;
}
otError NcpFrameBuffer::OutFrameRemove(void)
{
otError error = OT_ERROR_NONE;
uint8_t *bufPtr;
otMessage *message;
uint16_t header;
FrameTag tag;
VerifyOrExit(!IsEmpty(), error = OT_ERROR_NOT_FOUND);
// Save the frame start pointer as the tag associated with the frame being removed.
tag = mReadFrameStart;
// Begin at the start of current frame and move through all segments.
bufPtr = mReadFrameStart;
while (bufPtr != mWriteFrameStart)
{
// Read the segment header
header = ReadUint16At(bufPtr);
// If the current segment defines a new frame, and it is not the start of current frame, then we have reached
// end of current frame.
if (header & kSegmentHeaderNewFrameFlag)
{
if (bufPtr != mReadFrameStart)
{
break;
}
}
// If current segment has an appended message, remove it from message queue and free it.
if (header & kSegmentHeaderMessageIndicatorFlag)
{
if ((message = otMessageQueueGetHead(&mMessageQueue)) != NULL)
{
otMessageQueueDequeue(&mMessageQueue, message);
otMessageFree(message);
}
}
// Move the pointer to next segment.
bufPtr = Advance(bufPtr, kSegmentHeaderSize + (header & kSegmentHeaderLengthMask));
}
mReadFrameStart = bufPtr;
mReadState = kReadStateDone;
mReadFrameLength = kUnknownFrameLength;
if (mFrameRemovedCallback != NULL)
{
mFrameRemovedCallback(mFrameRemovedContext, tag, this);
}
exit:
return error;
}
uint16_t NcpFrameBuffer::OutFrameGetLength(void)
{
uint16_t frameLength = 0;
uint16_t header;
uint8_t *bufPtr;
otMessage *message = NULL;
// If the frame length was calculated before, return the previously calculated length.
VerifyOrExit(mReadFrameLength == kUnknownFrameLength, frameLength = mReadFrameLength);
VerifyOrExit(!IsEmpty(), frameLength = 0);
// Calculate frame length by adding length of all segments and messages within the current frame.
bufPtr = mReadFrameStart;
while (bufPtr != mWriteFrameStart)
{
// Read the segment header
header = ReadUint16At(bufPtr);
// If the current segment defines a new frame, and it is not the start of current frame, then we have reached
// end of current frame.
if (header & kSegmentHeaderNewFrameFlag)
{
if (bufPtr != mReadFrameStart)
{
break;
}
}
// If current segment has an associated message, add its length to frame length.
if (header & kSegmentHeaderMessageIndicatorFlag)
{
message = (message == NULL) ?
otMessageQueueGetHead(&mMessageQueue) :
otMessageQueueGetNext(&mMessageQueue, message);
if (message != NULL)
{
frameLength += otMessageGetLength(message);
}
}
// Add the length of current segment to the frame length.
frameLength += (header & kSegmentHeaderLengthMask);
// Move the pointer to next segment.
bufPtr = Advance(bufPtr, kSegmentHeaderSize + (header & kSegmentHeaderLengthMask));
}
// Remember the calculated frame length for current frame.
mReadFrameLength = frameLength;
exit:
return frameLength;
}
NcpFrameBuffer::FrameTag NcpFrameBuffer::OutFrameGetTag(void) const
{
// If buffer is empty use `kInvalidTag`, otherwise use the frame start pointer as the tag associated with
// current out frame being read
return IsEmpty() ? kInvalidTag : mReadFrameStart;
}
} // namespace ot