static void _networkReceiveCallback()

in device_firmware/libraries/c_sdk/standard/https/src/iot_https_client.c [1040:1328]


static void _networkReceiveCallback( void * pNetworkConnection,
                                     void * pReceiveContext )
{
    HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );

    IotHttpsReturnCode_t flushStatus = IOT_HTTPS_OK;
    IotHttpsReturnCode_t disconnectStatus = IOT_HTTPS_OK;
    IotHttpsReturnCode_t scheduleStatus = IOT_HTTPS_OK;
    _httpsConnection_t * pHttpsConnection = ( _httpsConnection_t * ) pReceiveContext;
    _httpsResponse_t * pCurrentHttpsResponse = NULL;
    _httpsRequest_t * pNextHttpsRequest = NULL;
    IotLink_t * pQItem = NULL;
    bool fatalDisconnect = false;

    /* The network connection is already in the connection context. */
    ( void ) pNetworkConnection;

    /* Get the response from the response queue. */
    IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
    pQItem = IotDeQueue_PeekHead( &( pHttpsConnection->respQ ) );
    IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );

    /* If the receive callback is invoked and there is no response expected, then this a violation of the HTTP/1.1
     * protocol. */
    if( pQItem == NULL )
    {
        IotLogError( "Received data on the network, when no response was expected..." );
        fatalDisconnect = true;
        HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_NETWORK_ERROR );
    }

    /* Set the current HTTP response context to use. */
    pCurrentHttpsResponse = IotLink_Container( _httpsResponse_t, pQItem, link );

    /* If the receive callback has invoked, but the request associated with this response has not finished sending
     * to the server, then this is a violation of the HTTP/1.1 protocol.  */
    if( pCurrentHttpsResponse->reqFinishedSending == false )
    {
        IotLogError( "Received response data on the network when the request was not finished sending. This is unexpected." );
        fatalDisconnect = true;
        HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_NETWORK_ERROR );
    }

    /* If the current response was cancelled, then don't bother receiving the headers and body. */
    if( pCurrentHttpsResponse->cancelled )
    {
        IotLogDebug( "Response ID: %d was cancelled.", pCurrentHttpsResponse );
        HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_RECEIVE_ABORT );
    }

    /* Reset the http-parser state to an initial state. This is done so that a new response can be parsed from the
     * beginning. */
    pCurrentHttpsResponse->parserState = PARSER_STATE_NONE;

    /* Receive the response from the network. */
    /* Receive the headers first. */
    status = _receiveHttpsHeaders( pHttpsConnection, pCurrentHttpsResponse );

    if( HTTPS_FAILED( status ) )
    {
        if( status == IOT_HTTPS_PARSING_ERROR )
        {
            /* There was an error parsing the HTTPS response body. This may be an indication of a server that does
             *  not adhere to protocol correctly. We should disconnect. */
            IotLogError( "Failed to parse the HTTPS headers for response %d, Error code: %d.",
                         pCurrentHttpsResponse,
                         status );
            fatalDisconnect = true;
        }
        else if( status == IOT_HTTPS_NETWORK_ERROR )
        {
            /* Given the function signature of IotNetworkInterface_t.receive, we can only receive 0 to the number of bytes
             * requested. Receiving less than the number of bytes requests is OK since we do not how much data is expected, so
             * we ask for the full size of the receive buffer. Thereofore, the only error that can be returned from receiving
             * the headers or body is a timeout. We always disconnect from the network when there is a timeout because the
             * server may be slow to respond. If the server happens to send the response later at the same time another response
             * is waiting in the queue, then the workflow is corrupted. Pipelining is not current supported in this library. */
            IotLogError( "Network error receiving the HTTPS headers for response %d. Error code: %d",
                         pCurrentHttpsResponse,
                         status );
            fatalDisconnect = true;
        }
        else /* Any other error. */
        {
            IotLogError( "Failed to retrive the HTTPS body for response %d. Error code: %d", pCurrentHttpsResponse, status );
        }

        HTTPS_GOTO_CLEANUP();
    }

    /* Check if we received all of the headers into the header buffer. */
    if( pCurrentHttpsResponse->parserState < PARSER_STATE_HEADERS_COMPLETE )
    {
        IotLogDebug( "Headers received on the network did not all fit into the configured header buffer for response %d."
                     " The length of the headers buffer is: %d",
                     pCurrentHttpsResponse,
                     pCurrentHttpsResponse->pHeadersEnd - pCurrentHttpsResponse->pHeaders );
        /* It is not error if the headers did not all fit into the buffer. */
    }

    /* Receive the body. */
    if( pCurrentHttpsResponse->isAsync )
    {
        status = _receiveHttpsBodyAsync( pCurrentHttpsResponse );
    }
    else
    {
        /* Otherwise receive synchronously. */
        status = _receiveHttpsBodySync( pCurrentHttpsResponse );
    }

    if( HTTPS_FAILED( status ) )
    {
        if( status == IOT_HTTPS_RECEIVE_ABORT )
        {
            /* If the request was cancelled, this is logged, but does not close the connection. */
            IotLogDebug( "User cancelled during the async readReadyCallback() for response %d.",
                         pCurrentHttpsResponse );
        }
        else if( status == IOT_HTTPS_PARSING_ERROR )
        {
            /* There was an error parsing the HTTPS response body. This may be an indication of a server that does
             *  not adhere to protocol correctly. We should disconnect. */
            IotLogError( "Failed to parse the HTTPS body for response %d, Error code: %d.",
                         pCurrentHttpsResponse,
                         status );
            fatalDisconnect = true;
        }
        else if( status == IOT_HTTPS_NETWORK_ERROR )
        {
            /* We walways disconnect for a network error because failure to receive the HTTPS body will result in a
             * corruption of the workflow. */
            IotLogError( "Network error receiving the HTTPS body for response %d. Error code: %d",
                         pCurrentHttpsResponse,
                         status );
            fatalDisconnect = true;
        }
        else /* Any other error. */
        {
            IotLogError( "Failed to retrive the HTTPS body for response %d. Error code: %d", pCurrentHttpsResponse, status );
        }

        HTTPS_GOTO_CLEANUP();
    }

    IOT_FUNCTION_CLEANUP_BEGIN();

    /* Disconnect and return in the event of an out-of-order response. If a response is received out of order
     * pCurrentHttpsResponse will be NULL because there will be no response in the connection's response queue.
     * If a response is received out of order that is an indication of a rogue server. */
    if( fatalDisconnect && !pCurrentHttpsResponse )
    {
        IotLogError( "An out-of-order response was received. The connection will be disconnected." );
        disconnectStatus = IotHttpsClient_Disconnect( pHttpsConnection );

        if( HTTPS_FAILED( disconnectStatus ) )
        {
            IotLogWarn( "Failed to disconnect after an out of order response. Error code: %d.", disconnectStatus );
        }

        /* In this case this routine returns immediately after to avoid further uses of pCurrentHttpsResponse. */
        return;
    }

    /* Report errors back to the application. */
    if( HTTPS_FAILED( status ) )
    {
        if( pCurrentHttpsResponse->isAsync && pCurrentHttpsResponse->pCallbacks->errorCallback )
        {
            pCurrentHttpsResponse->pCallbacks->errorCallback( pCurrentHttpsResponse->pUserPrivData, NULL, pCurrentHttpsResponse, status );
        }

        pCurrentHttpsResponse->syncStatus = status;
    }

    /* If this is not a persistent request, the server would have closed it after sending a response, but we
     * disconnect anyways. If we are disconnecting there is is no point in wasting time
     * flushing the network. If the network is being disconnected we also do not schedule any pending requests. */
    if( fatalDisconnect || pCurrentHttpsResponse->isNonPersistent )
    {
        IotLogDebug( "Disconnecting response %d.", pCurrentHttpsResponse );
        disconnectStatus = IotHttpsClient_Disconnect( pHttpsConnection );

        if( ( pCurrentHttpsResponse != NULL ) && pCurrentHttpsResponse->isAsync && pCurrentHttpsResponse->pCallbacks->connectionClosedCallback )
        {
            pCurrentHttpsResponse->pCallbacks->connectionClosedCallback( pCurrentHttpsResponse->pUserPrivData, pHttpsConnection, disconnectStatus );
        }

        if( HTTPS_FAILED( disconnectStatus ) )
        {
            IotLogWarn( "Failed to disconnect response %d. Error code: %d.", pCurrentHttpsResponse, disconnectStatus );
        }

        /* If we disconnect, we do not process anymore requests. */
    }
    else
    {
        /* Set the processing state of the buffer to finished for completeness. This is also to prevent the parsing of the flush
         * data from incrementing any pointer in the HTTP response context. */
        pCurrentHttpsResponse->bufferProcessingState = PROCESSING_STATE_FINISHED;

        /* Flush the socket of the rest of the data if there is data left from this response. We need to do this
         * so that for the next request on this connection, there is not left over response from this request in
         * the next response buffer.
         *
         * If a continuous stream of data is coming in from the connection, with an unknown end, we may not be able to
         * flush the network data. It may sit here forever. A continuous stream should be ingested with the async workflow.
         *
         * All network errors are ignore here because network read will have read the data from network buffer despite
         * errors. */
        flushStatus = _flushHttpsNetworkData( pHttpsConnection, pCurrentHttpsResponse );

        if( flushStatus == IOT_HTTPS_PARSING_ERROR )
        {
            IotLogWarn( "There an error parsing the network flush data. The network buffer might not be fully flushed." );
        }
        else if( flushStatus != IOT_HTTPS_OK )
        {
            IotLogDebug( "Network error when flushing the https network data: %d", flushStatus );
        }

        IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
        /* Get the next request to process. */
        pQItem = IotDeQueue_PeekHead( &( pHttpsConnection->reqQ ) );
        IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );

        /* If there is a next request to process, then create a taskpool job to send the request. */
        if( pQItem != NULL )
        {
            /* Set this next request to send. */
            pNextHttpsRequest = IotLink_Container( _httpsRequest_t, pQItem, link );

            if( pNextHttpsRequest->scheduled == false )
            {
                IotLogDebug( "Request %d is next in the queue. Now scheduling a task to send the request.", pNextHttpsRequest );
                scheduleStatus = _scheduleHttpsRequestSend( pNextHttpsRequest );

                /* If there was an error with scheduling the new task, then report it. */
                if( HTTPS_FAILED( scheduleStatus ) )
                {
                    IotLogError( "Error scheduling HTTPS request %d. Error code: %d", pNextHttpsRequest, scheduleStatus );

                    if( pNextHttpsRequest->isAsync && pNextHttpsRequest->pCallbacks->errorCallback )
                    {
                        pNextHttpsRequest->pCallbacks->errorCallback( pNextHttpsRequest->pUserPrivData, pNextHttpsRequest, NULL, scheduleStatus );
                    }
                    else
                    {
                        pNextHttpsRequest->pHttpsResponse->syncStatus = scheduleStatus;
                    }
                }
            }
        }
        else
        {
            IotLogDebug( "Network receive callback found the request queue empty. A network send task was not scheduled." );
        }
    }

    /* Dequeue response from the response queue now that it is finished. */
    IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );

    /* There could be a scenario where the request fails to send and the network server still responds,
     * In this case, the failed response will have been cancelled and removed from the queue. If the network
     * server still got a response, then the safest way to remove the current response is to remove it explicitly
     * from the queue instead of dequeuing the header of the queue which might not be the current response. */
    if( IotLink_IsLinked( &( pCurrentHttpsResponse->link ) ) )
    {
        IotDeQueue_Remove( &( pCurrentHttpsResponse->link ) );
    }

    IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );

    /* The first if-case below notifies IotHttpsClient_SendSync() that the response is finished receiving. When
     * IotHttpsClient_SendSync() returns the user is allowed to modify the user buffer used for the response context.
     * In the asynchronous case, the responseCompleteCallback notifies the application that the user buffer used for the
     * response context can be modified. Posting to the respFinishedSem or calling the responseCompleteCallback MUST be
     * mutually exclusive by wrapping in an if/else. If these were separate if-cases, then there could be a context
     * switch in between where the application modifies the buffer causing the next if-case to be executed. */
    if( pCurrentHttpsResponse->isAsync == false )
    {
        IotSemaphore_Post( &( pCurrentHttpsResponse->respFinishedSem ) );
    }
    else if( pCurrentHttpsResponse->pCallbacks->responseCompleteCallback )
    {
        /* Signal to a synchronous reponse that the response is complete. */
        pCurrentHttpsResponse->pCallbacks->responseCompleteCallback( pCurrentHttpsResponse->pUserPrivData, pCurrentHttpsResponse, status, pCurrentHttpsResponse->status );
    }
}