From a46d258ce7c476087099372f664c48c1cd4cf81f Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 25 Jul 2016 11:17:29 -0700 Subject: [PATCH] Ncp: Introducing `NcpFrame Buffer` class and its unit test. (#264) This commit is part of ncp buffer redesign - issue #59. --- src/ncp/Makefile.am | 2 + src/ncp/ncp_buffer.cpp | 656 +++++++++++++++++++++++++++++++++ src/ncp/ncp_buffer.hpp | 358 ++++++++++++++++++ tests/unit/Makefile.am | 15 + tests/unit/test_ncp_buffer.cpp | 478 ++++++++++++++++++++++++ 5 files changed, 1509 insertions(+) create mode 100644 src/ncp/ncp_buffer.cpp create mode 100644 src/ncp/ncp_buffer.hpp create mode 100644 tests/unit/test_ncp_buffer.cpp diff --git a/src/ncp/Makefile.am b/src/ncp/Makefile.am index 0009cd603..eb3b7ba92 100644 --- a/src/ncp/Makefile.am +++ b/src/ncp/Makefile.am @@ -56,6 +56,8 @@ libopenthread_ncp_a_CXXFLAGS = \ libopenthread_ncp_a_SOURCES = \ ncp_base.cpp \ ncp_base.hpp \ + ncp_buffer.cpp \ + ncp_buffer.hpp \ spinel.c \ spinel.h \ $(NULL) diff --git a/src/ncp/ncp_buffer.cpp b/src/ncp/ncp_buffer.cpp new file mode 100644 index 000000000..462af41ab --- /dev/null +++ b/src/ncp/ncp_buffer.cpp @@ -0,0 +1,656 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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 +#include + +namespace Thread { + +NcpFrameBuffer::NcpFrameBuffer(uint8_t *aBuffer, uint16_t aBufferLen) : + mBuffer(aBuffer), + mBufferEnd(aBuffer + aBufferLen), + mBufferLength(aBufferLen) +{ + SetCallbacks(NULL, NULL, NULL); + Clear(); +} + +NcpFrameBuffer::~NcpFrameBuffer() +{ + SetCallbacks(NULL, NULL, NULL); + Clear(); +} + +void NcpFrameBuffer::Clear(void) +{ + Message *message; + bool wasEmpty = IsEmpty(); + + // Write (InFrame) related variables + mWriteFrameStart = mBuffer; + mWriteSegmentHead = mBuffer; + mWriteSegmentTail = mBuffer; + + // 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 = mWriteFrameMessageQueue.GetHead()) != NULL) + { + mWriteFrameMessageQueue.Dequeue(*message); + Message::Free(*message); + } + + while ((message = mMessageQueue.GetHead()) != NULL) + { + mMessageQueue.Dequeue(*message); + Message::Free(*message); + } + + if (!wasEmpty) + { + if (mEmptyBufferCallback != NULL) + { + mEmptyBufferCallback(mCallbackContext, this); + } + } +} + +void NcpFrameBuffer::SetCallbacks(BufferCallback aEmptyBufferCallback, BufferCallback aNonEmptyBufferCallback, + void *aContext) +{ + mEmptyBufferCallback = aEmptyBufferCallback; + mNonEmtyBufferCallback = aNonEmptyBufferCallback; + mCallbackContext = aContext; +} + +// Returns the next buffer pointer addressing 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, uint8_t aOffset) const +{ + aBufPtr += aOffset; + + while (aBufPtr >= mBufferEnd) + { + aBufPtr -= mBufferLength; + } + + return aBufPtr; +} + +// Get 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 = aEndPtr - aStartPtr; + } + else + { + distance = mBufferEnd - aStartPtr; + distance += aEndPtr - mBuffer; + } + + return static_cast(distance); +} + +// Write 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); +} + +// Read a uint16 value at the given buffer pointer (big-endian style). +uint16_t NcpFrameBuffer::ReadUint16At(uint8_t *aBufPtr) +{ + uint16_t value; + + value = (*aBufPtr) << 8; + value += *Next(aBufPtr); + + return value; +} + +// Writes a bytes at the write tail, discards the frame if buffer gets full. +ThreadError NcpFrameBuffer::InFrameFeedByte(uint8_t aByte) +{ + ThreadError error = kThreadError_None; + uint8_t *newTail = Next(mWriteSegmentTail); + + VerifyOrExit(newTail != mReadFrameStart, error = kThreadError_NoBufs); + + *mWriteSegmentTail = aByte; + mWriteSegmentTail = newTail; + +exit: + if (error != kThreadError_None) + { + InFrameDiscard(); + } + + return error; +} + +// This method begins a new segment (if one is not already open) +ThreadError NcpFrameBuffer::InFrameBeginSegment(void) +{ + ThreadError error = kThreadError_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) +{ + Message *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 = mWriteFrameMessageQueue.GetHead()) != NULL) + { + mWriteFrameMessageQueue.Dequeue(*message); + Message::Free(*message); + } +} + +ThreadError NcpFrameBuffer::InFrameBegin(void) +{ + // Discard any previous frame. + InFrameDiscard(); + + return kThreadError_None; +} + +ThreadError NcpFrameBuffer::InFrameFeedData(const uint8_t *aDataBuffer, uint16_t aDataBufferLength) +{ + ThreadError error = kThreadError_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; +} + +ThreadError NcpFrameBuffer::InFrameFeedMessage(Message &aMessage) +{ + ThreadError error = kThreadError_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 = mWriteFrameMessageQueue.Enqueue(aMessage)); + + // End/Close the current segment marking the flag that it contains an associated message. + InFrameEndSegment(kSegmentHeaderMessageIndicatorFlag); + +exit: + return error; +} + +ThreadError NcpFrameBuffer::InFrameEnd(void) +{ + Message *message; + bool wasEmpty = IsEmpty(); + + // End/Close the current segment (if any). + InFrameEndSegment(kSegmentHeaderNoFlag); + + // 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 = mWriteFrameMessageQueue.GetHead()) != NULL) + { + mWriteFrameMessageQueue.Dequeue(*message); + mMessageQueue.Enqueue(*message); + } + + // If buffer was empty before, invoke the callback to signal that buffer is now non-empty. + if (wasEmpty) + { + if (mNonEmtyBufferCallback != NULL) + { + mNonEmtyBufferCallback(mCallbackContext, this); + } + } + + return kThreadError_None; +} + +bool NcpFrameBuffer::IsEmpty(void) const +{ + return (mReadFrameStart == mWriteFrameStart); +} + +// Start/Prepare a new segment for reading. +ThreadError NcpFrameBuffer::OutFramePrepareSegment(void) +{ + ThreadError error = kThreadError_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 = kThreadError_NotFound); + + // 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 = kThreadError_NotFound); + } + + // 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() == kThreadError_None) + { + ExitNow(); + } + + // If there is no message (`PrepareMessage()` returned an error), loop back to prepare the next segment. + } + +exit: + if (error != kThreadError_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. +ThreadError NcpFrameBuffer::OutFramePrepareMessage(void) +{ + ThreadError error = kThreadError_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 = kThreadError_NotFound); + + // Update the current message from the queue. + mReadMessage = (mReadMessage == NULL) ? mMessageQueue.GetHead() : mReadMessage->GetNext(); + + VerifyOrExit(mReadMessage != NULL, error = kThreadError_NotFound); + + // 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 kThreadError_NotFound if no more +// content in the current message. +ThreadError NcpFrameBuffer::OutFrameFillMessageBuffer(void) +{ + ThreadError error = kThreadError_None; + int readLength; + + VerifyOrExit(mReadMessage != NULL, error = kThreadError_NotFound); + + VerifyOrExit(mReadMessageOffset < mReadMessage->GetLength(), error = kThreadError_NotFound); + + // Read portion of current message from the offset into message buffer. + readLength = mReadMessage->Read(mReadMessageOffset, sizeof(mMessageBuffer), mMessageBuffer); + + VerifyOrExit(readLength > 0, error = kThreadError_NotFound); + + // 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; +} + +ThreadError NcpFrameBuffer::OutFrameBegin(void) +{ + ThreadError error = kThreadError_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) +{ + ThreadError 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 associated message with this segment. + error = OutFramePrepareMessage(); + + // If there is no message, move to next segment (if any). + if (error != kThreadError_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 != kThreadError_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; +} + +ThreadError NcpFrameBuffer::OutFrameRemove(void) +{ + ThreadError error = kThreadError_None; + uint8_t *bufPtr; + Message *message; + uint16_t header; + + VerifyOrExit(!IsEmpty(), error = kThreadError_NotFound); + + // 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 = mMessageQueue.GetHead()) != NULL) + { + mMessageQueue.Dequeue(*message); + Message::Free(*message); + } + } + + // Move the pointer to next segment. + bufPtr = Advance(bufPtr, kSegmentHeaderSize + (header & kSegmentHeaderLengthMask)); + } + + mReadFrameStart = bufPtr; + + mReadState = kReadStateDone; + mReadFrameLength = kUnknownFrameLength; + + // If the remove causes the buffer to become empty, invoke the callback to signal this. + if (IsEmpty()) + { + if (mEmptyBufferCallback != NULL) + { + mEmptyBufferCallback(mCallbackContext, this); + } + } + +exit: + return error; +} + + +uint16_t NcpFrameBuffer::OutFrameGetLength(void) +{ + uint16_t frameLength = 0; + uint16_t header; + uint8_t *bufPtr; + Message *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) ? mMessageQueue.GetHead() : message->GetNext(); + + if (message != NULL) + { + frameLength += message->GetLength(); + } + } + + // 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; +} + +} // namespace Thread diff --git a/src/ncp/ncp_buffer.hpp b/src/ncp/ncp_buffer.hpp new file mode 100644 index 000000000..06086721f --- /dev/null +++ b/src/ncp/ncp_buffer.hpp @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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 contains definitions for the NCP frame buffer class. + */ + +#ifndef NCP_FRAME_BUFFER_HPP_ +#define NCP_FRAME_BUFFER_HPP_ + +#include +#include + +namespace Thread { + +class NcpFrameBuffer +{ +public: + /** + * Defines a function pointer callback which is invoked to inform state transition of buffer, going from empty + * to non-empty or becoming empty. + * + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aNcpFrameBuffer A pointer to the NcpFrameBuffer. + * + */ + typedef void (*BufferCallback)(void *aContext, NcpFrameBuffer *aNcpFrameBuffer); + + /** + * This constructor creates an NCP frame buffer. + * + * @param[in] aBuffer A pointer to a buffer which will be used by NCP frame buffer. + * @param[in] aBufferLength The buffer size (in bytes). + * + */ + NcpFrameBuffer(uint8_t *aBuffer, uint16_t aBufferLength); + + /** + * This destructor clears the NCP frame buffer and clears all frames.. + * + */ + ~NcpFrameBuffer(); + + /** + * This method clears the NCP frame buffer. All the frames are cleared/removed. + * + * @returns Nothing (void). + */ + void Clear(void); + + /** + * This method sets the callbacks and context. Subsequent calls to this method will overwrite the previous + * callbacks and context. + * + * @param[in] aEmptyBufferCallback Callback invoked when buffer become empty. + * @param[in] aNonEmptyBufferCallback Callback invoked when buffer transition from empty to non-empty. + * @param[in] aContex A pointer to arbitrary context information. + * + * @returns Nothing (void). + * + */ + void SetCallbacks(BufferCallback aEmptyBufferCallback, BufferCallback aNonEmptyBufferCallback, void *aContext); + + /** + * This method begins a new input frame to be added/written to the frame buffer. + + * If there is a previous frame being written (`InFrameEnd()` has not yet been called on the frame), this method + * will discard and clear the previous unfinished frame. + * + * @retval kThreadError_None Successfully started a new frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to start a new frame. + * + */ + ThreadError InFrameBegin(void); + + /** + * This method adds data to the current input frame being written to the buffer. + * + * If no buffer space is available, this method will discard and clear the frame before returning an error status. + * + * @param[in] aDataBuffer A pointer to data buffer. + * @param[in] aDataBufferLength The length of the data buffer. + * + * @retval kThreadError_None Successfully added new data to the frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to add data. + * + */ + ThreadError InFrameFeedData(const uint8_t *aDataBuffer, uint16_t aDataBufferLength); + + /** + * This method adds a message to the current input frame being written to the buffer. + * + * If no buffer space is available, this method will discard and clear the frame before returning an error status. + * The passed-in message @aMessage will be freed by the frame buffer when the frame is removed or discarded. + * + * @param[in] aMessage A reference to the message to be added to current frame. + * + * @retval kThreadError_None Successfully added the message to the frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to add message. + * + */ + ThreadError InFrameFeedMessage(Message &aMessage); + + /** + * This method finalizes/ends the current input frame being written to the buffer. + * + * If no buffer space is available, this method will discard and clear the frame before returning an error status. + * + * @retval kThreadError_None Successfully added the message to the frame. + * @retval kThreadError_NoBufs Insufficient buffer space available to add message. + * + */ + ThreadError InFrameEnd(void); + + /** + * This method checks if the buffer is empty. An non-empty buffer contains at least one full frame for reading. + * + * @retval true Buffer is not empty and contains at least one full frame for reading. + * @retval false Buffer is empty and contains no frame for reading. + * + */ + bool IsEmpty(void) const; + + /** + * This method begins/prepares a new output frame to be read from the frame buffer. + * + * The NCP buffer maintains a read offset for the current frame being read. Before reading any bytes from the frame + * this method should be called to prepare the frame and set the read offset. + * + * If part of current frame has already been read, a sub-sequent call to this method will reset the read offset + * back to beginning of current output frame. + * + * @retval kThreadError_None Successfully started/prepared a new output frame for reading. + * @retval kThreadError_NotFound No frame available in buffer for reading. + * + */ + ThreadError OutFrameBegin(void); + + /** + * This method checks if the current output frame (being read) has ended. + * + * The NCP buffer maintains a read offset for the current output frame being read. This method returns true if the + * read offset is at the end of the frame and there are no more bytes available to read from current frame. + * + * @retval true Frame has ended (no more bytes available to read from current output frame). + * @retval false Frame has data (can read more bytes from current output frame). + * + */ + bool OutFrameHasEnded(void); + + /** + * This method reads and returns the next byte from the current output frame. + * + * The NCP buffer maintains a read offset for the current output frame being read. This method reads and returns + * the next byte from the current frame and moves the read offset forward. If read offset is already at the end + * current output frame, this method returns zero. + * + * @returns The next byte from the current output frame or zero if frame has ended. + * + */ + uint8_t OutFrameReadByte(void); + + /** + * This method reads bytes from the current output frame. + * + * The NCP buffer maintains a read offset for the current output frame being read. This method attempts to read + * the given number of bytes (@p aDataBufferLength) from the current frame and copies the bytes into the given + * data buffer (@p aDataBuffer). It also moves the read offset forward accordingly. If there are less bytes + * remaining in current frame, the available bytes are read/copied. This methods returns the actual number of bytes + * read. + * + * @param[in] aDataBuffer A pointer to a data buffer. + * @param[in] aReadLength Number of bytes to read. + * + * @returns The number of bytes read and copied into data buffer. + * + */ + uint16_t OutFrameRead(uint16_t aReadLength, uint8_t *aDataBuffer); + + /** + * This method removes the current/front output frame from the buffer. + * + * The NCP buffer stores the frames in FIFO order. This method removes the front frame (which may be the current + * output frame being read) from the buffer. There is no need to prepare/begin reading the current frame before + * removing it, so the front frame can be removed without a previous call to `OutFrameBegin()`. + * + * When a frame is removed all its associated messages will be freed. + * + * If the remove operation causes the buffer to become empty this method will invoke the `EmptyBufferCallback`. + * + * @retval kThreadError_None Successfully removed the front frame. + * @retval kThreadError_NotFound No frame available in NCP frame buffer to remove. + * + */ + ThreadError OutFrameRemove(void); + + /** + * This method returns the number of bytes (length) of current/front frame in the NCP frame buffer. + * + * The NCP buffer stores the frames in FIFO order. This method returns the length of the front frame (which may + * be the current output frame being read) from the buffer. There is no need to prepare/begin reading the current + * frame before calling this method so this method can be used without a previous call to `OutFrameBegin()`. + * + * If there is no frame in buffer, this method returns zero. + * + * @returns The number of bytes (length) of current/front frame, or zero if no frame in buffer. + * + */ + uint16_t OutFrameGetLength(void); + +private: + + /* + * NcpFrameBuffer Implementation + * ----------------------------- + * + * NcpFrameBuffer internally stores a frame as a sequence of data segments. The data segments are stored in the + * the main buffer `mBuffer`. mBuffer is utilized as a circular buffer. + + * Messages (which are added using `InFrameFeedMessaged()`) are not copied in the `mBuffer` but instead are + * enqueued in a message queue `mMessageQueue`. + * + * The data segments include a header before the data portion. The header is 2 bytes long is formated as follows + * + * Bit 0-13: Give the length of the data segment (max segment len is 2^14 = 16,384 bytes). + * Bit 14: Flag bit set to indicate that this segment has an associated `Message` (appended to its end). + * Bit 15: Flag bit set to indicate that this segment defines the start of a new frame. + * + * Bit 15 Bit 14 Bits: 0 - 13 + * +--------------+--------------+--------------------------------------------------------+ + * | New Frame | Has Message | Length of segment (excluding the header) | + * +--------------+--------------+--------------------------------------------------------+ + * + * The header is encoded in big-endian (msb first) style. + + * Consider the following calls to create a frame: + * + * ncpBuffer.InFrameBegin(); + * ncpBuffer.InFrameFeedData("Hello", 5); + * ncpBuffer.InFrameFeedData("There", 5); + * ncpBuffer.InFrameFeedMessage(*someMessage); + * ncpBuffer.InFrameFeedData("Bye", 3); + * ncpBuffer.InFrameEnd(); + * + * This frame is stored as two segments: + * + * - Segment #1 contains "HelloThere" with a header of `0xC00A` which shows that this segment contains 10 data + * bytes, and it starts a new frame, and also must include a message from the message queue. + * + * - Segment #2 contains "Bye" with a header value of `0x0003` showing length of 3 and no appended message. + * + * +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ + * | C0 | 0A | `H` | 'e' | 'l' | 'l' | 'o' | 'T' | 'h' | 'e' | 'r' | 'e' | 00 | 03 | 'B' | 'y' | 'e' | + * +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ + * \ / \ / + * Segment #1 Header Segment #2 Header + * + */ + + enum + { + kReadByteAfterFrameHasEnded = 0, // Value returned by ReadByte() when frame has ended. + kMessageReadBufferSize = 16, // Size of message buffer array. + kUnknownFrameLength = 0xffff, // Value used when frame length is unknown. + kSegmentHeaderSize = 2, // Length of the segment header. + kSegmentHeaderLengthMask = 0x3fff, // Bit mask to get the length from the segment header + + kSegmentHeaderNoFlag = 0, // No flags are set. + kSegmentHeaderNewFrameFlag = (1 << 15), // Indicates that this segment starts a new frame. + kSegmentHeaderMessageIndicatorFlag = (1 << 14), // Indicates this segment ends with a Message. + }; + + enum ReadState + { + kReadStateInSegment, // In middle of a data segment while reading current (out) frame. + kReadStateInMessage, // In middle of a message while reading current (out) frame. + kReadStateDone, // Current (out) frame is read fully. + }; + + // Private methods + + uint8_t * Next(uint8_t *aBufferPtr) const; + uint8_t * Advance(uint8_t *aBufPtr, uint8_t aOffset) const; + uint16_t GetDistance(uint8_t *aStartPtr, uint8_t *aEndPtr) const; + + uint16_t ReadUint16At(uint8_t *aBufPtr); + void WriteUint16At(uint8_t *aBufPtr, uint16_t aValue); + + ThreadError InFrameFeedByte(uint8_t aByte); + ThreadError InFrameBeginSegment(void); + void InFrameEndSegment(uint16_t aSegmentHeaderFlags); + void InFrameDiscard(void); + + ThreadError OutFramePrepareSegment(void); + void OutFrameMoveToNextSegment(void); + ThreadError OutFramePrepareMessage(void); + ThreadError OutFrameFillMessageBuffer(void); + + // Instance variables + + uint8_t * const mBuffer; // Pointer to the buffer used to store the data. + uint8_t * const mBufferEnd; // Points to after the end of buffer. + const uint16_t mBufferLength; // Length of the the buffer. + + BufferCallback mEmptyBufferCallback; // Callback to signal when buffer becomes empty. + BufferCallback mNonEmtyBufferCallback; // Callback to signal when buffer becomes non-empty. + void * mCallbackContext; // Context passed to callbacks. + + MessageQueue mMessageQueue; // Main message queue. + + MessageQueue mWriteFrameMessageQueue; // Message queue for the current frame being written. + uint8_t * mWriteFrameStart; // Pointer to start of current frame being written. + uint8_t * mWriteSegmentHead; // Pointer to start of current segment in the frame being written. + uint8_t * mWriteSegmentTail; // Pointer to end of current segment in the frame being written. + + ReadState mReadState; // Read state. + uint16_t mReadFrameLength; // Length of current frame being read. + + uint8_t * mReadFrameStart; // Pointer to start of current frame being read. + uint8_t * mReadSegmentHead; // Pointer to start of current segment in the frame being read. + uint8_t * mReadSegmentTail; // Pointer to end of current segment in the frame being read. + uint8_t * mReadPointer; // Pointer to next byte to read (either in segment or in msg buffer). + + Message * mReadMessage; // Current Message in the frame being read. + uint16_t mReadMessageOffset; // Offset within current message being read. + + uint8_t mMessageBuffer[kMessageReadBufferSize]; // Buffer to hold part of current message being read. + uint8_t * mReadMessageTail; // Pointer to end of current part in mMessageBuffer. +}; + +} // namespace Thread + +#endif // NCP_FRAME_BUFFER_HPP_ \ No newline at end of file diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 988a4781f..efbcd9d55 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -75,6 +75,18 @@ check_PROGRAMS = \ test-toolchain \ $(NULL) +if OPENTHREAD_ENABLE_NCP + +COMMON_LDADD += \ + $(top_builddir)/src/ncp/libopenthread-ncp.a \ + $(NULL) + +check_PROGRAMS += \ + test-ncp-buffer \ + $(NULL) + +endif # OPENTHREAD_ENABLE_NCP + # Test applications and scripts that should be built and run when the # 'check' target is run. @@ -108,6 +120,9 @@ test_mac_frame_SOURCES = test_mac_frame.cpp test_message_LDADD = $(COMMON_LDADD) test_message_SOURCES = test_message.cpp +test_ncp_buffer_LDADD = $(COMMON_LDADD) +test_ncp_buffer_SOURCES = test_ncp_buffer.cpp + test_timer_LDADD = $(COMMON_LDADD) test_timer_SOURCES = test_timer.cpp diff --git a/tests/unit/test_ncp_buffer.cpp b/tests/unit/test_ncp_buffer.cpp new file mode 100644 index 000000000..b580f8f17 --- /dev/null +++ b/tests/unit/test_ncp_buffer.cpp @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * 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. + */ + +#include +#include "test_util.h" +#include +#include +#include + +namespace Thread { + +// This module implements unit-test for NcpFrameBuffer class. + +extern"C" void otSignalTaskletPending(void) +{ +} + +enum +{ + kTestBufferSize = 101, // Size of backed buffer for NcpFrameBuffer. + kTestIterationAttemps = 120, +}; + +// Messages used for building frames... +static const uint8_t sOpenThreadText[] = "OpenThread Rocks"; +static const uint8_t sHelloText[] = "Hello there!"; +static const uint8_t sMottoText[] = "Think good thoughts, say good words, do good deeds!"; +static const uint8_t sMysteryText[] = "4871(\\):|(3$}{4|/4/2%14(\\)"; + + +struct CallbackContext +{ + uint16_t mEmptyCount; // Number of times BufferEmptyCallback is invoked. + uint16_t mNonEmptyCount; // Number of times BufferNonEmptyCallback is invoked. +}; + +// Initialize the test +void InitTest(void) +{ + Message::Init(); +} + +void BufferDidGetEmptyCallback(void *aContext, NcpFrameBuffer *aNcpBuffer) +{ + CallbackContext *callbackContext = reinterpret_cast(aContext); + + VerifyOrQuit(aNcpBuffer != NULL, "Null NcpFrameBuffer in the callback"); + VerifyOrQuit(callbackContext != NULL, "Null context in the callback"); + + callbackContext->mEmptyCount++; +} + +void BufferDidGetNonEmptyCallback(void *aContext, NcpFrameBuffer *aNcpBuffer) +{ + CallbackContext *callbackContext = reinterpret_cast(aContext); + + VerifyOrQuit(aNcpBuffer != NULL, "Null NcpFrameBuffer in the callback"); + VerifyOrQuit(callbackContext != NULL, "Null context in the callback"); + + callbackContext->mNonEmptyCount++; +} + + +// Dump the buffer content to screen. +void DumpBuffer(const char *aTextMessage, uint8_t *aBuffer, uint16_t aBufferLength) +{ + enum + { + kBytesPerLine = 32, // Number of bytes per line. + }; + + char charBuff[kBytesPerLine + 1]; + uint16_t counter; + uint8_t byte; + + printf("\n%s - len = %u\n ", aTextMessage, aBufferLength); + + counter = 0; + + while (aBufferLength--) + { + byte = *aBuffer++; + printf("%02X ", byte); + charBuff[counter] = isprint(byte) ? byte : '.'; + counter++; + + if (counter == kBytesPerLine) + { + charBuff[counter] = 0; + printf(" %s\n ", charBuff); + counter = 0; + } + } + + charBuff[counter] = 0; + + while (counter++ < kBytesPerLine) + { + printf(" "); + } + + printf(" %s\n", charBuff); +} + +// Reads bytes from the ncp buffer, and verifies that it matches with the given content buffer. +void ReadAndVerifyContent(NcpFrameBuffer &aNcpBuffer, const uint8_t *aContentBuffer, uint16_t aBufferLength) +{ + while (aBufferLength--) + { + VerifyOrQuit(aNcpBuffer.OutFrameHasEnded() == false, "Out frame ended before end of expected content."); + + VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == *aContentBuffer++, + "Out frame read byte does not match expected content"); + } +} + +void WriteTestFrame1(NcpFrameBuffer &aNcpBuffer) +{ + Message *message; + + message = Message::New(Message::kTypeIp6, 0); + VerifyOrQuit(message != NULL, "Null Message"); + SuccessOrQuit(message->SetLength(sizeof(sMottoText)), "Could not set the length of message."); + message->Write(0, sizeof(sMottoText), sMottoText); + + SuccessOrQuit(aNcpBuffer.InFrameBegin(), "InFrameBegin() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedData(sMottoText, sizeof(sMottoText)), "InFrameFeedData() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedData(sMysteryText, sizeof(sMysteryText)), "InFrameFeedData() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedMessage(*message), "InFrameFeedMessage() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedData(sHelloText, sizeof(sHelloText)), "InFrameFeedData() failed."); + SuccessOrQuit(aNcpBuffer.InFrameEnd(), "InFrameEnd() failed."); +} + +void VerifyAndRemoveFrame1(NcpFrameBuffer &aNcpBuffer) +{ + SuccessOrQuit(aNcpBuffer.OutFrameBegin(), "OutFrameBegin() failed unexpectedly."); + + VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sMottoText) + sizeof(sMysteryText) + sizeof(sMottoText) + + sizeof(sHelloText), "GetLength() is incorrect."); + + ReadAndVerifyContent(aNcpBuffer, sMottoText, sizeof(sMottoText)); + ReadAndVerifyContent(aNcpBuffer, sMysteryText, sizeof(sMysteryText)); + ReadAndVerifyContent(aNcpBuffer, sMottoText, sizeof(sMottoText)); + ReadAndVerifyContent(aNcpBuffer, sHelloText, sizeof(sHelloText)); + + VerifyOrQuit(aNcpBuffer.OutFrameHasEnded() == true, "Frame longer than expected."); + VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == 0, "ReadByte() returned non-zero after end of frame."); + SuccessOrQuit(aNcpBuffer.OutFrameRemove(), "Remove() failed."); +} + +void WriteTestFrame2(NcpFrameBuffer &aNcpBuffer) +{ + Message *message1; + Message *message2; + + message1 = Message::New(Message::kTypeIp6, 0); + VerifyOrQuit(message1 != NULL, "Null Message"); + SuccessOrQuit(message1->SetLength(sizeof(sMysteryText)), "Could not set the length of message."); + message1->Write(0, sizeof(sMysteryText), sMysteryText); + + message2 = Message::New(Message::kTypeIp6, 0); + VerifyOrQuit(message2 != NULL, "Null Message"); + SuccessOrQuit(message2->SetLength(sizeof(sHelloText)), "Could not set the length of message."); + message2->Write(0, sizeof(sHelloText), sHelloText); + + SuccessOrQuit(aNcpBuffer.InFrameBegin(), "InFrameFeedBegin() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedMessage(*message1), "InFrameFeedMessage() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedData(sOpenThreadText, sizeof(sOpenThreadText)), "InFrameFeedData() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedMessage(*message2), "InFrameFeedMessage() failed."); + SuccessOrQuit(aNcpBuffer.InFrameEnd(), "InFrameEnd() failed."); +} + +void VerifyAndRemoveFrame2(NcpFrameBuffer &aNcpBuffer) +{ + SuccessOrQuit(aNcpBuffer.OutFrameBegin(), "OutFrameBegin() failed unexpectedly."); + + VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sMysteryText) + sizeof(sHelloText) + sizeof(sOpenThreadText), + "GetLength() is incorrect."); + + ReadAndVerifyContent(aNcpBuffer, sMysteryText, sizeof(sMysteryText)); + ReadAndVerifyContent(aNcpBuffer, sOpenThreadText, sizeof(sOpenThreadText)); + ReadAndVerifyContent(aNcpBuffer, sHelloText, sizeof(sHelloText)); + + VerifyOrQuit(aNcpBuffer.OutFrameHasEnded() == true, "Frame longer than expected."); + VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == 0, "ReadByte() returned non-zero after end of frame."); + SuccessOrQuit(aNcpBuffer.OutFrameRemove(), "Remove() failed."); +} + +void WriteTestFrame3(NcpFrameBuffer &aNcpBuffer) +{ + Message *message1; + + message1 = Message::New(Message::kTypeIp6, 0); + VerifyOrQuit(message1 != NULL, "Null Message"); + + // An empty message with no content. + SuccessOrQuit(message1->SetLength(0), "Could not set the length of message."); + + SuccessOrQuit(aNcpBuffer.InFrameBegin(), "InFrameFeedBegin() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedMessage(*message1), "InFrameFeedMessage() failed."); + SuccessOrQuit(aNcpBuffer.InFrameFeedData(sMysteryText, sizeof(sMysteryText)), "InFrameFeedData() failed."); + SuccessOrQuit(aNcpBuffer.InFrameEnd(), "InFrameEnd() failed."); +} + +void VerifyAndRemoveFrame3(NcpFrameBuffer &aNcpBuffer) +{ + SuccessOrQuit(aNcpBuffer.OutFrameBegin(), "OutFrameBegin() failed unexpectedly."); + + VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sMysteryText), "GetLength() is incorrect."); + + ReadAndVerifyContent(aNcpBuffer, sMysteryText, sizeof(sMysteryText)); + + VerifyOrQuit(aNcpBuffer.OutFrameHasEnded() == true, "Frame longer than expected."); + VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == 0, "ReadByte() returned non-zero after end of frame."); + SuccessOrQuit(aNcpBuffer.OutFrameRemove(), "Remove() failed."); +} + +// This function implements the NcpFrameBuffer tests +void TestNcpFrameBuffer(void) +{ + unsigned i, j; + uint8_t buffer[kTestBufferSize]; + NcpFrameBuffer ncpBuffer(buffer, kTestBufferSize); + + Message *message; + CallbackContext context; + CallbackContext oldContext; + uint8_t readBuffer[16]; + uint16_t readLen, readOffset; + + for (i = 0; i < sizeof(buffer); i++) + { + buffer[i] = 0; + } + + context.mEmptyCount = 0; + context.mNonEmptyCount = 0; + + // Set the callbacks. + ncpBuffer.SetCallbacks(BufferDidGetEmptyCallback, BufferDidGetNonEmptyCallback, &context); + + printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + printf("\nTest 1: Write a frame 1 "); + + WriteTestFrame1(ncpBuffer); + DumpBuffer("\nBuffer after frame1", buffer, kTestBufferSize); + printf("\nFrameLen is %u", ncpBuffer.OutFrameGetLength()); + + VerifyAndRemoveFrame1(ncpBuffer); + + printf("\nIterations: "); + + // Repeat this multiple times. + for (j = 0; j < kTestIterationAttemps; j++) + { + printf("*"); + WriteTestFrame1(ncpBuffer); + VerifyOrQuit(ncpBuffer.IsEmpty() == false, "IsEmpty() is incorrect when buffer is non-empty"); + + VerifyAndRemoveFrame1(ncpBuffer); + VerifyOrQuit(ncpBuffer.IsEmpty() == true, "IsEmpty() is incorrect when buffer is empty."); + } + + printf(" -- PASS\n"); + + printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + printf("\nTest 2: Multiple frames write and read "); + + WriteTestFrame2(ncpBuffer); + WriteTestFrame3(ncpBuffer); + WriteTestFrame2(ncpBuffer); + WriteTestFrame2(ncpBuffer); + + DumpBuffer("\nBuffer after multiple frames", buffer, kTestBufferSize); + + VerifyAndRemoveFrame2(ncpBuffer); + VerifyAndRemoveFrame3(ncpBuffer); + VerifyAndRemoveFrame2(ncpBuffer); + VerifyAndRemoveFrame2(ncpBuffer); + + printf("\nIterations: "); + + // Repeat this multiple times. + for (j = 0; j < kTestIterationAttemps; j++) + { + printf("*"); + + WriteTestFrame2(ncpBuffer); + WriteTestFrame3(ncpBuffer); + WriteTestFrame2(ncpBuffer); + + VerifyAndRemoveFrame2(ncpBuffer); + VerifyAndRemoveFrame3(ncpBuffer); + + WriteTestFrame2(ncpBuffer); + WriteTestFrame3(ncpBuffer); + + VerifyAndRemoveFrame2(ncpBuffer); + VerifyAndRemoveFrame2(ncpBuffer); + VerifyAndRemoveFrame3(ncpBuffer); + + VerifyOrQuit(ncpBuffer.IsEmpty() == true, "IsEmpty() is incorrect when buffer is empty."); + } + + printf(" -- PASS\n"); + + printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + printf("\nTest 3: Frame discard when buffer full and partial read restart"); + + for (j = 0; j < kTestIterationAttemps; j++) + { + WriteTestFrame2(ncpBuffer); + WriteTestFrame3(ncpBuffer); + + ncpBuffer.InFrameBegin(); + ncpBuffer.InFrameFeedData(sHelloText, sizeof(sHelloText)); + + message = Message::New(Message::kTypeIp6, 0); + VerifyOrQuit(message != NULL, "Null Message"); + SuccessOrQuit(message->SetLength(sizeof(sMysteryText)), "Could not set the length of message."); + message->Write(0, sizeof(sMysteryText), sMysteryText); + + ncpBuffer.InFrameFeedMessage(*message); + + // Now cause a restart the current frame and test if it's discarded ok. + WriteTestFrame2(ncpBuffer); + + if (j == 0) + { + DumpBuffer("\nAfter frame gets discarded", buffer, kTestBufferSize); + printf("\nIterations: "); + } + else + { + printf("*"); + } + + VerifyAndRemoveFrame2(ncpBuffer); + + // Start reading few bytes from the frame + ncpBuffer.OutFrameBegin(); + ncpBuffer.OutFrameReadByte(); + ncpBuffer.OutFrameReadByte(); + ncpBuffer.OutFrameReadByte(); + + // Now reset the read pointer and read/verify the frame from start. + VerifyAndRemoveFrame3(ncpBuffer); + VerifyAndRemoveFrame2(ncpBuffer); + + VerifyOrQuit(ncpBuffer.IsEmpty() == true, "IsEmpty() is incorrect when buffer is empty."); + } + + printf(" -- PASS\n"); + + printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + printf("\nTest 4: Callbacks "); + + printf("\nIterations: "); + + // Repeat this multiple times. + for (j = 0; j < kTestIterationAttemps; j++) + { + printf("*"); + oldContext = context; + WriteTestFrame2(ncpBuffer); + VerifyOrQuit(ncpBuffer.IsEmpty() == false, "IsEmpty() is incorrect when buffer is non-empty"); + VerifyOrQuit(oldContext.mEmptyCount == context.mEmptyCount, "Empty callback called incorrectly"); + VerifyOrQuit(oldContext.mNonEmptyCount + 1 == context.mNonEmptyCount, "NonEmpty callback was not invoked."); + + oldContext = context; + WriteTestFrame3(ncpBuffer); + VerifyOrQuit(oldContext.mEmptyCount == context.mEmptyCount, "Empty callback called incorrectly"); + VerifyOrQuit(oldContext.mNonEmptyCount == context.mNonEmptyCount, "NonEmpty callback called incorrectly."); + + oldContext = context; + ncpBuffer.OutFrameRemove(); + VerifyOrQuit(ncpBuffer.IsEmpty() == false, "IsEmpty() is incorrect when buffer is non empty."); + VerifyOrQuit(oldContext.mEmptyCount == context.mEmptyCount, "Empty callback called incorrectly"); + VerifyOrQuit(oldContext.mNonEmptyCount == context.mNonEmptyCount, "NonEmpty callback called incorrectly."); + + oldContext = context; + ncpBuffer.OutFrameRemove(); + VerifyOrQuit(ncpBuffer.IsEmpty() == true, "IsEmpty() is incorrect when buffer is empty."); + VerifyOrQuit(oldContext.mEmptyCount + 1 == context.mEmptyCount, "Empty callback was not invoked."); + VerifyOrQuit(oldContext.mNonEmptyCount == context.mNonEmptyCount, "NonEmpty callback called incorrectly."); + } + + printf(" -- PASS\n"); + + printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + printf("\nTest 5: Clear() and empty buffer method tests"); + + WriteTestFrame1(ncpBuffer); + + oldContext = context; + ncpBuffer.Clear(); + + VerifyOrQuit(ncpBuffer.IsEmpty() == true, "IsEmpty() is incorrect when buffer is empty."); + VerifyOrQuit(ncpBuffer.OutFrameHasEnded() == true, "OutFrameHasEnded() is incorrect when no data in buffer."); + VerifyOrQuit(ncpBuffer.OutFrameRemove() == kThreadError_NotFound, + "Remove() returned incorrect error status when buffer is empty."); + VerifyOrQuit(ncpBuffer.OutFrameGetLength() == 0, "OutFrameGetLength() returned non-zero length when buffer is empty."); + VerifyOrQuit(oldContext.mEmptyCount + 1 == context.mEmptyCount, "Empty callback was not invoked."); + VerifyOrQuit(oldContext.mNonEmptyCount == context.mNonEmptyCount, "NonEmpty callback called incorrectly."); + + WriteTestFrame1(ncpBuffer); + + oldContext = context; + VerifyAndRemoveFrame1(ncpBuffer); + + VerifyOrQuit(ncpBuffer.IsEmpty() == true, "IsEmpty() is incorrect when buffer is empty."); + VerifyOrQuit(ncpBuffer.OutFrameHasEnded() == true, "OutFrameHasEnded() is incorrect when no data in buffer."); + VerifyOrQuit(ncpBuffer.OutFrameRemove() == kThreadError_NotFound, + "Remove() returned incorrect error status when buffer is empty."); + VerifyOrQuit(ncpBuffer.OutFrameGetLength() == 0, "OutFrameGetLength() returned non-zero length when buffer is empty."); + VerifyOrQuit(oldContext.mEmptyCount + 1 == context.mEmptyCount, "Empty callback was not invoked."); + VerifyOrQuit(oldContext.mNonEmptyCount == context.mNonEmptyCount, "NonEmpty callback called incorrectly."); + + printf(" -- PASS\n"); + + printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + printf("\nTest 6: OutFrameRead() in parts\n"); + + ncpBuffer.InFrameBegin(); + ncpBuffer.InFrameFeedData(sMottoText, sizeof(sMottoText)); + ncpBuffer.InFrameEnd(); + + ncpBuffer.OutFrameBegin(); + readOffset = 0; + + while ((readLen = ncpBuffer.OutFrameRead(sizeof(readBuffer), readBuffer)) != 0) + { + DumpBuffer("Read() returned", readBuffer, readLen); + + VerifyOrQuit(memcmp(readBuffer, sMottoText + readOffset, readLen) == 0, + "Read() does not match expected content."); + + readOffset += readLen; + } + + VerifyOrQuit(readOffset == sizeof(sMottoText), "Read len does not match expected length."); + printf("\n -- PASS\n"); +} + +} // namespace Thread + +int main(void) +{ + Thread::InitTest(); + Thread::TestNcpFrameBuffer(); + printf("\nAll tests passed.\n"); + return 0; +}