Ncp: Introducing NcpFrame Buffer class and its unit test. (#264)

This commit is part of ncp buffer redesign - issue #59.
This commit is contained in:
Abtin Keshavarzian
2016-07-25 11:17:29 -07:00
committed by Jonathan Hui
parent c70a3c37ab
commit a46d258ce7
5 changed files with 1509 additions and 0 deletions
+2
View File
@@ -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)
+656
View File
@@ -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 <common/code_utils.hpp>
#include <ncp/ncp_buffer.hpp>
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<uint16_t>(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
+358
View File
@@ -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 <openthread-types.h>
#include <common/message.hpp>
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_
+15
View File
@@ -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
+478
View File
@@ -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 <ctype.h>
#include "test_util.h"
#include <openthread.h>
#include <common/code_utils.hpp>
#include <ncp/ncp_buffer.hpp>
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<CallbackContext *>(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<CallbackContext *>(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;
}