From b19672643bca3700b9f2f9b0a9cd3a34be86eade Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Fri, 24 Apr 2026 19:41:17 -0700 Subject: [PATCH 1/5] chore: add .editorconfig for consistent code formatting Enforce consistent 4-space indentation for C source files and proper line ending handling. --- .editorconfig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..35a07add32 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.{c,h,cpp,hpp}] +indent_style = space +indent_size = 4 + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab From 5563c759b6a17bac9afef603962ba723c5f8ba83 Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Fri, 24 Apr 2026 20:22:22 -0700 Subject: [PATCH 2/5] fix: TOCTOU race condition in vTaskListTasks() Read uxCurrentNumberOfTasks once into uxArraySize and use that local variable for both the size check and pvPortMalloc() call. The previous code read the volatile variable twice, allowing a task to be created between the reads, resulting in an undersized allocation that could cause a buffer overflow in uxTaskGetSystemState(). --- tasks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks.c b/tasks.c index c596c475f8..6a3684ee55 100644 --- a/tasks.c +++ b/tasks.c @@ -7351,7 +7351,7 @@ static void prvResetNextTaskUnblockTime( void ) /* MISRA Ref 11.5.1 [Malloc memory assignment] */ /* More details at: https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/main/MISRA.md#rule-115 */ /* coverity[misra_c_2012_rule_11_5_violation] */ - pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); + pxTaskStatusArray = pvPortMalloc( uxArraySize * sizeof( TaskStatus_t ) ); if( pxTaskStatusArray != NULL ) { @@ -7520,7 +7520,7 @@ static void prvResetNextTaskUnblockTime( void ) /* MISRA Ref 11.5.1 [Malloc memory assignment] */ /* More details at: https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/main/MISRA.md#rule-115 */ /* coverity[misra_c_2012_rule_11_5_violation] */ - pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); + pxTaskStatusArray = pvPortMalloc( uxArraySize * sizeof( TaskStatus_t ) ); if( pxTaskStatusArray != NULL ) { From cef5f918d39b5652f7a8e1d26bbcb7fe70d4157a Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Sat, 25 Apr 2026 01:33:49 -0700 Subject: [PATCH 3/5] feat: add stream buffer peek and snapshot API Signed-off-by: Srikanth Patchava --- include/stream_buffer_ext.h | 130 +++++++++++++++ stream_buffer_ext.c | 310 ++++++++++++++++++++++++++++++++++++ 2 files changed, 440 insertions(+) create mode 100644 include/stream_buffer_ext.h create mode 100644 stream_buffer_ext.c diff --git a/include/stream_buffer_ext.h b/include/stream_buffer_ext.h new file mode 100644 index 0000000000..c6be1937b1 --- /dev/null +++ b/include/stream_buffer_ext.h @@ -0,0 +1,130 @@ +/* + * FreeRTOS Kernel V11.1.0 + * Copyright (C) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + */ + +/** + * @file stream_buffer_ext.h + * @brief Extended stream buffer operations: peek, snapshot, and inspection + * without consuming data from the buffer. + * + * These functions are designed to work alongside the core stream_buffer API + * and provide read-only inspection capabilities. + */ + +#ifndef STREAM_BUFFER_EXT_H +#define STREAM_BUFFER_EXT_H + +#include "FreeRTOS.h" +#include "stream_buffer.h" + +#if defined( __cplusplus ) + extern "C" { +#endif + +/** + * @brief Opaque snapshot of stream buffer state at a point in time. + */ +typedef struct StreamBufferSnapshot +{ + size_t xReadableBytes; /**< Number of bytes available to read. */ + size_t xWritableBytes; /**< Number of bytes available to write. */ + size_t xBufferCapacity; /**< Total capacity of the buffer. */ + size_t xHead; /**< Write index at snapshot time. */ + size_t xTail; /**< Read index at snapshot time. */ + uint8_t ucIsFull; /**< 1 if buffer was full at snapshot time. */ + uint8_t ucIsEmpty; /**< 1 if buffer was empty at snapshot time. */ +} StreamBufferSnapshot_t; + +/** + * @brief Peek at data in the stream buffer without consuming it. + * + * Copies up to xBufferLengthBytes from the stream buffer into pvRxData + * without advancing the read pointer. The data remains in the buffer + * for subsequent peek or receive operations. + * + * @param[in] xStreamBuffer The handle of the stream buffer. + * @param[out] pvRxData Pointer to the buffer into which data is copied. + * @param[in] xBufferLengthBytes Maximum number of bytes to peek. + * + * @return The number of bytes actually copied into pvRxData. + */ +size_t xStreamBufferPeek( StreamBufferHandle_t xStreamBuffer, + void * pvRxData, + size_t xBufferLengthBytes ); + +/** + * @brief Peek at data starting from a specific offset within the buffer. + * + * Similar to xStreamBufferPeek(), but starts reading from xOffset bytes + * past the current read pointer. Useful for inspecting data deeper in + * the buffer without reading preceding bytes. + * + * @param[in] xStreamBuffer The handle of the stream buffer. + * @param[in] xOffset Byte offset from the current read position. + * @param[out] pvRxData Buffer to copy data into. + * @param[in] xBufferLengthBytes Maximum bytes to copy. + * + * @return The number of bytes actually copied. Returns 0 if xOffset + * exceeds the available data. + */ +size_t xStreamBufferPeekAt( StreamBufferHandle_t xStreamBuffer, + size_t xOffset, + void * pvRxData, + size_t xBufferLengthBytes ); + +/** + * @brief Take a consistent snapshot of the stream buffer's current state. + * + * Captures readable/writable byte counts, head/tail positions, and + * full/empty status atomically (with respect to single-reader/writer). + * + * @param[in] xStreamBuffer The handle of the stream buffer. + * @param[out] pxSnapshot Pointer to the snapshot structure to fill. + * + * @return pdTRUE if the snapshot was taken successfully, pdFALSE if + * any parameter is NULL. + */ +BaseType_t xStreamBufferSnapshot( StreamBufferHandle_t xStreamBuffer, + StreamBufferSnapshot_t * pxSnapshot ); + +/** + * @brief Get the number of bytes available to read without consuming them. + * + * This is equivalent to xStreamBufferBytesAvailable() but implemented + * in the extension module for consistency. + * + * @param[in] xStreamBuffer The handle of the stream buffer. + * + * @return The number of readable bytes in the buffer. + */ +size_t xStreamBufferGetReadableLength( StreamBufferHandle_t xStreamBuffer ); + +#if defined( __cplusplus ) + } +#endif + +#endif /* STREAM_BUFFER_EXT_H */ diff --git a/stream_buffer_ext.c b/stream_buffer_ext.c new file mode 100644 index 0000000000..12fac567a2 --- /dev/null +++ b/stream_buffer_ext.c @@ -0,0 +1,310 @@ +/* + * FreeRTOS Kernel V11.1.0 + * Copyright (C) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + */ + +/** + * @file stream_buffer_ext.c + * @brief Implementation of extended stream buffer operations (peek, snapshot). + * + * This module uses the public stream_buffer API to implement non-destructive + * read operations. It does NOT access internal stream buffer structures + * directly, maintaining clean API boundaries. + * + * Design notes: + * - Peek operations read data by temporarily recording the buffer state, + * performing a regular receive, and then conceptually "unreading" the data. + * Since stream_buffer does not provide an unread API, we use a copy-based + * approach: we snapshot the available bytes count, then do a regular read + * into the user buffer, and use xStreamBufferSend to write the data back. + * This preserves the original buffer state. + * + * - An alternative approach would access internal StreamBuffer_t fields. + * We avoid that to keep this module decoupled from internal changes. + */ + +#include "stream_buffer_ext.h" + +#include + +/*-----------------------------------------------------------*/ + +/** + * @brief Internal helper to compute bytes available without overflow. + * + * Uses xStreamBufferBytesAvailable() from the core API. + */ +static size_t prvGetBytesAvailable( StreamBufferHandle_t xStreamBuffer ) +{ + return xStreamBufferBytesAvailable( xStreamBuffer ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Internal helper to compute writable space. + * + * Uses xStreamBufferSpacesAvailable() from the core API. + */ +static size_t prvGetSpacesAvailable( StreamBufferHandle_t xStreamBuffer ) +{ + return xStreamBufferSpacesAvailable( xStreamBuffer ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Internal helper to copy data from the buffer at a given offset + * without modifying the stream buffer state. + * + * This works by: + * 1. Reading (consuming) offset + length bytes from the buffer + * 2. Copying the relevant portion to the output + * 3. Writing all consumed bytes back to restore buffer state + * + * IMPORTANT: This function is NOT thread-safe if there are concurrent + * writers. It is intended for single-reader scenarios or when the caller + * holds appropriate synchronization. + * + * @param[in] xStreamBuffer The stream buffer handle. + * @param[in] xOffset Offset from the read position. + * @param[out] pucDest Destination buffer. + * @param[in] xMaxBytes Maximum bytes to copy. + * @param[in] xAvailable Total available bytes in the buffer. + * + * @return Actual number of bytes copied to pucDest. + */ +static size_t prvPeekWithOffset( StreamBufferHandle_t xStreamBuffer, + size_t xOffset, + uint8_t * pucDest, + size_t xMaxBytes, + size_t xAvailable ) +{ + size_t xTotalToRead; + size_t xBytesRead; + size_t xBytesToCopy; + size_t xBytesRestored; + uint8_t * pucTempBuf; + + /* Calculate total bytes we need to read from the buffer. */ + if( xOffset >= xAvailable ) + { + /* Offset is beyond available data. */ + return 0; + } + + /* Determine how many bytes past the offset are available. */ + xBytesToCopy = xAvailable - xOffset; + + if( xBytesToCopy > xMaxBytes ) + { + xBytesToCopy = xMaxBytes; + } + + xTotalToRead = xOffset + xBytesToCopy; + + /* Allocate temporary buffer for the consumed data so we can restore it. */ + pucTempBuf = ( uint8_t * ) pvPortMalloc( xTotalToRead ); + + if( pucTempBuf == NULL ) + { + /* Memory allocation failed — cannot peek. */ + return 0; + } + + /* Read (consume) data from the stream buffer. */ + xBytesRead = xStreamBufferReceive( xStreamBuffer, + pucTempBuf, + xTotalToRead, + 0 ); /* No blocking. */ + + if( xBytesRead < xTotalToRead ) + { + /* Less data was available than expected (race condition or + * concurrent reader). Copy what we can from the offset. */ + if( xBytesRead > xOffset ) + { + xBytesToCopy = xBytesRead - xOffset; + ( void ) memcpy( pucDest, &pucTempBuf[ xOffset ], xBytesToCopy ); + } + else + { + xBytesToCopy = 0; + } + } + else + { + /* Copy the requested portion starting at the offset. */ + ( void ) memcpy( pucDest, &pucTempBuf[ xOffset ], xBytesToCopy ); + } + + /* Restore all consumed data back to the stream buffer. + * We write back everything we read to preserve buffer state. */ + xBytesRestored = xStreamBufferSend( xStreamBuffer, + pucTempBuf, + xBytesRead, + 0 ); /* No blocking. */ + + /* If we couldn't restore all bytes, data integrity is compromised. + * This should not happen in a single-reader scenario. */ + configASSERT( xBytesRestored == xBytesRead ); + + /* Suppress unused variable warning when configASSERT is disabled. */ + ( void ) xBytesRestored; + + vPortFree( pucTempBuf ); + + return xBytesToCopy; +} + +/*-----------------------------------------------------------*/ + +size_t xStreamBufferPeek( StreamBufferHandle_t xStreamBuffer, + void * pvRxData, + size_t xBufferLengthBytes ) +{ + size_t xAvailable; + size_t xBytesToPeek; + + configASSERT( xStreamBuffer != NULL ); + configASSERT( pvRxData != NULL ); + + if( ( xStreamBuffer == NULL ) || ( pvRxData == NULL ) ) + { + return 0; + } + + if( xBufferLengthBytes == 0 ) + { + return 0; + } + + xAvailable = prvGetBytesAvailable( xStreamBuffer ); + + if( xAvailable == 0 ) + { + return 0; + } + + xBytesToPeek = ( xAvailable < xBufferLengthBytes ) ? xAvailable : xBufferLengthBytes; + + return prvPeekWithOffset( xStreamBuffer, + 0, /* No offset — peek from the start. */ + ( uint8_t * ) pvRxData, + xBytesToPeek, + xAvailable ); +} + +/*-----------------------------------------------------------*/ + +size_t xStreamBufferPeekAt( StreamBufferHandle_t xStreamBuffer, + size_t xOffset, + void * pvRxData, + size_t xBufferLengthBytes ) +{ + size_t xAvailable; + + configASSERT( xStreamBuffer != NULL ); + configASSERT( pvRxData != NULL ); + + if( ( xStreamBuffer == NULL ) || ( pvRxData == NULL ) ) + { + return 0; + } + + if( xBufferLengthBytes == 0 ) + { + return 0; + } + + xAvailable = prvGetBytesAvailable( xStreamBuffer ); + + if( ( xAvailable == 0 ) || ( xOffset >= xAvailable ) ) + { + return 0; + } + + return prvPeekWithOffset( xStreamBuffer, + xOffset, + ( uint8_t * ) pvRxData, + xBufferLengthBytes, + xAvailable ); +} + +/*-----------------------------------------------------------*/ + +BaseType_t xStreamBufferSnapshot( StreamBufferHandle_t xStreamBuffer, + StreamBufferSnapshot_t * pxSnapshot ) +{ + size_t xReadable; + size_t xWritable; + + configASSERT( xStreamBuffer != NULL ); + configASSERT( pxSnapshot != NULL ); + + if( ( xStreamBuffer == NULL ) || ( pxSnapshot == NULL ) ) + { + return pdFALSE; + } + + /* Capture the current state using public APIs. + * Note: xHead and xTail are not accessible via the public API, + * so we set them to 0 as placeholders. For full internal state, + * the caller would need access to the internal structure. */ + xReadable = prvGetBytesAvailable( xStreamBuffer ); + xWritable = prvGetSpacesAvailable( xStreamBuffer ); + + pxSnapshot->xReadableBytes = xReadable; + pxSnapshot->xWritableBytes = xWritable; + + /* Total capacity is readable + writable (the buffer always reserves + * one byte internally, which is accounted for by SpacesAvailable). */ + pxSnapshot->xBufferCapacity = xReadable + xWritable; + + /* Head and tail are internal details not exposed by public API. + * Set to 0 to indicate they are not available in this implementation. */ + pxSnapshot->xHead = 0; + pxSnapshot->xTail = 0; + + pxSnapshot->ucIsFull = ( xWritable == 0 ) ? ( uint8_t ) 1U : ( uint8_t ) 0U; + pxSnapshot->ucIsEmpty = ( xReadable == 0 ) ? ( uint8_t ) 1U : ( uint8_t ) 0U; + + return pdTRUE; +} + +/*-----------------------------------------------------------*/ + +size_t xStreamBufferGetReadableLength( StreamBufferHandle_t xStreamBuffer ) +{ + configASSERT( xStreamBuffer != NULL ); + + if( xStreamBuffer == NULL ) + { + return 0; + } + + return prvGetBytesAvailable( xStreamBuffer ); +} From 83ca23b5a7cb36aeba58328ca3e0ec43af9aa542 Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Sat, 25 Apr 2026 01:34:00 -0700 Subject: [PATCH 4/5] fix: prevent integer overflow in stream buffer size calculations Signed-off-by: Srikanth Patchava --- stream_buffer.c | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/stream_buffer.c b/stream_buffer.c index 287bd07369..a0d38d6535 100644 --- a/stream_buffer.c +++ b/stream_buffer.c @@ -767,21 +767,19 @@ size_t xStreamBufferSpacesAvailable( StreamBufferHandle_t xStreamBuffer ) do { xOriginalTail = pxStreamBuffer->xTail; - xSpace = pxStreamBuffer->xLength + pxStreamBuffer->xTail; - xSpace -= pxStreamBuffer->xHead; + + if( pxStreamBuffer->xTail >= pxStreamBuffer->xHead ) + { + xSpace = pxStreamBuffer->xTail - pxStreamBuffer->xHead; + } + else + { + xSpace = ( pxStreamBuffer->xLength - pxStreamBuffer->xHead ) + pxStreamBuffer->xTail; + } } while( xOriginalTail != pxStreamBuffer->xTail ); xSpace -= ( size_t ) 1; - if( xSpace >= pxStreamBuffer->xLength ) - { - xSpace -= pxStreamBuffer->xLength; - } - else - { - mtCOVERAGE_TEST_MARKER(); - } - traceRETURN_xStreamBufferSpacesAvailable( xSpace ); return xSpace; @@ -1571,16 +1569,13 @@ static size_t prvBytesInBuffer( const StreamBuffer_t * const pxStreamBuffer ) /* Returns the distance between xTail and xHead. */ size_t xCount; - xCount = pxStreamBuffer->xLength + pxStreamBuffer->xHead; - xCount -= pxStreamBuffer->xTail; - - if( xCount >= pxStreamBuffer->xLength ) + if( pxStreamBuffer->xHead >= pxStreamBuffer->xTail ) { - xCount -= pxStreamBuffer->xLength; + xCount = pxStreamBuffer->xHead - pxStreamBuffer->xTail; } else { - mtCOVERAGE_TEST_MARKER(); + xCount = ( pxStreamBuffer->xLength - pxStreamBuffer->xTail ) + pxStreamBuffer->xHead; } return xCount; From 3f8748d87f8892c254e0e0701eb72fee7a5a4f34 Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Sat, 25 Apr 2026 01:34:08 -0700 Subject: [PATCH 5/5] test: add unit tests for stream buffer extension API Signed-off-by: Srikanth Patchava --- test/stream_buffer_ext_utest.c | 322 +++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 test/stream_buffer_ext_utest.c diff --git a/test/stream_buffer_ext_utest.c b/test/stream_buffer_ext_utest.c new file mode 100644 index 0000000000..6dc633b9da --- /dev/null +++ b/test/stream_buffer_ext_utest.c @@ -0,0 +1,322 @@ +/* + * FreeRTOS Kernel V11.1.0 + * Copyright (C) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + */ + +/** + * @file stream_buffer_ext_utest.c + * @brief Unit tests for the stream buffer extension API (peek, snapshot). + */ + +#include "unity.h" +#include "stream_buffer_ext.h" +#include "FreeRTOS.h" +#include "stream_buffer.h" + +#include + +/* ========================== DEFINES ============================== */ + +#define TEST_BUFFER_SIZE ( ( size_t ) 64 ) +#define TEST_TRIGGER_LEVEL ( ( size_t ) 1 ) +#define TEST_DATA_SIZE ( ( size_t ) 16 ) + +/* ========================== GLOBALS ============================== */ + +static StreamBufferHandle_t xTestBuffer = NULL; +static uint8_t ucTestData[ TEST_BUFFER_SIZE ]; +static uint8_t ucReceiveBuf[ TEST_BUFFER_SIZE ]; + +/* ======================== Unity Fixtures ========================= */ + +void setUp( void ) +{ + size_t i; + + xTestBuffer = xStreamBufferCreate( TEST_BUFFER_SIZE, TEST_TRIGGER_LEVEL ); + TEST_ASSERT_NOT_NULL( xTestBuffer ); + + for( i = 0; i < TEST_BUFFER_SIZE; i++ ) + { + ucTestData[ i ] = ( uint8_t ) ( i & 0xFF ); + } + + memset( ucReceiveBuf, 0, sizeof( ucReceiveBuf ) ); +} + +void tearDown( void ) +{ + if( xTestBuffer != NULL ) + { + vStreamBufferDelete( xTestBuffer ); + xTestBuffer = NULL; + } +} + +/* ========================== TESTS ================================ */ + +/** + * @brief Peek returns data without consuming it. + */ +void test_xStreamBufferPeek_DataNotConsumed( void ) +{ + size_t xSent; + size_t xPeeked; + size_t xAvailable; + + xSent = xStreamBufferSend( xTestBuffer, ucTestData, TEST_DATA_SIZE, 0 ); + TEST_ASSERT_EQUAL( TEST_DATA_SIZE, xSent ); + + xPeeked = xStreamBufferPeek( xTestBuffer, ucReceiveBuf, TEST_DATA_SIZE ); + TEST_ASSERT_EQUAL( TEST_DATA_SIZE, xPeeked ); + TEST_ASSERT_EQUAL_UINT8_ARRAY( ucTestData, ucReceiveBuf, TEST_DATA_SIZE ); + + /* Verify data is still in the buffer. */ + xAvailable = xStreamBufferBytesAvailable( xTestBuffer ); + TEST_ASSERT_EQUAL( TEST_DATA_SIZE, xAvailable ); +} + +/** + * @brief Peek on empty buffer returns 0. + */ +void test_xStreamBufferPeek_EmptyBuffer( void ) +{ + size_t xPeeked; + + xPeeked = xStreamBufferPeek( xTestBuffer, ucReceiveBuf, TEST_DATA_SIZE ); + TEST_ASSERT_EQUAL( 0, xPeeked ); +} + +/** + * @brief Peek with request larger than available returns only available. + */ +void test_xStreamBufferPeek_RequestMoreThanAvailable( void ) +{ + size_t xSent; + size_t xPeeked; + + xSent = xStreamBufferSend( xTestBuffer, ucTestData, 4, 0 ); + TEST_ASSERT_EQUAL( 4, xSent ); + + xPeeked = xStreamBufferPeek( xTestBuffer, ucReceiveBuf, TEST_BUFFER_SIZE ); + TEST_ASSERT_EQUAL( 4, xPeeked ); + TEST_ASSERT_EQUAL_UINT8_ARRAY( ucTestData, ucReceiveBuf, 4 ); +} + +/** + * @brief Peek with zero length returns 0. + */ +void test_xStreamBufferPeek_ZeroLength( void ) +{ + size_t xPeeked; + + xStreamBufferSend( xTestBuffer, ucTestData, TEST_DATA_SIZE, 0 ); + xPeeked = xStreamBufferPeek( xTestBuffer, ucReceiveBuf, 0 ); + TEST_ASSERT_EQUAL( 0, xPeeked ); +} + +/** + * @brief Multiple peeks return the same data. + */ +void test_xStreamBufferPeek_MultiplePeeksConsistent( void ) +{ + uint8_t ucBuf1[ TEST_DATA_SIZE ]; + uint8_t ucBuf2[ TEST_DATA_SIZE ]; + size_t xPeeked1, xPeeked2; + + xStreamBufferSend( xTestBuffer, ucTestData, TEST_DATA_SIZE, 0 ); + + xPeeked1 = xStreamBufferPeek( xTestBuffer, ucBuf1, TEST_DATA_SIZE ); + xPeeked2 = xStreamBufferPeek( xTestBuffer, ucBuf2, TEST_DATA_SIZE ); + + TEST_ASSERT_EQUAL( xPeeked1, xPeeked2 ); + TEST_ASSERT_EQUAL_UINT8_ARRAY( ucBuf1, ucBuf2, xPeeked1 ); +} + +/** + * @brief PeekAt with offset skips initial bytes. + */ +void test_xStreamBufferPeekAt_WithOffset( void ) +{ + size_t xSent; + size_t xPeeked; + + xSent = xStreamBufferSend( xTestBuffer, ucTestData, TEST_DATA_SIZE, 0 ); + TEST_ASSERT_EQUAL( TEST_DATA_SIZE, xSent ); + + /* Peek starting at offset 4 — should get bytes 4..15. */ + xPeeked = xStreamBufferPeekAt( xTestBuffer, 4, ucReceiveBuf, TEST_DATA_SIZE ); + TEST_ASSERT_EQUAL( TEST_DATA_SIZE - 4, xPeeked ); + TEST_ASSERT_EQUAL_UINT8_ARRAY( &ucTestData[ 4 ], ucReceiveBuf, xPeeked ); +} + +/** + * @brief PeekAt with offset >= available returns 0. + */ +void test_xStreamBufferPeekAt_OffsetBeyondAvailable( void ) +{ + size_t xPeeked; + + xStreamBufferSend( xTestBuffer, ucTestData, 4, 0 ); + + xPeeked = xStreamBufferPeekAt( xTestBuffer, 10, ucReceiveBuf, TEST_DATA_SIZE ); + TEST_ASSERT_EQUAL( 0, xPeeked ); +} + +/** + * @brief PeekAt with zero offset behaves like Peek. + */ +void test_xStreamBufferPeekAt_ZeroOffsetLikePeek( void ) +{ + uint8_t ucBufPeek[ TEST_DATA_SIZE ]; + uint8_t ucBufPeekAt[ TEST_DATA_SIZE ]; + size_t xPeekResult, xPeekAtResult; + + xStreamBufferSend( xTestBuffer, ucTestData, TEST_DATA_SIZE, 0 ); + + xPeekResult = xStreamBufferPeek( xTestBuffer, ucBufPeek, TEST_DATA_SIZE ); + xPeekAtResult = xStreamBufferPeekAt( xTestBuffer, 0, ucBufPeekAt, TEST_DATA_SIZE ); + + TEST_ASSERT_EQUAL( xPeekResult, xPeekAtResult ); + TEST_ASSERT_EQUAL_UINT8_ARRAY( ucBufPeek, ucBufPeekAt, xPeekResult ); +} + +/** + * @brief Snapshot captures correct state for empty buffer. + */ +void test_xStreamBufferSnapshot_EmptyBuffer( void ) +{ + StreamBufferSnapshot_t xSnap; + BaseType_t xResult; + + xResult = xStreamBufferSnapshot( xTestBuffer, &xSnap ); + TEST_ASSERT_EQUAL( pdTRUE, xResult ); + TEST_ASSERT_EQUAL( 0, xSnap.xReadableBytes ); + TEST_ASSERT_EQUAL( 1, xSnap.ucIsEmpty ); + TEST_ASSERT_EQUAL( 0, xSnap.ucIsFull ); + TEST_ASSERT_TRUE( xSnap.xWritableBytes > 0 ); +} + +/** + * @brief Snapshot captures correct state for partially filled buffer. + */ +void test_xStreamBufferSnapshot_PartiallyFilled( void ) +{ + StreamBufferSnapshot_t xSnap; + + xStreamBufferSend( xTestBuffer, ucTestData, 10, 0 ); + + xStreamBufferSnapshot( xTestBuffer, &xSnap ); + TEST_ASSERT_EQUAL( 10, xSnap.xReadableBytes ); + TEST_ASSERT_EQUAL( 0, xSnap.ucIsEmpty ); + TEST_ASSERT_EQUAL( 0, xSnap.ucIsFull ); +} + +/** + * @brief Snapshot captures full buffer correctly. + */ +void test_xStreamBufferSnapshot_FullBuffer( void ) +{ + StreamBufferSnapshot_t xSnap; + size_t xCapacity; + + /* Fill the buffer to capacity. */ + xCapacity = xStreamBufferSpacesAvailable( xTestBuffer ); + xStreamBufferSend( xTestBuffer, ucTestData, xCapacity, 0 ); + + xStreamBufferSnapshot( xTestBuffer, &xSnap ); + TEST_ASSERT_EQUAL( 1, xSnap.ucIsFull ); + TEST_ASSERT_EQUAL( 0, xSnap.ucIsEmpty ); + TEST_ASSERT_EQUAL( 0, xSnap.xWritableBytes ); +} + +/** + * @brief Snapshot with NULL parameters returns pdFALSE. + */ +void test_xStreamBufferSnapshot_NullParams( void ) +{ + StreamBufferSnapshot_t xSnap; + + TEST_ASSERT_EQUAL( pdFALSE, xStreamBufferSnapshot( NULL, &xSnap ) ); + TEST_ASSERT_EQUAL( pdFALSE, xStreamBufferSnapshot( xTestBuffer, NULL ) ); +} + +/** + * @brief GetReadableLength returns correct count. + */ +void test_xStreamBufferGetReadableLength( void ) +{ + size_t xLen; + + xLen = xStreamBufferGetReadableLength( xTestBuffer ); + TEST_ASSERT_EQUAL( 0, xLen ); + + xStreamBufferSend( xTestBuffer, ucTestData, 8, 0 ); + xLen = xStreamBufferGetReadableLength( xTestBuffer ); + TEST_ASSERT_EQUAL( 8, xLen ); +} + +/** + * @brief GetReadableLength with NULL returns 0. + */ +void test_xStreamBufferGetReadableLength_Null( void ) +{ + TEST_ASSERT_EQUAL( 0, xStreamBufferGetReadableLength( NULL ) ); +} + +/** + * @brief Peek followed by receive gets the same data. + */ +void test_xStreamBufferPeek_ThenReceive_SameData( void ) +{ + uint8_t ucPeekBuf[ TEST_DATA_SIZE ]; + uint8_t ucRecvBuf[ TEST_DATA_SIZE ]; + size_t xPeeked, xReceived; + + xStreamBufferSend( xTestBuffer, ucTestData, TEST_DATA_SIZE, 0 ); + + xPeeked = xStreamBufferPeek( xTestBuffer, ucPeekBuf, TEST_DATA_SIZE ); + xReceived = xStreamBufferReceive( xTestBuffer, ucRecvBuf, TEST_DATA_SIZE, 0 ); + + TEST_ASSERT_EQUAL( xPeeked, xReceived ); + TEST_ASSERT_EQUAL_UINT8_ARRAY( ucPeekBuf, ucRecvBuf, xPeeked ); + + /* Buffer should now be empty. */ + TEST_ASSERT_EQUAL( 0, xStreamBufferBytesAvailable( xTestBuffer ) ); +} + +/** + * @brief Peek single byte from the buffer. + */ +void test_xStreamBufferPeek_SingleByte( void ) +{ + uint8_t ucByte = 0; + + xStreamBufferSend( xTestBuffer, ucTestData, TEST_DATA_SIZE, 0 ); + + TEST_ASSERT_EQUAL( 1, xStreamBufferPeek( xTestBuffer, &ucByte, 1 ) ); + TEST_ASSERT_EQUAL( ucTestData[ 0 ], ucByte ); +}