diff --git a/src/ncp/hdlc.cpp b/src/ncp/hdlc.cpp index 021b7f140..3cff3bf56 100644 --- a/src/ncp/hdlc.cpp +++ b/src/ncp/hdlc.cpp @@ -96,7 +96,7 @@ uint16_t UpdateFcs(uint16_t aFcs, uint8_t aByte) return (aFcs >> 8) ^ sFcsTable[(aFcs ^ aByte) & 0xff]; } -bool HdlcByteNeedsEscape(uint8_t aByte) +static bool HdlcByteNeedsEscape(uint8_t aByte) { bool rval; @@ -118,143 +118,112 @@ bool HdlcByteNeedsEscape(uint8_t aByte) return rval; } -Encoder::BufferWriteIterator::BufferWriteIterator(void) -{ - mWritePointer = NULL; - mRemainingLength = 0; -} - -otError Encoder::BufferWriteIterator::WriteByte(uint8_t aByte) -{ - otError error = OT_ERROR_NONE; - - VerifyOrExit(mRemainingLength > 0, error = OT_ERROR_NO_BUFS); - - *mWritePointer++ = aByte; - mRemainingLength--; - -exit: - return error; -} - -bool Encoder::BufferWriteIterator::CanWrite(uint16_t aWriteLength) const -{ - return (mRemainingLength >= aWriteLength); -} - -Encoder::Encoder(void) - : mFcs(0) -{ -} - -otError Encoder::Init(BufferWriteIterator &aIterator) -{ - mFcs = kInitFcs; - - return aIterator.WriteByte(kFlagSequence); -} - -otError Encoder::Encode(uint8_t aInByte, BufferWriteIterator &aIterator) -{ - otError error = OT_ERROR_NONE; - - if (HdlcByteNeedsEscape(aInByte)) - { - VerifyOrExit(aIterator.CanWrite(2), error = OT_ERROR_NO_BUFS); - - aIterator.WriteByte(kEscapeSequence); - aIterator.WriteByte(aInByte ^ 0x20); - } - else - { - SuccessOrExit(error = aIterator.WriteByte(aInByte)); - } - - mFcs = UpdateFcs(mFcs, aInByte); - -exit: - return error; -} - -otError Encoder::Encode(const uint8_t *aInBuf, uint16_t aInLength, BufferWriteIterator &aIterator) -{ - otError error = OT_ERROR_NONE; - BufferWriteIterator oldIterator(aIterator); - uint16_t oldFcs = mFcs; - - for (int i = 0; i < aInLength; i++) - { - SuccessOrExit(error = Encode(aInBuf[i], aIterator)); - } - -exit: - - if (error != OT_ERROR_NONE) - { - aIterator = oldIterator; - mFcs = oldFcs; - } - - return error; -} - -otError Encoder::Finalize(BufferWriteIterator &aIterator) -{ - otError error = OT_ERROR_NONE; - BufferWriteIterator oldIterator(aIterator); - uint16_t oldFcs = mFcs; - uint16_t fcs = mFcs; - - fcs ^= 0xffff; - - SuccessOrExit(error = Encode(fcs & 0xff, aIterator)); - SuccessOrExit(error = Encode(fcs >> 8, aIterator)); - - SuccessOrExit(error = aIterator.WriteByte(kFlagSequence)); - -exit: - - if (error != OT_ERROR_NONE) - { - aIterator = oldIterator; - mFcs = oldFcs; - } - - return error; -} - -Decoder::Decoder(uint8_t * aOutBuf, - uint16_t aOutLength, - FrameHandler aFrameHandler, - ErrorHandler aErrorHandler, - void * aContext) - : mState(kStateNoSync) - , mFrameHandler(aFrameHandler) - , mErrorHandler(aErrorHandler) - , mContext(aContext) - , mOutBuf(aOutBuf) - , mOutOffset(0) - , mOutLength(aOutLength) +Encoder::Encoder(FrameWritePointer &aWritePointer) + : mWritePointer(aWritePointer) , mFcs(0) { } -void Decoder::Decode(const uint8_t *aInBuf, uint16_t aInLength) +otError Encoder::BeginFrame(void) { - uint8_t byte; + mFcs = kInitFcs; - for (int i = 0; i < aInLength; i++) + return mWritePointer.WriteByte(kFlagSequence); +} + +otError Encoder::Encode(uint8_t aByte) +{ + otError error = OT_ERROR_NONE; + + if (HdlcByteNeedsEscape(aByte)) { - byte = aInBuf[i]; + VerifyOrExit(mWritePointer.CanWrite(2), error = OT_ERROR_NO_BUFS); + + mWritePointer.WriteByte(kEscapeSequence); + mWritePointer.WriteByte(aByte ^ 0x20); + } + else + { + SuccessOrExit(error = mWritePointer.WriteByte(aByte)); + } + + mFcs = UpdateFcs(mFcs, aByte); + +exit: + return error; +} + +otError Encoder::Encode(const uint8_t *aData, uint16_t aLength) +{ + otError error = OT_ERROR_NONE; + uint16_t oldFcs = mFcs; + FrameWritePointer oldPointer = mWritePointer; + + while (aLength--) + { + SuccessOrExit(error = Encode(*aData++)); + } + +exit: + + if (error != OT_ERROR_NONE) + { + mWritePointer = oldPointer; + mFcs = oldFcs; + } + + return error; +} + +otError Encoder::EndFrame(void) +{ + otError error = OT_ERROR_NONE; + FrameWritePointer oldPointer = mWritePointer; + uint16_t oldFcs = mFcs; + uint16_t fcs = mFcs; + + fcs ^= 0xffff; + + SuccessOrExit(error = Encode(fcs & 0xff)); + SuccessOrExit(error = Encode(fcs >> 8)); + + SuccessOrExit(error = mWritePointer.WriteByte(kFlagSequence)); + +exit: + + if (error != OT_ERROR_NONE) + { + mWritePointer = oldPointer; + mFcs = oldFcs; + } + + return error; +} + +Decoder::Decoder(FrameWritePointer &aWritePointer, FrameHandler aFrameHandler, void *aContext) + : mState(kStateNoSync) + , mWritePointer(aWritePointer) + , mFrameHandler(aFrameHandler) + , mContext(aContext) + , mFcs(0) + , mDecodedLength(0) +{ +} + +void Decoder::Decode(const uint8_t *aData, uint16_t aLength) +{ + while (aLength--) + { + uint8_t byte = *aData++; switch (mState) { case kStateNoSync: if (byte == kFlagSequence) { - mState = kStateSync; - mOutOffset = 0; - mFcs = kInitFcs; + mState = kStateSync; + mDecodedLength = 0; + mFcs = kInitFcs; } break; @@ -268,38 +237,38 @@ void Decoder::Decode(const uint8_t *aInBuf, uint16_t aInLength) case kFlagSequence: - // We ignore frames which are smaller - // than the size of the CRC check. - if (mOutOffset > sizeof(uint16_t)) + // Ignore frames which are smaller than the size of the FCS. + if (mDecodedLength > sizeof(uint16_t)) { + otError error = OT_ERROR_NONE; + if (mFcs == kGoodFcs) { - mFrameHandler(mContext, mOutBuf, mOutOffset - sizeof(uint16_t)); + // Remove the FCS from the frame. + mWritePointer.UndoLastWrites(sizeof(uint16_t)); } - else if (mErrorHandler != NULL) + else { - mErrorHandler(mContext, OT_ERROR_PARSE, mOutBuf, mOutOffset); + error = OT_ERROR_PARSE; } + + mFrameHandler(mContext, error); } - mOutOffset = 0; - mFcs = kInitFcs; - + mDecodedLength = 0; + mFcs = kInitFcs; break; default: - if (mOutOffset < mOutLength) + if (mWritePointer.CanWrite(sizeof(uint8_t))) { - mFcs = UpdateFcs(mFcs, byte); - mOutBuf[mOutOffset++] = byte; + mFcs = UpdateFcs(mFcs, byte); + mWritePointer.WriteByte(byte); + mDecodedLength++; } else { - if (mErrorHandler != NULL) - { - mErrorHandler(mContext, OT_ERROR_NO_BUFS, mOutBuf, mOutOffset); - } - + mFrameHandler(mContext, OT_ERROR_NO_BUFS); mState = kStateNoSync; } @@ -309,20 +278,17 @@ void Decoder::Decode(const uint8_t *aInBuf, uint16_t aInLength) break; case kStateEscaped: - if (mOutOffset < mOutLength) + if (mWritePointer.CanWrite(sizeof(uint8_t))) { byte ^= 0x20; - mFcs = UpdateFcs(mFcs, byte); - mOutBuf[mOutOffset++] = byte; - mState = kStateSync; + mFcs = UpdateFcs(mFcs, byte); + mWritePointer.WriteByte(byte); + mDecodedLength++; + mState = kStateSync; } else { - if (mErrorHandler != NULL) - { - mErrorHandler(mContext, OT_ERROR_NO_BUFS, mOutBuf, mOutOffset); - } - + mFrameHandler(mContext, OT_ERROR_NO_BUFS); mState = kStateNoSync; } diff --git a/src/ncp/hdlc.hpp b/src/ncp/hdlc.hpp index 6252d802c..f2895ca9f 100644 --- a/src/ncp/hdlc.hpp +++ b/src/ncp/hdlc.hpp @@ -36,8 +36,11 @@ #include "openthread-core-config.h" #include +#include #include +#include "common/encoding.hpp" +#include "utils/wrap_string.h" namespace ot { @@ -50,6 +53,327 @@ namespace ot { */ namespace Hdlc { +/** + * This class defines a frame write pointer used by `Hdlc::Encoder` or `Hdlc::Decoder`. + * + * This class defines the minimum set of APIs used by `Encoder/Decoder` for writing an encoded/decoded frame. It is + * simply a wrapper over a pointer into a buffer indicating where next byte should be written. Along with a write + * pointer, this class stores a remaining length variable indicating number of remaining bytes that can be written into + * the buffer. + * + * @note This class does NOT define the underlying buffer space or how it is being managed. + * + * `Encoder` or `Decoder` users are expected to use sub-classes of this class adding the buffer space and implementing + * the frame buffer management scheme. + * + * Two template sub-class `FrameBuffer` and `MultiFrameBuffer` are defined which respectively allow storing a single + * frame or multiple frames (FIFO queue of frame) in a buffer of given size + * + */ +class FrameWritePointer +{ +public: + /** + * This method indicates whether there is buffer space available to write @p aWriteLength bytes. + * + * param[in] aWriteLength Number of bytes to write. + * + * @retval TRUE Enough buffer space is available to write the requested number of bytes. + * @retval FALSE Insufficient buffer space to write the requested number of bytes. + * + */ + bool CanWrite(uint16_t aWriteLength) const { return (mRemainingLength >= aWriteLength); } + + /** + * This method writes a byte into the buffer and updates the write pointer (if space is available). + * + * @retval OT_ERROR_NONE Successfully wrote the byte and updated the pointer. + * @retval OT_ERROR_NO_BUFS Insufficient buffer space to write the byte. + * + */ + otError WriteByte(uint8_t aByte) + { + return CanWrite(sizeof(uint8_t)) ? (*mWritePointer++ = aByte, mRemainingLength--, OT_ERROR_NONE) + : OT_ERROR_NO_BUFS; + } + + /** + * This method undoes the last @p aUndoLength writes, removing them from frame. + * + * @note Caller should ensure that @p aUndoLength is less than or equal number of previously written bytes into + * the frame. This method does not perform any checks and its behavior is undefined if @p aUndoLength is larger + * than bytes previously written into frame. + * + * @param[in] aUndoLength Number of bytes to remove (number of last `WriteByte()` calls to undo). + * + */ + void UndoLastWrites(uint16_t aUndoLength) + { + mWritePointer -= aUndoLength; + mRemainingLength += aUndoLength; + } + +protected: + FrameWritePointer(void) + : mWritePointer(NULL) + , mRemainingLength(0) + { + } + + uint8_t *mWritePointer; ///< A pointer to current write position in the buffer. + uint16_t mRemainingLength; ///< Number of remaining bytes available to write. +}; + +/** + * This class defines a template frame buffer of a given size for storing a single frame. + * + * The template parameter `kSize` specifies the size of the buffer. + * + */ +template class FrameBuffer : public FrameWritePointer +{ +public: + /** + * This constructor initializes the `FrameBuffer` object. + * + */ + FrameBuffer(void) + : FrameWritePointer() + { + Clear(); + } + + /** + * This method clears the buffer, moving the write pointer to beginning of buffer. + * + */ + void Clear(void) + { + mWritePointer = mBuffer; + mRemainingLength = sizeof(mBuffer); + } + + /** + * This method indicates whether the buffer is empty or contains a frame. + * + * @retval TRUE Buffer is empty + * @retval FALSE Buffer contains a frame + * + */ + bool IsEmpty(void) const { return (mWritePointer == mBuffer); } + + /** + * This method gets the length (number of bytes) in the frame. + * + * @returns The length (number of bytes) in the frame. + * + */ + uint16_t GetLength(void) const { return static_cast(mWritePointer - mBuffer); } + + /** + * This method gets a pointer to start of the frame. + * + * @returns A pointer to start of the frame. + * + */ + uint8_t *GetFrame(void) { return mBuffer; } + +private: + uint8_t mBuffer[kSize]; +}; + +/** + * This class defines a template frame buffer of a given size for storing multiple frames. + * + * The template parameter `kSize` specifies the total size of the buffer. + * + * Unlike `FrameBuffer` class where a single frame can be stored, this class is capable of saving multiple frames + * in a FIFO queue format. + * + */ +template class MultiFrameBuffer : public FrameWritePointer +{ +public: + /** + * This constructor initializes the `MultiFrameBuffer` object. + * + */ + MultiFrameBuffer(void) + : FrameWritePointer() + { + Clear(); + } + + /** + * This method clears the buffer, removing current frame and all previously saved frames. + * + * It moves the write pointer to beginning of buffer. + * + */ + void Clear(void) + { + mSavedFrameStart = mBuffer; + mWriteFrameStart = mBuffer; + mWritePointer = mBuffer + kHeaderSize; + mRemainingLength = kSize - kHeaderSize; + } + + /** + * This method indicates whether the current frame (being written) is empty or not. + * + * @retval TRUE Current frame is empty. + * @retval FALSE Current frame is not empty. + * + */ + bool HasFrame(void) const { return (mWritePointer != mWriteFrameStart + kHeaderSize); } + + /** + * This method gets the length (number of bytes) in the current frame written into the buffer. + * + * @returns The length (number of bytes) in the frame. + * + */ + uint16_t GetLength(void) const { return static_cast(mWritePointer - mWriteFrameStart - kHeaderSize); } + + /** + * This method gets a pointer to start of the current frame. + * + * @returns A pointer to start of the frame. + * + */ + uint8_t *GetFrame(void) { return mWriteFrameStart + kHeaderSize; } + + /** + * This method saves the current frame and prepares the write pointer for a next frame to be written into the + * buffer. + * + * Saved frame can be read later using `ReadSavedFrame()`. + * + */ + void SaveFrame(void) + { + Encoding::LittleEndian::WriteUint16(GetLength(), mWriteFrameStart); + mWriteFrameStart = mWritePointer; + mWritePointer += kHeaderSize; + mRemainingLength = static_cast(mBuffer + kSize - mWritePointer); + } + + /** + * This method discards the current frame and resets the write pointer for a next frame. + * + * @note If all previously saved frames are already read, this method clears the buffer and moves the write pointer + * to start of the buffer. + * + */ + void DiscardFrame(void) + { + if (!HasSavedFrame()) + { + Clear(); + } + else + { + mWritePointer = mWriteFrameStart + kHeaderSize; + mRemainingLength = static_cast(mBuffer + kSize - mWritePointer); + } + } + + /** + * This method indicates whether there are unread saved frames in the buffer. + * + * @retval TRUE There is at least one unread saved frame in buffer. + * @retval FALSE There is no unread saved frame in buffer. + * + */ + bool HasSavedFrame(void) const { return (mSavedFrameStart != mWriteFrameStart); } + + /** + * This method reads a previously saved frame from the buffer. + * + * Subsequent call to this method reads the next saved frame in the buffer (if any). + * + * @param[out] aFrame A reference to a pointer variable to return start of the frame. + * @param[out] aLength A reference to a variable to return the frame length (number of bytes). + * + * @retval OT_ERROR_NONE Frame was read successfully, @p aFrame and @p aLength updated. + * @retval OT_ERROR_NOT_FOUND No more saved frame in buffer. + * + */ + otError ReadSavedFrame(uint8_t *&aFrame, uint16_t &aLength) + { + otError error = OT_ERROR_NONE; + + if (mSavedFrameStart != mWriteFrameStart) + { + aLength = Encoding::LittleEndian::ReadUint16(mSavedFrameStart); + aFrame = mSavedFrameStart + kHeaderSize; + mSavedFrameStart += aLength + kHeaderSize; + } + else + { + error = OT_ERROR_NOT_FOUND; + } + + return error; + } + + /** + * This method clears the read saved frames from buffer and adjusts all the pointers. + * + * @note This method moves the pointers into the buffer and also copies the content. Any previously retrieved + * pointer to buffer (from `GetFrame()` or `ReadSavedFrame()`) should be considered invalid after calling this + * method. + * + */ + void ClearReadFrames(void) + { + uint16_t readLen = static_cast(mSavedFrameStart - mBuffer); + + if (readLen > 0) + { + memmove(mBuffer, mSavedFrameStart, static_cast(mWritePointer - mSavedFrameStart)); + mWritePointer -= readLen; + mWriteFrameStart -= readLen; + mSavedFrameStart -= readLen; + mRemainingLength += readLen; + } + } + +private: + /* + * The diagram below illustrates how the frames are saved in the buffer. + * + * Each saved frame contains a header which is 2 bytes long and specifies the frame length (the length does not + * include the header itself). The frame length is stored in header bytes as a `uint16_t` value using little-endian + * encoding. + * + * The diagram shows `mBuffer` and different pointers into the buffer. It represent buffer state when there are two + * saved frames in the buffer and the first saved frame is already read (`mSavedFrameStart` is pointing to header + * of the second frame). + * + * Saved frame #1 Saved frame #2 Current frame being written + * / \ / \ / \ + * +-----------+-------------+-----------+------------+---------+--------------------------------------------+ + * | header #1 | ... | header #2 | ... | header | ... | ... | + * +-----------+-------------+-----------+------------+---------+--------------------------------------------+ + * ^ ^ ^ ^\ /^ + * | | | | mRemainingLength | + * mBuffer[0] mSavedFrameStart mWriteFrameStart | | + * | mBuffer[kSize] + * mWritePointer + * + */ + + enum + { + kHeaderSize = sizeof(uint16_t), + }; + + uint8_t mBuffer[kSize]; + uint8_t *mSavedFrameStart; // Pointer to start of next saved frame (for `ReadSavedFrame()`). + uint8_t *mWriteFrameStart; // Pointer to start of current frame being written. +}; + /** * This class implements the HDLC-lite encoder. * @@ -57,101 +381,63 @@ namespace Hdlc { class Encoder { public: - /** - * This class defines a write iterator into a buffer used by Encoder. - * - * Hdlc users should sub-class this to add the actual buffer space. - */ - class BufferWriteIterator - { - public: - /** - * This method writes a byte to the buffer and updates the iterator (if space is available). - * - * @retval OT_ERROR_NONE Successfully wrote the byte and updates the iterator. - * @retval OT_ERROR_NO_BUFS Insufficient buffer space. - * - */ - otError WriteByte(uint8_t aByte); - - /** - * This method checks if there is buffer space available to write @p aWriteLength bytes. - * - * param[in] aWriteLength Number of bytes to write. - * - * @retval true Enough buffer space available to write the requested number of bytes. - * @retval false Insufficient buffer space to write the requested number of bytes. - * - */ - bool CanWrite(uint16_t aWriteLength) const; - - protected: - BufferWriteIterator(void); ///< Protected constructor to ensure no direct instantiation. - - uint8_t *mWritePointer; ///< A pointer to current write position in the buffer. - uint16_t mRemainingLength; ///< Number of remaining bytes available to write. - }; - /** * This constructor initializes the object. * + * @param[in] aWritePointer The `FrameWritePointer` used by `Encoder` to write the encoded frames. + * */ - Encoder(void); + explicit Encoder(FrameWritePointer &aWritePointer); /** - * This method begins an HDLC frame and puts the initial bytes into a buffer at the given @p aIterator. - * - * @param[inout] aIterator A reference to a buffer write iterator. On successful exit, the iterator is updated. + * This method begins an HDLC frame. * * @retval OT_ERROR_NONE Successfully started the HDLC frame. * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to start the HDLC frame. * */ - otError Init(BufferWriteIterator &aIterator); + otError BeginFrame(void); /** - * This method encodes a single byte into a buffer at @p aIterator. + * This method encodes a single byte into current frame. * - * If there is no space to add the byte, the write iterator remains the same. + * If there is no space to add the byte, the write pointer in frame buffer remains the same. * - * @param[in] aInByte A byte to encode and add. - * @param[inout] aIterator A reference to a write buffer iterator. On successful exit, the iterator is updated. + * @param[in] aByte A byte value to encode and add to frame. * - * @retval OT_ERROR_NONE Successfully encoded and added the byte. + * @retval OT_ERROR_NONE Successfully encoded and added the byte to frame buffer. * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to encode and add the byte. * */ - otError Encode(uint8_t aInByte, BufferWriteIterator &aIterator); + otError Encode(uint8_t aByte); /** - * This method encodes the frame into a buffer at @p aIterator. + * This method encodes a given block of data into current frame. * - * This method returns success only if there is space in buffer to encode the entire frame. If there is no space - * to encode the entire frame, the write iterator remains the same. + * This method returns success only if there is space in buffer to encode the entire block of data. If there is no + * space to encode the entire block of data, the write pointer in frame buffer remains the same. * - * @param[in] aInBuf A pointer to the input buffer. - * @param[in] aInLength The number of bytes in @p aInBuf to encode. - * @param[inout] aIterator A reference to a write buffer iterator. On successful exit, the iterator is updated. + * @param[in] aData A pointer to a buffer containing the data to encode. + * @param[in] aLength The number of bytes in @p aData. * - * @retval OT_ERROR_NONE Successfully encoded the HDLC frame. - * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to encode the HDLC frame. + * @retval OT_ERROR_NONE Successfully encoded and added the data to frame. + * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to add the frame. * */ - otError Encode(const uint8_t *aInBuf, uint16_t aInLength, BufferWriteIterator &aIterator); + otError Encode(const uint8_t *aData, uint16_t aLength); /** - * This method finalizes an HDLC frame. - * - * @param[inout] aIterator A reference to a write buffer iterator. On successful exit, the iterator is updated. + * This method ends/finalizes the HDLC frame. * * @retval OT_ERROR_NONE Successfully ended the HDLC frame. * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to end the HDLC frame. * */ - otError Finalize(BufferWriteIterator &aIterator); + otError EndFrame(void); private: - uint16_t mFcs; + FrameWritePointer &mWritePointer; + uint16_t mFcs; }; /** @@ -162,68 +448,58 @@ class Decoder { public: /** - * This function pointer is called when a complete frame has been formed. + * This function pointer is called when either a complete frame has been decoded or an error occurs during + * decoding. * - * @param[in] aContext A pointer to arbitrary context information. - * @param[in] aFrame A pointer to the frame. - * @param[in] aFrameLength The frame length in bytes. + * The decoded frame (or the partially decoded frame in case of an error) is available in `aFrameWritePointer` + * buffer given in `Decoder` constructor. + * + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aError OT_ERROR_NONE if the frame was decoded successfully, + * OT_ERROR_PARSE if the Frame Check Sequence (FCS) was incorrect in decoded frame, + * OT_ERROR_NO_BUFS insufficient buffer space available to save the decoded frame. * */ - typedef void (*FrameHandler)(void *aContext, uint8_t *aFrame, uint16_t aFrameLength); - - /** - * This function pointer is called when an error has occurred. - * - * @param[in] aContext A pointer to arbitrary context information. - * @param[in] aError An error code describing the error. - * @param[in] aFrame A pointer to the frame. - * @param[in] aFrameLength The frame length in bytes. - * - */ - typedef void (*ErrorHandler)(void *aContext, otError aError, uint8_t *aFrame, uint16_t aFrameLength); + typedef void (*FrameHandler)(void *aContext, otError aError); /** * This constructor initializes the decoder. * - * @param[in] aOutBuf A pointer to the output buffer. - * @param[in] aOutLength Size of the output buffer in bytes. - * @param[in] aFrameHandler A pointer to a function that is called when a complete frame is received. - * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aFrameWritePointer The `FrameWritePointer` used by `Decoder` to write the decoded frames. + * @param[in] aFrameHandler The frame handler callback function pointer. + * @param[in] aContext A pointer to arbitrary context information. * */ - Decoder(uint8_t * aOutBuf, - uint16_t aOutLength, - FrameHandler aFrameHandler, - ErrorHandler aErrorHandler, - void * aContext); + Decoder(FrameWritePointer &aFrameWritePointer, FrameHandler aFrameHandler, void *aContext); /** - * This method streams bytes into the decoder. + * This method feeds a block of data into the decoder. * - * @param[in] aInBuf A pointer to the input buffer. - * @param[in] aInLength The number of bytes in @p aInBuf. + * If during decoding, a full HDLC frame is successfully decoded or an error occurs, the `FrameHandler` callback + * is called. The decoded frame (or the partially decoded frame in case of an error) is available in + * `aFrameWritePointer` buffer from the constructor. The `Decoder` user (if required) must update/reset the write + * pointer from this callback for the next frame to be decoded. + * + * @param[in] aData A pointer to a buffer containing data to be fed to decoder. + * @param[in] aLength The number of bytes in @p aData. * */ - void Decode(const uint8_t *aInBuf, uint16_t aInLength); + void Decode(const uint8_t *aData, uint16_t aLength); private: enum State { - kStateNoSync = 0, + kStateNoSync, kStateSync, kStateEscaped, }; - State mState; - FrameHandler mFrameHandler; - ErrorHandler mErrorHandler; - void * mContext; - - uint8_t *mOutBuf; - uint16_t mOutOffset; - uint16_t mOutLength; - - uint16_t mFcs; + State mState; + FrameWritePointer &mWritePointer; + FrameHandler mFrameHandler; + void * mContext; + uint16_t mFcs; + uint16_t mDecodedLength; }; } // namespace Hdlc diff --git a/src/ncp/ncp_uart.cpp b/src/ncp/ncp_uart.cpp index 3d3c97063..5e9ddf1aa 100644 --- a/src/ncp/ncp_uart.cpp +++ b/src/ncp/ncp_uart.cpp @@ -70,39 +70,14 @@ extern "C" void otNcpInit(otInstance *aInstance) #endif // OPENTHREAD_ENABLE_SPINEL_VENDOR_SUPPORT == 0 -NcpUart::UartTxBuffer::UartTxBuffer(void) - : Hdlc::Encoder::BufferWriteIterator() -{ - Clear(); -} - -void NcpUart::UartTxBuffer::Clear(void) -{ - mWritePointer = mBuffer; - mRemainingLength = sizeof(mBuffer); -} - -bool NcpUart::UartTxBuffer::IsEmpty(void) const -{ - return mWritePointer == mBuffer; -} - -uint16_t NcpUart::UartTxBuffer::GetLength(void) const -{ - return static_cast(mWritePointer - mBuffer); -} - -const uint8_t *NcpUart::UartTxBuffer::GetBuffer(void) const -{ - return mBuffer; -} - NcpUart::NcpUart(Instance *aInstance) : NcpBase(aInstance) - , mFrameDecoder(mRxBuffer, sizeof(mRxBuffer), &NcpUart::HandleFrame, &NcpUart::HandleError, this) + , mFrameEncoder(mUartBuffer) + , mFrameDecoder(mRxBuffer, &NcpUart::HandleFrame, this) , mUartBuffer() , mState(kStartingFrame) , mByte(0) + , mRxBuffer() , mUartSendImmediate(false) , mUartSendTask(*aInstance, EncodeAndSendToUart, this) #if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER @@ -165,7 +140,7 @@ void NcpUart::EncodeAndSendToUart(void) } VerifyOrExit(super_t::ShouldDeferHostSend() == false); - SuccessOrExit(mFrameEncoder.Init(mUartBuffer)); + SuccessOrExit(mFrameEncoder.BeginFrame()); txFrameBuffer.OutFrameBegin(); @@ -177,7 +152,7 @@ void NcpUart::EncodeAndSendToUart(void) case kEncodingFrame: - SuccessOrExit(mFrameEncoder.Encode(mByte, mUartBuffer)); + SuccessOrExit(mFrameEncoder.Encode(mByte)); } // track the change of mHostPowerStateInProgress by the @@ -202,7 +177,7 @@ void NcpUart::EncodeAndSendToUart(void) case kFinalizingFrame: - SuccessOrExit(mFrameEncoder.Finalize(mUartBuffer)); + SuccessOrExit(mFrameEncoder.EndFrame()); mState = kStartingFrame; @@ -220,7 +195,7 @@ exit: if (len > 0) { - if (otPlatUartSend(mUartBuffer.GetBuffer(), len) != OT_ERROR_NONE) + if (otPlatUartSend(mUartBuffer.GetFrame(), len) != OT_ERROR_NONE) { assert(false); } @@ -259,27 +234,34 @@ void NcpUart::HandleUartReceiveDone(const uint8_t *aBuf, uint16_t aBufLength) mFrameDecoder.Decode(aBuf, aBufLength); } -void NcpUart::HandleFrame(void *aContext, uint8_t *aBuf, uint16_t aBufLength) +void NcpUart::HandleFrame(void *aContext, otError aError) { - static_cast(aContext)->HandleFrame(aBuf, aBufLength); + static_cast(aContext)->HandleFrame(aError); } -void NcpUart::HandleFrame(uint8_t *aBuf, uint16_t aBufLength) +void NcpUart::HandleFrame(otError aError) { -#if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER - size_t dataLen = aBufLength; - if (SpinelEncrypter::DecryptInbound(aBuf, sizeof(mRxBuffer), &dataLen)) + uint8_t *buf = mRxBuffer.GetFrame(); + uint16_t bufLength = mRxBuffer.GetLength(); + + if (aError == OT_ERROR_NONE) { - super_t::HandleReceive(aBuf, dataLen); - } +#if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER + size_t dataLen = bufLength; + if (SpinelEncrypter::DecryptInbound(buf, kRxBufferSize, &dataLen)) + { + super_t::HandleReceive(buf, dataLen); + } #else - super_t::HandleReceive(aBuf, aBufLength); + super_t::HandleReceive(buf, bufLength); #endif // OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER -} + } + else + { + HandleError(aError, buf, bufLength); + } -void NcpUart::HandleError(void *aContext, otError aError, uint8_t *aBuf, uint16_t aBufLength) -{ - static_cast(aContext)->HandleError(aError, aBuf, aBufLength); + mRxBuffer.Clear(); } void NcpUart::HandleError(otError aError, uint8_t *aBuf, uint16_t aBufLength) diff --git a/src/ncp/ncp_uart.hpp b/src/ncp/ncp_uart.hpp index 87895659e..a61fa5819 100644 --- a/src/ncp/ncp_uart.hpp +++ b/src/ncp/ncp_uart.hpp @@ -85,20 +85,6 @@ private: kFinalizingFrame, // Finalizing a frame. }; - class UartTxBuffer : public Hdlc::Encoder::BufferWriteIterator - { - public: - UartTxBuffer(void); - - void Clear(void); - bool IsEmpty(void) const; - uint16_t GetLength(void) const; - const uint8_t *GetBuffer(void) const; - - private: - uint8_t mBuffer[kUartTxBufferSize]; - }; - #if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER /** * Wraps NcpFrameBuffer allowing to read data through spinel encrypter. @@ -129,27 +115,26 @@ private: #endif // OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER void EncodeAndSendToUart(void); - void HandleFrame(uint8_t *aBuf, uint16_t aBufLength); + void HandleFrame(otError aError); void HandleError(otError aError, uint8_t *aBuf, uint16_t aBufLength); void TxFrameBufferHasData(void); void HandleFrameAddedToNcpBuffer(void); static void EncodeAndSendToUart(Tasklet &aTasklet); - static void HandleFrame(void *context, uint8_t *aBuf, uint16_t aBufLength); - static void HandleError(void *context, otError aError, uint8_t *aBuf, uint16_t aBufLength); + static void HandleFrame(void *aConext, otError aError); static void HandleFrameAddedToNcpBuffer(void * aContext, NcpFrameBuffer::FrameTag aTag, NcpFrameBuffer::Priority aPriority, NcpFrameBuffer * aNcpFrameBuffer); - Hdlc::Encoder mFrameEncoder; - Hdlc::Decoder mFrameDecoder; - UartTxBuffer mUartBuffer; - UartTxState mState; - uint8_t mByte; - uint8_t mRxBuffer[kRxBufferSize]; - bool mUartSendImmediate; - Tasklet mUartSendTask; + Hdlc::Encoder mFrameEncoder; + Hdlc::Decoder mFrameDecoder; + Hdlc::FrameBuffer mUartBuffer; + UartTxState mState; + uint8_t mByte; + Hdlc::FrameBuffer mRxBuffer; + bool mUartSendImmediate; + Tasklet mUartSendTask; #if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER NcpFrameBufferEncrypterReader mTxFrameBufferEncrypterReader; diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index aa6dcf2d9..6daa61033 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -104,6 +104,7 @@ check_PROGRAMS = \ test-aes \ test-child \ test-child-table \ + test-hdlc \ test-heap \ test-hmac-sha256 \ test-link-quality \ @@ -165,6 +166,9 @@ test_child_SOURCES = test_platform.cpp test_child.cpp test_child_table_LDADD = $(COMMON_LDADD) test_child_table_SOURCES = test_platform.cpp test_child_table.cpp +test_hdlc_LDADD = $(COMMON_LDADD) +test_hdlc_SOURCES = test_platform.cpp test_hdlc.cpp + test_heap_LDADD = $(COMMON_LDADD) test_heap_SOURCES = test_platform.cpp test_heap.cpp @@ -227,6 +231,7 @@ PRETTY_FILES = \ $(test_aes_SOURCES) \ $(test_child_SOURCES) \ $(test_child_table_SOURCES) \ + $(test_hdlc_SOURCES) \ $(test_heap_SOURCES) \ $(test_hmac_sha256_SOURCES) \ $(test_link_quality_SOURCES) \ diff --git a/tests/unit/test_hdlc.cpp b/tests/unit/test_hdlc.cpp new file mode 100644 index 000000000..1e9c23434 --- /dev/null +++ b/tests/unit/test_hdlc.cpp @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "ncp/hdlc.hpp" + +#include "test_util.h" + +namespace ot { +namespace Ncp { + +enum +{ + kBufferSize = 1500, // Frame buffer size + kMaxFrameLength = 500, // Maximum allowed frame length (used when randomly generating frames) + kFuzzTestIteration = 50000, // Number of iteration during fuzz test (randomly generating frames) + kUseTrueRandomNumberGenerator = 1, // To use true random number generator or not. + + kFlagXOn = 0x11, + kFlagXOff = 0x13, + kFlagSequence = 0x7e, ///< HDLC Flag value + kEscapeSequence = 0x7d, ///< HDLC Escape value + kFlagSpecial = 0xf8, + +}; + +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 sHexText[] = "0123456789abcdef"; +static const uint8_t sHdlcSpeicals[] = {kFlagSequence, kFlagXOn, kFlagXOff, + kFlagSequence, kEscapeSequence, kFlagSpecial}; + +otError WriteToBuffer(const uint8_t *aText, Hdlc::FrameWritePointer &aWritePointer) +{ + otError error = OT_ERROR_NONE; + + while (*aText != 0) + { + SuccessOrExit(aWritePointer.WriteByte(*aText++)); + } + +exit: + return error; +} + +void TestHdlcFrameBuffer(void) +{ + Hdlc::FrameBuffer frameBuffer; + + printf("Testing Hdlc::FrameBuffer"); + + VerifyOrQuit(frameBuffer.IsEmpty(), "IsEmpty() failed after constructor"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after constructor"); + + SuccessOrQuit(WriteToBuffer(sOpenThreadText, frameBuffer), "WriteByte() failed"); + + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sOpenThreadText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sOpenThreadText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + VerifyOrQuit(frameBuffer.CanWrite(1), "CanWrite() failed"); + VerifyOrQuit(!frameBuffer.IsEmpty(), "IsEmpty() failed"); + + SuccessOrQuit(WriteToBuffer(sHelloText, frameBuffer), "WriteByte() failed"); + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sOpenThreadText) + sizeof(sHelloText) - 2, "GetLength() failed"); + + frameBuffer.UndoLastWrites(sizeof(sHelloText) - 1); + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sOpenThreadText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sOpenThreadText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + VerifyOrQuit(!frameBuffer.IsEmpty(), "IsEmpty() failed"); + frameBuffer.Clear(); + VerifyOrQuit(frameBuffer.IsEmpty(), "IsEmpty() failed after Clear()"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after Clear()"); + + SuccessOrQuit(WriteToBuffer(sMottoText, frameBuffer), "WriteByte() failed"); + + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sMottoText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sMottoText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + frameBuffer.Clear(); + VerifyOrQuit(frameBuffer.CanWrite(kBufferSize), "CanWrite(kBufferSize) failed unexpectedly"); + VerifyOrQuit(frameBuffer.CanWrite(kBufferSize + 1) == false, "CanWrite(kBufferSize + 1) did not fail as expected"); + + for (uint16_t i = 0; i < kBufferSize; i++) + { + VerifyOrQuit(frameBuffer.CanWrite(1), "CanWrite() failed unexpectedly"); + SuccessOrQuit(frameBuffer.WriteByte(i & 0xff), "WriteByte() failed unexpectedly"); + } + + VerifyOrQuit(frameBuffer.CanWrite(1) == false, "CanWrite() did not fail with full buffer"); + VerifyOrQuit(frameBuffer.WriteByte(0) == OT_ERROR_NO_BUFS, "WriteByte() did not fail with full buffer"); + + printf(" -- PASS\n"); +} + +void TestHdlcMultiFrameBuffer(void) +{ + Hdlc::MultiFrameBuffer frameBuffer; + uint8_t * frame; + uint16_t length; + + printf("Testing Hdlc::MultiFrameBuffer"); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Check state after constructor + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() failed after constructor"); + VerifyOrQuit(!frameBuffer.HasSavedFrame(), "HasSavedFrame() failed after constructor"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after constructor"); + VerifyOrQuit(frameBuffer.ReadSavedFrame(frame, length) == OT_ERROR_NOT_FOUND, + "ReadSavedFrame() incorrect behavior after constructor"); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Write multiple frames, save them and read later + + SuccessOrQuit(WriteToBuffer(sMottoText, frameBuffer), "WriteByte() failed"); + + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sMottoText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sMottoText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + frameBuffer.SaveFrame(); + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() failed after SaveFrame()"); + VerifyOrQuit(frameBuffer.HasSavedFrame(), "HasSavedFrame() failed after SaveFrame()"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after SaveFrame()"); + + SuccessOrQuit(WriteToBuffer(sHelloText, frameBuffer), "WriteByte() failed"); + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sHelloText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sHelloText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + frameBuffer.SaveFrame(); + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() failed after SaveFrame()"); + VerifyOrQuit(frameBuffer.HasSavedFrame(), "HasSavedFrame() failed after SaveFrame()"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after SaveFrame()"); + + SuccessOrQuit(WriteToBuffer(sOpenThreadText, frameBuffer), "WriteByte() failed"); + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sOpenThreadText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sOpenThreadText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + frameBuffer.DiscardFrame(); + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() failed after DiscardFrame()"); + VerifyOrQuit(frameBuffer.HasSavedFrame(), "HasSavedFrame() failed after SaveFrame()"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after DiscardFrame()"); + + SuccessOrQuit(WriteToBuffer(sMottoText, frameBuffer), "WriteByte() failed"); + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sMottoText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sMottoText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + frameBuffer.DiscardFrame(); + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() failed after DiscardFrame()"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after DiscardFrame()"); + + SuccessOrQuit(WriteToBuffer(sHexText, frameBuffer), "WriteByte() failed"); + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sHexText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sHexText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + frameBuffer.SaveFrame(); + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() failed after SaveFrame()"); + VerifyOrQuit(frameBuffer.HasSavedFrame(), "HasSavedFrame() failed after SaveFrame()"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after SaveFrame()"); + + SuccessOrQuit(WriteToBuffer(sOpenThreadText, frameBuffer), "WriteByte() failed"); + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sOpenThreadText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sOpenThreadText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + // Read the first saved frame and check the content + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(length == sizeof(sMottoText) - 1, "ReadSavedFrame() returned length is incorrect"); + VerifyOrQuit(memcmp(frame, sMottoText, length) == 0, "ReadSavedFrame() returned frame content is incorrect"); + + // Read the second saved frame and check the content + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(length == sizeof(sHelloText) - 1, "ReadSavedFrame() returned length is incorrect"); + VerifyOrQuit(memcmp(frame, sHelloText, length) == 0, "ReadSavedFrame() returned frame content is incorrect"); + + // Read the third saved frame and check the content + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(length == sizeof(sHexText) - 1, "ReadSavedFrame() returned length is incorrect"); + VerifyOrQuit(memcmp(frame, sHexText, length) == 0, "ReadSavedFrame() returned frame content is incorrect"); + + VerifyOrQuit(frameBuffer.ReadSavedFrame(frame, length) == OT_ERROR_NOT_FOUND, + "ReadSavedFrame() incorrect behavior after all frames were read"); + + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sOpenThreadText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sOpenThreadText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + frameBuffer.SaveFrame(); + + // Read the fourth saved frame and check the content + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(length == sizeof(sOpenThreadText) - 1, "ReadSavedFrame() returned length is incorrect"); + VerifyOrQuit(memcmp(frame, sOpenThreadText, length) == 0, "ReadSavedFrame() returned frame content is incorrect"); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Verify behavior of `Clear()` + + frameBuffer.Clear(); + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() failed after Clear()"); + VerifyOrQuit(!frameBuffer.HasSavedFrame(), "HasSavedFrame() failed after Clear()"); + VerifyOrQuit(frameBuffer.GetLength() == 0, "GetLength() failed after Clear()"); + + SuccessOrQuit(WriteToBuffer(sOpenThreadText, frameBuffer), "WriteByte() failed"); + frameBuffer.SaveFrame(); + + SuccessOrQuit(WriteToBuffer(sHelloText, frameBuffer), "WriteByte() failed"); + frameBuffer.SaveFrame(); + VerifyOrQuit(frameBuffer.HasSavedFrame(), "HasFrame() incorrect behavior after SaveFrame()"); + + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(frameBuffer.HasSavedFrame(), "HasFrame() incorrect behavior after SaveFrame()"); + + frameBuffer.Clear(); + + VerifyOrQuit(frameBuffer.ReadSavedFrame(frame, length) == OT_ERROR_NOT_FOUND, + "ReadSavedFrame() incorrect behavior after Clear()"); + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() incorrect behavior after Clear()"); + VerifyOrQuit(!frameBuffer.HasSavedFrame(), "HasFrame() incorrect behavior after Clear()"); + VerifyOrQuit(frameBuffer.CanWrite(kBufferSize - 1) == false, "CanWrite() incorrect behavior after Clear()"); + VerifyOrQuit(frameBuffer.CanWrite(kBufferSize - 2) == true, "CanWrite() incorrect behavior after Clear()"); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Verify behavior of `ClearReadFrames()` + + SuccessOrQuit(WriteToBuffer(sHelloText, frameBuffer), "WriteByte() failed"); + frameBuffer.SaveFrame(); + SuccessOrQuit(WriteToBuffer(sOpenThreadText, frameBuffer), "WriteByte() failed"); + frameBuffer.SaveFrame(); + SuccessOrQuit(WriteToBuffer(sMottoText, frameBuffer), "WriteByte() failed"); + frameBuffer.SaveFrame(); + SuccessOrQuit(WriteToBuffer(sHexText, frameBuffer), "WriteByte() failed"); + + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(length == sizeof(sHelloText) - 1, "ReadSavedFrame() returned length is incorrect"); + VerifyOrQuit(memcmp(frame, sHelloText, length) == 0, "ReadSavedFrame() returned frame content is incorrect"); + + frameBuffer.ClearReadFrames(); + + VerifyOrQuit(frameBuffer.HasFrame(), "HasFrame() failed after ClearReadFrames()"); + VerifyOrQuit(frameBuffer.HasSavedFrame(), "HasSavedFrame() failed after ClearReadFrames()"); + + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sHexText) - 1, "Frame length is incorrect after ClearReadFrames()"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sHexText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect after ClearReadFrames()"); + + frameBuffer.SaveFrame(); + + SuccessOrQuit(WriteToBuffer(sHelloText, frameBuffer), "WriteByte() failed"); + + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(length == sizeof(sOpenThreadText) - 1, "ReadSavedFrame() returned length is incorrect"); + VerifyOrQuit(memcmp(frame, sOpenThreadText, length) == 0, "ReadSavedFrame() returned frame content is incorrect"); + + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(length == sizeof(sMottoText) - 1, "ReadSavedFrame() returned length is incorrect"); + VerifyOrQuit(memcmp(frame, sMottoText, length) == 0, "ReadSavedFrame() returned frame content is incorrect"); + + SuccessOrQuit(frameBuffer.ReadSavedFrame(frame, length), "ReadSavedFrame() failed unexpectedly"); + VerifyOrQuit(length == sizeof(sHexText) - 1, "ReadSavedFrame() returned length is incorrect"); + VerifyOrQuit(memcmp(frame, sHexText, length) == 0, "ReadSavedFrame() returned frame content is incorrect"); + + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sHelloText) - 1, + "Frame length is incorrect after ClearReadFrames()"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sHelloText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect after ClearReadFrames()"); + + frameBuffer.DiscardFrame(); + + // Since all previously saved frames are read and last frame is discarded + // the `DiscardFrame()` call should basically `Clear()` the buffer. + + VerifyOrQuit(!frameBuffer.HasFrame(), "HasFrame() incorrect behavior after all frames are read and discarded"); + VerifyOrQuit(!frameBuffer.HasSavedFrame(), "HasFrame() incorrect behavior after all read or discarded"); + VerifyOrQuit(frameBuffer.CanWrite(kBufferSize - 1) == false, + "CanWrite() incorrect behavior after all read or discarded"); + VerifyOrQuit(frameBuffer.CanWrite(kBufferSize - 2) == true, + "CanWrite() incorrect behavior after all read of discarded"); + + frameBuffer.Clear(); + SuccessOrQuit(WriteToBuffer(sHelloText, frameBuffer), "WriteByte() failed"); + + // Since there are no saved frames, the `ClearReadFrames()` call + // here, should practically do nothing + + frameBuffer.ClearReadFrames(); + + VerifyOrQuit(frameBuffer.GetLength() == sizeof(sHelloText) - 1, "GetLength() failed"); + VerifyOrQuit(memcmp(frameBuffer.GetFrame(), sHelloText, frameBuffer.GetLength()) == 0, + "GetFrame() content is incorrect"); + + printf(" -- PASS\n"); +} + +struct DecoderContext +{ + bool mWasCalled; + otError mError; +}; + +void ProcessDecodedFrame(void *aContext, otError aError) +{ + DecoderContext &decoderContext = *static_cast(aContext); + + decoderContext.mError = aError; + decoderContext.mWasCalled = true; +} + +void TestEncoderDecoder(void) +{ + otError error; + uint8_t byte; + Hdlc::MultiFrameBuffer encoderBuffer; + Hdlc::MultiFrameBuffer decoderBuffer; + DecoderContext decoderContext; + Hdlc::Encoder encoder(encoderBuffer); + Hdlc::Decoder decoder(decoderBuffer, ProcessDecodedFrame, &decoderContext); + uint8_t * frame; + uint16_t length; + + printf("Testing Hdlc::Encoder and Hdlc::Decoder"); + + SuccessOrQuit(encoder.BeginFrame(), "Encoder::BeginFrame() failed"); + SuccessOrQuit(encoder.Encode(sOpenThreadText, sizeof(sOpenThreadText) - 1), "Encoder::Encode() failed"); + SuccessOrQuit(encoder.EndFrame(), "Encoder::EndFrame() failed"); + encoderBuffer.SaveFrame(); + + SuccessOrQuit(encoder.BeginFrame(), "Encoder::BeginFrame() failed"); + SuccessOrQuit(encoder.Encode(sMottoText, sizeof(sMottoText) - 1), "Encoder::Encode() failed"); + SuccessOrQuit(encoder.EndFrame(), "Encoder::EndFrame() failed"); + encoderBuffer.SaveFrame(); + + SuccessOrQuit(encoder.BeginFrame(), "Encoder::BeginFrame() failed"); + SuccessOrQuit(encoder.Encode(sHdlcSpeicals, sizeof(sHdlcSpeicals)), "Encoder::Encode() failed"); + SuccessOrQuit(encoder.EndFrame(), "Encoder::EndFrame() failed"); + encoderBuffer.SaveFrame(); + + SuccessOrQuit(encoder.BeginFrame(), "Encoder::BeginFrame() failed"); + SuccessOrQuit(encoder.Encode(sHelloText, sizeof(sHelloText) - 1), "Encoder::Encode() failed"); + SuccessOrQuit(encoder.EndFrame(), "Encoder::EndFrame() failed"); + encoderBuffer.SaveFrame(); + + byte = kFlagSequence; + SuccessOrQuit(encoder.BeginFrame(), "Encoder::BeginFrame() failed"); + SuccessOrQuit(encoder.Encode(&byte, sizeof(uint8_t)), "Encoder::Encode() failed"); + SuccessOrQuit(encoder.EndFrame(), "Encoder::EndFrame() failed"); + encoderBuffer.SaveFrame(); + + // Feed the encoded frame to decoder and saved the content + while (encoderBuffer.ReadSavedFrame(frame, length) == OT_ERROR_NONE) + { + decoderContext.mWasCalled = false; + + decoder.Decode(frame, length); + + VerifyOrQuit(decoderContext.mWasCalled, "Decoder::Decode() failed"); + VerifyOrQuit(decoderContext.mError == OT_ERROR_NONE, "Decoder::Decode() returned incorrect error code"); + + decoderBuffer.SaveFrame(); + } + + // Verify the decoded frame match the original frames + + SuccessOrQuit(decoderBuffer.ReadSavedFrame(frame, length), "Incorrect decoded frame"); + VerifyOrQuit(length == sizeof(sOpenThreadText) - 1, "Decoded frame length does not match original frame"); + VerifyOrQuit(memcmp(frame, sOpenThreadText, length) == 0, "Decoded frame content does not match original frame"); + + SuccessOrQuit(decoderBuffer.ReadSavedFrame(frame, length), "Incorrect decoded frame"); + VerifyOrQuit(length == sizeof(sMottoText) - 1, "Decoded frame length does not match original frame"); + VerifyOrQuit(memcmp(frame, sMottoText, length) == 0, "Decoded frame content does not match original frame"); + + SuccessOrQuit(decoderBuffer.ReadSavedFrame(frame, length), "Incorrect decoded frame"); + VerifyOrQuit(length == sizeof(sHdlcSpeicals), "Decoded frame length does not match original frame"); + VerifyOrQuit(memcmp(frame, sHdlcSpeicals, length) == 0, "Decoded frame content does not match original frame"); + + SuccessOrQuit(decoderBuffer.ReadSavedFrame(frame, length), "Incorrect decoded frame"); + VerifyOrQuit(length == sizeof(sHelloText) - 1, "Decoded frame length does not match original frame"); + VerifyOrQuit(memcmp(frame, sHelloText, length) == 0, "Decoded frame content does not match original frame"); + + SuccessOrQuit(decoderBuffer.ReadSavedFrame(frame, length), "Incorrect decoded frame"); + VerifyOrQuit(length == sizeof(uint8_t), "Decoded frame length does not match original frame"); + VerifyOrQuit(*frame == kFlagSequence, "Decoded frame content does not match original frame"); + + VerifyOrQuit(decoderBuffer.ReadSavedFrame(frame, length) == OT_ERROR_NOT_FOUND, "Extra decoded frame"); + + encoderBuffer.Clear(); + decoderBuffer.Clear(); + + // Test `Encoder` behavior when running out of buffer space + SuccessOrQuit(encoder.BeginFrame(), "Encoder::BeginFrame() failed"); + + error = OT_ERROR_NONE; + + for (uint16_t i = 0; error == OT_ERROR_NONE; i++) + { + byte = i & 0xff; + error = encoder.Encode(&byte, sizeof(uint8_t)); + } + + VerifyOrQuit(encoder.Encode(&byte, sizeof(uint8_t)) == OT_ERROR_NO_BUFS, + "Encoder::Encode() did not fail with a full buffer"); + VerifyOrQuit(encoder.EndFrame(), "Encoder::EndFrame() did not fail with a full buffer"); + + encoderBuffer.Clear(); + + // Test `Decoder` behavior with incorrect FCS + + SuccessOrQuit(encoder.BeginFrame(), "Encoder::BeginFrame() failed"); + SuccessOrQuit(encoder.Encode(sMottoText, sizeof(sMottoText) - 1), "Encoder::Encode() failed"); + SuccessOrQuit(encoder.EndFrame(), "Encoder::EndFrame() failed"); + + encoderBuffer.GetFrame()[0] ^= 0x0a; // Change the first byte in the frame to cause FCS failure + + decoderContext.mWasCalled = false; + decoder.Decode(encoderBuffer.GetFrame(), encoderBuffer.GetLength()); + VerifyOrQuit(decoderContext.mWasCalled, "Decoder::Decode() failed"); + VerifyOrQuit(decoderContext.mError == OT_ERROR_PARSE, "Decoder::Decode() did not fail with bad FCS"); + + printf(" -- PASS\n"); +} + +uint32_t GetRandom(uint32_t max) +{ + uint32_t value; + + if (kUseTrueRandomNumberGenerator) + { + otPlatRandomGetTrue(reinterpret_cast(&value), sizeof(value)); + } + else + { + value = otPlatRandomGet(); + } + + return value % max; +} + +void TestFuzzEncoderDecoder(void) +{ + uint16_t length; + uint8_t frame[kMaxFrameLength]; + Hdlc::FrameBuffer encoderBuffer; + Hdlc::FrameBuffer decoderBuffer; + DecoderContext decoderContext; + Hdlc::Encoder encoder(encoderBuffer); + Hdlc::Decoder decoder(decoderBuffer, ProcessDecodedFrame, &decoderContext); + + printf("Testing Hdlc::Encoder and Hdlc::Decoder with randomly generated frames"); + + for (uint32_t iter = 0; iter < kFuzzTestIteration; iter++) + { + encoderBuffer.Clear(); + decoderBuffer.Clear(); + + do + { + length = static_cast(GetRandom(kMaxFrameLength)); + } while (length == 0); + + for (uint16_t i = 0; i < length; i++) + { + frame[i] = static_cast(GetRandom(256)); + } + + SuccessOrQuit(encoder.BeginFrame(), "Encoder::BeginFrame() failed"); + SuccessOrQuit(encoder.Encode(frame, length), "Encoder::Encode() failed"); + SuccessOrQuit(encoder.EndFrame(), "Encoder::EndFrame() failed"); + + VerifyOrQuit(!encoderBuffer.IsEmpty(), "Encoded frame is empty"); + VerifyOrQuit(encoderBuffer.GetLength() > length, "Encoded frame is too short"); + + decoderContext.mWasCalled = false; + decoder.Decode(encoderBuffer.GetFrame(), encoderBuffer.GetLength()); + VerifyOrQuit(decoderContext.mWasCalled, "Decoder::Decode() failed"); + VerifyOrQuit(decoderContext.mError == OT_ERROR_NONE, "Decoder::Decode() returned incorrect error code"); + + VerifyOrQuit(!decoderBuffer.IsEmpty(), "Decoded frame is empty"); + VerifyOrQuit(decoderBuffer.GetLength() == length, "Decoded frame length does not match original frame"); + VerifyOrQuit(memcmp(decoderBuffer.GetFrame(), frame, length) == 0, + "Decoded frame content does not match original frame"); + } + + printf(" -- PASS\n"); +} + +} // namespace Ncp +} // namespace ot + +#ifdef ENABLE_TEST_MAIN +int main(void) +{ + ot::Ncp::TestHdlcFrameBuffer(); + ot::Ncp::TestHdlcMultiFrameBuffer(); + ot::Ncp::TestEncoderDecoder(); + ot::Ncp::TestFuzzEncoderDecoder(); + printf("\nAll tests passed.\n"); + return 0; +} +#endif