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 );
}
}