/*
 * FreeRTOS V202107.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * 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.
 *
 * http://www.FreeRTOS.org
 * http://aws.amazon.com/freertos
 *
 * 1 tab == 4 spaces!
 */

/* Standard includes. */
#include "stdio.h"
#include "string.h"

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "message_buffer.h"

/* Demo app includes. */
#include "MessageBufferDemo.h"

/* The number of bytes of storage in the message buffers used in this test. */
#define mbMESSAGE_BUFFER_LENGTH_BYTES      ( ( size_t ) 50 )

/* The number of additional bytes used to store the length of each message. */
#define mbBYTES_TO_STORE_MESSAGE_LENGTH    ( sizeof( configMESSAGE_BUFFER_LENGTH_TYPE ) )

/* Start and end ASCII characters used in messages sent to the buffers. */
#define mbASCII_SPACE                      32
#define mbASCII_TILDA                      126

/* Defines the number of tasks to create in this test and demo. */
#define mbNUMBER_OF_ECHO_CLIENTS           ( 2 )
#define mbNUMBER_OF_SENDER_TASKS           ( 2 )

/* Priority of the test tasks.  The send and receive go from low to high
 * priority tasks, and from high to low priority tasks. */
#define mbLOWER_PRIORITY                   ( tskIDLE_PRIORITY )
#define mbHIGHER_PRIORITY                  ( tskIDLE_PRIORITY + 1 )

/* Block times used when sending and receiving from the message buffers. */
#define mbRX_TX_BLOCK_TIME                 pdMS_TO_TICKS( 175UL )

/* A block time of 0 means "don't block". */
#define mbDONT_BLOCK                       ( 0 )

/*-----------------------------------------------------------*/

/*
 * Performs various tests that do not require multiple tasks to interact.
 */
static void prvSingleTaskTests( MessageBufferHandle_t xMessageBuffer );

/*
 * Tests sending and receiving various lengths of messages via a message buffer.
 * The echo client sends the messages to the echo server, which then sends the
 * message back to the echo client which, checks it receives exactly what it
 * sent.
 */
static void prvEchoClient( void * pvParameters );
static void prvEchoServer( void * pvParameters );

/*
 * Tasks that send and receive to a message buffer at a low priority and without
 * blocking, so the send and receive functions interleave in time as the tasks
 * are switched in and out.
 */
static void prvNonBlockingReceiverTask( void * pvParameters );
static void prvNonBlockingSenderTask( void * pvParameters );

#if ( configSUPPORT_STATIC_ALLOCATION == 1 )

/* This file tests both statically and dynamically allocated message buffers.
 * Allocate the structures and buffers to be used by the statically allocated
 * objects, which get used in the echo tests. */
    static void prvReceiverTask( void * pvParameters );
    static void prvSenderTask( void * pvParameters );

    static StaticMessageBuffer_t xStaticMessageBuffers[ mbNUMBER_OF_ECHO_CLIENTS ];
    static uint8_t ucBufferStorage[ mbNUMBER_OF_SENDER_TASKS ][ mbMESSAGE_BUFFER_LENGTH_BYTES + 1 ];
    static uint32_t ulSenderLoopCounters[ mbNUMBER_OF_SENDER_TASKS ] = { 0 };
#endif /* configSUPPORT_STATIC_ALLOCATION */


#if ( configRUN_ADDITIONAL_TESTS == 1 )
    #define mbCOHERENCE_TEST_BUFFER_SIZE                  20
    #define mbCOHERENCE_TEST_BYTES_WRITTEN                5
    #define mbBYTES_TO_STORE_MESSAGE_LENGTH               ( sizeof( configMESSAGE_BUFFER_LENGTH_TYPE ) )
    #define mbEXPECTED_FREE_BYTES_AFTER_WRITING_STRING    ( mbCOHERENCE_TEST_BUFFER_SIZE - ( mbCOHERENCE_TEST_BYTES_WRITTEN + mbBYTES_TO_STORE_MESSAGE_LENGTH ) )

    static void prvSpaceAvailableCoherenceActor( void * pvParameters );
    static void prvSpaceAvailableCoherenceTester( void * pvParameters );
    static MessageBufferHandle_t xCoherenceTestMessageBuffer = NULL;

    static uint32_t ulSizeCoherencyTestCycles = 0UL;
#endif /* if ( configRUN_ADDITIONAL_TESTS == 1 ) */

/*-----------------------------------------------------------*/

/* The buffers used by the echo client and server tasks. */
typedef struct ECHO_MESSAGE_BUFFERS
{
    /* Handles to the data structures that describe the message buffers. */
    MessageBufferHandle_t xEchoClientBuffer;
    MessageBufferHandle_t xEchoServerBuffer;
} EchoMessageBuffers_t;
static uint32_t ulEchoLoopCounters[ mbNUMBER_OF_ECHO_CLIENTS ] = { 0 };

/* The non-blocking tasks monitor their operation, and if no errors have been
 * found, increment ulNonBlockingRxCounter.  xAreMessageBufferTasksStillRunning()
 * then checks ulNonBlockingRxCounter and only returns pdPASS if
 * ulNonBlockingRxCounter is still incrementing. */
static uint32_t ulNonBlockingRxCounter = 0;

/* A message that is longer than the buffer, parts of which are written to the
 * message buffer to test writing different lengths at different offsets. */
static const char * pc55ByteString = "One two three four five six seven eight nine ten eleve";

/* Remember the required stack size so tasks can be created at run time (after
 * initialisation time. */
static configSTACK_DEPTH_TYPE xBlockingStackSize = 0;

/*-----------------------------------------------------------*/

