diff --git a/platform/posix/transport/src/plaintext_posix.c b/platform/posix/transport/src/plaintext_posix.c index 8f703e7291..711e4559aa 100644 --- a/platform/posix/transport/src/plaintext_posix.c +++ b/platform/posix/transport/src/plaintext_posix.c @@ -118,63 +118,98 @@ int32_t Plaintext_Recv( NetworkContext_t * pNetworkContext, int32_t bytesReceived = -1, pollStatus = 1; struct pollfd pollFds; - assert( pNetworkContext != NULL && pNetworkContext->pParams != NULL ); - assert( pBuffer != NULL ); - assert( bytesToRecv > 0 ); - - /* Get receive timeout from the socket to use as the timeout for #select. */ - pPlaintextParams = pNetworkContext->pParams; - - /* Initialize the file descriptor. - * #POLLPRI corresponds to high-priority data while #POLLIN corresponds - * to any other data that may be read. */ - pollFds.events = POLLIN | POLLPRI; - pollFds.revents = 0; - /* Set the file descriptor for poll. */ - pollFds.fd = pPlaintextParams->socketDescriptor; - - /* Speculative read for the start of a payload. - * Note: This is done to avoid blocking when - * no data is available to be read from the socket. */ - if( bytesToRecv == 1U ) + if( pNetworkContext == NULL ) { - /* Check if there is data to read (without blocking) from the socket. */ - pollStatus = poll( &pollFds, 1, 0 ); - } - - if( pollStatus > 0 ) - { - /* The socket is available for receiving data. */ - bytesReceived = ( int32_t ) recv( pPlaintextParams->socketDescriptor, - pBuffer, - bytesToRecv, - 0 ); - } - else if( pollStatus < 0 ) - { - /* An error occurred while polling. */ + LogError( ( "Parameter check failed: pNetworkContext is NULL." ) ); bytesReceived = -1; } - else + else if( pNetworkContext->pParams == NULL ) { - /* No data available to receive. */ - bytesReceived = 0; + LogError( ( "Parameter check failed: pNetworkContext->pParams is NULL." ) ); + bytesReceived = -1; } - - /* Note: A zero value return from recv() represents - * closure of TCP connection by the peer. */ - if( ( pollStatus > 0 ) && ( bytesReceived == 0 ) ) + else if( pBuffer == NULL ) { - /* Peer has closed the connection. Treat as an error. */ + LogError( ( "Parameter check failed: pBuffer is NULL." ) ); bytesReceived = -1; } - else if( bytesReceived < 0 ) + else if( bytesToRecv == 0 ) { - logTransportError( errno ); + LogError( ( "Parameter check failed: bytesToRecv is zero." ) ); + bytesReceived = -1; } else { - /* Empty else MISRA 15.7 */ + /* Get receive timeout from the socket to use as the timeout for #select. */ + pPlaintextParams = pNetworkContext->pParams; + + /* Initialize the file descriptor. + * #POLLPRI corresponds to high-priority data while #POLLIN corresponds + * to any other data that may be read. */ + pollFds.events = POLLIN | POLLPRI; + pollFds.revents = 0; + /* Set the file descriptor for poll. */ + pollFds.fd = pPlaintextParams->socketDescriptor; + + /* Speculative read for the start of a payload. + * Note: This is done to avoid blocking when + * no data is available to be read from the socket. */ + if( bytesToRecv == 1U ) + { + /* Check if there is data to read (without blocking) from the socket. */ + pollStatus = poll( &pollFds, 1, 0 ); + } + + if( pollStatus > 0 ) + { + /* The socket is available for receiving data. */ + bytesReceived = ( int32_t ) recv( pPlaintextParams->socketDescriptor, + pBuffer, + bytesToRecv, + 0 ); + } + else if( pollStatus < 0 ) + { + /* An error occurred while polling. */ + bytesReceived = -1; + } + else + { + /* No data available to receive. */ + bytesReceived = 0; + } + + /* Note: A zero value return from recv() represents + * closure of TCP connection by the peer. */ + if( ( pollStatus > 0 ) && ( bytesReceived == 0 ) ) + { + /* Peer has closed the connection. Treat as an error. */ + bytesReceived = -1; + } + else if( ( pollStatus > 0 ) && ( bytesReceived < 0 ) ) + { + /* The socket blocked for timeout and no data is received situation. + * Return zero to indicate this operation can be retried. */ + #if EAGAIN == EWOULDBLOCK + if( errno == EAGAIN ) + { + bytesReceived = 0; + } + #else + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + bytesReceived = 0; + } + #endif + } + else if( bytesReceived < 0 ) + { + logTransportError( errno ); + } + else + { + /* Empty else MISRA 15.7 */ + } } return bytesReceived; @@ -192,56 +227,91 @@ int32_t Plaintext_Send( NetworkContext_t * pNetworkContext, int32_t bytesSent = -1, pollStatus = -1; struct pollfd pollFds; - assert( pNetworkContext != NULL && pNetworkContext->pParams != NULL ); - assert( pBuffer != NULL ); - assert( bytesToSend > 0 ); - - /* Get send timeout from the socket to use as the timeout for #select. */ - pPlaintextParams = pNetworkContext->pParams; - - /* Initialize the file descriptor. */ - pollFds.events = POLLOUT; - pollFds.revents = 0; - /* Set the file descriptor for poll. */ - pollFds.fd = pPlaintextParams->socketDescriptor; - - /* Check if data can be written to the socket. - * Note: This is done to avoid blocking on send() when - * the socket is not ready to accept more data for network - * transmission (possibly due to a full TX buffer). */ - pollStatus = poll( &pollFds, 1, 0 ); - - if( pollStatus > 0 ) - { - /* The socket is available for sending data. */ - bytesSent = ( int32_t ) send( pPlaintextParams->socketDescriptor, - pBuffer, - bytesToSend, - 0 ); - } - else if( pollStatus < 0 ) + if( pNetworkContext == NULL ) { - /* An error occurred while polling. */ + LogError( ( "Parameter check failed: pNetworkContext is NULL." ) ); bytesSent = -1; } - else + else if( pNetworkContext->pParams == NULL ) { - /* Socket is not available for sending data. */ - bytesSent = 0; + LogError( ( "Parameter check failed: pNetworkContext->pParams is NULL." ) ); + bytesSent = -1; } - - if( ( pollStatus > 0 ) && ( bytesSent == 0 ) ) + else if( pBuffer == NULL ) { - /* Peer has closed the connection. Treat as an error. */ + LogError( ( "Parameter check failed: pBuffer is NULL." ) ); bytesSent = -1; } - else if( bytesSent < 0 ) + else if( bytesToSend == 0 ) { - logTransportError( errno ); + LogError( ( "Parameter check failed: bytesToSend is zero." ) ); + bytesSent = -1; } else { - /* Empty else MISRA 15.7 */ + /* Get send timeout from the socket to use as the timeout for #select. */ + pPlaintextParams = pNetworkContext->pParams; + + /* Initialize the file descriptor. */ + pollFds.events = POLLOUT; + pollFds.revents = 0; + /* Set the file descriptor for poll. */ + pollFds.fd = pPlaintextParams->socketDescriptor; + + /* Check if data can be written to the socket. + * Note: This is done to avoid blocking on send() when + * the socket is not ready to accept more data for network + * transmission (possibly due to a full TX buffer). */ + pollStatus = poll( &pollFds, 1, 0 ); + + if( pollStatus > 0 ) + { + /* The socket is available for sending data. */ + bytesSent = ( int32_t ) send( pPlaintextParams->socketDescriptor, + pBuffer, + bytesToSend, + 0 ); + } + else if( pollStatus < 0 ) + { + /* An error occurred while polling. */ + bytesSent = -1; + } + else + { + /* Socket is not available for sending data. */ + bytesSent = 0; + } + + if( ( pollStatus > 0 ) && ( bytesSent == 0 ) ) + { + /* Peer has closed the connection. Treat as an error. */ + bytesSent = -1; + } + else if( ( pollStatus > 0 ) && ( bytesSent < 0 ) ) + { + /* The socket blocked for timeout and no data is sent situation. + * Return zero to indicate this operation can be retried. */ + #if EAGAIN == EWOULDBLOCK + if( errno == EAGAIN ) + { + bytesSent = 0; + } + #else + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + bytesSent = 0; + } + #endif + } + else if( bytesSent < 0 ) + { + logTransportError( errno ); + } + else + { + /* Empty else MISRA 15.7 */ + } } return bytesSent; diff --git a/platform/posix/transport/utest/plaintext_utest.c b/platform/posix/transport/utest/plaintext_utest.c index 8ef5aba253..8825b832c2 100644 --- a/platform/posix/transport/utest/plaintext_utest.c +++ b/platform/posix/transport/utest/plaintext_utest.c @@ -70,10 +70,10 @@ static uint8_t plaintextBuffer[ BUFFER_LEN ] = { 0 }; * one is used for the default case. */ static uint8_t errorNumbers[] = { - EBADF, ECONNRESET, EDESTADDRREQ, EINTR, - EINVAL, ENOTCONN, ENOTSOCK, EOPNOTSUPP, - ETIMEDOUT, EMSGSIZE, EPIPE, - EAGAIN, EWOULDBLOCK, UNKNOWN_ERRNO + EBADF, ECONNRESET, EDESTADDRREQ, EINTR, + EINVAL, ENOTCONN, ENOTSOCK, EOPNOTSUPP, + ETIMEDOUT, EMSGSIZE, EPIPE, + UNKNOWN_ERRNO }; /* ============================ UNITY FIXTURES ============================ */ @@ -175,6 +175,30 @@ void test_Plaintext_Disconnect_Invalid_Params( void ) TEST_ASSERT_EQUAL( SOCKETS_INVALID_PARAMETER, socketStatus ); } +/** + * @brief Test that invalid parameters returns an error. + */ +void test_Plaintext_Recv_Invalid_Params( void ) +{ + int32_t bytesReceived; + NetworkContext_t networkContext = { 0 }; + + bytesReceived = Plaintext_Recv( NULL, plaintextBuffer, BUFFER_LEN ); + TEST_ASSERT_EQUAL( -1, bytesReceived ); + + networkContext.pParams = NULL; + bytesReceived = Plaintext_Recv( &networkContext, plaintextBuffer, BUFFER_LEN ); + TEST_ASSERT_EQUAL( -1, bytesReceived ); + + networkContext.pParams = &plaintextParams; + bytesReceived = Plaintext_Recv( &networkContext, NULL, BUFFER_LEN ); + TEST_ASSERT_EQUAL( -1, bytesReceived ); + + networkContext.pParams = &plaintextParams; + bytesReceived = Plaintext_Recv( &networkContext, plaintextBuffer, 0 ); + TEST_ASSERT_EQUAL( -1, bytesReceived ); +} + /** * @brief Test that #Plaintext_Recv returns an error when #recv fails to * receive data over the network stack. @@ -255,6 +279,54 @@ void test_Plaintext_Recv_Network_Error( void ) } } +/** + * @brief Test that #Plaintext_Recv returns 0 bytes when the socket has blocked for + * timeout. + */ +void test_Plaintext_Recv_Socket_Timeout_Blocked( void ) +{ + int32_t bytesReceived; + + poll_ExpectAnyArgsAndReturn( 1 ); + recv_ExpectAnyArgsAndReturn( SEND_RECV_ERROR ); + errno = EAGAIN; + bytesReceived = Plaintext_Recv( &networkContext, + plaintextBuffer, + 1U ); + TEST_ASSERT_EQUAL( 0, bytesReceived ); + + poll_ExpectAnyArgsAndReturn( 1 ); + recv_ExpectAnyArgsAndReturn( SEND_RECV_ERROR ); + errno = EWOULDBLOCK; + bytesReceived = Plaintext_Recv( &networkContext, + plaintextBuffer, + 1U ); + TEST_ASSERT_EQUAL( 0, bytesReceived ); +} + +/** + * @brief Test that invalid parameters returns an error. + */ +void test_Plaintext_Send_Invalid_Params( void ) +{ + int32_t bytesSent; + NetworkContext_t networkContext = { 0 }; + + bytesSent = Plaintext_Send( NULL, plaintextBuffer, BUFFER_LEN ); + TEST_ASSERT_EQUAL( -1, bytesSent ); + + networkContext.pParams = NULL; + bytesSent = Plaintext_Send( &networkContext, plaintextBuffer, BUFFER_LEN ); + TEST_ASSERT_EQUAL( -1, bytesSent ); + + networkContext.pParams = &plaintextParams; + bytesSent = Plaintext_Send( &networkContext, NULL, BUFFER_LEN ); + TEST_ASSERT_EQUAL( -1, bytesSent ); + + bytesSent = Plaintext_Send( &networkContext, plaintextBuffer, 0 ); + TEST_ASSERT_EQUAL( -1, bytesSent ); +} + /** * @brief Test the happy path case when #Plaintext_Send is able to send all bytes * over the network stack successfully. @@ -337,3 +409,28 @@ void test_Plaintext_Send_Poll_Error( void ) BYTES_TO_SEND ); TEST_ASSERT_EQUAL( SEND_RECV_ERROR, bytesSent ); } + +/** + * @brief Test that #Plaintext_Send returns 0 bytes when the socket has blocked for + * timeout. + */ +void test_Plaintext_Send_Socket_Timeout_Blocked( void ) +{ + int32_t bytesSent; + + poll_ExpectAnyArgsAndReturn( 1 ); + send_ExpectAnyArgsAndReturn( SEND_RECV_ERROR ); + errno = EAGAIN; + bytesSent = Plaintext_Send( &networkContext, + plaintextBuffer, + BYTES_TO_SEND ); + TEST_ASSERT_EQUAL( 0, bytesSent ); + + poll_ExpectAnyArgsAndReturn( 1 ); + send_ExpectAnyArgsAndReturn( SEND_RECV_ERROR ); + errno = EWOULDBLOCK; + bytesSent = Plaintext_Send( &networkContext, + plaintextBuffer, + BYTES_TO_SEND ); + TEST_ASSERT_EQUAL( 0, bytesSent ); +}