mirror of
https://github.com/espressif/openthread.git
synced 2026-08-17 16:09:51 +00:00
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:
committed by
Jonathan Hui
parent
c70a3c37ab
commit
a46d258ce7
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user