void vStartMessageBufferTasks( configSTACK_DEPTH_TYPE xStackSize )
{
    MessageBufferHandle_t xMessageBuffer;

    #ifndef configMESSAGE_BUFFER_BLOCK_TASK_STACK_SIZE
        xBlockingStackSize = ( xStackSize + ( xStackSize >> 1U ) );
    #else
        xBlockingStackSize = configMESSAGE_BUFFER_BLOCK_TASK_STACK_SIZE;
    #endif

    /* The echo servers sets up the message buffers before creating the echo
     * client tasks.  One set of tasks has the server as the higher priority, and
     * the other has the client as the higher priority. */
    xTaskCreate( prvEchoServer, "1EchoServer", xBlockingStackSize, NULL, mbHIGHER_PRIORITY, NULL );
    xTaskCreate( prvEchoServer, "2EchoServer", xBlockingStackSize, NULL, mbLOWER_PRIORITY, NULL );

    /* The non blocking tasks run continuously and will interleave with each
     * other, so must be created at the lowest priority.  The message buffer they
     * use is created and passed in using the task's parameter. */
    xMessageBuffer = xMessageBufferCreate( mbMESSAGE_BUFFER_LENGTH_BYTES );
    xTaskCreate( prvNonBlockingReceiverTask, "NonBlkRx", xStackSize, ( void * ) xMessageBuffer, tskIDLE_PRIORITY, NULL );
    xTaskCreate( prvNonBlockingSenderTask, "NonBlkTx", xStackSize, ( void * ) xMessageBuffer, tskIDLE_PRIORITY, NULL );

    #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            /* The sender tasks set up the message buffers before creating the
             * receiver tasks.  Priorities must be 0 and 1 as the priority is used to
             * index into the xStaticMessageBuffers and ucBufferStorage arrays. */
            xTaskCreate( prvSenderTask, "1Sender", xBlockingStackSize, NULL, mbHIGHER_PRIORITY, NULL );
            xTaskCreate( prvSenderTask, "2Sender", xBlockingStackSize, NULL, mbLOWER_PRIORITY, NULL );
        }
    #endif /* configSUPPORT_STATIC_ALLOCATION */

    #if ( configRUN_ADDITIONAL_TESTS == 1 )
        {
            xCoherenceTestMessageBuffer = xMessageBufferCreate( mbCOHERENCE_TEST_BUFFER_SIZE );
            configASSERT( xCoherenceTestMessageBuffer );

            xTaskCreate( prvSpaceAvailableCoherenceActor, "mbsanity1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );
            xTaskCreate( prvSpaceAvailableCoherenceTester, "mbsanity2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );
        }
    #endif
}
/*-----------------------------------------------------------*/

static void prvSingleTaskTests( MessageBufferHandle_t xMessageBuffer )
{
    size_t xReturned, xItem, xExpectedSpace, xNextLength;
    const size_t xMax6ByteMessages = mbMESSAGE_BUFFER_LENGTH_BYTES / ( 6 + mbBYTES_TO_STORE_MESSAGE_LENGTH );
    const size_t x6ByteLength = 6, x17ByteLength = 17;
    uint8_t * pucFullBuffer, * pucData, * pucReadData;
    TickType_t xTimeBeforeCall, xTimeAfterCall;
    const TickType_t xBlockTime = pdMS_TO_TICKS( 25 ), xAllowableMargin = pdMS_TO_TICKS( 3 );
    UBaseType_t uxOriginalPriority;

    /* Remove warning in case configASSERT() is not defined. */
    ( void ) xAllowableMargin;

    /* To minimise stack and heap usage a full size buffer is allocated from
     * the heap, then buffers which hold smaller amounts of data are overlayed
     * with the larger buffer - just make sure not to use both at once!. */
    pucFullBuffer = pvPortMalloc( mbMESSAGE_BUFFER_LENGTH_BYTES );
    configASSERT( pucFullBuffer );

    pucData = pucFullBuffer;
    pucReadData = pucData + x17ByteLength;

    /* Nothing has been added or removed yet, so expect the free space to be
     * exactly as created and the length of the next message to be 0. */
    xExpectedSpace = xMessageBufferSpaceAvailable( xMessageBuffer );
    configASSERT( xExpectedSpace == mbMESSAGE_BUFFER_LENGTH_BYTES );
    configASSERT( xMessageBufferIsEmpty( xMessageBuffer ) == pdTRUE );
    xNextLength = xMessageBufferNextLengthBytes( xMessageBuffer );
    configASSERT( xNextLength == 0 );
    /* In case configASSERT() is not define. */
    ( void ) xExpectedSpace;
    ( void ) xNextLength;

    /* Try sending more bytes than possible, first using the FromISR version, then
     * with an infinite block time to ensure this task does not lock up. */
    xReturned = xMessageBufferSendFromISR( xMessageBuffer, ( void * ) pucData, mbMESSAGE_BUFFER_LENGTH_BYTES + sizeof( configMESSAGE_BUFFER_LENGTH_TYPE ), NULL );
    configASSERT( xReturned == ( size_t ) 0 );
    /* In case configASSERT() is not defined. */
    ( void ) xReturned;
    xReturned = xMessageBufferSend( xMessageBuffer, ( void * ) pucData, mbMESSAGE_BUFFER_LENGTH_BYTES + sizeof( configMESSAGE_BUFFER_LENGTH_TYPE ), portMAX_DELAY );
    configASSERT( xReturned == ( size_t ) 0 );
    /* In case configASSERT() is not defined. */
    ( void ) xReturned;

    /* The buffer is 50 bytes long.  When an item is added to the buffer an
     * additional 4 bytes are added to hold the item's size.  That means adding
     * 6 bytes to the buffer will actually add 10 bytes to the buffer.  Therefore,
     * with a 50 byte buffer, a maximum of 5 6 bytes items can be added before the
     * buffer is completely full.  NOTE:  The numbers in this paragraph assume
     * sizeof( configMESSAGE_BUFFER_LENGTH_TYPE ) == 4. */
    for( xItem = 0; xItem < xMax6ByteMessages; xItem++ )
    {
        configASSERT( xMessageBufferIsFull( xMessageBuffer ) == pdFALSE );

        /* Generate recognisable data to write to the buffer.  This is just
         * ascii characters that shows which loop iteration the data was written
         * in. The 'FromISR' version is used to give it some exercise as a block
         * time is not used.  That requires the call to be in a critical section
         * so this code can also run on FreeRTOS ports that do not support
         * interrupt nesting (and so don't have interrupt safe critical
         * sections).*/
        memset( ( void * ) pucData, ( ( int ) '0' ) + ( int ) xItem, x6ByteLength );
        taskENTER_CRITICAL();
        {
            xReturned = xMessageBufferSendFromISR( xMessageBuffer, ( void * ) pucData, x6ByteLength, NULL );
        }
        taskEXIT_CRITICAL();
        configASSERT( xReturned == x6ByteLength );
        ( void ) xReturned; /* In case configASSERT() is not defined. */

        /* The space in the buffer will have reduced by the amount of user data
         * written into the buffer and the amount of space used to store the length
         * of the data written into the buffer. */
        xExpectedSpace -= ( x6ByteLength + mbBYTES_TO_STORE_MESSAGE_LENGTH );
        xReturned = xMessageBufferSpaceAvailable( xMessageBuffer );
        configASSERT( xReturned == xExpectedSpace );
        ( void ) xReturned; /* In case configASSERT() is not defined. */

        /* Only 6 byte messages are written. */
        xNextLength = xMessageBufferNextLengthBytes( xMessageBuffer );
        configASSERT( xNextLength == x6ByteLength );
        ( void ) xNextLength; /* In case configASSERT() is not defined. */
    }

    /* Now the buffer should be full, and attempting to add anything will should
     * fail. */
    configASSERT( xMessageBufferIsFull( xMessageBuffer ) == pdTRUE );
    xReturned = xMessageBufferSend( xMessageBuffer, ( void * ) pucData, sizeof( pucData[ 0 ] ), mbDONT_BLOCK );
    configASSERT( xReturned == 0 );
    ( void ) xReturned; /* In case configASSERT() is not defined. */

    /* Adding with a timeout should also fail after the appropriate time.  The
     * priority is temporarily boosted in this part of the test to keep the
     * allowable margin to a minimum. */
    uxOriginalPriority = uxTaskPriorityGet( NULL );
    vTaskPrioritySet( NULL, configMAX_PRIORITIES - 1 );
    xTimeBeforeCall = xTaskGetTickCount();
    xReturned = xMessageBufferSend( xMessageBuffer, ( void * ) pucData, sizeof( pucData[ 0 ] ), xBlockTime );
    xTimeAfterCall = xTaskGetTickCount();
    vTaskPrioritySet( NULL, uxOriginalPriority );
    configASSERT( ( ( TickType_t ) ( xTimeAfterCall - xTimeBeforeCall ) ) >= xBlockTime );
    configASSERT( ( ( TickType_t ) ( xTimeAfterCall - xTimeBeforeCall ) ) < ( xBlockTime + xAllowableMargin ) );
    configASSERT( xReturned == 0 );
    ( void ) xReturned; /* In case configASSERT() is not defined. */
    ( void ) xTimeBeforeCall;
    ( void ) xTimeAfterCall;

    /* The buffer is now full of data in the form "000000", "111111", etc.  Make
     * sure the data is read out as expected. */
    for( xItem = 0; xItem < xMax6ByteMessages; xItem++ )
    {
        /* Generate the data that is expected to be read out for this loop
         * iteration. */
        memset( ( void * ) pucData, ( ( int ) '0' ) + ( int ) xItem, x6ByteLength );

        /* Try reading the message into a buffer that is too small.  The message
         * should remain in the buffer. */
        xReturned = xMessageBufferReceive( xMessageBuffer, ( void * ) pucReadData, x6ByteLength - 1, mbDONT_BLOCK );
        configASSERT( xReturned == 0 );
        ( void ) xReturned; /* In case configASSERT() is not defined. */

        /* Should still be at least one 6 byte message still available. */
        xNextLength = xMessageBufferNextLengthBytes( xMessageBuffer );
        configASSERT( xNextLength == x6ByteLength );
        ( void ) xNextLength; /* In case configASSERT() is not defined. */

        /* Read the next 6 bytes out.  The 'FromISR' version is used to give it
         * some exercise as a block time is not used.  THa requires the code to be
         * in a critical section so this test can be run with FreeRTOS ports that
         * do not support interrupt nesting (and therefore don't have interrupt
         * safe critical sections). */
        taskENTER_CRITICAL();
        {
            xReturned = xMessageBufferReceiveFromISR( xMessageBuffer, ( void * ) pucReadData, x6ByteLength, NULL );
        }
        taskEXIT_CRITICAL();
        configASSERT( xReturned == x6ByteLength );
        ( void ) xReturned; /* In case configASSERT() is not defined. */

        /* Does the data read out match that expected? */
        configASSERT( memcmp( ( void * ) pucData, ( void * ) pucReadData, x6ByteLength ) == 0 );

        /* The space in the buffer will have increased by the amount of user
         * data read from into the buffer and the amount of space used to store the
         * length of the data read into the buffer. */
        xExpectedSpace += ( x6ByteLength + mbBYTES_TO_STORE_MESSAGE_LENGTH );
        xReturned = xMessageBufferSpaceAvailable( xMessageBuffer );
        configASSERT( xReturned == xExpectedSpace );
        ( void ) xReturned; /* In case configASSERT() is not defined. */
    }

    /* The buffer should be empty again. */
    configASSERT( xMessageBufferIsEmpty( xMessageBuffer ) == pdTRUE );
    xExpectedSpace = xMessageBufferSpaceAvailable( xMessageBuffer );
    configASSERT( xExpectedSpace == mbMESSAGE_BUFFER_LENGTH_BYTES );
    ( void ) xExpectedSpace; /* In case configASSERT() is not defined. */
    xNextLength = xMessageBufferNextLengthBytes( xMessageBuffer );
    configASSERT( xNextLength == 0 );
    ( void ) xNextLength; /* In case configASSERT() is not defined. */


    /* Reading with a timeout should also fail after the appropriate time.  The
     * priority is temporarily boosted in this part of the test to keep the
     * allowable margin to a minimum. */
    vTaskPrioritySet( NULL, configMAX_PRIORITIES - 1 );
    xTimeBeforeCall = xTaskGetTickCount();
    xReturned = xMessageBufferReceive( xMessageBuffer, ( void * ) pucReadData, x6ByteLength, xBlockTime );
    xTimeAfterCall = xTaskGetTickCount();
    vTaskPrioritySet( NULL, uxOriginalPriority );
    configASSERT( ( xTimeAfterCall - xTimeBeforeCall ) >= xBlockTime );
    configASSERT( ( xTimeAfterCall - xTimeBeforeCall ) < ( xBlockTime + xAllowableMargin ) );
    configASSERT( xReturned == 0 );
    ( void ) xReturned; /* In case configASSERT() is not defined. */
    ( void ) xTimeBeforeCall;
    ( void ) xTimeAfterCall;


    /* In the next loop 17 bytes are written to then read out on each iteration.
     * The expected length variable is always used after 17 bytes have been written
     * into the buffer - the length of the message is also written, making a total
     * of 21 bytes consumed for each 17 byte message. */
    xExpectedSpace = mbMESSAGE_BUFFER_LENGTH_BYTES - ( x17ByteLength + mbBYTES_TO_STORE_MESSAGE_LENGTH );

    /* Reading and writing 17 bytes at a time will result in 21 bytes being
     * written into the buffer, and as 50 is not divisible by 21, writing multiple
     * times will cause the data to wrap in the buffer.*/
    for( xItem = 0; xItem < 100; xItem++ )
    {
        /* Generate recognisable data to write to the queue.  This is just
         * ascii characters that shows which loop iteration the data was written
         * in. */
        memset( ( void * ) pucData, ( ( int ) '0' ) + ( int ) xItem, x17ByteLength );
        xReturned = xMessageBufferSend( xMessageBuffer, ( void * ) pucData, x17ByteLength, mbDONT_BLOCK );
        configASSERT( xReturned == x17ByteLength );
        ( void ) xReturned; /* In case configASSERT() is not defined. */

        /* Only 17 byte messages are written. */
        xNextLength = xMessageBufferNextLengthBytes( xMessageBuffer );
        configASSERT( xNextLength == x17ByteLength );
        ( void ) xNextLength; /* In case configASSERT() is not defined. */

        /* The space in the buffer will have reduced by the amount of user data
         * written into the buffer and the amount of space used to store the length
         * of the data written into the buffer. */
        xReturned = xMessageBufferSpaceAvailable( xMessageBuffer );
        configASSERT( xReturned == xExpectedSpace );
        ( void ) xReturned; /* In case configASSERT() is not defined. */

        /* Read the 17 bytes out again. */
        xReturned = xMessageBufferReceive( xMessageBuffer, ( void * ) pucReadData, x17ByteLength, mbDONT_BLOCK );
        configASSERT( xReturned == x17ByteLength );
        ( void ) xReturned; /* In case configASSERT() is not defined. */

        /* Does the data read out match that expected? */
        configASSERT( memcmp( ( void * ) pucData, ( void * ) pucReadData, x17ByteLength ) == 0 );

        /* Don't expect any messages to be available as the data was read out
         * again. */
        xNextLength = xMessageBufferNextLengthBytes( xMessageBuffer );
        configASSERT( xNextLength == 0 );
        ( void ) xNextLength; /* In case configASSERT() is not defined. */
    }

    /* The buffer should be empty again. */
    configASSERT( xMessageBufferIsEmpty( xMessageBuffer ) == pdTRUE );
    xExpectedSpace = xMessageBufferSpaceAvailable( xMessageBuffer );
    configASSERT( xExpectedSpace == mbMESSAGE_BUFFER_LENGTH_BYTES );

    /* Cannot write within sizeof( size_t ) (assumed to be 4 bytes in this test)
     * bytes of the full 50 bytes, as that would not leave space for the four bytes
     * taken by the data length. */
    xReturned = xMessageBufferSend( xMessageBuffer, ( const void * ) pc55ByteString, mbMESSAGE_BUFFER_LENGTH_BYTES, mbDONT_BLOCK );
    configASSERT( xReturned == 0 );
    ( void ) xReturned; /* In case configASSERT() is not defined. */
    #ifndef configMESSAGE_BUFFER_LENGTH_TYPE
        {
            /* The following will fail if configMESSAGE_BUFFER_LENGTH_TYPE is set
             * to a non 32-bit type. */
            xReturned = xMessageBufferSend( xMessageBuffer, ( const void * ) pc55ByteString, mbMESSAGE_BUFFER_LENGTH_BYTES - 1, mbDONT_BLOCK );
            configASSERT( xReturned == 0 );
            ( void ) xReturned; /* In case configASSERT() is not defined. */
            xReturned = xMessageBufferSend( xMessageBuffer, ( const void * ) pc55ByteString, mbMESSAGE_BUFFER_LENGTH_BYTES - 2, mbDONT_BLOCK );
            configASSERT( xReturned == 0 );
            ( void ) xReturned; /* In case configASSERT() is not defined. */
            xReturned = xMessageBufferSend( xMessageBuffer, ( const void * ) pc55ByteString, mbMESSAGE_BUFFER_LENGTH_BYTES - 3, mbDONT_BLOCK );
            configASSERT( xReturned == 0 );
            ( void ) xReturned; /* In case configASSERT() is not defined. */
        }
    #endif /* ifndef configMESSAGE_BUFFER_LENGTH_TYPE */

    /* Don't expect any messages to be available as the above were too large to
     * get written. */
    xNextLength = xMessageBufferNextLengthBytes( xMessageBuffer );
    configASSERT( xNextLength == 0 );
    ( void ) xNextLength; /* In case configASSERT() is not defined. */

    /* Can write mbMESSAGE_BUFFER_LENGTH_BYTES - sizeof( size_t ) bytes though. */
    xReturned = xMessageBufferSend( xMessageBuffer, ( const void * ) pc55ByteString, mbMESSAGE_BUFFER_LENGTH_BYTES - sizeof( size_t ), mbDONT_BLOCK );
    configASSERT( xReturned == mbMESSAGE_BUFFER_LENGTH_BYTES - sizeof( size_t ) );
    ( void ) xReturned; /* In case configASSERT() is not defined. */
    xNextLength = xMessageBufferNextLengthBytes( xMessageBuffer );
    configASSERT( xNextLength == ( mbMESSAGE_BUFFER_LENGTH_BYTES - sizeof( size_t ) ) );
    ( void ) xNextLength; /* In case configASSERT() is not defined. */
    xReturned = xMessageBufferReceive( xMessageBuffer, ( void * ) pucFullBuffer, mbMESSAGE_BUFFER_LENGTH_BYTES - sizeof( size_t ), mbDONT_BLOCK );
    configASSERT( xReturned == ( mbMESSAGE_BUFFER_LENGTH_BYTES - sizeof( size_t ) ) );
    ( void ) xReturned; /* In case configASSERT() is not defined. */
    configASSERT( memcmp( ( const void * ) pucFullBuffer, pc55ByteString, mbMESSAGE_BUFFER_LENGTH_BYTES - sizeof( size_t ) ) == 0 );

    /* Clean up. */
    vPortFree( pucFullBuffer );
    xMessageBufferReset( xMessageBuffer );
}
/*-----------------------------------------------------------*/

static void prvNonBlockingSenderTask( void * pvParameters )
{
    MessageBufferHandle_t xMessageBuffer;
    int32_t iDataToSend = 0;
    size_t xStringLength;
    const int32_t iMaxValue = 1500;
    char cTxString[ 12 ]; /* Large enough to hold a 32 number in ASCII. */

    /* In this case the message buffer has already been created and is passed
     * into the task using the task's parameter. */

    xMessageBuffer = ( MessageBufferHandle_t ) pvParameters;

    /* Create a string from an incrementing number.  The length of the
     * string will increase and decrease as the value of the number increases
     * then overflows. */
    memset( cTxString, 0x00, sizeof( cTxString ) );
    sprintf( cTxString, "%d", ( int ) iDataToSend );
    xStringLength = strlen( cTxString );

    for( ; ; )
    {
        /* Doesn't block so calls can interleave with the non-blocking
         * receives performed by prvNonBlockingReceiverTask(). */
        if( xMessageBufferSend( xMessageBuffer, ( void * ) cTxString, strlen( cTxString ), mbDONT_BLOCK ) == xStringLength )
        {
            iDataToSend++;

            if( iDataToSend > iMaxValue )
            {
                /* The value sent is reset back to 0 to ensure the string being sent
                 * does not remain at the same length for too long. */
                iDataToSend = 0;
            }

            /* Create the next string. */
            memset( cTxString, 0x00, sizeof( cTxString ) );
            sprintf( cTxString, "%d", ( int ) iDataToSend );
            xStringLength = strlen( cTxString );
        }
    }
}
/*-----------------------------------------------------------*/

static void prvNonBlockingReceiverTask( void * pvParameters )
{
    MessageBufferHandle_t xMessageBuffer;
    BaseType_t xNonBlockingReceiveError = pdFALSE;
    int32_t iDataToSend = 0;
    size_t xStringLength, xReceiveLength;
    const int32_t iMaxValue = 1500;
    char cExpectedString[ 12 ]; /* Large enough to hold a 32 number in ASCII. */
    char cRxString[ 12 ];

    /* In this case the message buffer has already been created and is passed
     * into the task using the task's parameter. */
    xMessageBuffer = ( MessageBufferHandle_t ) pvParameters;

    /* Create a string from an incrementing number.  The length of the
     * string will increase and decrease as the value of the number increases
     * then overflows.  This should always match the string sent to the buffer by
     * the non blocking sender task. */
    memset( cExpectedString, 0x00, sizeof( cExpectedString ) );
    memset( cRxString, 0x00, sizeof( cRxString ) );
    sprintf( cExpectedString, "%d", ( int ) iDataToSend );
    xStringLength = strlen( cExpectedString );

    for( ; ; )
    {
        /* Doesn't block so calls can interleave with the non-blocking
         * receives performed by prvNonBlockingReceiverTask(). */
        xReceiveLength = xMessageBufferReceive( xMessageBuffer, ( void * ) cRxString, sizeof( cRxString ), mbDONT_BLOCK );

        /* Should only ever receive no data is available, or the expected
         * length of data is available. */
        if( ( xReceiveLength != 0 ) && ( xReceiveLength != xStringLength ) )
        {
            xNonBlockingReceiveError = pdTRUE;
        }

        if( xReceiveLength == xStringLength )
        {
            /* Ensure the received data was that expected, then generate the
             * next expected string. */
            if( strcmp( cRxString, cExpectedString ) != 0 )
            {
                xNonBlockingReceiveError = pdTRUE;
            }

            iDataToSend++;

            if( iDataToSend > iMaxValue )
            {
                /* The value sent is reset back to 0 to ensure the string being sent
                 * does not remain at the same length for too long. */
                iDataToSend = 0;
            }

            memset( cExpectedString, 0x00, sizeof( cExpectedString ) );
            memset( cRxString, 0x00, sizeof( cRxString ) );
            sprintf( cExpectedString, "%d", ( int ) iDataToSend );
            xStringLength = strlen( cExpectedString );

            if( xNonBlockingReceiveError == pdFALSE )
            {
                /* No errors detected so increment the counter that lets the
                 *  check task know this test is still functioning correctly. */
                ulNonBlockingRxCounter++;
            }
        }

    }
}
/*-----------------------------------------------------------*/

#if ( configSUPPORT_STATIC_ALLOCATION == 1 )

    static void prvSenderTask( void * pvParameters )
    {
        MessageBufferHandle_t xMessageBuffer, xTempMessageBuffer;
        int32_t iDataToSend = 0;
        const int32_t iSendsBetweenIncrements = 100;
        char cTxString[ 12 ]; /* Large enough to hold a 32 number in ASCII. */
        const TickType_t xTicksToWait = mbRX_TX_BLOCK_TIME, xShortDelay = pdMS_TO_TICKS( 50 );
        StaticMessageBuffer_t xStaticMessageBuffer;
        size_t xBytesSent;


        /* The task's priority is used as an index into the loop counters used to
         * indicate this task is still running. */
        UBaseType_t uxIndex = uxTaskPriorityGet( NULL );

        /* Make sure a change in priority does not inadvertently result in an
         * invalid array index. */
        configASSERT( uxIndex < mbNUMBER_OF_ECHO_CLIENTS );

        /* Avoid compiler warnings about unused parameters. */
        ( void ) pvParameters;

        xMessageBuffer = xMessageBufferCreateStatic( sizeof( ucBufferStorage ) / mbNUMBER_OF_SENDER_TASKS, /* The number of bytes in each buffer in the array. */
                                                     &( ucBufferStorage[ uxIndex ][ 0 ] ),                 /* The address of the buffer to use within the array. */
                                                     &( xStaticMessageBuffers[ uxIndex ] ) );              /* The static message buffer structure to use within the array. */

        /* Now the message buffer has been created the receiver task can be created.
         * If this sender task has the higher priority then the receiver task is
         * created at the lower priority - if this sender task has the lower priority
         * then the receiver task is created at the higher priority. */
        if( uxTaskPriorityGet( NULL ) == mbLOWER_PRIORITY )
        {
            /* Here prvSingleTaskTests() performs various tests on a message buffer
             * that was created statically. */
            prvSingleTaskTests( xMessageBuffer );
            xTaskCreate( prvReceiverTask, "MsgReceiver", xBlockingStackSize, ( void * ) xMessageBuffer, mbHIGHER_PRIORITY, NULL );
        }
        else
        {
            xTaskCreate( prvReceiverTask, "MsgReceiver", xBlockingStackSize, ( void * ) xMessageBuffer, mbLOWER_PRIORITY, NULL );
        }

        for( ; ; )
        {
            /* Create a string from an incrementing number.  The length of the
             * string will increase and decrease as the value of the number increases
             * then overflows. */
            memset( cTxString, 0x00, sizeof( cTxString ) );
            sprintf( cTxString, "%d", ( int ) iDataToSend );

            do
            {
                xBytesSent = xMessageBufferSend( xMessageBuffer, ( void * ) cTxString, strlen( cTxString ), xTicksToWait );
            } while( xBytesSent == 0 ); /* Buffer may become full when receiver is running at the idle priority. */

            iDataToSend++;

            if( ( iDataToSend % iSendsBetweenIncrements ) == 0 )
            {
                /* Increment a loop counter so a check task can tell this task is
                 * still running as expected. */
                ulSenderLoopCounters[ uxIndex ]++;

                if( uxTaskPriorityGet( NULL ) == mbHIGHER_PRIORITY )
                {
                    /* Allow other tasks to run. */
                    vTaskDelay( xShortDelay );
                }

                /* This message buffer is just created and deleted to ensure no
                 * issues when attempting to delete a message buffer that was
                 * created using statically allocated memory.  To save stack space
                 * the buffer is set to point to the cTxString array - this is
                 * ok because nothing is actually written to the memory. */
                xTempMessageBuffer = xMessageBufferCreateStatic( sizeof( cTxString ), ( uint8_t * ) cTxString, &xStaticMessageBuffer );
                vMessageBufferDelete( xTempMessageBuffer );
            }
        }
    }

#endif /* configSUPPORT_STATIC_ALLOCATION */
/*-----------------------------------------------------------*/

#if ( configSUPPORT_STATIC_ALLOCATION == 1 )

    static void prvReceiverTask( void * pvParameters )
    {
        MessageBufferHandle_t * const pxMessageBuffer = ( MessageBufferHandle_t * ) pvParameters;
        char cExpectedString[ 12 ]; /* Large enough to hold a 32-bit number in ASCII. */
        char cReceivedString[ 12 ]; /* Large enough to hold a 32-bit number in ASCII. */
        int32_t iExpectedData = 0;
        const TickType_t xTicksToWait = pdMS_TO_TICKS( 5UL );
        size_t xReceivedBytes;

        for( ; ; )
        {
            /* Generate the next expected string in the cExpectedString buffer. */
            memset( cExpectedString, 0x00, sizeof( cExpectedString ) );
            sprintf( cExpectedString, "%d", ( int ) iExpectedData );

            /* Receive the next string from the message buffer. */
            memset( cReceivedString, 0x00, sizeof( cReceivedString ) );

            do
            {
                xReceivedBytes = xMessageBufferReceive( pxMessageBuffer, ( void * ) cReceivedString, sizeof( cExpectedString ), xTicksToWait );
            } while( xReceivedBytes == 0 );

            /* Ensure the received string matches the expected string. */
            configASSERT( strcmp( cExpectedString, cReceivedString ) == 0 );

            iExpectedData++;
        }
    }

#endif /* configSUPPORT_STATIC_ALLOCATION */
/*-----------------------------------------------------------*/

static void prvEchoClient( void * pvParameters )
{
    size_t xSendLength = 0, ux;
    char * pcStringToSend, * pcStringReceived, cNextChar = mbASCII_SPACE;
    const TickType_t xTicksToWait = pdMS_TO_TICKS( 50 );

/* The task's priority is used as an index into the loop counters used to
 * indicate this task is still running. */
    UBaseType_t uxIndex = uxTaskPriorityGet( NULL );

/* Pointers to the client and server message buffers are passed into this task
 * using the task's parameter. */
    EchoMessageBuffers_t * pxMessageBuffers = ( EchoMessageBuffers_t * ) pvParameters;

    /* Prevent compiler warnings. */
    ( void ) pvParameters;

    /* Create the buffer into which strings to send to the server will be
     * created, and the buffer into which strings echoed back from the server will
     * be copied. */
    pcStringToSend = ( char * ) pvPortMalloc( mbMESSAGE_BUFFER_LENGTH_BYTES );
    pcStringReceived = ( char * ) pvPortMalloc( mbMESSAGE_BUFFER_LENGTH_BYTES );

    configASSERT( pcStringToSend );
    configASSERT( pcStringReceived );

    for( ; ; )
    {
        /* Generate the length of the next string to send. */
        xSendLength++;

        /* The message buffer is being used to hold variable length data, so
         * each data item requires sizeof( size_t ) bytes to hold the data's
         * length, hence the sizeof() in the if() condition below. */
        if( xSendLength > ( mbMESSAGE_BUFFER_LENGTH_BYTES - sizeof( size_t ) ) )
        {
            /* Back to a string length of 1. */
            xSendLength = sizeof( char );

            /* Maintain a count of the number of times this code executes so a
             * check task can determine if this task is still functioning as
             * expected or not.  As there are two client tasks, and the priorities
             * used are 0 and 1, the task's priority is used as an index into the
             * loop count array. */
            ulEchoLoopCounters[ uxIndex ]++;
        }

        memset( pcStringToSend, 0x00, mbMESSAGE_BUFFER_LENGTH_BYTES );

        for( ux = 0; ux < xSendLength; ux++ )
        {
            pcStringToSend[ ux ] = cNextChar;

            cNextChar++;

            if( cNextChar > mbASCII_TILDA )
            {
                cNextChar = mbASCII_SPACE;
            }
        }

        /* Send the generated string to the buffer. */
        do
        {
            ux = xMessageBufferSend( pxMessageBuffers->xEchoClientBuffer, ( void * ) pcStringToSend, xSendLength, xTicksToWait );

            if( ux == 0 )
            {
                mtCOVERAGE_TEST_MARKER();
            }
        } while( ux == 0 );

        /* Wait for the string to be echoed back. */
        memset( pcStringReceived, 0x00, mbMESSAGE_BUFFER_LENGTH_BYTES );
        xMessageBufferReceive( pxMessageBuffers->xEchoServerBuffer, ( void * ) pcStringReceived, xSendLength, portMAX_DELAY );

        configASSERT( strcmp( pcStringToSend, pcStringReceived ) == 0 );
    }
}
/*-----------------------------------------------------------*/

static void prvEchoServer( void * pvParameters )
{
    MessageBufferHandle_t xTempMessageBuffer;
    size_t xReceivedLength;
    char * pcReceivedString;
    EchoMessageBuffers_t xMessageBuffers;
    TickType_t xTimeOnEntering;
    const TickType_t xTicksToBlock = pdMS_TO_TICKS( 250UL );

    /* Prevent compiler warnings about unused parameters. */
    ( void ) pvParameters;

    /* Create the message buffer used to send data from the client to the server,
     * and the message buffer used to echo the data from the server back to the
     * client. */
    xMessageBuffers.xEchoClientBuffer = xMessageBufferCreate( mbMESSAGE_BUFFER_LENGTH_BYTES );
    xMessageBuffers.xEchoServerBuffer = xMessageBufferCreate( mbMESSAGE_BUFFER_LENGTH_BYTES );
    configASSERT( xMessageBuffers.xEchoClientBuffer );
    configASSERT( xMessageBuffers.xEchoServerBuffer );

    /* Create the buffer into which received strings will be copied. */
    pcReceivedString = ( char * ) pvPortMalloc( mbMESSAGE_BUFFER_LENGTH_BYTES );
    configASSERT( pcReceivedString );

    /* Don't expect to receive anything yet! */
    xTimeOnEntering = xTaskGetTickCount();
    xReceivedLength = xMessageBufferReceive( xMessageBuffers.xEchoClientBuffer, ( void * ) pcReceivedString, mbMESSAGE_BUFFER_LENGTH_BYTES, xTicksToBlock );
    configASSERT( ( ( TickType_t ) ( xTaskGetTickCount() - xTimeOnEntering ) ) >= xTicksToBlock );
    configASSERT( xReceivedLength == 0 );
    ( void ) xTimeOnEntering; /* In case configASSERT() is not defined. */

    /* Now the message buffers have been created the echo client task can be
     * created.  If this server task has the higher priority then the client task
     * is created at the lower priority - if this server task has the lower
     * priority then the client task is created at the higher priority. */
    if( uxTaskPriorityGet( NULL ) == mbLOWER_PRIORITY )
    {
        xTaskCreate( prvEchoClient, "EchoClient", configMINIMAL_STACK_SIZE, ( void * ) &xMessageBuffers, mbHIGHER_PRIORITY, NULL );
    }
    else
    {
        /* Here prvSingleTaskTests() performs various tests on a message buffer
         * that was created dynamically. */
        prvSingleTaskTests( xMessageBuffers.xEchoClientBuffer );
        xTaskCreate( prvEchoClient, "EchoClient", configMINIMAL_STACK_SIZE, ( void * ) &xMessageBuffers, mbLOWER_PRIORITY, NULL );
    }

    for( ; ; )
    {
        memset( pcReceivedString, 0x00, mbMESSAGE_BUFFER_LENGTH_BYTES );

        /* Has any data been sent by the client? */
        xReceivedLength = xMessageBufferReceive( xMessageBuffers.xEchoClientBuffer, ( void * ) pcReceivedString, mbMESSAGE_BUFFER_LENGTH_BYTES, portMAX_DELAY );

        /* Should always receive data as max delay was used. */
        configASSERT( xReceivedLength > 0 );

        /* Echo the received data back to the client. */
        xMessageBufferSend( xMessageBuffers.xEchoServerBuffer, ( void * ) pcReceivedString, xReceivedLength, portMAX_DELAY );

        /* This message buffer is just created and deleted to ensure no memory
         * leaks. */
        xTempMessageBuffer = xMessageBufferCreate( mbMESSAGE_BUFFER_LENGTH_BYTES );
        vMessageBufferDelete( xTempMessageBuffer );
    }
}
/*-----------------------------------------------------------*/

/* Tests within configRUN_ADDITIONAL_TESTS blocks only execute on larger
 * platforms or have been added to pre-existing files that are already in use
 * by other test projects without ensuring they don't cause those pre-existing
 * projects to run out of program or data memory. */
#if ( configRUN_ADDITIONAL_TESTS == 1 )

    static void prvSpaceAvailableCoherenceActor( void * pvParameters )
    {
        static char * cTxString = "12345";
        char cRxString[ mbCOHERENCE_TEST_BYTES_WRITTEN + 1 ]; /* +1 for NULL terminator. */

        ( void ) pvParameters;

        for( ; ; )
        {
            /* Add bytes to the buffer so the other task should see
             * mbEXPECTED_FREE_BYTES_AFTER_WRITING_STRING bytes free. */
            xMessageBufferSend( xCoherenceTestMessageBuffer, ( void * ) cTxString, strlen( cTxString ), 0 );
            configASSERT( xMessageBufferSpacesAvailable( xCoherenceTestMessageBuffer ) == mbEXPECTED_FREE_BYTES_AFTER_WRITING_STRING );

            /* Read out message again so the other task should read the full
             * mbCOHERENCE_TEST_BUFFER_SIZE bytes free again. */
            memset( ( void * ) cRxString, 0x00, sizeof( cRxString ) );
            xMessageBufferReceive( xCoherenceTestMessageBuffer, ( void * ) cRxString, mbCOHERENCE_TEST_BYTES_WRITTEN, 0 );
            configASSERT( strcmp( cTxString, cRxString ) == 0 );
        }
    }
    /*-----------------------------------------------------------*/

    static void prvSpaceAvailableCoherenceTester( void * pvParameters )
    {
        size_t xSpaceAvailable;
        BaseType_t xErrorFound = pdFALSE;

        ( void ) pvParameters;

        for( ; ; )
        {
            /* This message buffer is only ever empty or contains 5 bytes.  So all
             * queries of its free space should result in one of the two values tested
             * below. */
            xSpaceAvailable = xMessageBufferSpacesAvailable( xCoherenceTestMessageBuffer );

            if( ( xSpaceAvailable == mbCOHERENCE_TEST_BUFFER_SIZE ) ||
                ( xSpaceAvailable == mbEXPECTED_FREE_BYTES_AFTER_WRITING_STRING ) )
            {
                /* Only continue to increment the variable that shows this task
                 * is still executing if no errors have been found. */
                if( xErrorFound == pdFALSE )
                {
                    ulSizeCoherencyTestCycles++;
                }
            }
            else
            {
                xErrorFound = pdTRUE;
            }

            configASSERT( xErrorFound == pdFALSE );
        }
    }

#endif /* configRUN_ADDITIONAL_TESTS == 1  */
/*-----------------------------------------------------------*/

BaseType_t xAreMessageBufferTasksStillRunning( void )
{
    static uint32_t ulLastEchoLoopCounters[ mbNUMBER_OF_ECHO_CLIENTS ] = { 0 };
    static uint32_t ulLastNonBlockingRxCounter = 0;
    BaseType_t xReturn = pdPASS, x;

    for( x = 0; x < mbNUMBER_OF_ECHO_CLIENTS; x++ )
    {
        if( ulLastEchoLoopCounters[ x ] == ulEchoLoopCounters[ x ] )
        {
            xReturn = pdFAIL;
        }
        else
        {
            ulLastEchoLoopCounters[ x ] = ulEchoLoopCounters[ x ];
        }
    }

    if( ulNonBlockingRxCounter == ulLastNonBlockingRxCounter )
    {
        xReturn = pdFAIL;
    }
    else
    {
        ulLastNonBlockingRxCounter = ulNonBlockingRxCounter;
    }

    #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            static uint32_t ulLastSenderLoopCounters[ mbNUMBER_OF_ECHO_CLIENTS ] = { 0 };

            for( x = 0; x < mbNUMBER_OF_SENDER_TASKS; x++ )
            {
                if( ulLastSenderLoopCounters[ x ] == ulSenderLoopCounters[ x ] )
                {
                    xReturn = pdFAIL;
                }
                else
                {
                    ulLastSenderLoopCounters[ x ] = ulSenderLoopCounters[ x ];
                }
            }
        }
    #endif /* configSUPPORT_STATIC_ALLOCATION */

    #if ( configRUN_ADDITIONAL_TESTS == 1 )
        {
            static uint32_t ullastSizeCoherencyTestCycles = 0UL;

            if( ullastSizeCoherencyTestCycles == ulSizeCoherencyTestCycles )
            {
                xReturn = pdFAIL;
            }
            else
            {
                ullastSizeCoherencyTestCycles = ulSizeCoherencyTestCycles;
            }
        }
    #endif /* if ( configRUN_ADDITIONAL_TESTS == 1 ) */

    return xReturn;
}
/*-----------------------------------------------------------*/
