2 * Amazon FreeRTOS HTTPS Client V1.1.0
\r
3 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
\r
5 * Permission is hereby granted, free of charge, to any person obtaining a copy of
\r
6 * this software and associated documentation files (the "Software"), to deal in
\r
7 * the Software without restriction, including without limitation the rights to
\r
8 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
\r
9 * the Software, and to permit persons to whom the Software is furnished to do so,
\r
10 * subject to the following conditions:
\r
12 * The above copyright notice and this permission notice shall be included in all
\r
13 * copies or substantial portions of the Software.
\r
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
\r
17 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
\r
18 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
\r
19 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
\r
20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
22 * http://aws.amazon.com/freertos
\r
23 * http://www.FreeRTOS.org
\r
27 * @file iot_https_client.c
\r
28 * @brief Implementation of the user-facing functions of the Amazon FreeRTOS HTTPS Client library.
\r
31 /* The config header is always included first. */
\r
32 #include "iot_config.h"
\r
34 /* HTTPS Client library private includes. */
\r
35 #include "private/iot_https_internal.h"
\r
37 /*-----------------------------------------------------------*/
\r
40 * @brief Partial HTTPS request first line.
\r
42 * This is used for the calculation of the requestUserBufferMinimumSize.
\r
43 * The minimum path is "/" because we cannot know how long the application requested path is is going to be.
\r
44 * CONNECT is the longest string length HTTP method according to RFC 2616.
\r
46 #define HTTPS_PARTIAL_REQUEST_LINE HTTPS_CONNECT_METHOD " " HTTPS_EMPTY_PATH " " HTTPS_PROTOCOL_VERSION
\r
49 * @brief The User-Agent header line string.
\r
51 * This is of the form:
\r
52 * "User-Agent: <configured-user-agent>\r\n"
\r
53 * This is used for the calculation of the requestUserBufferMinimumSize.
\r
55 #define HTTPS_USER_AGENT_HEADER_LINE HTTPS_USER_AGENT_HEADER HTTPS_HEADER_FIELD_SEPARATOR IOT_HTTPS_USER_AGENT HTTPS_END_OF_HEADER_LINES_INDICATOR
\r
58 * @brief The Host header line with the field only and not the value.
\r
60 * This is of the form:
\r
62 * This is used for the calculation of the requestUserBufferMinimumSize. The Host value is not specified because we
\r
63 * cannot anticipate what server the client is making requests to.
\r
65 #define HTTPS_PARTIAL_HOST_HEADER_LINE HTTPS_HOST_HEADER HTTPS_HEADER_FIELD_SEPARATOR HTTPS_END_OF_HEADER_LINES_INDICATOR
\r
68 * String constants for the Connection header and possible values.
\r
70 * This is used for writing headers automatically during the sending of the HTTP request.
\r
71 * "Connection: keep-alive\r\n" is written automatically for a persistent connection.
\r
72 * "Connection: close\r\n" is written automatically for a non-persistent connection.
\r
74 #define HTTPS_CONNECTION_KEEP_ALIVE_HEADER_LINE HTTPS_CONNECTION_HEADER HTTPS_HEADER_FIELD_SEPARATOR HTTPS_CONNECTION_KEEP_ALIVE_HEADER_VALUE HTTPS_END_OF_HEADER_LINES_INDICATOR /**< @brief String literal for "Connection: keep-alive\r\n". */
\r
75 #define HTTPS_CONNECTION_CLOSE_HEADER_LINE HTTPS_CONNECTION_HEADER HTTPS_HEADER_FIELD_SEPARATOR HTTPS_CONNECTION_CLOSE_HEADER_VALUE HTTPS_END_OF_HEADER_LINES_INDICATOR /**< @brief String literal for "Connection: close\r\n". */
\r
78 * @brief The length of the "Connection: keep-alive\r\n" header.
\r
80 * This is used for sizing a local buffer for the final headers to send that include the "Connection: keep-alive\r\n"
\r
83 * This is used to initialize a local array for the final headers to send.
\r
85 #define HTTPS_CONNECTION_KEEP_ALIVE_HEADER_LINE_LENGTH ( 24 )
\r
88 * Indicates for the http-parser parsing execution function to tell it to keep parsing or to stop parsing.
\r
90 * A value of 0 means the parser should keep parsing if there is more unparsed length.
\r
91 * A value greater than 0 tells the parser to stop parsing.
\r
93 #define KEEP_PARSING ( ( int ) 0 ) /**< @brief Indicator in the http-parser callback to keep parsing when the function returns. */
\r
94 #define STOP_PARSING ( ( int ) 1 ) /**< @brief Indicator in the http-parser callback to stop parsing when the function returns. */
\r
96 /*-----------------------------------------------------------*/
\r
99 * @brief Minimum size of the request user buffer.
\r
101 * The request user buffer is configured in IotHttpsClientRequestInfo_t.userBuffer. This buffer stores the internal context
\r
102 * of the request and then the request headers right after. The minimum size for the buffer is the total size of the
\r
103 * internal request context, the HTTP formatted request line, the User-Agent header line, and the part of the Host
\r
106 const uint32_t requestUserBufferMinimumSize = sizeof( _httpsRequest_t ) +
\r
107 sizeof( HTTPS_PARTIAL_REQUEST_LINE ) +
\r
108 sizeof( HTTPS_USER_AGENT_HEADER_LINE ) +
\r
109 sizeof( HTTPS_PARTIAL_HOST_HEADER_LINE );
\r
112 * @brief Minimum size of the response user buffer.
\r
114 * The response user buffer is configured in IotHttpsClientRequestInfo_t.userBuffer. This buffer stores the internal context
\r
115 * of the response and then the response headers right after. This minimum size is calculated for the case if no bytes
\r
116 * from the HTTP response headers are to be stored.
\r
118 const uint32_t responseUserBufferMinimumSize = sizeof( _httpsResponse_t );
\r
121 * @brief Minimum size of the connection user buffer.
\r
123 * The connection user buffer is configured in IotHttpsConnectionInfo_t.userBuffer. This buffer stores the internal context of the
\r
126 const uint32_t connectionUserBufferMinimumSize = sizeof( _httpsConnection_t );
\r
128 /*-----------------------------------------------------------*/
\r
131 * @brief Callback from http-parser to indicate the start of the HTTP response message is reached.
\r
133 * See https://github.com/nodejs/http-parser for more information.
\r
135 * @param[in] pHttpParser - http-parser state structure.
\r
137 * @return 0 to tell http-parser to keep parsing.
\r
138 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_message_begin.
\r
140 static int _httpParserOnMessageBeginCallback( http_parser * pHttpParser );
\r
143 * @brief Callback from http-parser to indicate it found the HTTP response status code.
\r
145 * See https://github.com/nodejs/http-parser for more information.
\r
147 * @param[in] pHttpParser - http-parser state structure.
\r
148 * @param[in] pLoc - Pointer to the HTTP response status code string in the response message buffer.
\r
149 * @param[in] length - The length of the HTTP response status code string.
\r
151 * @return 0 to tell http-parser to keep parsing.
\r
152 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_status.
\r
154 static int _httpParserOnStatusCallback( http_parser * pHttpParser,
\r
159 * @brief Callback from http-parser to indicate it found an HTTP response header field.
\r
161 * If only part of the header field was returned here in this callback, then this callback will be invoked again the
\r
162 * next time the parser executes on the next part of the header field.
\r
164 * See https://github.com/nodejs/http-parser for more information.
\r
166 * @param[in] pHttpParser - http-parser state structure.
\r
167 * @param[in] pLoc - Pointer to the header field string in the response message buffer.
\r
168 * @param[in] length - The length of the header field.
\r
170 * @return 0 to tell http-parser to keep parsing.
\r
171 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_header_field.
\r
173 static int _httpParserOnHeaderFieldCallback( http_parser * pHttpParser,
\r
178 * @brief Callback from http-parser to indicate it found an HTTP response header value.
\r
180 * This value corresponds to the field that was found in the _httpParserOnHeaderFieldCallback() called immediately
\r
181 * before this callback was called.
\r
183 * If only part of the header value was returned here in this callback, then this callback will be invoked again the
\r
184 * next time the parser executes on the next part of the header value.
\r
186 * See https://github.com/nodejs/http-parser for more information.
\r
188 * @param[in] pHttpParser - http-parser state structure.
\r
189 * @param[in] pLoc - Pointer to the header value string in the response message buffer.
\r
190 * @param[in] length - The length of the header value.
\r
192 * @return 0 to tell http-parser to keep parsing.
\r
193 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_header_value.
\r
195 static int _httpParserOnHeaderValueCallback( http_parser * pHttpParser,
\r
200 * @brief Callback from http-parser to indicate it reached the end of the headers in the HTTP response message.
\r
202 * The end of the headers is signalled in a HTTP response message by another "\r\n" after the final header line.
\r
204 * See https://github.com/nodejs/http-parser for more information.
\r
206 * @param[in] pHttpParser - http-parser state structure.
\r
208 * @return 0 to tell http-parser to keep parsing.
\r
209 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_headers_complete.
\r
211 static int _httpParserOnHeadersCompleteCallback( http_parser * pHttpParser );
\r
214 * @brief Callback from http-parser to indicate it found HTTP response body.
\r
216 * This callback will be invoked multiple times if the response body is of "Transfer-Encoding: chunked".
\r
217 * _httpParserOnChunkHeaderCallback() will be invoked first, then _httpParserOnBodyCallback(), then
\r
218 * _httpParserOnChunkCompleteCallback(), then repeated back to _httpParserOnChunkHeaderCallback() if there are more
\r
221 * See https://github.com/nodejs/http-parser for more information.
\r
223 * @param[in] pHttpParser - http-parser state structure.
\r
224 * @param[in] pLoc - Pointer to the body string in the response message buffer.
\r
225 * @param[in] length - The length of the body found.
\r
227 * @return 0 to tell http-parser to keep parsing.
\r
228 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_body.
\r
230 static int _httpParserOnBodyCallback( http_parser * pHttpParser,
\r
235 * @brief Callback from http-parser to indicate it reached the end of the HTTP response message.
\r
237 * The end of the message is signalled in a HTTP response message by another "\r\n" after the final header line, with no
\r
238 * entity body; or it is signaled by "\r\n" at the end of the entity body.
\r
240 * For a Transfer-Encoding: chunked type of response message, the end of the message is signalled by a terminating
\r
241 * chunk header with length zero.
\r
243 * See https://github.com/nodejs/http-parser for more information.
\r
245 * @param[in] pHttpParser - http-parser state structure.
\r
247 * @return 0 to tell http-parser to keep parsing.
\r
248 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_message_complete.
\r
250 static int _httpParserOnMessageCompleteCallback( http_parser * pHttpParser );
\r
252 /* This code prints debugging information and is, therefore, compiled only when
\r
253 * log level is set to IOT_LOG_DEBUG. */
\r
254 #if ( LIBRARY_LOG_LEVEL == IOT_LOG_DEBUG )
\r
257 * @brief Callback from http-parser to indicate it found an HTTP Transfer-Encoding: chunked header.
\r
259 * Transfer-Encoding: chunked headers are embedded in the HTTP response entity body by a "\r\n" followed by the size of
\r
260 * the chunk followed by another "\r\n".
\r
262 * If only part of the header field was returned here in this callback, then this callback will be invoked again the
\r
263 * next time the parser executes on the next part of the header field.
\r
265 * See https://github.com/nodejs/http-parser for more information.
\r
267 * @param[in] pHttpParser - http-parser state structure.
\r
269 * @return 0 to tell http-parser to keep parsing.
\r
270 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_chunk_header.
\r
272 static int _httpParserOnChunkHeaderCallback( http_parser * pHttpParser );
\r
275 * @brief Callback from http-parser to indicate it reached the end of an HTTP response message "chunk".
\r
277 * A chunk is complete when the chunk header size is read fully in the body.
\r
279 * See https://github.com/nodejs/http-parser for more information.
\r
281 * @param[in] pHttpParser - http-parser state structure.
\r
283 * @return 0 to tell http-parser to keep parsing.
\r
284 * 1 to tell http-parser that parsing should stop return from http_parser_execute with error HPE_CB_chunk_complete.
\r
286 static int _httpParserOnChunkCompleteCallback( http_parser * pHttpParser );
\r
290 * @brief Network receive callback for the HTTPS Client library.
\r
292 * This function is called by the network layer whenever data is available for the HTTP library.
\r
294 * @param[in] pNetworkConnection - The network connection with the HTTPS connection, passed by the network stack.
\r
295 * @param[in] pReceiveContext - A pointer to the HTTPS Client connection handle for which the packet was received.
\r
297 static void _networkReceiveCallback( void * pNetworkConnection,
\r
298 void * pReceiveContext );
\r
301 * @brief Connects to HTTPS server and initializes the connection context.
\r
303 * @param[out] pConnHandle - The out parameter to return handle representing the open connection.
\r
304 * @param[in] pConnInfo - The connection configuration.
\r
306 * @return #IOT_HTTPS_OK if the connection context initialization was successful.
\r
307 * #IOT_HTTPS_CONNECTION_ERROR if the connection failed.
\r
308 * #IOT_HTTPS_INTERNAL_ERROR if the context initialization failed.
\r
310 static IotHttpsReturnCode_t _createHttpsConnection( IotHttpsConnectionHandle_t * pConnHandle,
\r
311 IotHttpsConnectionInfo_t * pConnInfo );
\r
314 * @brief Disconnects from the network.
\r
316 * @param[in] pHttpsConnection - HTTPS connection handle.
\r
318 static void _networkDisconnect( _httpsConnection_t * pHttpsConnection );
\r
321 * @brief Destroys the network connection.
\r
323 * @param[in] pHttpsConnection - HTTPS connection handle.
\r
325 static void _networkDestroy( _httpsConnection_t * pHttpsConnection );
\r
328 * @brief Add a header to the current HTTP request.
\r
330 * The headers are stored in reqHandle->pHeaders.
\r
332 * @param[in] pHttpsRequest - HTTP request context.
\r
333 * @param[in] pName - The name of the header to add.
\r
334 * @param[in] nameLen - The length of the header name string.
\r
335 * @param[in] pValue - The buffer containing the value string.
\r
336 * @param[in] valueLen - The length of the header value string.
\r
338 * @return #IOT_HTTPS_OK if the header was added to the request successfully.
\r
339 * #IOT_HTTPS_INSUFFICIENT_MEMORY if there was not enough room in the IotHttpsRequestHandle_t->pHeaders.
\r
341 static IotHttpsReturnCode_t _addHeader( _httpsRequest_t * pHttpsRequest,
\r
342 const char * pName,
\r
344 const char * pValue,
\r
345 uint32_t valueLen );
\r
348 * @brief Send data on the network.
\r
350 * @param[in] pHttpsConnection - HTTP connection context.
\r
351 * @param[in] pBuf - The buffer containing the data to send.
\r
352 * @param[in] len - The length of the data to send.
\r
354 * @return #IOT_HTTPS_OK if the data sent successfully.
\r
355 * #IOT_HTTPS_NETWORK_ERROR if there was an error sending the data on the network.
\r
357 static IotHttpsReturnCode_t _networkSend( _httpsConnection_t * pHttpsConnection,
\r
362 * @brief Receive data on the network.
\r
364 * @param[in] pHttpsConnection - HTTP connection context.
\r
365 * @param[in] pBuf - The buffer to receive the data into.
\r
366 * @param[in] bufLen - The length of the data to receive.
\r
367 * @param[in] numBytesRecv - The number of bytes read from the network.
\r
369 * @return #IOT_HTTPS_OK if the data was received successfully.
\r
370 * #IOT_HTTPS_NETWORK_ERROR if we timedout trying to receive data from the network.
\r
372 static IotHttpsReturnCode_t _networkRecv( _httpsConnection_t * pHttpsConnection,
\r
375 size_t * numBytesRecv );
\r
378 * @brief Send all of the HTTP request headers in the pHeadersBuf and the final Content-Length and Connection headers.
\r
380 * All of the headers in headerbuf are sent first followed by the computed content length and persistent connection
\r
383 * @param[in] pHttpsConnection - HTTP connection context.
\r
384 * @param[in] pHeadersBuf - The buffer containing the request headers to send. This buffer must contain HTTP headers
\r
385 * lines without the indicator for the the end of the HTTP headers.
\r
386 * @param[in] headersLength - The length of the request headers to send.
\r
387 * @param[in] isNonPersistent - Indicator of whether the connection is persistent or not.
\r
388 * @param[in] contentLength - The length of the request body used for automatically creating a "Content-Length" header.
\r
390 * @return #IOT_HTTPS_OK if the headers were fully sent successfully.
\r
391 * #IOT_HTTPS_NETWORK_ERROR if there was an error receiving the data on the network.
\r
393 static IotHttpsReturnCode_t _sendHttpsHeaders( _httpsConnection_t * pHttpsConnection,
\r
394 uint8_t * pHeadersBuf,
\r
395 uint32_t headersLength,
\r
396 bool isNonPersistent,
\r
397 uint32_t contentLength );
\r
400 * @brief Send all of the HTTP request body in pBodyBuf.
\r
402 * @param[in] pHttpsConnection - HTTP connection context.
\r
403 * @param[in] pBodyBuf - Buffer of the request body to send.
\r
404 * @param[in] bodyLength - The length of the body to send.
\r
406 * @return #IOT_HTTPS_OK if the body was fully sent successfully.
\r
407 * #IOT_HTTPS_NETWORK_ERROR if there was an error receiving the data on the network.
\r
409 static IotHttpsReturnCode_t _sendHttpsBody( _httpsConnection_t * pHttpsConnection,
\r
410 uint8_t * pBodyBuf,
\r
411 uint32_t bodyLength );
\r
414 * @brief Parse the HTTP response message in pBuf.
\r
416 * @param[in] pHttpParserInfo - Pointer to the information containing the instance of the http-parser and the execution function.
\r
417 * @param[in] pBuf - The buffer containing the data to parse.
\r
418 * @param[in] len - The length of data to parse.
\r
420 * @return #IOT_HTTPS_OK if the data was parsed successfully.
\r
421 * #IOT_HTTPS_PARSING_ERROR if there was an error with parsing the data.
\r
423 static IotHttpsReturnCode_t _parseHttpsMessage( _httpParserInfo_t * pHttpParserInfo,
\r
428 * @brief Receive any part of an HTTP response.
\r
430 * This function is used for both receiving the body into the body buffer and receiving the header into the header
\r
433 * @param[in] pHttpsConnection - HTTP Connection context.
\r
434 * @param[in] pParser - Pointer to the instance of the http-parser.
\r
435 * @param[in] pCurrentParserState - The current state of what has been parsed in the HTTP response.
\r
436 * @param[in] finalParserState - The final state of the parser expected after this function finishes.
\r
437 * @param[in] currentBufferProcessingState - The current buffer that is the HTTPS message is being received into.
\r
438 * @param[in] pBufCur - Pointer to the next location to write data into the buffer pBuf. This is a double pointer to update the response context buffer pointers.
\r
439 * @param[in] pBufEnd - Pointer to the end of the buffer to receive the HTTP response into.
\r
441 * @return #IOT_HTTPS_OK if we received the HTTP response message part successfully.
\r
442 * #IOT_HTTPS_PARSING_ERROR if there was an error with parsing the data.
\r
443 * #IOT_HTTPS_NETWORK_ERROR if there was an error receiving the data on the network.
\r
445 static IotHttpsReturnCode_t _receiveHttpsMessage( _httpsConnection_t * pHttpsConnection,
\r
446 _httpParserInfo_t * pParser,
\r
447 IotHttpsResponseParserState_t * pCurrentParserState,
\r
448 IotHttpsResponseParserState_t finalParserState,
\r
449 IotHttpsResponseBufferState_t currentBufferProcessingState,
\r
450 uint8_t ** pBufCur,
\r
451 uint8_t ** pBufEnd );
\r
454 * @brief Receive the HTTP response headers.
\r
456 * Receiving the response headers is always the first step in receiving the response, therefore the
\r
457 * pHttpsResponse->httpParserInfo will be initialized to a starting state when this function is called.
\r
459 * This function also sets internal states to indicate that the header buffer is being processed now for a new response.
\r
461 * @param[in] pHttpsConnection - HTTP connection context.
\r
462 * @param[in] pHttpsResponse - HTTP response context.
\r
464 * @return #IOT_HTTPS_OK if we received the HTTP headers successfully.
\r
465 * #IOT_HTTPS_PARSING_ERROR if there was an error with parsing the header buffer.
\r
466 * #IOT_HTTPS_NETWORK_ERROR if there was an error receiving the data on the network.
\r
468 static IotHttpsReturnCode_t _receiveHttpsHeaders( _httpsConnection_t * pHttpsConnection,
\r
469 _httpsResponse_t * pHttpsResponse );
\r
472 * @brief Receive the HTTP response body.
\r
474 * Sets internal states to indicate that the the body buffer is being processed now for a new response.
\r
476 * @param[in] pHttpsConnection - HTTP connection context.
\r
477 * @param[in] pHttpsResponse - HTTP response context.
\r
479 * @return #IOT_HTTPS_OK if we received the HTTP body successfully.
\r
480 * #IOT_HTTPS_PARSING_ERROR if there was an error with parsing the body buffer.
\r
481 * #IOT_HTTPS_NETWORK_ERROR if there was an error receiving the data on the network.
\r
483 static IotHttpsReturnCode_t _receiveHttpsBody( _httpsConnection_t * pHttpsConnection,
\r
484 _httpsResponse_t * pHttpsResponse );
\r
487 * @brief Read the rest of any HTTP response that may be on the network.
\r
489 * This reads the rest of any left over response data that might still be on the network buffers. We do not want this
\r
490 * data left over because it will spill into the header and body buffers of next response that we try to receive.
\r
492 * If we performed a request without a body and the headers received exceeds the size of the
\r
493 * pHttpsResponse->pHeaders buffer, then we need to flush the network buffer.
\r
495 * If the application configured the body buffer as null in IotHttpsResponseInfo_t.syncInfo.respData and the server
\r
496 * sends body in the response, but it exceeds the size of pHttpsResponse->pHeaders buffer, then we need to flush the
\r
499 * If the amount of body received on the network does not fit into a non-null IotHttpsResponseInfo_t.syncInfo.respData,
\r
500 * then we need to flush the network buffer.
\r
502 * If an asynchronous request cancels in the middle of a response process, after already sending the request message,
\r
503 * then we need to flush the network buffer.
\r
505 * @param[in] pHttpsConnection - HTTP connection context.
\r
506 * @param[in] pHttpsResponse - HTTP response context.
\r
508 * @return #IOT_HTTPS_OK if we successfully flushed the network data.
\r
509 * #IOT_HTTPS_PARSING_ERROR if there was an error with parsing the data.
\r
510 * #IOT_HTTPS_NETWORK_ERROR if there was an error receiving the data on the network.
\r
512 static IotHttpsReturnCode_t _flushHttpsNetworkData( _httpsConnection_t * pHttpsConnection,
\r
513 _httpsResponse_t * pHttpsResponse );
\r
516 * @brief Task pool job routine to send the HTTP request within the pUserContext.
\r
518 * @param[in] pTaskPool Pointer to the system task pool.
\r
519 * @param[in] pJob Pointer the to the HTTP request sending job.
\r
520 * @param[in] pUserContext Pointer to an HTTP request, passed as an opaque context.
\r
522 static void _sendHttpsRequest( IotTaskPool_t pTaskPool,
\r
523 IotTaskPoolJob_t pJob,
\r
524 void * pUserContext );
\r
528 * @brief Receive the HTTPS body specific to an asynchronous type of response.
\r
530 * @param[in] pHttpsResponse - HTTP response context.
\r
532 * @return #IOT_HTTPS_OK - If the the response body was received with no issues.
\r
533 * #IOT_HTTPS_RECEIVE_ABORT - If the request was cancelled by the Application
\r
534 * #IOT_HTTPS_PARSING_ERROR - If there was an issue parsing the HTTP response body.
\r
535 * #IOT_HTTPS_NETWORK_ERROR if there was an error receiving the data on the network.
\r
537 static IotHttpsReturnCode_t _receiveHttpsBodyAsync( _httpsResponse_t * pHttpsResponse );
\r
540 * @brief Receive the HTTPS body specific to a synchronous type of response.
\r
542 * @param[in] pHttpsResponse - HTTP response context.
\r
544 * @return #IOT_HTTPS_OK - If the the response body was received with no issues.
\r
545 * #IOT_HTTPS_MESSAGE_TOO_LARGE - If the body from the network is too large to fit into the configured body buffer.
\r
546 * #IOT_HTTPS_PARSING_ERROR - If there was an issue parsing the HTTP response body.
\r
547 * #IOT_HTTPS_NETWORK_ERROR if there was an error receiving the data on the network.
\r
549 static IotHttpsReturnCode_t _receiveHttpsBodySync( _httpsResponse_t * pHttpsResponse );
\r
552 * @brief Schedule the task to send the the HTTP request.
\r
554 * @param[in] pHttpsRequest - HTTP request context.
\r
556 * @return #IOT_HTTPS_OK - If the task to send the HTTP request was successfully scheduled.
\r
557 * #IOT_HTTPS_INTERNAL_ERROR - If a taskpool job could not be created.
\r
558 * #IOT_HTTPS_ASYNC_SCHEDULING_ERROR - If there was an error scheduling the job.
\r
560 IotHttpsReturnCode_t _scheduleHttpsRequestSend( _httpsRequest_t * pHttpsRequest );
\r
563 * @brief Add the request to the connection's request queue.
\r
565 * This will schedule a task if the request is first and only request in the queue.
\r
567 * @param[in] pHttpsRequest - HTTP request context.
\r
569 * @return #IOT_HTTPS_OK - If the request was successfully added to the connection's request queue.
\r
570 * #IOT_HTTPS_INTERNAL_ERROR - If a taskpool job could not be created.
\r
571 * #IOT_HTTPS_ASYNC_SCHEDULING_ERROR - If there was an error scheduling the job.
\r
573 IotHttpsReturnCode_t _addRequestToConnectionReqQ( _httpsRequest_t * pHttpsRequest );
\r
576 * @brief Cancel the HTTP request's processing.
\r
578 * pHttpsRequest->cancelled will be checked and the request cancelled if specified so at the following intervals:
\r
579 * - Before sending the HTTPS headers at the start of the scheduled sending of the HTTPS request.
\r
580 * - After Sending the HTTPS headers.
\r
581 * - After Sending the HTTPS body.
\r
583 * @param[in] pHttpsRequest - HTTP request context.
\r
585 static void _cancelRequest( _httpsRequest_t * pHttpsRequest );
\r
588 * @brief Cancel the HTTP response's processing.
\r
590 * pHttpsResponse->cancelled will be checked and the response cancelled if specified so at the following intervals:
\r
591 * - At the start of the network receive callback.
\r
592 * - After receiving the HTTPS headers.
\r
593 * - After Receiving the HTTPS body.
\r
595 * @param[in] pHttpsResponse - HTTP response context.
\r
597 static void _cancelResponse( _httpsResponse_t * pHttpsResponse );
\r
600 * @brief Initialize the input pHttpsResponse with pRespInfo.
\r
602 * @param[in] pRespHandle - Non-null HTTP response context.
\r
603 * @param[in] pRespInfo - Response configuration information.
\r
604 * @param[in] pHttpsRequest - HTTP request to grab async information, persistence, and method from.
\r
606 static IotHttpsReturnCode_t _initializeResponse( IotHttpsResponseHandle_t * pRespHandle,
\r
607 IotHttpsResponseInfo_t * pRespInfo,
\r
608 _httpsRequest_t * pHttpsRequest );
\r
611 * @brief Increment the pointer stored in pBufCur depending on the character found in there.
\r
613 * This function increments the pHeadersCur pointer further if the message ended with a header line delimiter.
\r
615 * @param[in] pBufCur - Pointer to the next location to write data into the buffer pBuf. This is a double pointer to update the response context buffer pointers.
\r
616 * @param[in] pBufEnd - Pointer to the end of the buffer to receive the HTTP response into.
\r
618 static void _incrementNextLocationToWriteBeyondParsed( uint8_t ** pBufCur,
\r
619 uint8_t ** pBufEnd );
\r
622 * @brief Send the HTTPS headers and body referenced in pHttpsRequest.
\r
624 * Sends both the headers and body over the network.
\r
626 * @param[in] pHttpsConnection - HTTPS connection context.
\r
627 * @param[in] pHttpsRequest - HTTPS request context.
\r
629 static IotHttpsReturnCode_t _sendHttpsHeadersAndBody( _httpsConnection_t * pHttpsConnection,
\r
630 _httpsRequest_t * pHttpsRequest );
\r
632 /*-----------------------------------------------------------*/
\r
635 * @brief Definition of the http-parser settings.
\r
637 * The http_parser_settings holds all of the callbacks invoked by the http-parser.
\r
639 static http_parser_settings _httpParserSettings = { 0 };
\r
641 /*-----------------------------------------------------------*/
\r
643 static int _httpParserOnMessageBeginCallback( http_parser * pHttpParser )
\r
645 int retVal = KEEP_PARSING;
\r
647 IotLogDebug( "Parser: Start of HTTPS Response message." );
\r
649 _httpsResponse_t * pHttpsResponse = ( _httpsResponse_t * ) ( pHttpParser->data );
\r
650 /* Set the state of the parser. The headers are at the start of the message always. */
\r
651 pHttpsResponse->parserState = PARSER_STATE_IN_HEADERS;
\r
655 /*-----------------------------------------------------------*/
\r
657 static int _httpParserOnStatusCallback( http_parser * pHttpParser,
\r
661 _httpsResponse_t * pHttpsResponse = ( _httpsResponse_t * ) ( pHttpParser->data );
\r
663 IotLogDebug( "Parser: Status %.*s retrieved from HTTPS response.", length, pLoc );
\r
665 /* Save the status code so it can be retrieved with IotHttpsClient_ReadResponseStatus(). */
\r
666 pHttpsResponse->status = ( uint16_t ) ( pHttpParser->status_code );
\r
668 /* If we are parsing the network data received in the header buffer then we
\r
669 * increment pHttpsResponse->pHeadersCur. The status line in the response is
\r
670 * part of the data stored in header buffer _httpResponse->pHeaders. */
\r
671 if( pHttpsResponse->bufferProcessingState == PROCESSING_STATE_FILLING_HEADER_BUFFER )
\r
673 /* pHeadersCur will never exceed the pHeadersEnd here because PROCESSING_STATE_FILLING_HEADER_BUFFER
\r
674 * indicates we are currently in the header buffer and the total size of the header buffer is passed
\r
675 * into http_parser_execute() as the maximum length to parse. */
\r
676 pHttpsResponse->pHeadersCur = ( uint8_t * ) ( pLoc += length );
\r
679 return KEEP_PARSING;
\r
682 /*-----------------------------------------------------------*/
\r
684 static int _httpParserOnHeaderFieldCallback( http_parser * pHttpParser,
\r
688 IotLogDebug( "Parser: HTTPS header field parsed %.*s", length, pLoc );
\r
690 _httpsResponse_t * pHttpsResponse = ( _httpsResponse_t * ) ( pHttpParser->data );
\r
692 /* If we are parsing the network data received in the header buffer then we can increment
\r
693 * pHttpsResponse->pHeadersCur. */
\r
694 if( pHttpsResponse->bufferProcessingState == PROCESSING_STATE_FILLING_HEADER_BUFFER )
\r
696 pHttpsResponse->pHeadersCur = ( uint8_t * ) ( pLoc += length );
\r
699 /* If the IotHttpsClient_ReadHeader() was called, then we check for the header field of interest. */
\r
700 if( pHttpsResponse->bufferProcessingState == PROCESSING_STATE_SEARCHING_HEADER_BUFFER )
\r
702 if( pHttpsResponse->readHeaderFieldLength != length )
\r
704 pHttpsResponse->foundHeaderField = false;
\r
706 else if( strncmp( pHttpsResponse->pReadHeaderField, pLoc, length ) == 0 )
\r
708 pHttpsResponse->foundHeaderField = true;
\r
712 return KEEP_PARSING;
\r
715 /*-----------------------------------------------------------*/
\r
717 static int _httpParserOnHeaderValueCallback( http_parser * pHttpParser,
\r
721 int retVal = KEEP_PARSING;
\r
723 IotLogDebug( "Parser: HTTPS header value parsed %.*s", length, pLoc );
\r
724 _httpsResponse_t * pHttpsResponse = ( _httpsResponse_t * ) ( pHttpParser->data );
\r
726 /* If we are parsing the network data received in the header buffer then we can increment
\r
727 * pHttpsResponse->pHeadersCur. */
\r
728 if( pHttpsResponse->bufferProcessingState == PROCESSING_STATE_FILLING_HEADER_BUFFER )
\r
730 pHttpsResponse->pHeadersCur = ( uint8_t * ) ( pLoc += length );
\r
733 /* If the IotHttpsClient_ReadHeader() was called, then we check if we found the header field of interest. */
\r
734 if( pHttpsResponse->bufferProcessingState == PROCESSING_STATE_SEARCHING_HEADER_BUFFER )
\r
736 if( pHttpsResponse->foundHeaderField )
\r
738 pHttpsResponse->pReadHeaderValue = ( char * ) ( pLoc );
\r
739 pHttpsResponse->readHeaderValueLength = length;
\r
740 /* We found a header field so we don't want to keep parsing.*/
\r
741 retVal = STOP_PARSING;
\r
748 /*-----------------------------------------------------------*/
\r
750 static int _httpParserOnHeadersCompleteCallback( http_parser * pHttpParser )
\r
752 IotLogDebug( "Parser: End of the headers reached." );
\r
754 int retVal = KEEP_PARSING;
\r
755 _httpsResponse_t * pHttpsResponse = ( _httpsResponse_t * ) ( pHttpParser->data );
\r
756 pHttpsResponse->parserState = PARSER_STATE_HEADERS_COMPLETE;
\r
758 /* If the IotHttpsClient_ReadHeader() was called, we return after finishing looking through all of the headers.
\r
759 * Returning a non-zero value exits the http parsing. */
\r
760 if( pHttpsResponse->bufferProcessingState == PROCESSING_STATE_SEARCHING_HEADER_BUFFER )
\r
762 retVal = STOP_PARSING;
\r
765 /* When in this callback the pHeaderCur pointer is at the first "\r" in the last header line. HTTP/1.1
\r
766 * headers end with another "\r\n" at the end of the last line. This means we must increment
\r
767 * the headerCur pointer to the length of "\r\n\r\n". */
\r
768 if( pHttpsResponse->bufferProcessingState == PROCESSING_STATE_FILLING_HEADER_BUFFER )
\r
770 pHttpsResponse->pHeadersCur += ( 2 * HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH );
\r
773 /* This if-case is not incrementing any pHeaderCur pointers, so this case is safe to call when flushing the
\r
774 * network buffer. Flushing the network buffer needs the logic below to reach PARSER_STATE_BODY_COMPLETE if the
\r
775 * response is for a HEAD request. Before flushing the network buffer the bufferProcessingState is set to
\r
776 * PROCESSING_STATE_FINISHED so that other callback functions don't update header or body current pointers in the
\r
777 * response context. We don't want those pointers incremented because flushing the network uses a different buffer
\r
778 * to receive the rest of the response. */
\r
779 if( pHttpsResponse->bufferProcessingState <= PROCESSING_STATE_FINISHED )
\r
781 /* For a HEAD method, there is no body expected in the response, so we return 1 to skip body parsing. */
\r
782 if( ( pHttpsResponse->method == IOT_HTTPS_METHOD_HEAD ) )
\r
784 retVal = STOP_PARSING;
\r
786 /* Since the message is considered complete now for a HEAD response, then we set the parser state
\r
787 * to the completed state. */
\r
788 pHttpsResponse->parserState = PARSER_STATE_BODY_COMPLETE;
\r
791 /* If this is NOT a HEAD method and there is body configured, but the server does not send a body in the
\r
792 * response, then the body buffer will be filled with the zeros from rest of the header buffer. http-parser
\r
793 * will invoke the on_body callback and consider the zeros following the headers as body. */
\r
795 /* If there is not body configured for a synchronous reponse, we do not stop the parser from continueing. */
\r
797 /* Skipping the body will cause the parser to invoke the _httpParserOnMessageComplete() callback. This is
\r
798 * not desired when there is actually HTTP response body sent by the server because this will set the parser
\r
799 * state to PARSER_STATE_BODY_COMPLETE. If this state is set then the rest of possible body will not be
\r
800 * flushed out. The network flush looks for the state being PARSER_STATE_BODY_COMPLETE to finish flushing. */
\r
806 /*-----------------------------------------------------------*/
\r
808 static int _httpParserOnBodyCallback( http_parser * pHttpParser,
\r
812 IotLogDebug( "Parser: Reached the HTTPS message body. It is of length: %d", length );
\r
814 _httpsResponse_t * pHttpsResponse = ( _httpsResponse_t * ) ( pHttpParser->data );
\r
815 pHttpsResponse->parserState = PARSER_STATE_IN_BODY;
\r
817 /* If the header buffer is currently being processed, but HTTP response body was found, then for an asynchronous
\r
818 * request this if-case saves where the body is located. In the asynchronous case, the body buffer is not available
\r
819 * until the readReadyCallback is invoked, which happens after the headers are processed. */
\r
820 if( ( pHttpsResponse->bufferProcessingState == PROCESSING_STATE_FILLING_HEADER_BUFFER ) && ( pHttpsResponse->isAsync ) )
\r
822 /* For an asynchronous response, the buffer to store the body will be available after the headers
\r
823 * are read first. We may receive part of the body in the header buffer. We will want to leave this here
\r
824 * and copy it over when the body buffer is available in the _readReadyCallback().
\r
826 if( pHttpsResponse->pBodyInHeaderBuf == NULL )
\r
828 pHttpsResponse->pBodyInHeaderBuf = ( uint8_t * ) ( pLoc );
\r
829 pHttpsResponse->pBodyCurInHeaderBuf = pHttpsResponse->pBodyInHeaderBuf;
\r
832 /* If there is a chunk encoded body in the header buffer, we will want to overwrite the chunk headers with the
\r
833 * actual body. This is so that when the application calls IotHttpsClient_ReadResponseBody(), in the
\r
834 * readReadyCallback(), we can pass the body into the body buffer provided right away. */
\r
835 if( pHttpsResponse->pBodyCurInHeaderBuf != ( uint8_t * ) pLoc )
\r
837 memcpy( pHttpsResponse->pBodyCurInHeaderBuf, pLoc, length );
\r
840 pHttpsResponse->pBodyCurInHeaderBuf += length;
\r
842 else if( pHttpsResponse->bufferProcessingState < PROCESSING_STATE_FINISHED )
\r
844 /* Has the user provided a buffer and is it large enough to fit the body? The
\r
845 * case of body buffer not being large enough can happen if the body was received
\r
846 * in the header buffer and the body buffer can not fit in all the body. */
\r
847 if( ( pHttpsResponse->pBodyCur != NULL ) && ( pHttpsResponse->pBodyEnd - pHttpsResponse->pBodyCur > 0 ) )
\r
849 /* There are two scenarios when we need to copy data around:
\r
850 * 1. Some or all of the response body may have been received in the header
\r
851 * buffer. If that is the case, we copy the response body received in the
\r
852 * header buffer to the user provided body buffer.
\r
853 * 2. When we receive chunked header, the actual body is separated in
\r
854 * multiple chunks which are preceeded by length. For example, a chunked
\r
855 * body may look like:
\r
866 * In this case, we want the parsed body buffer to contain actual body only
\r
867 * (MozillaDeveloperNetwork in the above example).
\r
870 /* If the response body found by the parser (pLoc) is not equal to the
\r
871 * current writable location in the body buffer (_httpsResponse->pBodyCur),
\r
872 * it indicates that:
\r
873 * - Either the data is in the header buffer and needs to be copied into the
\r
875 * - Or it is a chunked response and the data needs to be moved up in the
\r
877 if( ( pHttpsResponse->pBodyCur + length ) <= pHttpsResponse->pBodyEnd )
\r
879 if( pHttpsResponse->pBodyCur != ( uint8_t * ) pLoc )
\r
881 memcpy( pHttpsResponse->pBodyCur, pLoc, length );
\r
884 pHttpsResponse->pBodyCur += length;
\r
889 return KEEP_PARSING;
\r
892 /*-----------------------------------------------------------*/
\r
894 static int _httpParserOnMessageCompleteCallback( http_parser * pHttpParser )
\r
896 IotLogDebug( "Parser: End of the HTTPS message reached." );
\r
898 _httpsResponse_t * pHttpsResponse = ( _httpsResponse_t * ) ( pHttpParser->data );
\r
899 pHttpsResponse->parserState = PARSER_STATE_BODY_COMPLETE;
\r
901 /* This callback is invoked when the complete HTTP response has been received.
\r
902 * We tell the parser to parse the whole body buffer as opposed to the size of
\r
903 * the response body. For example, if the size of the body buffer is 1000 but
\r
904 * the size of the actual body is 500, we tell the parser to parse the whole
\r
905 * buffer of length 1000. We do zero out the buffer in the beginning so that all
\r
906 * the buffer after the actual body contains zeros. We return greater than zero to stop parsing
\r
907 * since the end of the HTTP message has been reached. Any data beyond the end of the message is
\r
909 return STOP_PARSING;
\r
912 /*-----------------------------------------------------------*/
\r
914 /* This code prints debugging information and is, therefore, compiled only when
\r
915 * log level is set to IOT_LOG_DEBUG. */
\r
916 #if ( LIBRARY_LOG_LEVEL == IOT_LOG_DEBUG )
\r
917 static int _httpParserOnChunkHeaderCallback( http_parser * pHttpParser )
\r
919 ( void ) pHttpParser;
\r
920 IotLogDebug( "Parser: HTTPS message Chunked encoding header callback." );
\r
921 IotLogDebug( "Parser: HTTPS message Chunk size: %d", pHttpParser->content_length );
\r
925 /*-----------------------------------------------------------*/
\r
927 static int _httpParserOnChunkCompleteCallback( http_parser * pHttpParser )
\r
929 ( void ) pHttpParser;
\r
930 IotLogDebug( "End of a HTTPS message Chunk complete callback." );
\r
933 #endif /* if ( LIBRARY_LOG_LEVEL == IOT_LOG_DEBUG ) */
\r
935 /*-----------------------------------------------------------*/
\r
937 static IotHttpsReturnCode_t _receiveHttpsBodyAsync( _httpsResponse_t * pHttpsResponse )
\r
939 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
941 if( pHttpsResponse->pCallbacks->readReadyCallback )
\r
943 /* If there is still more body that has not been passed back to the user, then this callback is invoked again. */
\r
946 IotLogDebug( "Invoking the readReadyCallback." );
\r
947 pHttpsResponse->pCallbacks->readReadyCallback( pHttpsResponse->pUserPrivData,
\r
949 pHttpsResponse->bodyRxStatus,
\r
950 pHttpsResponse->status );
\r
952 if( pHttpsResponse->cancelled == true )
\r
954 IotLogDebug( "Cancelled HTTP response %d.", pHttpsResponse );
\r
955 status = IOT_HTTPS_RECEIVE_ABORT;
\r
957 /* We break out of the loop and do not goto clean up because we want to print debugging logs for
\r
958 * the parser state and the networks status. */
\r
961 } while( ( pHttpsResponse->parserState < PARSER_STATE_BODY_COMPLETE ) && ( pHttpsResponse->bodyRxStatus == IOT_HTTPS_OK ) );
\r
963 if( HTTPS_FAILED( pHttpsResponse->bodyRxStatus ) )
\r
965 IotLogError( "Error receiving the HTTP response body for response %d. Error code: %d",
\r
967 pHttpsResponse->bodyRxStatus );
\r
968 /* An error in the network or the parser takes precedence */
\r
969 status = pHttpsResponse->bodyRxStatus;
\r
972 if( pHttpsResponse->parserState < PARSER_STATE_BODY_COMPLETE )
\r
974 IotLogDebug( "Did not receive all of the HTTP response body for response %d.",
\r
979 /* This GOTO cleanup is here for compiler warnings about using HTTPS_FUNCTION_EXIT_NO_CLEANUP() without a
\r
980 * corresponding goto. */
\r
981 HTTPS_GOTO_CLEANUP();
\r
982 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
985 /*-----------------------------------------------------------*/
\r
987 static IotHttpsReturnCode_t _receiveHttpsBodySync( _httpsResponse_t * pHttpsResponse )
\r
989 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
990 _httpsConnection_t * pHttpsConnection = pHttpsResponse->pHttpsConnection;
\r
992 /* The header buffer is now filled or the end of the headers has been reached already. If part of the response
\r
993 * body was read from the network into the header buffer, then it was already copied to the body buffer in the
\r
994 * _httpParserOnBodyCallback(). */
\r
995 if( pHttpsResponse->pBody != NULL )
\r
997 /* If there is room left in the body buffer and we have not received the whole response body,
\r
998 * then try to receive more. */
\r
999 if( ( ( pHttpsResponse->pBodyEnd - pHttpsResponse->pBodyCur ) > 0 ) &&
\r
1000 ( pHttpsResponse->parserState < PARSER_STATE_BODY_COMPLETE ) )
\r
1002 status = _receiveHttpsBody( pHttpsConnection,
\r
1005 if( HTTPS_FAILED( status ) )
\r
1007 IotLogError( "Error receiving the HTTPS response body for response %d. Error code: %d.",
\r
1010 HTTPS_GOTO_CLEANUP();
\r
1015 IotLogDebug( "Received the maximum amount of HTTP body when filling the header buffer for response %d.",
\r
1019 /* If we don't reach the end of the HTTPS body in the parser, then we only received part of the body.
\r
1020 * The rest of body will be on the network socket. */
\r
1021 if( HTTPS_SUCCEEDED( status ) && ( pHttpsResponse->parserState < PARSER_STATE_BODY_COMPLETE ) )
\r
1023 IotLogError( "HTTPS response body does not fit into application provided response buffer at location 0x%x "
\r
1024 "with length: %d",
\r
1025 pHttpsResponse->pBody,
\r
1026 pHttpsResponse->pBodyEnd - pHttpsResponse->pBody );
\r
1027 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_MESSAGE_TOO_LARGE );
\r
1032 IotLogDebug( "No response body was configure for response %d.", pHttpsResponse );
\r
1035 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1038 /*-----------------------------------------------------------*/
\r
1040 static void _networkReceiveCallback( void * pNetworkConnection,
\r
1041 void * pReceiveContext )
\r
1043 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1045 IotHttpsReturnCode_t flushStatus = IOT_HTTPS_OK;
\r
1046 IotHttpsReturnCode_t disconnectStatus = IOT_HTTPS_OK;
\r
1047 IotHttpsReturnCode_t scheduleStatus = IOT_HTTPS_OK;
\r
1048 _httpsConnection_t * pHttpsConnection = ( _httpsConnection_t * ) pReceiveContext;
\r
1049 _httpsResponse_t * pCurrentHttpsResponse = NULL;
\r
1050 _httpsRequest_t * pNextHttpsRequest = NULL;
\r
1051 IotLink_t * pQItem = NULL;
\r
1052 bool fatalDisconnect = false;
\r
1054 /* The network connection is already in the connection context. */
\r
1055 ( void ) pNetworkConnection;
\r
1057 /* Get the response from the response queue. */
\r
1058 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
1059 pQItem = IotDeQueue_PeekHead( &( pHttpsConnection->respQ ) );
\r
1060 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
1062 /* If the receive callback is invoked and there is no response expected, then this a violation of the HTTP/1.1
\r
1064 if( pQItem == NULL )
\r
1066 IotLogError( "Received data on the network, when no response was expected..." );
\r
1067 fatalDisconnect = true;
\r
1068 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_NETWORK_ERROR );
\r
1071 /* Set the current HTTP response context to use. */
\r
1072 pCurrentHttpsResponse = IotLink_Container( _httpsResponse_t, pQItem, link );
\r
1074 /* If the receive callback has invoked, but the request associated with this response has not finished sending
\r
1075 * to the server, then this is a violation of the HTTP/1.1 protocol. */
\r
1076 if( pCurrentHttpsResponse->reqFinishedSending == false )
\r
1078 IotLogError( "Received response data on the network when the request was not finished sending. This is unexpected." );
\r
1079 fatalDisconnect = true;
\r
1080 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_NETWORK_ERROR );
\r
1083 /* If the current response was cancelled, then don't bother receiving the headers and body. */
\r
1084 if( pCurrentHttpsResponse->cancelled )
\r
1086 IotLogDebug( "Response ID: %d was cancelled.", pCurrentHttpsResponse );
\r
1087 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_RECEIVE_ABORT );
\r
1090 /* Reset the http-parser state to an initial state. This is done so that a new response can be parsed from the
\r
1092 pCurrentHttpsResponse->parserState = PARSER_STATE_NONE;
\r
1094 /* Receive the response from the network. */
\r
1095 /* Receive the headers first. */
\r
1096 status = _receiveHttpsHeaders( pHttpsConnection, pCurrentHttpsResponse );
\r
1098 if( HTTPS_FAILED( status ) )
\r
1100 if( status == IOT_HTTPS_PARSING_ERROR )
\r
1102 /* There was an error parsing the HTTPS response body. This may be an indication of a server that does
\r
1103 * not adhere to protocol correctly. We should disconnect. */
\r
1104 IotLogError( "Failed to parse the HTTPS headers for response %d, Error code: %d.",
\r
1105 pCurrentHttpsResponse,
\r
1107 fatalDisconnect = true;
\r
1109 else if( status == IOT_HTTPS_NETWORK_ERROR )
\r
1111 /* Given the function signature of IotNetworkInterface_t.receive, we can only receive 0 to the number of bytes
\r
1112 * requested. Receiving less than the number of bytes requests is OK since we do not how much data is expected, so
\r
1113 * we ask for the full size of the receive buffer. Therefore, the only error that can be returned from receiving
\r
1114 * the headers or body is a timeout. We always disconnect from the network when there is a timeout because the
\r
1115 * server may be slow to respond. If the server happens to send the response later at the same time another response
\r
1116 * is waiting in the queue, then the workflow is corrupted. Pipelining is not current supported in this library. */
\r
1117 IotLogError( "Network error receiving the HTTPS headers for response %d. Error code: %d",
\r
1118 pCurrentHttpsResponse,
\r
1120 fatalDisconnect = true;
\r
1122 else /* Any other error. */
\r
1124 IotLogError( "Failed to retrive the HTTPS body for response %d. Error code: %d", pCurrentHttpsResponse, status );
\r
1127 HTTPS_GOTO_CLEANUP();
\r
1130 /* Check if we received all of the headers into the header buffer. */
\r
1131 if( pCurrentHttpsResponse->parserState < PARSER_STATE_HEADERS_COMPLETE )
\r
1133 IotLogDebug( "Headers received on the network did not all fit into the configured header buffer for response %d."
\r
1134 " The length of the headers buffer is: %d",
\r
1135 pCurrentHttpsResponse,
\r
1136 pCurrentHttpsResponse->pHeadersEnd - pCurrentHttpsResponse->pHeaders );
\r
1137 /* It is not error if the headers did not all fit into the buffer. */
\r
1140 /* Receive the body. */
\r
1141 if( pCurrentHttpsResponse->isAsync )
\r
1143 status = _receiveHttpsBodyAsync( pCurrentHttpsResponse );
\r
1147 /* Otherwise receive synchronously. */
\r
1148 status = _receiveHttpsBodySync( pCurrentHttpsResponse );
\r
1151 if( HTTPS_FAILED( status ) )
\r
1153 if( status == IOT_HTTPS_RECEIVE_ABORT )
\r
1155 /* If the request was cancelled, this is logged, but does not close the connection. */
\r
1156 IotLogDebug( "User cancelled during the async readReadyCallback() for response %d.",
\r
1157 pCurrentHttpsResponse );
\r
1159 else if( status == IOT_HTTPS_PARSING_ERROR )
\r
1161 /* There was an error parsing the HTTPS response body. This may be an indication of a server that does
\r
1162 * not adhere to protocol correctly. We should disconnect. */
\r
1163 IotLogError( "Failed to parse the HTTPS body for response %d, Error code: %d.",
\r
1164 pCurrentHttpsResponse,
\r
1166 fatalDisconnect = true;
\r
1168 else if( status == IOT_HTTPS_NETWORK_ERROR )
\r
1170 /* We always disconnect for a network error because failure to receive the HTTPS body will result in a
\r
1171 * corruption of the workflow. */
\r
1172 IotLogError( "Network error receiving the HTTPS body for response %d. Error code: %d",
\r
1173 pCurrentHttpsResponse,
\r
1175 fatalDisconnect = true;
\r
1177 else /* Any other error. */
\r
1179 IotLogError( "Failed to retrive the HTTPS body for response %d. Error code: %d", pCurrentHttpsResponse, status );
\r
1182 HTTPS_GOTO_CLEANUP();
\r
1185 IOT_FUNCTION_CLEANUP_BEGIN();
\r
1187 /* Disconnect and return in the event of an out-of-order response. If a response is received out of order
\r
1188 * pCurrentHttpsResponse will be NULL because there will be no response in the connection's response queue.
\r
1189 * If a response is received out of order that is an indication of a rogue server. */
\r
1190 if( fatalDisconnect && !pCurrentHttpsResponse )
\r
1192 IotLogError( "An out-of-order response was received. The connection will be disconnected." );
\r
1193 disconnectStatus = IotHttpsClient_Disconnect( pHttpsConnection );
\r
1195 if( HTTPS_FAILED( disconnectStatus ) )
\r
1197 IotLogWarn( "Failed to disconnect after an out of order response. Error code: %d.", disconnectStatus );
\r
1200 /* In this case this routine returns immediately after to avoid further uses of pCurrentHttpsResponse. */
\r
1204 /* Report errors back to the application. */
\r
1205 if( HTTPS_FAILED( status ) )
\r
1207 if( pCurrentHttpsResponse->isAsync && pCurrentHttpsResponse->pCallbacks->errorCallback )
\r
1209 pCurrentHttpsResponse->pCallbacks->errorCallback( pCurrentHttpsResponse->pUserPrivData, NULL, pCurrentHttpsResponse, status );
\r
1212 pCurrentHttpsResponse->syncStatus = status;
\r
1215 /* If this is not a persistent request, the server would have closed it after sending a response, but we
\r
1216 * disconnect anyways. If we are disconnecting there is is no point in wasting time
\r
1217 * flushing the network. If the network is being disconnected we also do not schedule any pending requests. */
\r
1218 if( fatalDisconnect || pCurrentHttpsResponse->isNonPersistent )
\r
1220 IotLogDebug( "Disconnecting response %d.", pCurrentHttpsResponse );
\r
1221 disconnectStatus = IotHttpsClient_Disconnect( pHttpsConnection );
\r
1223 if( ( pCurrentHttpsResponse != NULL ) && pCurrentHttpsResponse->isAsync && pCurrentHttpsResponse->pCallbacks->connectionClosedCallback )
\r
1225 pCurrentHttpsResponse->pCallbacks->connectionClosedCallback( pCurrentHttpsResponse->pUserPrivData, pHttpsConnection, disconnectStatus );
\r
1228 if( HTTPS_FAILED( disconnectStatus ) )
\r
1230 IotLogWarn( "Failed to disconnect response %d. Error code: %d.", pCurrentHttpsResponse, disconnectStatus );
\r
1233 /* If we disconnect, we do not process anymore requests. */
\r
1237 /* Set the processing state of the buffer to finished for completeness. This is also to prevent the parsing of the flush
\r
1238 * data from incrementing any pointer in the HTTP response context. */
\r
1239 pCurrentHttpsResponse->bufferProcessingState = PROCESSING_STATE_FINISHED;
\r
1241 /* Flush the socket of the rest of the data if there is data left from this response. We need to do this
\r
1242 * so that for the next request on this connection, there is not left over response from this request in
\r
1243 * the next response buffer.
\r
1245 * If a continuous stream of data is coming in from the connection, with an unknown end, we may not be able to
\r
1246 * flush the network data. It may sit here forever. A continuous stream should be ingested with the async workflow.
\r
1248 * All network errors are ignore here because network read will have read the data from network buffer despite
\r
1250 flushStatus = _flushHttpsNetworkData( pHttpsConnection, pCurrentHttpsResponse );
\r
1252 if( flushStatus == IOT_HTTPS_PARSING_ERROR )
\r
1254 IotLogWarn( "There an error parsing the network flush data. The network buffer might not be fully flushed." );
\r
1256 else if( flushStatus != IOT_HTTPS_OK )
\r
1258 IotLogDebug( "Network error when flushing the https network data: %d", flushStatus );
\r
1261 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
1262 /* Get the next request to process. */
\r
1263 pQItem = IotDeQueue_PeekHead( &( pHttpsConnection->reqQ ) );
\r
1264 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
1266 /* If there is a next request to process, then create a taskpool job to send the request. */
\r
1267 if( pQItem != NULL )
\r
1269 /* Set this next request to send. */
\r
1270 pNextHttpsRequest = IotLink_Container( _httpsRequest_t, pQItem, link );
\r
1272 if( pNextHttpsRequest->scheduled == false )
\r
1274 IotLogDebug( "Request %d is next in the queue. Now scheduling a task to send the request.", pNextHttpsRequest );
\r
1275 scheduleStatus = _scheduleHttpsRequestSend( pNextHttpsRequest );
\r
1277 /* If there was an error with scheduling the new task, then report it. */
\r
1278 if( HTTPS_FAILED( scheduleStatus ) )
\r
1280 IotLogError( "Error scheduling HTTPS request %d. Error code: %d", pNextHttpsRequest, scheduleStatus );
\r
1282 if( pNextHttpsRequest->isAsync && pNextHttpsRequest->pCallbacks->errorCallback )
\r
1284 pNextHttpsRequest->pCallbacks->errorCallback( pNextHttpsRequest->pUserPrivData, pNextHttpsRequest, NULL, scheduleStatus );
\r
1288 pNextHttpsRequest->pHttpsResponse->syncStatus = scheduleStatus;
\r
1295 IotLogDebug( "Network receive callback found the request queue empty. A network send task was not scheduled." );
\r
1299 /* Dequeue response from the response queue now that it is finished. */
\r
1300 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
1302 /* There could be a scenario where the request fails to send and the network server still responds,
\r
1303 * In this case, the failed response will have been cancelled and removed from the queue. If the network
\r
1304 * server still got a response, then the safest way to remove the current response is to remove it explicitly
\r
1305 * from the queue instead of dequeuing the header of the queue which might not be the current response. */
\r
1306 if( IotLink_IsLinked( &( pCurrentHttpsResponse->link ) ) )
\r
1308 IotDeQueue_Remove( &( pCurrentHttpsResponse->link ) );
\r
1311 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
1313 /* The first if-case below notifies IotHttpsClient_SendSync() that the response is finished receiving. When
\r
1314 * IotHttpsClient_SendSync() returns the user is allowed to modify the user buffer used for the response context.
\r
1315 * In the asynchronous case, the responseCompleteCallback notifies the application that the user buffer used for the
\r
1316 * response context can be modified. Posting to the respFinishedSem or calling the responseCompleteCallback MUST be
\r
1317 * mutually exclusive by wrapping in an if/else. If these were separate if-cases, then there could be a context
\r
1318 * switch in between where the application modifies the buffer causing the next if-case to be executed. */
\r
1319 if( pCurrentHttpsResponse->isAsync == false )
\r
1321 IotSemaphore_Post( &( pCurrentHttpsResponse->respFinishedSem ) );
\r
1323 else if( pCurrentHttpsResponse->pCallbacks->responseCompleteCallback )
\r
1325 /* Signal to a synchronous reponse that the response is complete. */
\r
1326 pCurrentHttpsResponse->pCallbacks->responseCompleteCallback( pCurrentHttpsResponse->pUserPrivData, pCurrentHttpsResponse, status, pCurrentHttpsResponse->status );
\r
1330 /*-----------------------------------------------------------*/
\r
1332 static IotHttpsReturnCode_t _createHttpsConnection( IotHttpsConnectionHandle_t * pConnHandle,
\r
1333 IotHttpsConnectionInfo_t * pConnInfo )
\r
1335 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1337 IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS;
\r
1339 /* The maximum string length of the ALPN protocols is configured in IOT_HTTPS_MAX_ALPN_PROTOCOLS_LENGTH.
\r
1340 * The +1 is for the NULL terminator needed by IotNetworkCredentials_t.pAlpnProtos. */
\r
1341 char pAlpnProtos[ IOT_HTTPS_MAX_ALPN_PROTOCOLS_LENGTH + 1 ] = { 0 };
\r
1343 /* The maximum string length of the Server host name is configured in IOT_HTTPS_MAX_HOST_NAME_LENGTH.
\r
1344 * This +1 is for the NULL terminator needed by IotNetworkServerInfo_t.pHostName. */
\r
1345 char pHostName[ IOT_HTTPS_MAX_HOST_NAME_LENGTH + 1 ] = { 0 };
\r
1346 bool connectionMutexCreated = false;
\r
1347 struct IotNetworkServerInfo networkServerInfo = { 0 };
\r
1348 struct IotNetworkCredentials networkCredentials = { 0 };
\r
1349 _httpsConnection_t * pHttpsConnection = NULL;
\r
1350 IotNetworkCredentials_t pNetworkCredentials = NULL;
\r
1352 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pConnInfo->userBuffer.pBuffer );
\r
1353 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pConnInfo->pNetworkInterface );
\r
1354 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pConnInfo->pAddress );
\r
1355 HTTPS_ON_ARG_ERROR_GOTO_CLEANUP( pConnInfo->addressLen > 0 );
\r
1357 /* Make sure the connection context can fit in the user buffer. */
\r
1358 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( pConnInfo->userBuffer.bufferLen >= connectionUserBufferMinimumSize,
\r
1359 IOT_HTTPS_INSUFFICIENT_MEMORY,
\r
1360 "Buffer size is too small to initialize the connection context. User buffer size: %d, required minimum size; %d.",
\r
1361 ( *pConnInfo ).userBuffer.bufferLen,
\r
1362 connectionUserBufferMinimumSize );
\r
1364 /* Make sure that the server address does not exceed the maximum permitted length. */
\r
1365 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( pConnInfo->addressLen <= IOT_HTTPS_MAX_HOST_NAME_LENGTH,
\r
1366 IOT_HTTPS_INVALID_PARAMETER,
\r
1367 "IotHttpsConnectionInfo_t.addressLen has a host name length %d that exceeds maximum length %d.",
\r
1368 pConnInfo->addressLen,
\r
1369 IOT_HTTPS_MAX_HOST_NAME_LENGTH );
\r
1371 /* Make sure that the ALPN protocols does not exceed the maximum permitted length. */
\r
1372 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( pConnInfo->alpnProtocolsLen <= IOT_HTTPS_MAX_ALPN_PROTOCOLS_LENGTH,
\r
1373 IOT_HTTPS_INVALID_PARAMETER,
\r
1374 "IotHttpsConnectionInfo_t.alpnProtocolsLen of %d exceeds the configured maximum protocol length %d. See IOT_HTTPS_MAX_ALPN_PROTOCOLS_LENGTH for more information.",
\r
1375 pConnInfo->alpnProtocolsLen,
\r
1376 IOT_HTTPS_MAX_ALPN_PROTOCOLS_LENGTH );
\r
1378 pHttpsConnection = ( _httpsConnection_t * ) ( pConnInfo->userBuffer.pBuffer );
\r
1380 /* Start with the disconnected state. */
\r
1381 pHttpsConnection->isConnected = false;
\r
1383 /* Initialize disconnection state keeper. */
\r
1384 pHttpsConnection->isDestroyed = false;
\r
1386 /* Initialize the queue of responses and requests. */
\r
1387 IotDeQueue_Create( &( pHttpsConnection->reqQ ) );
\r
1388 IotDeQueue_Create( &( pHttpsConnection->respQ ) );
\r
1390 /* This timeout is used to wait for a response on the connection as well as
\r
1391 * for the timeout for the connect operation. */
\r
1392 if( pConnInfo->timeout == 0 )
\r
1394 pHttpsConnection->timeout = IOT_HTTPS_RESPONSE_WAIT_MS;
\r
1398 pHttpsConnection->timeout = pConnInfo->timeout;
\r
1401 /* pNetworkInterface contains all the routines to be able to send/receive data on the network. */
\r
1402 pHttpsConnection->pNetworkInterface = pConnInfo->pNetworkInterface;
\r
1404 /* The address from the connection configuration information is copied to a local buffer because a NULL pointer
\r
1405 * is required in IotNetworkServerInfo_t.pHostName. IotNetworkServerInfo_t contains the server information needed
\r
1406 * by the network interface to create the connection. */
\r
1407 memcpy( pHostName, pConnInfo->pAddress, pConnInfo->addressLen );
\r
1408 pHostName[ pConnInfo->addressLen ] = '\0';
\r
1409 /* Set it in the IOT network abstractions server information parameter. */
\r
1410 networkServerInfo.pHostName = pHostName;
\r
1411 networkServerInfo.port = pConnInfo->port;
\r
1413 /* If this is TLS connection, then set the network credentials. */
\r
1414 if( ( pConnInfo->flags & IOT_HTTPS_IS_NON_TLS_FLAG ) == 0 )
\r
1416 if( pConnInfo->flags & IOT_HTTPS_DISABLE_SNI )
\r
1418 networkCredentials.disableSni = true;
\r
1422 networkCredentials.disableSni = false;
\r
1425 if( pConnInfo->pAlpnProtocols != NULL )
\r
1427 /* The alpn protocol strings in IotNetworkCredentials_t require a NULL terminator, so the alpn protocol
\r
1428 * string in the connection configuration information is copied to a local buffer to append the NULL
\r
1430 memcpy( pAlpnProtos, pConnInfo->pAlpnProtocols, pConnInfo->alpnProtocolsLen );
\r
1431 pAlpnProtos[ pConnInfo->alpnProtocolsLen ] = '\0';
\r
1432 networkCredentials.pAlpnProtos = pAlpnProtos;
\r
1436 networkCredentials.pAlpnProtos = NULL;
\r
1439 /* If any of these are NULL a network error will result when trying to make the connection. Because there is
\r
1440 * no invalid memory access resulting from these configurations being NULL, it is not check at the start
\r
1441 * of the function. */
\r
1442 networkCredentials.pRootCa = pConnInfo->pCaCert;
\r
1443 networkCredentials.rootCaSize = pConnInfo->caCertLen;
\r
1444 networkCredentials.pClientCert = pConnInfo->pClientCert;
\r
1445 networkCredentials.clientCertSize = pConnInfo->clientCertLen;
\r
1446 networkCredentials.pPrivateKey = pConnInfo->pPrivateKey;
\r
1447 networkCredentials.privateKeySize = pConnInfo->privateKeyLen;
\r
1449 pNetworkCredentials = &networkCredentials;
\r
1453 /* create() takes a NULL if there is no TLS configuration. */
\r
1454 pNetworkCredentials = NULL;
\r
1457 /* create() will connect to the server specified in addition to creating other network layer
\r
1458 * specific resources. */
\r
1459 networkStatus = pHttpsConnection->pNetworkInterface->create( &networkServerInfo,
\r
1460 pNetworkCredentials,
\r
1461 &( pHttpsConnection->pNetworkConnection ) );
\r
1463 /* Check to see if the network connection succeeded. If it did not succeed,
\r
1464 * then the output parameter pConnHandle will be used to return NULL and the
\r
1465 * function returns an error. */
\r
1466 if( networkStatus != IOT_NETWORK_SUCCESS )
\r
1468 IotLogError( "Failed to connect to the server at %.*s on port %d with error: %d",
\r
1469 pConnInfo->addressLen,
\r
1470 pConnInfo->pAddress,
\r
1473 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_CONNECTION_ERROR );
\r
1476 /* The connection succeeded so set the state to connected. */
\r
1477 pHttpsConnection->isConnected = true;
\r
1479 /* The receive callback is invoked by the network layer when data is ready
\r
1480 * to be read from the network. */
\r
1481 networkStatus = pHttpsConnection->pNetworkInterface->setReceiveCallback( pHttpsConnection->pNetworkConnection,
\r
1482 _networkReceiveCallback,
\r
1483 pHttpsConnection );
\r
1485 if( networkStatus != IOT_NETWORK_SUCCESS )
\r
1487 IotLogError( "Failed to connect to set the HTTPS receive callback. " );
\r
1488 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INTERNAL_ERROR );
\r
1491 /* Connection was successful, so create synchronization primitives. */
\r
1493 connectionMutexCreated = IotMutex_Create( &( pHttpsConnection->connectionMutex ), false );
\r
1495 if( !connectionMutexCreated )
\r
1497 IotLogError( "Failed to create an internal mutex." );
\r
1498 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INTERNAL_ERROR );
\r
1501 /* Return the new connection information. */
\r
1502 *pConnHandle = pHttpsConnection;
\r
1504 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
1506 /* If we failed anywhere in the connection process, then destroy the semaphores created. */
\r
1507 if( HTTPS_FAILED( status ) )
\r
1509 /* If there was a connect was successful, disconnect from the network. */
\r
1510 if( ( pHttpsConnection != NULL ) && ( pHttpsConnection->isConnected ) )
\r
1512 _networkDisconnect( pHttpsConnection );
\r
1513 _networkDestroy( pHttpsConnection );
\r
1516 if( connectionMutexCreated )
\r
1518 IotMutex_Destroy( &( pHttpsConnection->connectionMutex ) );
\r
1521 /* Set the connection handle as NULL if everything failed. */
\r
1522 *pConnHandle = NULL;
\r
1525 HTTPS_FUNCTION_CLEANUP_END();
\r
1528 /*-----------------------------------------------------------*/
\r
1530 static void _networkDisconnect( _httpsConnection_t * pHttpsConnection )
\r
1532 IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS;
\r
1534 networkStatus = pHttpsConnection->pNetworkInterface->close( pHttpsConnection->pNetworkConnection );
\r
1536 if( networkStatus != IOT_NETWORK_SUCCESS )
\r
1538 IotLogWarn( "Failed to shutdown the socket with error code: %d", networkStatus );
\r
1542 /*-----------------------------------------------------------*/
\r
1544 static void _networkDestroy( _httpsConnection_t * pHttpsConnection )
\r
1546 IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS;
\r
1548 networkStatus = pHttpsConnection->pNetworkInterface->destroy( pHttpsConnection->pNetworkConnection );
\r
1550 if( networkStatus != IOT_NETWORK_SUCCESS )
\r
1552 IotLogWarn( "Failed to shutdown the socket with error code: %d", networkStatus );
\r
1556 /*-----------------------------------------------------------*/
\r
1558 static IotHttpsReturnCode_t _addHeader( _httpsRequest_t * pHttpsRequest,
\r
1559 const char * pName,
\r
1561 const char * pValue,
\r
1562 uint32_t valueLen )
\r
1564 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1566 int headerFieldSeparatorLen = HTTPS_HEADER_FIELD_SEPARATOR_LENGTH;
\r
1567 uint32_t additionalLength = nameLen + headerFieldSeparatorLen + valueLen + HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH;
\r
1568 uint32_t possibleLastHeaderAdditionalLength = HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH;
\r
1570 /* Check if there is enough space to add the header field and value
\r
1571 * (name:value\r\n). We need to add a "\r\n" at the end of headers. The use of
\r
1572 * possibleLastHeaderAdditionalLength is to make sure that there is always
\r
1573 * space for the last "\r\n". */
\r
1574 if( ( additionalLength + possibleLastHeaderAdditionalLength ) > ( ( uint32_t ) ( pHttpsRequest->pHeadersEnd - pHttpsRequest->pHeadersCur ) ) )
\r
1576 IotLogError( "There is %d space left in the header buffer, but we want to add %d more of header.",
\r
1577 pHttpsRequest->pHeadersEnd - pHttpsRequest->pHeadersCur,
\r
1578 additionalLength + possibleLastHeaderAdditionalLength );
\r
1579 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INSUFFICIENT_MEMORY );
\r
1582 memcpy( pHttpsRequest->pHeadersCur, pName, nameLen );
\r
1583 pHttpsRequest->pHeadersCur += nameLen;
\r
1584 memcpy( pHttpsRequest->pHeadersCur, HTTPS_HEADER_FIELD_SEPARATOR, headerFieldSeparatorLen );
\r
1585 pHttpsRequest->pHeadersCur += headerFieldSeparatorLen;
\r
1586 memcpy( pHttpsRequest->pHeadersCur, pValue, valueLen );
\r
1587 pHttpsRequest->pHeadersCur += valueLen;
\r
1588 memcpy( pHttpsRequest->pHeadersCur, HTTPS_END_OF_HEADER_LINES_INDICATOR, HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH );
\r
1589 pHttpsRequest->pHeadersCur += HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH;
\r
1590 IotLogDebug( "Wrote header: \"%s: %.*s\r\n\". Space left in request user buffer: %d",
\r
1594 pHttpsRequest->pHeadersEnd - pHttpsRequest->pHeadersCur );
\r
1596 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1599 /*-----------------------------------------------------------*/
\r
1601 static IotHttpsReturnCode_t _networkSend( _httpsConnection_t * pHttpsConnection,
\r
1605 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1607 size_t numBytesSent = 0;
\r
1608 size_t numBytesSentTotal = 0;
\r
1609 size_t sendLength = len;
\r
1611 while( numBytesSentTotal < sendLength )
\r
1613 numBytesSent = pHttpsConnection->pNetworkInterface->send( pHttpsConnection->pNetworkConnection,
\r
1614 &( pBuf[ numBytesSentTotal ] ),
\r
1615 sendLength - numBytesSentTotal );
\r
1617 /* pNetworkInterface->send returns 0 on error. */
\r
1618 if( numBytesSent == 0 )
\r
1620 IotLogError( "Error in sending the HTTPS headers. Error code: %d", numBytesSent );
\r
1624 numBytesSentTotal += numBytesSent;
\r
1627 if( numBytesSentTotal != sendLength )
\r
1629 IotLogError( "Error sending data on the network. We sent %d but there were total %d.", numBytesSentTotal, sendLength );
\r
1630 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_NETWORK_ERROR );
\r
1633 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1636 /*-----------------------------------------------------------*/
\r
1638 static IotHttpsReturnCode_t _networkRecv( _httpsConnection_t * pHttpsConnection,
\r
1641 size_t * numBytesRecv )
\r
1643 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1645 /* The HTTP server could send the header and the body in two separate TCP packets. If that is the case, then
\r
1646 * receiveUpTo will return return the full headers first. Then on a second call, the body will be returned.
\r
1647 * If the http parser receives just the headers despite the content length being greater than */
\r
1648 *numBytesRecv = pHttpsConnection->pNetworkInterface->receiveUpto( pHttpsConnection->pNetworkConnection,
\r
1652 IotLogDebug( "The network interface receive returned %d.", numBytesRecv );
\r
1654 /* We return IOT_HTTPS_NETWORK_ERROR only if we receive nothing. Receiving less
\r
1655 * data than requested is okay because it is not known in advance how much data
\r
1656 * we are going to receive and therefore we request for the available buffer
\r
1658 if( *numBytesRecv == 0 )
\r
1660 IotLogError( "Error in receiving the HTTPS response message. Socket Error code %d", *numBytesRecv );
\r
1661 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_NETWORK_ERROR );
\r
1663 /* A network error is returned when zero is received because that would indicate that either there
\r
1664 * was a network error or there was a timeout reading data. If there was timeout reading data, then
\r
1665 * the server was too slow to respond. If the server is too slow to respond, then a network error must
\r
1666 * be returned to trigger a connection close. The connection must close after the network error so
\r
1667 * that the response from this request does not piggyback on the response from the next request. */
\r
1670 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1673 /*-----------------------------------------------------------*/
\r
1675 static IotHttpsReturnCode_t _sendHttpsHeaders( _httpsConnection_t * pHttpsConnection,
\r
1676 uint8_t * pHeadersBuf,
\r
1677 uint32_t headersLength,
\r
1678 bool isNonPersistent,
\r
1679 uint32_t contentLength )
\r
1681 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1683 const char * connectionHeader = NULL;
\r
1684 int numWritten = 0;
\r
1685 int connectionHeaderLen = 0;
\r
1686 /* The Content-Length header of the form "Content-Length: N\r\n" with a NULL terminator for snprintf. */
\r
1687 char contentLengthHeaderStr[ HTTPS_MAX_CONTENT_LENGTH_LINE_LENGTH + 1 ];
\r
1689 /* The HTTP headers to send after the headers in pHeadersBuf are the Content-Length and the Connection type and
\r
1690 * the final "\r\n" to indicate the end of the the header lines. Note that we are using
\r
1691 * HTTPS_CONNECTION_KEEP_ALIVE_HEADER_LINE_LENGTH because length of "Connection: keep-alive\r\n" is
\r
1692 * more than "Connection: close\r\n". Creating a buffer of bigger size ensures that
\r
1693 * both the connection type strings will fit in the buffer. */
\r
1694 char finalHeaders[ HTTPS_MAX_CONTENT_LENGTH_LINE_LENGTH + HTTPS_CONNECTION_KEEP_ALIVE_HEADER_LINE_LENGTH + HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH ] = { 0 };
\r
1696 /* Send the headers passed into this function first. These headers are not terminated with a second set of "\r\n". */
\r
1697 status = _networkSend( pHttpsConnection, pHeadersBuf, headersLength );
\r
1699 if( HTTPS_FAILED( status ) )
\r
1701 IotLogError( "Error sending the HTTPS headers in the request user buffer. Error code: %d", status );
\r
1702 HTTPS_GOTO_CLEANUP();
\r
1705 /* If there is a Content-Length, then write that to the finalHeaders to send. */
\r
1706 if( contentLength > 0 )
\r
1708 numWritten = snprintf( contentLengthHeaderStr,
\r
1709 sizeof( contentLengthHeaderStr ),
\r
1711 HTTPS_CONTENT_LENGTH_HEADER,
\r
1712 ( unsigned int ) contentLength );
\r
1715 if( ( numWritten < 0 ) || ( numWritten >= sizeof( contentLengthHeaderStr ) ) )
\r
1717 IotLogError( "Internal error in snprintf() in _sendHttpsHeaders(). Error code %d.", numWritten );
\r
1718 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INTERNAL_ERROR );
\r
1721 /* snprintf() succeeded so copy that to the finalHeaders. */
\r
1722 memcpy( finalHeaders, contentLengthHeaderStr, numWritten );
\r
1724 /* Write the connection persistence type to the final headers. */
\r
1725 if( isNonPersistent )
\r
1727 connectionHeader = HTTPS_CONNECTION_CLOSE_HEADER_LINE;
\r
1728 connectionHeaderLen = FAST_MACRO_STRLEN( HTTPS_CONNECTION_CLOSE_HEADER_LINE );
\r
1732 connectionHeader = HTTPS_CONNECTION_KEEP_ALIVE_HEADER_LINE;
\r
1733 connectionHeaderLen = FAST_MACRO_STRLEN( HTTPS_CONNECTION_KEEP_ALIVE_HEADER_LINE );
\r
1736 memcpy( &finalHeaders[ numWritten ], connectionHeader, connectionHeaderLen );
\r
1737 numWritten += connectionHeaderLen;
\r
1738 memcpy( &finalHeaders[ numWritten ], HTTPS_END_OF_HEADER_LINES_INDICATOR, HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH );
\r
1739 numWritten += HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH;
\r
1741 status = _networkSend( pHttpsConnection, ( uint8_t * ) finalHeaders, numWritten );
\r
1743 if( HTTPS_FAILED( status ) )
\r
1745 IotLogError( "Error sending final HTTPS Headers \r\n%s. Error code: %d", finalHeaders, status );
\r
1746 HTTPS_GOTO_CLEANUP();
\r
1749 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1752 /*-----------------------------------------------------------*/
\r
1754 static IotHttpsReturnCode_t _sendHttpsBody( _httpsConnection_t * pHttpsConnection,
\r
1755 uint8_t * pBodyBuf,
\r
1756 uint32_t bodyLength )
\r
1758 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1760 status = _networkSend( pHttpsConnection, pBodyBuf, bodyLength );
\r
1762 if( HTTPS_FAILED( status ) )
\r
1764 IotLogError( "Error sending final HTTPS body at location 0x%x. Error code: %d", pBodyBuf, status );
\r
1765 HTTPS_GOTO_CLEANUP();
\r
1768 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1771 /*-----------------------------------------------------------*/
\r
1773 static IotHttpsReturnCode_t _parseHttpsMessage( _httpParserInfo_t * pHttpParserInfo,
\r
1777 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1779 size_t parsedBytes = 0;
\r
1780 const char * pHttpParserErrorDescription = NULL;
\r
1781 http_parser * pHttpParser = &( pHttpParserInfo->responseParser );
\r
1783 IotLogDebug( "Now parsing HTTP message buffer to process a response." );
\r
1784 parsedBytes = pHttpParserInfo->parseFunc( pHttpParser, &_httpParserSettings, pBuf, len );
\r
1785 IotLogDebug( "http-parser parsed %d bytes out of %d specified.", parsedBytes, len );
\r
1787 /* If the parser fails with HPE_CLOSED_CONNECTION or HPE_INVALID_CONSTANT that simply means there
\r
1788 * was data beyond the end of the message. We do not fail in this case because we give the whole
\r
1789 * header buffer or body buffer to the parser even if it is only partly filled with data.
\r
1790 * Errors <= HPE_CB_chunk_complete means that a non-zero number was returned from some callback.
\r
1791 * A nonzero number is returned from some callbacks when we want to stop the parser early
\r
1792 * for example - a HEAD request or the user explicitly asked to ignore the body by not
\r
1793 * providing the body buffer. */
\r
1794 if( ( pHttpParser->http_errno != 0 ) &&
\r
1795 ( HTTP_PARSER_ERRNO( pHttpParser ) != HPE_CLOSED_CONNECTION ) &&
\r
1796 ( HTTP_PARSER_ERRNO( pHttpParser ) != HPE_INVALID_CONSTANT ) &&
\r
1797 ( HTTP_PARSER_ERRNO( pHttpParser ) > HPE_CB_chunk_complete ) )
\r
1799 pHttpParserErrorDescription = http_errno_description( HTTP_PARSER_ERRNO( pHttpParser ) );
\r
1800 IotLogError( "http_parser failed on the http response with error: %s", pHttpParserErrorDescription );
\r
1801 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_PARSING_ERROR );
\r
1804 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1807 /*-----------------------------------------------------------*/
\r
1809 static void _incrementNextLocationToWriteBeyondParsed( uint8_t ** pBufCur,
\r
1810 uint8_t ** pBufEnd )
\r
1812 /* There is an edge case where the final one or two character received in the header buffer is part of
\r
1813 * the header field separator ": " or part of the header line end "\r\n" delimiters. When this
\r
1814 * happens, pHeadersCur in the response will point not the end of the buffer, but to a character in
\r
1815 * the delimiter. For example:
\r
1816 * Let's say this is our current header buffer after receiving and parsing:
\r
1817 * ["HTTP/1.1 200 OK\r\n\header0: value0\r\nheader1: value1\r\n"]
\r
1818 * pHeadersCur will point to \r because the http-parser does not invoke a callback on the
\r
1819 * delimiters. Since no callback is invoked, pHeadersCur is not incremented. pHeadersEnd points to
\r
1820 * the end of the header buffer which is the unwritable memory location right after the final '\n'.
\r
1821 * Because pHeadersCur is less than pHeaderEnd we loop again and receive on the network causing the
\r
1822 * buffer to look like this:
\r
1823 * ["HTTP/1.1 200 OK\r\n\header0: value0\r\nheader1: value1he"]
\r
1824 * Which will cause an incorrect header1 value to be read if the application decides to read it with
\r
1825 * IotHttpsClient_ReadHeader().
\r
1827 * If our header buffer looks like:
\r
1828 * ["HTTP/1.1 200 OK\r\n\header0: value0\r\nheader1: "]
\r
1829 * then pHeaderCur will point to the colon.
\r
1831 * If our header buffer looks like:
\r
1832 * ["HTTP/1.1 200 OK\r\n\header0: value0\r\nheader1:"]
\r
1833 * then pHeaderCur will point to the colon.
\r
1835 * If our header buffer looks like
\r
1836 * ["HTTP/1.1 200 OK\r\n\header0: value0\r\nheader1: value1 "]
\r
1837 * then http-parser will consider that space as part of value1.
\r
1839 * If our header buffer looks like
\r
1840 * ["HTTP/1.1 200 OK\r\n\header0: value0\r\nheader1: value1\r"]
\r
1841 * then pHeaderCur will point to the carriage return.
\r
1843 * If our header buffer looks like
\r
1844 * ["HTTP/1.1 200 OK\r\n\header0: value0\r\nheader1: value1\r\n"]
\r
1845 * As explained in the example above, pHeaderCur will point to the carriage return.
\r
1847 * If we somehow receive a partial HTTP response message in our zeroed-out header buffer:
\r
1848 * case 1: ["HTTP/1.1 200 OK\r\nheader0: value0\r\nheader1: value1\r\0\0\0\0\0\0\0"]
\r
1849 * case 2: ["HTTP/1.1 200 OK\r\nheader0: value0\r\nheader1: value1\r\n\0\0\0\0\0\0"]
\r
1850 * case 3: ["HTTP/1.1 200 OK\r\nheader0: value0\r\nheader1:\0\0\0\0\0\0\0\0\0\0\0"]
\r
1851 * case 4: ["HTTP/1.1 200 OK\r\nheader0: value0\r\nheader1: \0\0\0\0\0\0\0\0\0\0\0"]
\r
1852 * then parser may fail or append all of the NULL characters to a header field name or value. */
\r
1853 while( *pBufCur < *pBufEnd )
\r
1855 if( **pBufCur == CARRIAGE_RETURN_CHARACTER )
\r
1859 else if( **pBufCur == NEWLINE_CHARACTER )
\r
1864 else if( **pBufCur == COLON_CHARACTER )
\r
1868 else if( ( **pBufCur == SPACE_CHARACTER ) && ( *( *pBufCur - 1 ) == COLON_CHARACTER ) )
\r
1880 /*-----------------------------------------------------------*/
\r
1882 static IotHttpsReturnCode_t _receiveHttpsMessage( _httpsConnection_t * pHttpsConnection,
\r
1883 _httpParserInfo_t * pHttpParserInfo,
\r
1884 IotHttpsResponseParserState_t * pCurrentParserState,
\r
1885 IotHttpsResponseParserState_t finalParserState,
\r
1886 IotHttpsResponseBufferState_t currentBufferProcessingState,
\r
1887 uint8_t ** pBufCur,
\r
1888 uint8_t ** pBufEnd )
\r
1890 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1892 size_t numBytesRecv = 0;
\r
1894 /* The final parser state is either the end of the header lines or the end of the entity body. This state is set in
\r
1895 * the http-parser callbacks. */
\r
1896 while( ( *pCurrentParserState < finalParserState ) && ( *pBufEnd - *pBufCur > 0 ) )
\r
1898 status = _networkRecv( pHttpsConnection,
\r
1900 *pBufEnd - *pBufCur,
\r
1903 /* A network error in _networkRecv is returned only when we received zero bytes. In that case, there is
\r
1904 * no point in parsing we return immediately with the network error. */
\r
1905 if( HTTPS_FAILED( status ) )
\r
1907 IotLogError( "Network error receiving the HTTPS response headers. Error code: %d", status );
\r
1911 status = _parseHttpsMessage( pHttpParserInfo, ( char * ) ( *pBufCur ), numBytesRecv );
\r
1913 if( HTTPS_FAILED( status ) )
\r
1915 IotLogError( "Failed to parse the message buffer with error: %d", pHttpParserInfo->responseParser.http_errno );
\r
1919 /* If the current buffer being filled is the header buffer, then \r\n header line separators should not get
\r
1920 * overwritten on the next network read. See _incrementNextLocationToWriteBeyondParsed() for more
\r
1922 if( currentBufferProcessingState == PROCESSING_STATE_FILLING_HEADER_BUFFER )
\r
1924 _incrementNextLocationToWriteBeyondParsed( pBufCur, pBufEnd );
\r
1927 /* The _httpsResponse->pHeadersCur pointer is updated in the http_parser callbacks. */
\r
1928 IotLogDebug( "There is %d of space left in the buffer.", *pBufEnd - *pBufCur );
\r
1931 /* If we did not reach the end of the headers or body in the parser callbacks, then the buffer configured does not
\r
1932 * fit all of that part of the HTTP message. */
\r
1933 if( *pCurrentParserState < finalParserState )
\r
1935 IotLogDebug( "There are still more data on the network. It could not fit into the specified length %d.",
\r
1936 *pBufEnd - *pBufCur );
\r
1939 HTTPS_GOTO_CLEANUP();
\r
1940 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1943 /*-----------------------------------------------------------*/
\r
1945 static IotHttpsReturnCode_t _receiveHttpsHeaders( _httpsConnection_t * pHttpsConnection,
\r
1946 _httpsResponse_t * pHttpsResponse )
\r
1948 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1950 pHttpsResponse->bufferProcessingState = PROCESSING_STATE_FILLING_HEADER_BUFFER;
\r
1952 IotLogDebug( "Now attempting to receive the HTTP response headers into a buffer with length %d.",
\r
1953 pHttpsResponse->pHeadersEnd - pHttpsResponse->pHeadersCur );
\r
1955 status = _receiveHttpsMessage( pHttpsConnection,
\r
1956 &( pHttpsResponse->httpParserInfo ),
\r
1957 &( pHttpsResponse->parserState ),
\r
1958 PARSER_STATE_HEADERS_COMPLETE,
\r
1959 PROCESSING_STATE_FILLING_HEADER_BUFFER,
\r
1960 &( pHttpsResponse->pHeadersCur ),
\r
1961 &( pHttpsResponse->pHeadersEnd ) );
\r
1963 if( HTTPS_FAILED( status ) )
\r
1965 IotLogError( "Error receiving the HTTP headers. Error code %d", status );
\r
1966 HTTPS_GOTO_CLEANUP();
\r
1969 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
1972 /*-----------------------------------------------------------*/
\r
1974 /* _receiveHttpsHeaders() must be called first before this function is called. */
\r
1975 static IotHttpsReturnCode_t _receiveHttpsBody( _httpsConnection_t * pHttpsConnection,
\r
1976 _httpsResponse_t * pHttpsResponse )
\r
1978 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
1980 IotLogDebug( "Now attempting to receive the HTTP response body into a buffer with length %d.",
\r
1981 pHttpsResponse->pBodyEnd - pHttpsResponse->pBodyCur );
\r
1983 pHttpsResponse->bufferProcessingState = PROCESSING_STATE_FILLING_BODY_BUFFER;
\r
1985 status = _receiveHttpsMessage( pHttpsConnection,
\r
1986 &( pHttpsResponse->httpParserInfo ),
\r
1987 &( pHttpsResponse->parserState ),
\r
1988 PARSER_STATE_BODY_COMPLETE,
\r
1989 PROCESSING_STATE_FILLING_BODY_BUFFER,
\r
1990 &( pHttpsResponse->pBodyCur ),
\r
1991 &( pHttpsResponse->pBodyEnd ) );
\r
1993 if( HTTPS_FAILED( status ) )
\r
1995 IotLogError( "Error receiving the HTTP body. Error code %d", status );
\r
1996 HTTPS_GOTO_CLEANUP();
\r
1999 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
2001 IotLogDebug( "The remaining content length on the network is %d.",
\r
2002 pHttpsResponse->httpParserInfo.responseParser.content_length );
\r
2004 HTTPS_FUNCTION_CLEANUP_END();
\r
2007 /*-----------------------------------------------------------*/
\r
2009 static IotHttpsReturnCode_t _flushHttpsNetworkData( _httpsConnection_t * pHttpsConnection,
\r
2010 _httpsResponse_t * pHttpsResponse )
\r
2012 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2014 static uint8_t flushBuffer[ IOT_HTTPS_MAX_FLUSH_BUFFER_SIZE ] = { 0 };
\r
2015 const char * pHttpParserErrorDescription = NULL;
\r
2016 IotHttpsReturnCode_t parserStatus = IOT_HTTPS_OK;
\r
2017 IotHttpsReturnCode_t networkStatus = IOT_HTTPS_OK;
\r
2018 size_t numBytesRecv = 0;
\r
2020 /* Even if there is not body, the parser state will become body complete after the headers finish. */
\r
2021 while( pHttpsResponse->parserState < PARSER_STATE_BODY_COMPLETE )
\r
2023 IotLogDebug( "Now clearing the rest of the response data on the socket. " );
\r
2024 networkStatus = _networkRecv( pHttpsConnection, flushBuffer, IOT_HTTPS_MAX_FLUSH_BUFFER_SIZE, &numBytesRecv );
\r
2026 /* Run this through the parser so that we can get the end of the HTTP message, instead of simply timing out the socket to stop.
\r
2027 * If we relied on the socket timeout to stop reading the network socket, then the server may close the connection. */
\r
2028 parserStatus = _parseHttpsMessage( &( pHttpsResponse->httpParserInfo ), ( char * ) flushBuffer, numBytesRecv );
\r
2030 if( HTTPS_FAILED( parserStatus ) )
\r
2032 pHttpParserErrorDescription = http_errno_description( HTTP_PARSER_ERRNO( &pHttpsResponse->httpParserInfo.responseParser ) );
\r
2033 IotLogError( "Network Flush: Failed to parse the response body buffer with error: %d, %s",
\r
2034 pHttpsResponse->httpParserInfo.responseParser.http_errno,
\r
2035 pHttpParserErrorDescription );
\r
2039 /* If there is a network error then we want to stop clearing out the buffer. */
\r
2040 if( HTTPS_FAILED( networkStatus ) )
\r
2042 IotLogWarn( "Network Flush: Error receiving the rest of the HTTP response. Error code: %d",
\r
2048 /* All network errors except timeouts are returned. */
\r
2049 if( HTTPS_FAILED( networkStatus ) )
\r
2051 status = networkStatus;
\r
2055 status = parserStatus;
\r
2058 HTTPS_GOTO_CLEANUP();
\r
2060 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
2063 /*-----------------------------------------------------------*/
\r
2065 static IotHttpsReturnCode_t _sendHttpsHeadersAndBody( _httpsConnection_t * pHttpsConnection,
\r
2066 _httpsRequest_t * pHttpsRequest )
\r
2068 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2070 /* Send the HTTP headers. */
\r
2071 status = _sendHttpsHeaders( pHttpsConnection,
\r
2072 pHttpsRequest->pHeaders,
\r
2073 pHttpsRequest->pHeadersCur - pHttpsRequest->pHeaders,
\r
2074 pHttpsRequest->isNonPersistent,
\r
2075 pHttpsRequest->bodyLength );
\r
2077 if( HTTPS_FAILED( status ) )
\r
2079 IotLogError( "Error sending the HTTPS headers with error code: %d", status );
\r
2080 HTTPS_GOTO_CLEANUP();
\r
2083 if( ( pHttpsRequest->pBody != NULL ) && ( pHttpsRequest->bodyLength > 0 ) )
\r
2085 status = _sendHttpsBody( pHttpsConnection, pHttpsRequest->pBody, pHttpsRequest->bodyLength );
\r
2087 if( HTTPS_FAILED( status ) )
\r
2089 IotLogError( "Error sending final HTTPS body. Return code: %d", status );
\r
2090 HTTPS_GOTO_CLEANUP();
\r
2094 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
2097 /*-----------------------------------------------------------*/
\r
2099 static void _sendHttpsRequest( IotTaskPool_t pTaskPool,
\r
2100 IotTaskPoolJob_t pJob,
\r
2101 void * pUserContext )
\r
2103 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2105 _httpsRequest_t * pHttpsRequest = ( _httpsRequest_t * ) ( pUserContext );
\r
2106 _httpsConnection_t * pHttpsConnection = pHttpsRequest->pHttpsConnection;
\r
2107 _httpsResponse_t * pHttpsResponse = pHttpsRequest->pHttpsResponse;
\r
2108 IotHttpsReturnCode_t disconnectStatus = IOT_HTTPS_OK;
\r
2109 IotHttpsReturnCode_t scheduleStatus = IOT_HTTPS_OK;
\r
2110 IotLink_t * pQItem = NULL;
\r
2111 _httpsRequest_t * pNextHttpsRequest = NULL;
\r
2113 ( void ) pTaskPool;
\r
2116 IotLogDebug( "Task with request ID: %d started.", pHttpsRequest );
\r
2118 if( pHttpsRequest->cancelled == true )
\r
2120 IotLogDebug( "Request ID: %d was cancelled.", pHttpsRequest );
\r
2121 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_SEND_ABORT );
\r
2124 /* To protect against out of order network data from a rouge server, signal that the request is
\r
2125 * not finished sending. */
\r
2126 pHttpsResponse->reqFinishedSending = false;
\r
2128 /* Queue the response to expect from the network. */
\r
2129 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
2130 IotDeQueue_EnqueueTail( &( pHttpsConnection->respQ ), &( pHttpsResponse->link ) );
\r
2131 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
2133 /* Get the headers from the application. For a synchronous request the application should have appended extra
\r
2134 * headers before this point. */
\r
2135 if( pHttpsRequest->isAsync && pHttpsRequest->pCallbacks->appendHeaderCallback )
\r
2137 pHttpsRequest->pCallbacks->appendHeaderCallback( pHttpsRequest->pUserPrivData, pHttpsRequest );
\r
2140 if( pHttpsRequest->cancelled == true )
\r
2142 IotLogDebug( "Request ID: %d was cancelled.", pHttpsRequest );
\r
2143 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_SEND_ABORT );
\r
2146 /* Ask the user for data to write body to the network. We only ask the user once. This is so that
\r
2147 * we can calculate the Content-Length to send.*/
\r
2148 if( pHttpsRequest->isAsync && pHttpsRequest->pCallbacks->writeCallback )
\r
2150 /* If there is data, then a Content-Length header value will be provided and we send the headers
\r
2151 * before that user data. */
\r
2152 pHttpsRequest->pCallbacks->writeCallback( pHttpsRequest->pUserPrivData, pHttpsRequest );
\r
2155 if( HTTPS_FAILED( pHttpsRequest->bodyTxStatus ) )
\r
2157 IotLogError( "Failed to send the headers and body over the network during the writeCallback. Error code: %d.",
\r
2159 HTTPS_SET_AND_GOTO_CLEANUP( pHttpsRequest->bodyTxStatus );
\r
2162 if( pHttpsRequest->cancelled == true )
\r
2164 IotLogDebug( "Request ID: %d was cancelled.", pHttpsRequest );
\r
2165 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_SEND_ABORT );
\r
2168 /* If this is a synchronous request then the header and body were configured beforehand. The header and body
\r
2169 * are sent now. For an asynchronous request, the header and body are sent in IotHttpsClient_WriteRequestBody()
\r
2170 * which is to be invoked in #IotHttpsClientCallbacks_t.writeCallback(). If the application never invokes
\r
2171 * IotHttpsClient_WriteRequestBody(), then pHttpsRequest->pBody will be NULL. In this case we still want to
\r
2172 * send whatever headers we have. */
\r
2173 if( ( pHttpsRequest->isAsync == false ) ||
\r
2174 ( ( pHttpsRequest->isAsync ) && ( pHttpsRequest->pBody == NULL ) ) )
\r
2176 status = _sendHttpsHeadersAndBody( pHttpsConnection, pHttpsRequest );
\r
2178 if( HTTPS_FAILED( status ) )
\r
2180 IotLogError( "Failed to send the headers and body on the network. Error code: %d", status );
\r
2181 HTTPS_GOTO_CLEANUP();
\r
2185 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
2187 /* The request has finished sending. This indicates to the network receive callback that the request was
\r
2188 * finished, so a response received on the network is valid. This also lets a possible application called
\r
2189 * IotHttpsClient_Disconnect() know that the connection is not busy, so the connection can be destroyed. */
\r
2190 pHttpsResponse->reqFinishedSending = true;
\r
2192 if( HTTPS_FAILED( status ) )
\r
2194 /* If the headers or body failed to send, then there should be no response expected from the server. */
\r
2195 /* Cancel the response incase there is a response from the server. */
\r
2196 _cancelResponse( pHttpsResponse );
\r
2197 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
2199 if( IotLink_IsLinked( &( pHttpsResponse->link ) ) )
\r
2201 IotDeQueue_Remove( &( pHttpsResponse->link ) );
\r
2204 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
2206 /* Set the error status in the sync workflow. */
\r
2207 pHttpsResponse->syncStatus = status;
\r
2209 /* Return the error status or cancel status to the application for an asynchronous workflow. */
\r
2210 if( pHttpsRequest->isAsync && pHttpsRequest->pCallbacks->errorCallback )
\r
2212 pHttpsRequest->pCallbacks->errorCallback( pHttpsRequest->pUserPrivData, pHttpsRequest, NULL, status );
\r
2215 /* We close the connection on all network errors. All network errors in receiving the response, close the
\r
2216 * connection. For consistency in behavior, if there is a network error in send, the connection should also be
\r
2218 if( status == IOT_HTTPS_NETWORK_ERROR )
\r
2220 IotLogDebug( "Disconnecting request %d.", pHttpsRequest );
\r
2221 disconnectStatus = IotHttpsClient_Disconnect( pHttpsConnection );
\r
2223 if( pHttpsRequest->isAsync && pHttpsRequest->pCallbacks->connectionClosedCallback )
\r
2225 pHttpsRequest->pCallbacks->connectionClosedCallback( pHttpsRequest->pUserPrivData,
\r
2227 disconnectStatus );
\r
2230 if( HTTPS_FAILED( disconnectStatus ) )
\r
2232 IotLogWarn( "Failed to disconnect request %d. Error code: %d.", pHttpsRequest, disconnectStatus );
\r
2237 /* Because this request failed, the network receive callback may never be invoked to schedule other possible
\r
2238 * requests in the queue. In order to avoid requests never getting scheduled on a connected connection,
\r
2239 * the first item in the queue is scheduled if it can be. */
\r
2240 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
2242 /* Get the next item in the queue by removing this current (which is the first) and peeking at the head
\r
2244 IotDeQueue_Remove( &( pHttpsRequest->link ) );
\r
2245 pQItem = IotDeQueue_PeekHead( &( pHttpsConnection->reqQ ) );
\r
2246 /* This current request is put back because it is removed again for all cases at the end of this routine. */
\r
2247 IotDeQueue_EnqueueHead( &( pHttpsConnection->reqQ ), &( pHttpsRequest->link ) );
\r
2248 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
2250 if( pQItem != NULL )
\r
2252 /* Set this next request to send. */
\r
2253 pNextHttpsRequest = IotLink_Container( _httpsRequest_t, pQItem, link );
\r
2255 if( pNextHttpsRequest->scheduled == false )
\r
2257 IotLogDebug( "Request %d is next in the queue. Now scheduling a task to send the request.", pNextHttpsRequest );
\r
2258 scheduleStatus = _scheduleHttpsRequestSend( pNextHttpsRequest );
\r
2260 /* If there was an error with scheduling the new task, then report it. */
\r
2261 if( HTTPS_FAILED( scheduleStatus ) )
\r
2263 IotLogError( "Error scheduling HTTPS request %d. Error code: %d", pNextHttpsRequest, scheduleStatus );
\r
2265 if( pNextHttpsRequest->isAsync && pNextHttpsRequest->pCallbacks->errorCallback )
\r
2267 pNextHttpsRequest->pCallbacks->errorCallback( pNextHttpsRequest->pUserPrivData, pNextHttpsRequest, NULL, scheduleStatus );
\r
2271 pNextHttpsRequest->pHttpsResponse->syncStatus = scheduleStatus;
\r
2278 /* Post to the response finished semaphore to unlock the application waiting on a synchronous request. */
\r
2279 if( pHttpsRequest->isAsync == false )
\r
2281 IotSemaphore_Post( &( pHttpsResponse->respFinishedSem ) );
\r
2283 else if( pHttpsRequest->pCallbacks->responseCompleteCallback )
\r
2285 /* Call the response complete callback. We always call this even if we did not receive the response to
\r
2286 * let the application know that the request has completed. */
\r
2287 pHttpsRequest->pCallbacks->responseCompleteCallback( pHttpsRequest->pUserPrivData, NULL, status, 0 );
\r
2291 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
2292 /* Now that the current request is finished, we dequeue the current request from the queue. */
\r
2293 IotDeQueue_DequeueHead( &( pHttpsConnection->reqQ ) );
\r
2294 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
2296 /* This routine returns a void so there is no HTTPS_FUNCTION_CLEANUP_END();. */
\r
2299 /*-----------------------------------------------------------*/
\r
2301 IotHttpsReturnCode_t _scheduleHttpsRequestSend( _httpsRequest_t * pHttpsRequest )
\r
2303 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2305 IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS;
\r
2306 _httpsConnection_t * pHttpsConnection = pHttpsRequest->pHttpsConnection;
\r
2308 /* Set the request to scheduled even if scheduling fails. */
\r
2309 pHttpsRequest->scheduled = true;
\r
2311 taskPoolStatus = IotTaskPool_CreateJob( _sendHttpsRequest,
\r
2312 ( void * ) ( pHttpsRequest ),
\r
2313 &( pHttpsConnection->taskPoolJobStorage ),
\r
2314 &( pHttpsConnection->taskPoolJob ) );
\r
2316 /* Creating a task pool job should never fail when parameters are valid. */
\r
2317 if( taskPoolStatus != IOT_TASKPOOL_SUCCESS )
\r
2319 IotLogError( "Error creating a taskpool job for request servicing. Error code: %d", taskPoolStatus );
\r
2320 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INTERNAL_ERROR );
\r
2323 taskPoolStatus = IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, pHttpsConnection->taskPoolJob, 0 );
\r
2325 if( taskPoolStatus != IOT_TASKPOOL_SUCCESS )
\r
2327 IotLogError( "Failed to schedule taskpool job. Error code: %d", taskPoolStatus );
\r
2328 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_ASYNC_SCHEDULING_ERROR );
\r
2331 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
2334 /*-----------------------------------------------------------*/
\r
2336 IotHttpsReturnCode_t _addRequestToConnectionReqQ( _httpsRequest_t * pHttpsRequest )
\r
2338 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2340 _httpsConnection_t * pHttpsConnection = pHttpsRequest->pHttpsConnection;
\r
2341 bool scheduleRequest = false;
\r
2343 /* Log information about the request*/
\r
2344 IotLogDebug( "Now queueing request %d.", pHttpsRequest );
\r
2346 if( pHttpsRequest->isNonPersistent )
\r
2348 IotLogDebug( "Request %d is non-persistent.", pHttpsRequest );
\r
2352 IotLogDebug( "Request %d is persistent. ", pHttpsRequest );
\r
2355 if( pHttpsRequest->isAsync )
\r
2357 IotLogDebug( " Request %d is asynchronous.", pHttpsRequest );
\r
2361 IotLogDebug( " Request %d is synchronous.", pHttpsRequest );
\r
2364 /* This is a new request and has not been scheduled if this routine is called. */
\r
2365 pHttpsRequest->scheduled = false;
\r
2367 /* Place the request into the queue. */
\r
2368 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
2370 /* If there is an active response, scheduling the next request at the same time may corrupt the workflow. Part of
\r
2371 * the next response for the next request may be present in the currently receiving response's buffers. To avoid
\r
2372 * this, check if there are pending responses to determine if this request should be scheduled right away or not.
\r
2374 * If there are other requests in the queue, and there are responses in the queue, then the network receive callback
\r
2375 * will handle scheduling the next requests (or is already scheduled and currently sending). */
\r
2376 if( ( IotDeQueue_IsEmpty( &( pHttpsConnection->reqQ ) ) ) &&
\r
2377 ( IotDeQueue_IsEmpty( &( pHttpsConnection->respQ ) ) ) )
\r
2379 scheduleRequest = true;
\r
2382 /* Place into the connection's request to have a taskpool worker schedule to serve it later. */
\r
2383 IotDeQueue_EnqueueTail( &( pHttpsConnection->reqQ ), &( pHttpsRequest->link ) );
\r
2384 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
2386 if( scheduleRequest )
\r
2388 /* This routine schedules a task pool worker to send the request. If a worker is available immediately, then
\r
2389 * the request is sent right away. */
\r
2390 status = _scheduleHttpsRequestSend( pHttpsRequest );
\r
2392 if( HTTPS_FAILED( status ) )
\r
2394 IotLogError( "Failed to schedule the request in the queue for request %d. Error code: %d", pHttpsRequest, status );
\r
2396 /* If we fail to schedule the only request in the queue we should remove it. */
\r
2397 IotMutex_Lock( &( pHttpsConnection->connectionMutex ) );
\r
2398 IotDeQueue_Remove( &( pHttpsRequest->link ) );
\r
2399 IotMutex_Unlock( &( pHttpsConnection->connectionMutex ) );
\r
2401 HTTPS_GOTO_CLEANUP();
\r
2405 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
2408 /*-----------------------------------------------------------*/
\r
2410 static void _cancelRequest( _httpsRequest_t * pHttpsRequest )
\r
2412 pHttpsRequest->cancelled = true;
\r
2415 /*-----------------------------------------------------------*/
\r
2417 static void _cancelResponse( _httpsResponse_t * pHttpsResponse )
\r
2419 pHttpsResponse->cancelled = true;
\r
2422 /*-----------------------------------------------------------*/
\r
2424 IotHttpsReturnCode_t IotHttpsClient_Init( void )
\r
2426 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2428 /* This sets all member in the _httpParserSettings to zero. It does not return any errors. */
\r
2429 http_parser_settings_init( &_httpParserSettings );
\r
2431 /* Set the http-parser callbacks. */
\r
2432 _httpParserSettings.on_message_begin = _httpParserOnMessageBeginCallback;
\r
2433 _httpParserSettings.on_status = _httpParserOnStatusCallback;
\r
2434 _httpParserSettings.on_header_field = _httpParserOnHeaderFieldCallback;
\r
2435 _httpParserSettings.on_header_value = _httpParserOnHeaderValueCallback;
\r
2436 _httpParserSettings.on_headers_complete = _httpParserOnHeadersCompleteCallback;
\r
2437 _httpParserSettings.on_body = _httpParserOnBodyCallback;
\r
2438 _httpParserSettings.on_message_complete = _httpParserOnMessageCompleteCallback;
\r
2440 /* This code prints debugging information and is, therefore, compiled only when
\r
2441 * log level is set to IOT_LOG_DEBUG. */
\r
2442 #if ( LIBRARY_LOG_LEVEL == IOT_LOG_DEBUG )
\r
2443 _httpParserSettings.on_chunk_header = _httpParserOnChunkHeaderCallback;
\r
2444 _httpParserSettings.on_chunk_complete = _httpParserOnChunkCompleteCallback;
\r
2446 HTTPS_GOTO_CLEANUP();
\r
2447 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
2450 /*-----------------------------------------------------------*/
\r
2452 static IotHttpsReturnCode_t _initializeResponse( IotHttpsResponseHandle_t * pRespHandle,
\r
2453 IotHttpsResponseInfo_t * pRespInfo,
\r
2454 _httpsRequest_t * pHttpsRequest )
\r
2456 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2458 _httpsResponse_t * pHttpsResponse = NULL;
\r
2460 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pRespInfo->userBuffer.pBuffer );
\r
2462 /* Check of the user buffer is large enough for the response context + default headers. */
\r
2463 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( pRespInfo->userBuffer.bufferLen >= responseUserBufferMinimumSize,
\r
2464 IOT_HTTPS_INSUFFICIENT_MEMORY,
\r
2465 "Buffer size is too small to initialize the response context. User buffer size: %d, required minimum size; %d.",
\r
2466 pRespInfo->userBuffer.bufferLen,
\r
2467 responseUserBufferMinimumSize );
\r
2469 /* Initialize the corresponding response to this request. */
\r
2470 pHttpsResponse = ( _httpsResponse_t * ) ( pRespInfo->userBuffer.pBuffer );
\r
2472 /* Clear out the response user buffer. This is important because we
\r
2473 * give the whole buffer to the parser as opposed to the actual content
\r
2474 * length and rely on the parser to stop when a complete HTTP response
\r
2475 * is found. To make sure that any data in the buffer which is not part
\r
2476 * of the received HTTP response, does not get interpreted as part of
\r
2477 * the HTTP repose, we zero out the buffer here. */
\r
2478 memset( pRespInfo->userBuffer.pBuffer, 0, pRespInfo->userBuffer.bufferLen );
\r
2480 pHttpsResponse->pHeaders = ( uint8_t * ) ( pHttpsResponse ) + sizeof( _httpsResponse_t );
\r
2481 pHttpsResponse->pHeadersEnd = ( uint8_t * ) ( pHttpsResponse ) + pRespInfo->userBuffer.bufferLen;
\r
2482 pHttpsResponse->pHeadersCur = pHttpsResponse->pHeaders;
\r
2484 if( pHttpsRequest->isAsync )
\r
2486 pHttpsResponse->isAsync = true;
\r
2488 /* For an asynchronous request the response body is provided by the application in the
\r
2489 * IotHttpsCallbacks_t.readReadyCallback(). These pointers will be updated when IotHttpsClient_ReadResponseBody()
\r
2491 pHttpsResponse->pBody = NULL;
\r
2492 pHttpsResponse->pBodyCur = NULL;
\r
2493 pHttpsResponse->pBodyEnd = NULL;
\r
2495 pHttpsResponse->pCallbacks = pHttpsRequest->pCallbacks;
\r
2496 pHttpsResponse->pUserPrivData = pHttpsRequest->pUserPrivData;
\r
2500 pHttpsResponse->isAsync = false;
\r
2501 /* The request body pointer is allowed to be NULL. u.pSyncInfo was checked for NULL earlier in this function. */
\r
2502 pHttpsResponse->pBody = pRespInfo->pSyncInfo->pBody;
\r
2503 pHttpsResponse->pBodyCur = pHttpsResponse->pBody;
\r
2504 pHttpsResponse->pBodyEnd = pHttpsResponse->pBody + pRespInfo->pSyncInfo->bodyLen;
\r
2506 /* Clear out the body bufffer. This is important because we give the
\r
2507 * whole buffer to the parser as opposed to the actual content length and
\r
2508 * rely on the parser to stop when a complete HTTP response is found. To
\r
2509 * make sure that any data in the buffer which is not part of the received
\r
2510 * HTTP response, does not get interpreted as part of the HTTP repose, we
\r
2511 * zero out the buffer here. */
\r
2512 memset( pRespInfo->pSyncInfo->pBody, 0, pRespInfo->pSyncInfo->bodyLen );
\r
2515 /* Reinitialize the parser and set the fill buffer state to empty. This does not return any errors. */
\r
2516 http_parser_init( &( pHttpsResponse->httpParserInfo.responseParser ), HTTP_RESPONSE );
\r
2517 http_parser_init( &( pHttpsResponse->httpParserInfo.readHeaderParser ), HTTP_RESPONSE );
\r
2518 /* Set the third party http parser function. */
\r
2519 pHttpsResponse->httpParserInfo.parseFunc = http_parser_execute;
\r
2520 pHttpsResponse->httpParserInfo.readHeaderParser.data = ( void * ) ( pHttpsResponse );
\r
2521 pHttpsResponse->httpParserInfo.responseParser.data = ( void * ) ( pHttpsResponse );
\r
2523 pHttpsResponse->status = 0;
\r
2524 pHttpsResponse->method = pHttpsRequest->method;
\r
2525 pHttpsResponse->parserState = PARSER_STATE_NONE;
\r
2526 pHttpsResponse->bufferProcessingState = PROCESSING_STATE_NONE;
\r
2527 pHttpsResponse->pReadHeaderField = NULL;
\r
2528 pHttpsResponse->readHeaderFieldLength = 0;
\r
2529 pHttpsResponse->pReadHeaderValue = NULL;
\r
2530 pHttpsResponse->readHeaderValueLength = 0;
\r
2531 pHttpsResponse->foundHeaderField = 0;
\r
2532 pHttpsResponse->pHttpsConnection = NULL;
\r
2534 pHttpsResponse->pBodyInHeaderBuf = NULL;
\r
2535 pHttpsResponse->pBodyCurInHeaderBuf = NULL;
\r
2536 pHttpsResponse->bodyRxStatus = IOT_HTTPS_OK;
\r
2537 pHttpsResponse->cancelled = false;
\r
2538 pHttpsResponse->syncStatus = IOT_HTTPS_OK;
\r
2539 /* There is no request associated with this response right now, so it is finished sending. */
\r
2540 pHttpsResponse->reqFinishedSending = true;
\r
2541 pHttpsResponse->isNonPersistent = pHttpsRequest->isNonPersistent;
\r
2543 /* Set the response handle to return. */
\r
2544 *pRespHandle = pHttpsResponse;
\r
2546 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
2548 if( HTTPS_FAILED( status ) )
\r
2550 pRespHandle = NULL;
\r
2553 HTTPS_FUNCTION_CLEANUP_END();
\r
2556 /*-----------------------------------------------------------*/
\r
2558 void IotHttpsClient_Cleanup( void )
\r
2560 /* There is nothing to clean up here as of now. */
\r
2563 /* --------------------------------------------------------- */
\r
2565 IotHttpsReturnCode_t IotHttpsClient_Connect( IotHttpsConnectionHandle_t * pConnHandle,
\r
2566 IotHttpsConnectionInfo_t * pConnInfo )
\r
2568 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2570 /* Check for NULL parameters in a public API. */
\r
2571 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pConnHandle );
\r
2572 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pConnInfo );
\r
2574 /* If a valid connection handle is passed in. */
\r
2575 if( *pConnHandle != NULL )
\r
2577 /* If the handle in a connected state, then we want to disconnect before reconnecting. The ONLY way to put the
\r
2578 * handle is a disconnect state is to call IotHttpsClient_Disconnect(). */
\r
2579 if( ( *pConnHandle )->isConnected )
\r
2581 status = IotHttpsClient_Disconnect( *pConnHandle );
\r
2583 if( HTTPS_FAILED( status ) )
\r
2585 IotLogError( "Error disconnecting a connected *pConnHandle passed to IotHttpsClient_Connect().Error code %d", status );
\r
2586 *pConnHandle = NULL;
\r
2587 HTTPS_GOTO_CLEANUP();
\r
2592 /* Connect to the server now. Initialize all resources needed for the connection context as well here. */
\r
2593 status = _createHttpsConnection( pConnHandle, pConnInfo );
\r
2595 if( HTTPS_FAILED( status ) )
\r
2597 IotLogError( "Error in IotHttpsClient_Connect(). Error code %d.", status );
\r
2600 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
2603 /*-----------------------------------------------------------*/
\r
2605 IotHttpsReturnCode_t IotHttpsClient_Disconnect( IotHttpsConnectionHandle_t connHandle )
\r
2607 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2609 _httpsRequest_t * pHttpsRequest = NULL;
\r
2610 _httpsResponse_t * pHttpsResponse = NULL;
\r
2611 IotLink_t * pRespItem = NULL;
\r
2612 IotLink_t * pReqItem = NULL;
\r
2614 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( connHandle );
\r
2616 /* If this routine is currently is progress by another thread, for instance the taskpool worker that received a
\r
2617 * network error after sending, then return right away because connection resources are being used. */
\r
2618 if( IotMutex_TryLock( &( connHandle->connectionMutex ) ) == false )
\r
2620 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_BUSY );
\r
2623 /* Do not attempt to disconnect an already disconnected connection.
\r
2624 * It can happen when a user calls this functions and we return IOT_HTTPS_BUSY. */
\r
2625 if( connHandle->isConnected )
\r
2627 /* Mark the network as disconnected whether the disconnect passes or not. */
\r
2628 connHandle->isConnected = false;
\r
2629 _networkDisconnect( connHandle );
\r
2632 /* If there is a response in the connection's response queue and the associated request has not finished sending,
\r
2633 * then we cannot destroy the connection until it finishes. */
\r
2634 pRespItem = IotDeQueue_DequeueHead( &( connHandle->respQ ) );
\r
2636 if( pRespItem != NULL )
\r
2638 pHttpsResponse = IotLink_Container( _httpsResponse_t, pRespItem, link );
\r
2640 if( pHttpsResponse->reqFinishedSending == false )
\r
2642 IotLogError( "Connection is in use. Disconnected, but cannot destroy the connection." );
\r
2643 status = IOT_HTTPS_BUSY;
\r
2645 /* The request is busy, to as quickly as possible allow a successful retry call of this function we must
\r
2646 * cancel the busy request which is the first in the queue. */
\r
2647 pReqItem = IotDeQueue_PeekHead( &( connHandle->reqQ ) );
\r
2649 if( pReqItem != NULL )
\r
2651 pHttpsRequest = IotLink_Container( _httpsRequest_t, pReqItem, link );
\r
2652 _cancelRequest( pHttpsRequest );
\r
2655 /* We set the status as busy, but we do not goto the cleanup right away because we still want to remove
\r
2656 * all pending requests. */
\r
2659 /* Delete all possible pending responses. (This is defensive.) */
\r
2660 IotDeQueue_RemoveAll( &( connHandle->respQ ), NULL, 0 );
\r
2662 /* Put the response that was dequeued back so that the application can call this function again to check later
\r
2663 * that is exited and marked itself as finished sending.
\r
2664 * If during the last check and this check reqFinishedSending gets set to true, that is OK because on the next
\r
2665 * call to this routine, the disconnect will succeed. */
\r
2666 if( pHttpsResponse->reqFinishedSending == false )
\r
2668 IotDeQueue_EnqueueHead( &( connHandle->respQ ), pRespItem );
\r
2672 /* Remove all pending requests. If this routine is called from the application context and there is a
\r
2673 * network receive callback in process, this routine will wait in _networkDestroy until that routine returns.
\r
2674 * If this is routine is called from the network receive callback context, then the destroy happens after the
\r
2675 * network receive callback context returns. */
\r
2676 IotDeQueue_RemoveAll( &( connHandle->reqQ ), NULL, 0 );
\r
2678 /* Do not attempt to destroy an already destroyed connection. This can happen when the user calls this function and
\r
2679 * IOT_HTTPS_BUSY is returned. */
\r
2680 if( HTTPS_SUCCEEDED( status ) )
\r
2682 if( connHandle->isDestroyed == false )
\r
2684 connHandle->isDestroyed = true;
\r
2685 _networkDestroy( connHandle );
\r
2689 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
2691 /* This function is no longer in process, so disconnecting is no longer in process. This signals to the retry
\r
2692 * on this function that it can proceed with the disconnecting activities. */
\r
2693 if( connHandle != NULL )
\r
2695 IotMutex_Unlock( &( connHandle->connectionMutex ) );
\r
2698 HTTPS_FUNCTION_CLEANUP_END();
\r
2701 /*-----------------------------------------------------------*/
\r
2703 IotHttpsReturnCode_t IotHttpsClient_InitializeRequest( IotHttpsRequestHandle_t * pReqHandle,
\r
2704 IotHttpsRequestInfo_t * pReqInfo )
\r
2706 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2708 _httpsRequest_t * pHttpsRequest = NULL;
\r
2709 size_t additionalLength = 0;
\r
2710 size_t spaceLen = 1;
\r
2711 char * pSpace = " ";
\r
2712 size_t httpsMethodLen = 0;
\r
2713 size_t httpsProtocolVersionLen = FAST_MACRO_STRLEN( HTTPS_PROTOCOL_VERSION );
\r
2715 /* Check for NULL parameters in the public API. */
\r
2716 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pReqHandle );
\r
2717 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pReqInfo );
\r
2718 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pReqInfo->userBuffer.pBuffer );
\r
2719 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pReqInfo->pHost );
\r
2721 if( pReqInfo->isAsync )
\r
2723 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pReqInfo->u.pAsyncInfo );
\r
2727 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pReqInfo->u.pSyncInfo );
\r
2730 /* Check of the user buffer is large enough for the request context + default headers. */
\r
2731 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( pReqInfo->userBuffer.bufferLen >= requestUserBufferMinimumSize,
\r
2732 IOT_HTTPS_INSUFFICIENT_MEMORY,
\r
2733 "Buffer size is too small to initialize the request context. User buffer size: %d, required minimum size; %d.",
\r
2734 pReqInfo->userBuffer.bufferLen,
\r
2735 requestUserBufferMinimumSize );
\r
2737 /* Set the request contet to the start of the userbuffer. */
\r
2738 pHttpsRequest = ( _httpsRequest_t * ) ( pReqInfo->userBuffer.pBuffer );
\r
2739 /* Clear out the user buffer. */
\r
2740 memset( pReqInfo->userBuffer.pBuffer, 0, pReqInfo->userBuffer.bufferLen );
\r
2742 /* Set the start of the headers to the end of the request context in the user buffer. */
\r
2743 pHttpsRequest->pHeaders = ( uint8_t * ) pHttpsRequest + sizeof( _httpsRequest_t );
\r
2744 pHttpsRequest->pHeadersEnd = ( uint8_t * ) pHttpsRequest + pReqInfo->userBuffer.bufferLen;
\r
2745 pHttpsRequest->pHeadersCur = pHttpsRequest->pHeaders;
\r
2747 /* Get the length of the HTTP method. */
\r
2748 httpsMethodLen = strlen( _pHttpsMethodStrings[ pReqInfo->method ] );
\r
2750 /* Add the request line to the header buffer. */
\r
2751 additionalLength = httpsMethodLen + \
\r
2753 pReqInfo->pathLen + \
\r
2755 httpsProtocolVersionLen + \
\r
2756 HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH;
\r
2758 if( ( additionalLength + pHttpsRequest->pHeadersCur ) > ( pHttpsRequest->pHeadersEnd ) )
\r
2760 IotLogError( "Request line does not fit into the request user buffer: \"%s %.*s HTTP/1.1\\r\\n\" . ",
\r
2761 _pHttpsMethodStrings[ pReqInfo->method ],
\r
2762 pReqInfo->pathLen,
\r
2763 pReqInfo->pPath );
\r
2764 IotLogError( "The length needed is %d and the space available is %d.", additionalLength, pHttpsRequest->pHeadersEnd - pHttpsRequest->pHeadersCur );
\r
2765 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INSUFFICIENT_MEMORY );
\r
2768 /* Write "<METHOD> <PATH> HTTP/1.1\r\n" to the start of the header space. */
\r
2769 memcpy( pHttpsRequest->pHeadersCur, _pHttpsMethodStrings[ pReqInfo->method ], httpsMethodLen );
\r
2770 pHttpsRequest->pHeadersCur += httpsMethodLen;
\r
2771 memcpy( pHttpsRequest->pHeadersCur, pSpace, spaceLen );
\r
2772 pHttpsRequest->pHeadersCur += spaceLen;
\r
2774 if( pReqInfo->pPath == NULL )
\r
2776 pReqInfo->pPath = HTTPS_EMPTY_PATH;
\r
2777 pReqInfo->pathLen = FAST_MACRO_STRLEN( HTTPS_EMPTY_PATH );
\r
2780 memcpy( pHttpsRequest->pHeadersCur, pReqInfo->pPath, pReqInfo->pathLen );
\r
2781 pHttpsRequest->pHeadersCur += pReqInfo->pathLen;
\r
2782 memcpy( pHttpsRequest->pHeadersCur, pSpace, spaceLen );
\r
2783 pHttpsRequest->pHeadersCur += spaceLen;
\r
2784 memcpy( pHttpsRequest->pHeadersCur, HTTPS_PROTOCOL_VERSION, httpsProtocolVersionLen );
\r
2785 pHttpsRequest->pHeadersCur += httpsProtocolVersionLen;
\r
2786 memcpy( pHttpsRequest->pHeadersCur, HTTPS_END_OF_HEADER_LINES_INDICATOR, HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH );
\r
2787 pHttpsRequest->pHeadersCur += HTTPS_END_OF_HEADER_LINES_INDICATOR_LENGTH;
\r
2789 /* Add the User-Agent header. */
\r
2790 status = _addHeader( pHttpsRequest, HTTPS_USER_AGENT_HEADER, FAST_MACRO_STRLEN( HTTPS_USER_AGENT_HEADER ), IOT_HTTPS_USER_AGENT, FAST_MACRO_STRLEN( IOT_HTTPS_USER_AGENT ) );
\r
2792 if( HTTPS_FAILED( status ) )
\r
2794 IotLogError( "Failed to write header to the request user buffer: \"User-Agent: %s\\r\\n\" . Error code: %d",
\r
2795 IOT_HTTPS_USER_AGENT,
\r
2797 HTTPS_GOTO_CLEANUP();
\r
2800 status = _addHeader( pHttpsRequest, HTTPS_HOST_HEADER, FAST_MACRO_STRLEN( HTTPS_HOST_HEADER ), pReqInfo->pHost, pReqInfo->hostLen );
\r
2802 if( HTTPS_FAILED( status ) )
\r
2804 IotLogError( "Failed to write \"Host: %.*s\\r\\n\" to the request user buffer. Error code: %d",
\r
2805 pReqInfo->hostLen,
\r
2808 HTTPS_GOTO_CLEANUP();
\r
2811 if( pReqInfo->isAsync )
\r
2813 pHttpsRequest->isAsync = true;
\r
2814 /* If this is an asynchronous request then save the callbacks to use. */
\r
2815 pHttpsRequest->pCallbacks = &( pReqInfo->u.pAsyncInfo->callbacks );
\r
2816 pHttpsRequest->pUserPrivData = pReqInfo->u.pAsyncInfo->pPrivData;
\r
2817 /* The body pointer and body length will be filled in when the application sends data in the writeCallback. */
\r
2818 pHttpsRequest->pBody = NULL;
\r
2819 pHttpsRequest->bodyLength = 0;
\r
2823 pHttpsRequest->isAsync = false;
\r
2824 /* Set the HTTP request entity body. This is allowed to be NULL for no body like for a GET request. */
\r
2825 pHttpsRequest->pBody = pReqInfo->u.pSyncInfo->pBody;
\r
2826 pHttpsRequest->bodyLength = pReqInfo->u.pSyncInfo->bodyLen;
\r
2829 /* Save the method of this request. */
\r
2830 pHttpsRequest->method = pReqInfo->method;
\r
2831 /* Set the connection persistence flag for keeping the connection open after receiving a response. */
\r
2832 pHttpsRequest->isNonPersistent = pReqInfo->isNonPersistent;
\r
2833 /* Initialize the request cancellation. */
\r
2834 pHttpsRequest->cancelled = false;
\r
2835 /* Initialize the status of sending the body over the network in a possible asynchronous request. */
\r
2836 pHttpsRequest->bodyTxStatus = IOT_HTTPS_OK;
\r
2837 /* This is a new request and therefore not scheduled yet. */
\r
2838 pHttpsRequest->scheduled = false;
\r
2840 /* Set the request handle to return. */
\r
2841 *pReqHandle = pHttpsRequest;
\r
2843 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
2845 if( HTTPS_FAILED( status ) && ( pReqHandle != NULL ) )
\r
2847 /* Set the request handle to return to NULL, if we failed anywhere. */
\r
2848 *pReqHandle = NULL;
\r
2851 HTTPS_FUNCTION_CLEANUP_END();
\r
2854 /*-----------------------------------------------------------*/
\r
2856 IotHttpsReturnCode_t IotHttpsClient_AddHeader( IotHttpsRequestHandle_t reqHandle,
\r
2860 uint32_t valueLen )
\r
2862 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2864 /* Check for NULL pointer paramters. */
\r
2865 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pName );
\r
2866 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pValue );
\r
2867 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( reqHandle );
\r
2869 /* Check for name long enough for header length calculation to overflow */
\r
2870 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( nameLen <= ( UINT32_MAX >> 2 ),
\r
2871 IOT_HTTPS_INVALID_PARAMETER,
\r
2872 "Attempting to generate headers with name length %d > %d. This is not allowed.",
\r
2873 nameLen, UINT32_MAX >> 2 );
\r
2875 /* Check for value long enough for header length calculation to overflow */
\r
2876 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( valueLen <= ( UINT32_MAX >> 2 ),
\r
2877 IOT_HTTPS_INVALID_PARAMETER,
\r
2878 "Attempting to generate headers with value length %d > %d. This is not allowed.",
\r
2879 valueLen, UINT32_MAX >> 2 );
\r
2881 /* Check for auto-generated header "Content-Length". This header is created and send automatically when right before
\r
2882 * request body is sent on the network. */
\r
2883 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( strncmp( pName, HTTPS_CONTENT_LENGTH_HEADER, FAST_MACRO_STRLEN( HTTPS_CONTENT_LENGTH_HEADER ) ) != 0,
\r
2884 IOT_HTTPS_INVALID_PARAMETER,
\r
2885 "Attempting to add auto-generated header %s. This is not allowed.",
\r
2886 HTTPS_CONTENT_LENGTH_HEADER );
\r
2888 /* Check for auto-generated header "Connection". This header is created and send automatically when right before
\r
2889 * request body is sent on the network. */
\r
2890 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( strncmp( pName, HTTPS_CONNECTION_HEADER, FAST_MACRO_STRLEN( HTTPS_CONNECTION_HEADER ) ) != 0,
\r
2891 IOT_HTTPS_INVALID_PARAMETER,
\r
2892 "Attempting to add auto-generated header %s. This is not allowed.",
\r
2893 HTTPS_CONNECTION_HEADER );
\r
2895 /* Check for auto-generated header "Host". This header is created and placed into the header buffer space
\r
2896 * in IotHttpsClient_InitializeRequest(). */
\r
2897 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( strncmp( pName, HTTPS_HOST_HEADER, FAST_MACRO_STRLEN( HTTPS_HOST_HEADER ) ) != 0,
\r
2898 IOT_HTTPS_INVALID_PARAMETER,
\r
2899 "Attempting to add auto-generated header %s. This is not allowed.",
\r
2900 HTTPS_HOST_HEADER );
\r
2902 /* Check for auto-generated header "User-Agent". This header is created and placed into the header buffer space
\r
2903 * in IotHttpsClient_InitializeRequest(). */
\r
2904 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( strncmp( pName, HTTPS_USER_AGENT_HEADER, FAST_MACRO_STRLEN( HTTPS_USER_AGENT_HEADER ) ) != 0,
\r
2905 IOT_HTTPS_INVALID_PARAMETER,
\r
2906 "Attempting to add auto-generated header %s. This is not allowed.",
\r
2907 HTTPS_USER_AGENT_HEADER );
\r
2910 status = _addHeader( reqHandle, pName, nameLen, pValue, valueLen );
\r
2912 if( HTTPS_FAILED( status ) )
\r
2914 IotLogError( "Error in IotHttpsClient_AddHeader(), error code %d.", status );
\r
2915 HTTPS_GOTO_CLEANUP();
\r
2918 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
2921 /*-----------------------------------------------------------*/
\r
2923 IotHttpsReturnCode_t IotHttpsClient_SendSync( IotHttpsConnectionHandle_t connHandle,
\r
2924 IotHttpsRequestHandle_t reqHandle,
\r
2925 IotHttpsResponseHandle_t * pRespHandle,
\r
2926 IotHttpsResponseInfo_t * pRespInfo,
\r
2927 uint32_t timeoutMs )
\r
2929 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
2931 bool respFinishedSemCreated = false;
\r
2932 _httpsResponse_t * pHttpsResponse = NULL;
\r
2934 /* Parameter checks. */
\r
2935 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( connHandle );
\r
2936 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( reqHandle );
\r
2937 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pRespHandle );
\r
2938 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pRespInfo );
\r
2939 /* Stop the application from scheduling requests on a closed connection. */
\r
2940 HTTPS_ON_ARG_ERROR_GOTO_CLEANUP( connHandle->isConnected );
\r
2942 /* If an asynchronous request/response is configured, that is invalid for this API. */
\r
2943 if( reqHandle->isAsync )
\r
2945 IotLogError( "Called IotHttpsClient_SendSync on an asynchronous configured request." );
\r
2946 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INVALID_PARAMETER );
\r
2949 /* Initialize the response handle to return. */
\r
2950 status = _initializeResponse( pRespHandle, pRespInfo, reqHandle );
\r
2952 if( HTTPS_FAILED( status ) )
\r
2954 IotLogError( "Failed to initialize the response on the synchronous request %d.", reqHandle );
\r
2955 HTTPS_GOTO_CLEANUP();
\r
2958 /* Set the internal response to use. */
\r
2959 pHttpsResponse = *pRespHandle;
\r
2961 /* The implicit connection passed and we need to the set the connection handle in the request and response. */
\r
2962 reqHandle->pHttpsConnection = connHandle;
\r
2963 pHttpsResponse->pHttpsConnection = connHandle;
\r
2965 /* Create the semaphore used to wait on the response to finish being received. */
\r
2966 respFinishedSemCreated = IotSemaphore_Create( &( pHttpsResponse->respFinishedSem ), 0 /* initialValue */, 1 /* maxValue */ );
\r
2968 if( respFinishedSemCreated == false )
\r
2970 IotLogError( "Failed to create an internal semaphore." );
\r
2971 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INTERNAL_ERROR );
\r
2974 /* Associate the response to the request so that we can schedule it to be received when the request gets scheduled to send. */
\r
2975 reqHandle->pHttpsResponse = pHttpsResponse;
\r
2977 /* Schedule this request to be sent by adding it to the connection's request queue. */
\r
2978 status = _addRequestToConnectionReqQ( reqHandle );
\r
2980 if( HTTPS_FAILED( status ) )
\r
2982 IotLogError( "Failed to schedule the synchronous request. Error code: %d", status );
\r
2983 HTTPS_GOTO_CLEANUP();
\r
2986 /* Wait for the request to finish. */
\r
2987 if( timeoutMs == 0 )
\r
2989 IotSemaphore_Wait( &( pHttpsResponse->respFinishedSem ) );
\r
2993 if( IotSemaphore_TimedWait( &( pHttpsResponse->respFinishedSem ), timeoutMs ) == false )
\r
2995 IotLogError( "Timed out waiting for the synchronous request to finish. Timeout ms: %d", timeoutMs );
\r
2996 _cancelRequest( reqHandle );
\r
2997 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_TIMEOUT_ERROR );
\r
3001 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
3003 if( respFinishedSemCreated )
\r
3005 IotSemaphore_Destroy( &( pHttpsResponse->respFinishedSem ) );
\r
3008 /* If the syncStatus is anything other than IOT_HTTPS_OK, then the request was scheduled. */
\r
3009 if( ( pHttpsResponse != NULL ) && HTTPS_FAILED( pHttpsResponse->syncStatus ) )
\r
3011 status = pHttpsResponse->syncStatus;
\r
3014 if( HTTPS_FAILED( status ) )
\r
3016 if( pRespHandle != NULL )
\r
3018 *pRespHandle = NULL;
\r
3021 IotLogError( "IotHttpsClient_SendSync() failed." );
\r
3024 HTTPS_FUNCTION_CLEANUP_END();
\r
3027 /*-----------------------------------------------------------*/
\r
3029 IotHttpsReturnCode_t IotHttpsClient_WriteRequestBody( IotHttpsRequestHandle_t reqHandle,
\r
3034 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
3036 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( reqHandle );
\r
3037 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pBuf );
\r
3039 /* This function is not valid for a synchronous response. Applications need to configure the request body in
\r
3040 * IotHttpsRequestInfo_t.pSyncInfo_t.reqData before calling IotHttpsClient_SendSync(). */
\r
3041 HTTPS_ON_ARG_ERROR_GOTO_CLEANUP( reqHandle->isAsync );
\r
3042 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( isComplete == 1,
\r
3043 IOT_HTTPS_NOT_SUPPORTED,
\r
3044 "isComplete must be 1 in IotHttpsClient_WriteRequestBody() for the current version of the HTTPS Client library." );
\r
3046 /* If the bodyLength is greater than 0, then we already called this function and we need to enforce that this
\r
3047 * function must only be called once. We only call this function once so that we can calculate the Content-Length. */
\r
3048 if( reqHandle->bodyLength > 0 )
\r
3050 IotLogError( "Error this function must be called once with the data needed to send. Variable length HTTP "
\r
3051 "request body is not supported in this library." );
\r
3052 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_MESSAGE_FINISHED );
\r
3055 /* Set the pointer to the body and the length for the content-length calculation. */
\r
3056 reqHandle->pBody = ( uint8_t * ) pBuf;
\r
3057 reqHandle->bodyLength = len;
\r
3059 /* We send the HTTPS headers and body in this function so that the application has the freedom to specify a body
\r
3060 * that may be buffer on stack. */
\r
3061 status = _sendHttpsHeadersAndBody( reqHandle->pHttpsConnection, reqHandle );
\r
3063 if( HTTPS_FAILED( status ) )
\r
3065 IotLogError( "Failed to send the headers and body. Error code %d.", status );
\r
3066 HTTPS_GOTO_CLEANUP();
\r
3069 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
3071 if( reqHandle != NULL )
\r
3073 reqHandle->bodyTxStatus = status;
\r
3076 HTTPS_FUNCTION_CLEANUP_END();
\r
3079 /*-----------------------------------------------------------*/
\r
3081 IotHttpsReturnCode_t IotHttpsClient_ReadResponseBody( IotHttpsResponseHandle_t respHandle,
\r
3085 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
3087 uint32_t bodyLengthInHeaderBuf = 0;
\r
3089 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( respHandle );
\r
3090 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pBuf );
\r
3091 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pLen );
\r
3092 HTTPS_ON_ARG_ERROR_GOTO_CLEANUP( respHandle->isAsync );
\r
3094 /* Set the current body in the respHandle to use in _receiveHttpsBody(). _receiveHttpsBody is generic
\r
3095 * to both async and sync request/response handling. In the sync version the body is configured during
\r
3096 * initializing the request. In the async version the body is given in this function on the fly. */
\r
3097 respHandle->pBody = pBuf;
\r
3098 respHandle->pBodyCur = respHandle->pBody;
\r
3099 respHandle->pBodyEnd = respHandle->pBodyCur + *pLen;
\r
3101 /* When there is part of the body in the header pBuffer. We need to move that data to this body pBuffer
\r
3102 * provided in this function. */
\r
3103 bodyLengthInHeaderBuf = respHandle->pBodyCurInHeaderBuf - respHandle->pBodyInHeaderBuf;
\r
3105 if( bodyLengthInHeaderBuf > 0 )
\r
3107 uint32_t copyLength = bodyLengthInHeaderBuf > *pLen ? *pLen : bodyLengthInHeaderBuf;
\r
3108 memcpy( respHandle->pBodyCur, respHandle->pBodyInHeaderBuf, copyLength );
\r
3109 respHandle->pBodyCur += copyLength;
\r
3111 /* This function may be called multiple times until all of the body that may be present in the header buffer is
\r
3113 respHandle->pBodyInHeaderBuf += copyLength;
\r
3116 /* If there is room in the body buffer just provided by the application and we have not completed the current
\r
3117 * HTTP response message, then try to receive more body. */
\r
3118 if( ( ( respHandle->pBodyEnd - respHandle->pBodyCur ) > 0 ) && ( respHandle->parserState < PARSER_STATE_BODY_COMPLETE ) )
\r
3120 status = _receiveHttpsBody( respHandle->pHttpsConnection, respHandle );
\r
3122 if( HTTPS_FAILED( status ) )
\r
3124 IotLogError( "Failed to receive the HTTP response body on the network. Error code: %d.", status );
\r
3125 HTTPS_GOTO_CLEANUP();
\r
3129 *pLen = respHandle->pBodyCur - respHandle->pBody;
\r
3131 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
3133 if( respHandle != NULL )
\r
3135 respHandle->bodyRxStatus = status;
\r
3138 HTTPS_FUNCTION_CLEANUP_END();
\r
3141 /*-----------------------------------------------------------*/
\r
3143 IotHttpsReturnCode_t IotHttpsClient_CancelRequestAsync( IotHttpsRequestHandle_t reqHandle )
\r
3145 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
3147 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( reqHandle );
\r
3149 _cancelRequest( reqHandle );
\r
3151 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
3154 /*-----------------------------------------------------------*/
\r
3156 IotHttpsReturnCode_t IotHttpsClient_CancelResponseAsync( IotHttpsResponseHandle_t respHandle )
\r
3158 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
3160 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( respHandle );
\r
3162 _cancelResponse( respHandle );
\r
3164 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
3168 /*-----------------------------------------------------------*/
\r
3170 IotHttpsReturnCode_t IotHttpsClient_SendAsync( IotHttpsConnectionHandle_t connHandle,
\r
3171 IotHttpsRequestHandle_t reqHandle,
\r
3172 IotHttpsResponseHandle_t * pRespHandle,
\r
3173 IotHttpsResponseInfo_t * pRespInfo )
\r
3175 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
3177 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( connHandle );
\r
3178 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( reqHandle );
\r
3179 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pRespHandle );
\r
3180 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pRespInfo );
\r
3181 HTTPS_ON_ARG_ERROR_GOTO_CLEANUP( reqHandle->isAsync );
\r
3182 /* Stop the application from scheduling requests on a closed connection. */
\r
3183 HTTPS_ON_ARG_ERROR_GOTO_CLEANUP( connHandle->isConnected );
\r
3185 /* Initialize the response handle to return. */
\r
3186 status = _initializeResponse( pRespHandle, pRespInfo, reqHandle );
\r
3188 if( HTTPS_FAILED( status ) )
\r
3190 IotLogError( "Failed to initialize the response on the synchronous request %d.", reqHandle );
\r
3191 HTTPS_GOTO_CLEANUP();
\r
3194 /* Set the connection handle in the request handle so that we can use it in the _writeRequestBody() callback. */
\r
3195 reqHandle->pHttpsConnection = connHandle;
\r
3197 /* Set the connection handle in the response handle sp that we can use it in the _readReadyCallback() callback. */
\r
3198 ( *pRespHandle )->pHttpsConnection = connHandle;
\r
3200 /* Associate the response to the request so that we can schedule it to be received when the request gets scheduled to send. */
\r
3201 reqHandle->pHttpsResponse = *pRespHandle;
\r
3203 /* Add the request to the connection's request queue. */
\r
3204 status = _addRequestToConnectionReqQ( reqHandle );
\r
3206 if( HTTPS_FAILED( status ) )
\r
3208 IotLogError( "Failed to add request %d to the connection's request queue. Error code: %d.", reqHandle, status );
\r
3209 HTTPS_GOTO_CLEANUP();
\r
3212 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
3215 /*-----------------------------------------------------------*/
\r
3217 IotHttpsReturnCode_t IotHttpsClient_ReadResponseStatus( IotHttpsResponseHandle_t respHandle,
\r
3218 uint16_t * pStatus )
\r
3220 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
3222 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( respHandle );
\r
3223 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pStatus );
\r
3225 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( respHandle->status != 0,
\r
3226 IOT_HTTPS_NOT_FOUND,
\r
3227 "The HTTP response status was not found in the HTTP response header buffer." );
\r
3229 *pStatus = respHandle->status;
\r
3231 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
3234 /*-----------------------------------------------------------*/
\r
3236 IotHttpsReturnCode_t IotHttpsClient_ReadHeader( IotHttpsResponseHandle_t respHandle,
\r
3240 uint32_t valueLen )
\r
3242 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
3244 const char * pHttpParserErrorDescription = NULL;
\r
3245 IotHttpsResponseBufferState_t savedBufferState = PROCESSING_STATE_NONE;
\r
3246 IotHttpsResponseParserState_t savedParserState = PARSER_STATE_NONE;
\r
3247 size_t numParsed = 0;
\r
3249 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( respHandle );
\r
3250 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pName );
\r
3251 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pValue );
\r
3252 HTTPS_ON_ARG_ERROR_MSG_GOTO_CLEANUP( valueLen > 0,
\r
3253 IOT_HTTPS_INVALID_PARAMETER,
\r
3254 "pValue has insufficient space to store a value string (length is 0)" );
\r
3256 /* The buffer processing state is changed to searching the header buffer in this function. The parser state is
\r
3257 * changed in the response to wherever the parser is currently located in the response. If this function is called
\r
3258 * in the middle of processing a response (for example in readReadyCallback() routine of an asynchronous response),
\r
3259 * then parsing the response need to be able to start at the same place it was before calling this function. */
\r
3260 savedBufferState = respHandle->bufferProcessingState;
\r
3261 savedParserState = respHandle->parserState;
\r
3263 /* The header search parameters in the response handle are used as context in the http-parser callbacks. During
\r
3264 * the callback, pReadHeaderField is checked against the currently parsed header name. foundHeaderField is set to
\r
3265 * true when the pReadHeaderField is found in a header field callback. The bufferProcessingState tells the callback
\r
3266 * to skip the logic pertaining to when the response is being parsed for the first time. pReadHeaderValue will store
\r
3267 * the header value found. readHeaderValueLength will store the length of the header value found from within the
\r
3268 * response headers. */
\r
3269 respHandle->pReadHeaderField = pName;
\r
3270 respHandle->readHeaderFieldLength = nameLen;
\r
3271 respHandle->foundHeaderField = false;
\r
3272 respHandle->bufferProcessingState = PROCESSING_STATE_SEARCHING_HEADER_BUFFER;
\r
3273 respHandle->pReadHeaderValue = NULL;
\r
3274 respHandle->readHeaderValueLength = 0;
\r
3276 /* Start over the HTTP parser so that it will parser from the beginning of the message. */
\r
3277 http_parser_init( &( respHandle->httpParserInfo.readHeaderParser ), HTTP_RESPONSE );
\r
3279 IotLogDebug( "Now parsing HTTP Message buffer to read a header." );
\r
3280 numParsed = respHandle->httpParserInfo.parseFunc( &( respHandle->httpParserInfo.readHeaderParser ), &_httpParserSettings, ( char * ) ( respHandle->pHeaders ), respHandle->pHeadersCur - respHandle->pHeaders );
\r
3281 IotLogDebug( "Parsed %d characters in IotHttpsClient_ReadHeader().", numParsed );
\r
3283 /* There shouldn't be any errors parsing the response body given that the handle is from a validly
\r
3284 * received response, so this check is defensive. If there were errors parsing the original response headers, then
\r
3285 * the response handle would have been invalidated and the connection closed. */
\r
3286 if( ( respHandle->httpParserInfo.readHeaderParser.http_errno != 0 ) &&
\r
3287 ( HTTP_PARSER_ERRNO( &( respHandle->httpParserInfo.readHeaderParser ) ) > HPE_CB_chunk_complete ) )
\r
3289 pHttpParserErrorDescription = http_errno_description( HTTP_PARSER_ERRNO( &( respHandle->httpParserInfo.readHeaderParser ) ) );
\r
3290 IotLogError( "http_parser failed on the http response with error: %s", pHttpParserErrorDescription );
\r
3291 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_PARSING_ERROR );
\r
3294 /* Not only do we need an indication that the header field was found, but also that the value was found as well.
\r
3295 * The value is found when it is non-NULL. The case where the header field is found, but the value is not found
\r
3296 * occurs when there are incomplete headers stored in the header buffer. The header buffer could end with a header
\r
3298 if( respHandle->foundHeaderField && ( respHandle->pReadHeaderValue != NULL ) )
\r
3300 /* The len of the pValue buffer must account for the NULL terminator. */
\r
3301 if( respHandle->readHeaderValueLength > ( valueLen - 1 ) )
\r
3303 IotLogError( "IotHttpsClient_ReadHeader(): The length of the pValue buffer specified is less than the actual length of the pValue. " );
\r
3304 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_INSUFFICIENT_MEMORY );
\r
3308 memcpy( pValue, respHandle->pReadHeaderValue, respHandle->readHeaderValueLength );
\r
3309 pValue[ respHandle->readHeaderValueLength ] = '\0';
\r
3314 IotLogWarn( "IotHttpsClient_ReadHeader(): The header field %s was not found.", pName );
\r
3315 HTTPS_SET_AND_GOTO_CLEANUP( IOT_HTTPS_NOT_FOUND );
\r
3318 HTTPS_FUNCTION_CLEANUP_BEGIN();
\r
3320 /* Always restore the state back to what it was before entering this function. */
\r
3321 if( respHandle != NULL )
\r
3323 respHandle->bufferProcessingState = savedBufferState;
\r
3324 respHandle->parserState = savedParserState;
\r
3327 HTTPS_FUNCTION_CLEANUP_END();
\r
3330 /*-----------------------------------------------------------*/
\r
3332 IotHttpsReturnCode_t IotHttpsClient_ReadContentLength( IotHttpsResponseHandle_t respHandle,
\r
3333 uint32_t * pContentLength )
\r
3335 HTTPS_FUNCTION_ENTRY( IOT_HTTPS_OK );
\r
3337 const int CONTENT_LENGTH_NUMBERIC_BASE = 10;
\r
3338 char pContentLengthStr[ HTTPS_MAX_CONTENT_LENGTH_LINE_LENGTH ] = { 0 };
\r
3340 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( respHandle );
\r
3341 HTTPS_ON_NULL_ARG_GOTO_CLEANUP( pContentLength );
\r
3343 /* If there is no content-length header or if we were not able to store it in the header buffer this will be
\r
3344 * invalid. We do not use the content-length member of the http-parser state structure to get the content
\r
3345 * length as this is a PRIVATE member. Because it is a PRIVATE member it can be any value. */
\r
3346 status = IotHttpsClient_ReadHeader( respHandle, HTTPS_CONTENT_LENGTH_HEADER, FAST_MACRO_STRLEN( HTTPS_CONTENT_LENGTH_HEADER ), pContentLengthStr, HTTPS_MAX_CONTENT_LENGTH_LINE_LENGTH );
\r
3348 if( HTTPS_FAILED( status ) )
\r
3350 *pContentLength = 0;
\r
3351 IotLogError( "Could not read the Content-Length for the response." );
\r
3352 HTTPS_GOTO_CLEANUP();
\r
3355 *pContentLength = strtoul( pContentLengthStr, NULL, CONTENT_LENGTH_NUMBERIC_BASE );
\r
3357 HTTPS_FUNCTION_EXIT_NO_CLEANUP();
\r
3360 /*-----------------------------------------------------------*/
\r
3362 /* Provide access to internal functions and variables if testing. */
\r
3363 #if IOT_BUILD_TESTS == 1
\r
3364 #include "iot_test_access_https_client.c"
\r