| /* |
| * 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 |