Browse Source

Merge branch 'bugfix/advanced_https_ota_example_v4.0' into 'release/v4.0'

esp_https_ota in sync with v4.x (backport v4.0)

See merge request espressif/esp-idf!7153
Mahavir Jain 5 years ago
parent
commit
08f401b74e

+ 30 - 1
components/esp_http_client/esp_http_client.c

@@ -796,6 +796,22 @@ static int esp_http_client_get_data(esp_http_client_handle_t client)
     return rlen;
     return rlen;
 }
 }
 
 
+bool esp_http_client_is_complete_data_received(esp_http_client_handle_t client)
+{
+    if (client->response->is_chunked) {
+        if (!client->is_chunk_complete) {
+            ESP_LOGD(TAG, "Chunks were not completely read");
+            return false;
+        }
+    } else {
+        if (client->response->data_process != client->response->content_length) {
+            ESP_LOGD(TAG, "Data processed %d != Data specified in content length %d", client->response->data_process, client->response->content_length);
+            return false;
+        }
+    }
+    return true;
+}
+
 int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
 int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
 {
 {
     esp_http_buffer_t *res_buffer = client->response->buffer;
     esp_http_buffer_t *res_buffer = client->response->buffer;
@@ -819,7 +835,7 @@ int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
         } else {
         } else {
             is_data_remain = client->response->data_process < client->response->content_length;
             is_data_remain = client->response->data_process < client->response->content_length;
         }
         }
-        ESP_LOGD(TAG, "is_data_remain=%d, is_chunked=%d", is_data_remain, client->response->is_chunked);
+        ESP_LOGD(TAG, "is_data_remain=%d, is_chunked=%d, content_length=%d", is_data_remain, client->response->is_chunked, client->response->content_length);
         if (!is_data_remain) {
         if (!is_data_remain) {
             break;
             break;
         }
         }
@@ -827,10 +843,23 @@ int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
         if (byte_to_read > client->buffer_size_rx) {
         if (byte_to_read > client->buffer_size_rx) {
             byte_to_read = client->buffer_size_rx;
             byte_to_read = client->buffer_size_rx;
         }
         }
+        errno = 0;
         rlen = esp_transport_read(client->transport, res_buffer->data, byte_to_read, client->timeout_ms);
         rlen = esp_transport_read(client->transport, res_buffer->data, byte_to_read, client->timeout_ms);
         ESP_LOGD(TAG, "need_read=%d, byte_to_read=%d, rlen=%d, ridx=%d", need_read, byte_to_read, rlen, ridx);
         ESP_LOGD(TAG, "need_read=%d, byte_to_read=%d, rlen=%d, ridx=%d", need_read, byte_to_read, rlen, ridx);
 
 
         if (rlen <= 0) {
         if (rlen <= 0) {
+            if (errno != 0) {
+                esp_log_level_t sev = ESP_LOG_WARN;
+                /* On connection close from server, recv should ideally return 0 but we have error conversion
+                 * in `tcp_transport` SSL layer which translates it `-1` and hence below additional checks */
+                if (rlen == -1 && errno == ENOTCONN && client->response->is_chunked) {
+                    /* Explicit call to parser for invoking `message_complete` callback */
+                    http_parser_execute(client->parser, client->parser_settings, res_buffer->data, 0);
+                    /* ...and lowering the message severity, as closed connection from server side is expected in chunked transport */
+                    sev = ESP_LOG_DEBUG;
+                }
+                ESP_LOG_LEVEL(sev, TAG, "esp_transport_read returned:%d and errno:%d ", rlen, errno);
+            }
             return ridx;
             return ridx;
         }
         }
         res_buffer->output_ptr = buffer + ridx;
         res_buffer->output_ptr = buffer + ridx;

+ 11 - 0
components/esp_http_client/include/esp_http_client.h

@@ -473,6 +473,17 @@ esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client);
  */
  */
 void esp_http_client_add_auth(esp_http_client_handle_t client);
 void esp_http_client_add_auth(esp_http_client_handle_t client);
 
 
+/**
+ * @brief      Checks if entire data in the response has been read without any error.
+ *
+ * @param[in]  client   The esp_http_client handle
+ * 
+ * @return
+ *     - true
+ *     - false
+ */
+bool esp_http_client_is_complete_data_received(esp_http_client_handle_t client);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif

+ 1 - 1
components/esp_https_ota/CMakeLists.txt

@@ -1,4 +1,4 @@
 idf_component_register(SRCS "src/esp_https_ota.c"
 idf_component_register(SRCS "src/esp_https_ota.c"
                     INCLUDE_DIRS "include"
                     INCLUDE_DIRS "include"
-                    REQUIRES esp_http_client
+                    REQUIRES esp_http_client bootloader_support
                     PRIV_REQUIRES log app_update)
                     PRIV_REQUIRES log app_update)

+ 14 - 1
components/esp_https_ota/include/esp_https_ota.h

@@ -15,7 +15,7 @@
 #pragma once
 #pragma once
 
 
 #include <esp_http_client.h>
 #include <esp_http_client.h>
-#include <esp_ota_ops.h>
+#include <bootloader_common.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
@@ -110,6 +110,19 @@ esp_err_t esp_https_ota_begin(esp_https_ota_config_t *ota_config, esp_https_ota_
  */
  */
 esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle);
 esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle);
 
 
+/**
+ * @brief   Checks if complete data was received or not
+ *
+ * @note    This API can be called just before esp_https_ota_finish() to validate if the complete image was indeed received.
+ *
+ * @param[in]   https_ota_handle pointer to esp_https_ota_handle_t structure
+ *
+ * @return
+ *    - false
+ *    - true
+ */
+bool esp_https_ota_is_complete_data_received(esp_https_ota_handle_t https_ota_handle);
+
 /**
 /**
  * @brief    Clean-up HTTPS OTA Firmware upgrade and close HTTPS connection
  * @brief    Clean-up HTTPS OTA Firmware upgrade and close HTTPS connection
  *
  *

+ 68 - 18
components/esp_https_ota/src/esp_https_ota.c

@@ -17,6 +17,8 @@
 #include <string.h>
 #include <string.h>
 #include <esp_https_ota.h>
 #include <esp_https_ota.h>
 #include <esp_log.h>
 #include <esp_log.h>
+#include <esp_ota_ops.h>
+#include <errno.h>
 
 
 #define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1
 #define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1
 #define DEFAULT_OTA_BUF_SIZE IMAGE_HEADER_SIZE
 #define DEFAULT_OTA_BUF_SIZE IMAGE_HEADER_SIZE
@@ -68,8 +70,13 @@ static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client
     }
     }
     
     
     char upgrade_data_buf[DEFAULT_OTA_BUF_SIZE];
     char upgrade_data_buf[DEFAULT_OTA_BUF_SIZE];
+    // process_again() returns true only in case of redirection.
     if (process_again(status_code)) {
     if (process_again(status_code)) {
         while (1) {
         while (1) {
+            /*
+             *  In case of redirection, esp_http_client_read() is called
+             *  to clear the response buffer of http_client.
+             */
             int data_read = esp_http_client_read(http_client, upgrade_data_buf, DEFAULT_OTA_BUF_SIZE);
             int data_read = esp_http_client_read(http_client, upgrade_data_buf, DEFAULT_OTA_BUF_SIZE);
             if (data_read < 0) {
             if (data_read < 0) {
                 ESP_LOGE(TAG, "Error: SSL data read error");
                 ESP_LOGE(TAG, "Error: SSL data read error");
@@ -85,17 +92,21 @@ static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client
 static esp_err_t _http_connect(esp_http_client_handle_t http_client)
 static esp_err_t _http_connect(esp_http_client_handle_t http_client)
 {
 {
     esp_err_t err = ESP_FAIL;
     esp_err_t err = ESP_FAIL;
-    int status_code;
+    int status_code, header_ret;
     do {
     do {
         err = esp_http_client_open(http_client, 0);
         err = esp_http_client_open(http_client, 0);
         if (err != ESP_OK) {
         if (err != ESP_OK) {
             ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
             ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
             return err;
             return err;
         }
         }
-        esp_http_client_fetch_headers(http_client);
+        header_ret = esp_http_client_fetch_headers(http_client);
+        if (header_ret < 0) {
+            return header_ret;
+        }
         status_code = esp_http_client_get_status_code(http_client);
         status_code = esp_http_client_get_status_code(http_client);
-        if (_http_handle_response_code(http_client, status_code) != ESP_OK) {
-            return ESP_FAIL;
+        err = _http_handle_response_code(http_client, status_code);
+        if (err != ESP_OK) {
+            return err;
         }
         }
     } while (process_again(status_code));
     } while (process_again(status_code));
     return err;
     return err;
@@ -209,19 +220,37 @@ esp_err_t esp_https_ota_get_img_desc(esp_https_ota_handle_t https_ota_handle, es
         ESP_LOGE(TAG, "esp_https_ota_read_img_desc: Invalid state");
         ESP_LOGE(TAG, "esp_https_ota_read_img_desc: Invalid state");
         return ESP_FAIL;
         return ESP_FAIL;
     }
     }
+    /*
+     * `data_read_size` holds number of bytes needed to read complete header.
+     * `bytes_read` holds number of bytes read.
+     */
     int data_read_size = IMAGE_HEADER_SIZE;
     int data_read_size = IMAGE_HEADER_SIZE;
-    int data_read = esp_http_client_read(handle->http_client,
-                                         handle->ota_upgrade_buf,
-                                         data_read_size);
-    if (data_read < 0) {
-        return ESP_FAIL;
+    int data_read = 0, bytes_read = 0;
+    /*
+     * while loop is added to download complete image headers, even if the headers
+     * are not sent in a single packet.
+     */
+    while (data_read_size > 0 && !esp_https_ota_is_complete_data_received(https_ota_handle)) {
+        data_read = esp_http_client_read(handle->http_client,
+                                          (handle->ota_upgrade_buf + bytes_read),
+                                          data_read_size);
+        /*
+         * As esp_http_client_read never returns negative error code, we rely on
+         * `errno` to check for underlying transport connectivity closure if any
+         */
+        if (errno == ENOTCONN || errno == ECONNRESET || errno == ECONNABORTED) {
+            ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
+            break;
+        }
+        data_read_size -= data_read;
+        bytes_read += data_read;
     }
     }
-    if (data_read >= sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
-        memcpy(new_app_info, &handle->ota_upgrade_buf[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
-        handle->binary_file_len += data_read;
-    } else {
+    if (data_read_size > 0) {
+        ESP_LOGE(TAG, "Complete headers were not received");
         return ESP_FAIL;
         return ESP_FAIL;
     }
     }
+    handle->binary_file_len = bytes_read;
+    memcpy(new_app_info, &handle->ota_upgrade_buf[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
     return ESP_OK;                                
     return ESP_OK;                                
 }
 }
 
 
@@ -259,10 +288,25 @@ esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle)
                                              handle->ota_upgrade_buf,
                                              handle->ota_upgrade_buf,
                                              handle->ota_upgrade_buf_size);
                                              handle->ota_upgrade_buf_size);
             if (data_read == 0) {
             if (data_read == 0) {
-                ESP_LOGI(TAG, "Connection closed, all data received");
-            } else if (data_read < 0) {
-                ESP_LOGE(TAG, "Error: SSL data read error");
-                return ESP_FAIL;
+                /*
+                 *  esp_https_ota_is_complete_data_received is added to check whether
+                 *  complete image is received.
+                 */
+                bool is_recv_complete = esp_https_ota_is_complete_data_received(https_ota_handle);
+                /*
+                 * As esp_http_client_read never returns negative error code, we rely on
+                 * `errno` to check for underlying transport connectivity closure if any.
+                 * Incase the complete data has not been received but the server has sent
+                 * an ENOTCONN or ECONNRESET, failure is returned. We close with success
+                 * if complete data has been received.
+                 */
+                if ((errno == ENOTCONN || errno == ECONNRESET || errno == ECONNABORTED) && !is_recv_complete) {
+                    ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
+                    return ESP_FAIL;
+                } else if (!is_recv_complete) {
+                    return ESP_ERR_HTTPS_OTA_IN_PROGRESS;
+                }
+                ESP_LOGI(TAG, "Connection closed");
             } else if (data_read > 0) {
             } else if (data_read > 0) {
                 return _ota_write(handle, (const void *)handle->ota_upgrade_buf, data_read);
                 return _ota_write(handle, (const void *)handle->ota_upgrade_buf, data_read);
             }
             }
@@ -276,6 +320,12 @@ esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle)
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
+bool esp_https_ota_is_complete_data_received(esp_https_ota_handle_t https_ota_handle)
+{
+    esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
+    return esp_http_client_is_complete_data_received(handle->http_client);
+}
+
 esp_err_t esp_https_ota_finish(esp_https_ota_handle_t https_ota_handle)
 esp_err_t esp_https_ota_finish(esp_https_ota_handle_t https_ota_handle)
 {
 {
     esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
     esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
@@ -361,4 +411,4 @@ esp_err_t esp_https_ota(const esp_http_client_config_t *config)
         return ota_finish_err;
         return ota_finish_err;
     }
     }
     return ESP_OK;
     return ESP_OK;
-}
+}

+ 435 - 0
examples/system/ota/advanced_https_ota/example_test.py

@@ -0,0 +1,435 @@
+import re
+import os
+import socket
+import BaseHTTPServer
+import SimpleHTTPServer
+from threading import Thread
+import ssl
+
+from tiny_test_fw import DUT
+import ttfw_idf
+import random
+import subprocess
+
+server_cert = "-----BEGIN CERTIFICATE-----\n" \
+              "MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\
+              "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"\
+              "aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF\n"\
+              "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"\
+              "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"\
+              "CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0\n"\
+              "nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC\n"\
+              "9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA\n"\
+              "w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF\n"\
+              "3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M\n"\
+              "lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY\n"\
+              "IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww\n"\
+              "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd\n"\
+              "/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA\n"\
+              "lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl\n"\
+              "6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2\n"\
+              "fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu\n"\
+              "y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy\n"\
+              "hA==\n"\
+              "-----END CERTIFICATE-----\n"
+
+server_key = "-----BEGIN PRIVATE KEY-----\n"\
+             "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXN8LK/eYi/tOU\n"\
+             "uQ5vG6cp8J2sn/OB0A2uzHREG2kQ+FXnrSFYnR8SKfScg4yklKIX0TpCw92vpD56\n"\
+             "iAfhec4xT0kT6Ibvjc3V098YTMm9GXJy38KTxKzAo8L35Veyc51mZTD3L/G0A1tR\n"\
+             "ED9Oym8/PPcC+/fzZ999+skaHuig6ZplIJkWVJgctkDD9eVGvSxJFsukUZjSBeNo\n"\
+             "BXyIceI8NgvLiRk56mNX1TnXGL4gawvjDvnO4yCwfIXecl4ZgeC0ZUGuQvRkobm5\n"\
+             "1jTBwGPKyO5sMkLiJKU2KrYcPd+GzuPoJl11Xa/zjkyVUo3E/SQ7hSPgPyv7lRJY\n"\
+             "Lwkp8DDFAgMBAAECggEAfBhAfQE7mUByNbxgAgI5fot9eaqR1Nf+QpJ6X2H3KPwC\n"\
+             "02sa0HOwieFwYfj6tB1doBoNq7i89mTc+QUlIn4pHgIowHO0OGawomeKz5BEhjCZ\n"\
+             "4XeLYGSoODary2+kNkf2xY8JTfFEcyvGBpJEwc4S2VyYgRRx+IgnumTSH+N5mIKZ\n"\
+             "SXWNdZIuHEmkwod+rPRXs6/r+PH0eVW6WfpINEbr4zVAGXJx2zXQwd2cuV1GTJWh\n"\
+             "cPVOXLu+XJ9im9B370cYN6GqUnR3fui13urYbnWnEf3syvoH/zuZkyrVChauoFf8\n"\
+             "8EGb74/HhXK7Q2s8NRakx2c7OxQifCbcy03liUMmyQKBgQDFAob5B/66N4Q2cq/N\n"\
+             "MWPf98kYBYoLaeEOhEJhLQlKk0pIFCTmtpmUbpoEes2kCUbH7RwczpYko8tlKyoB\n"\
+             "6Fn6RY4zQQ64KZJI6kQVsjkYpcP/ihnOY6rbds+3yyv+4uPX7Eh9sYZwZMggE19M\n"\
+             "CkFHkwAjiwqhiiSlUxe20sWmowKBgQDEfx4lxuFzA1PBPeZKGVBTxYPQf+DSLCre\n"\
+             "ZFg3ZmrxbCjRq1O7Lra4FXWD3dmRq7NDk79JofoW50yD8wD7I0B7opdDfXD2idO8\n"\
+             "0dBnWUKDr2CAXyoLEINce9kJPbx4kFBQRN9PiGF7VkDQxeQ3kfS8CvcErpTKCOdy\n"\
+             "5wOwBTwJdwKBgDiTFTeGeDv5nVoVbS67tDao7XKchJvqd9q3WGiXikeELJyuTDqE\n"\
+             "zW22pTwMF+m3UEAxcxVCrhMvhkUzNAkANHaOatuFHzj7lyqhO5QPbh4J3FMR0X9X\n"\
+             "V8VWRSg+jA/SECP9koOl6zlzd5Tee0tW1pA7QpryXscs6IEhb3ns5R2JAoGAIkzO\n"\
+             "RmnhEOKTzDex611f2D+yMsMfy5BKK2f4vjLymBH5TiBKDXKqEpgsW0huoi8Gq9Uu\n"\
+             "nvvXXAgkIyRYF36f0vUe0nkjLuYAQAWgC2pZYgNLJR13iVbol0xHJoXQUHtgiaJ8\n"\
+             "GLYFzjHQPqFMpSalQe3oELko39uOC1CoJCHFySECgYBeycUnRBikCO2n8DNhY4Eg\n"\
+             "9Y3oxcssRt6ea5BZwgW2eAYi7/XqKkmxoSoOykUt3MJx9+EkkrL17bxFSpkj1tvL\n"\
+             "qvxn7egtsKjjgGNAxwXC4MwCvhveyUQQxtQb8AqGrGqo4jEEN0L15cnP38i2x1Uo\n"\
+             "muhfskWf4MABV0yTUaKcGg==\n"\
+             "-----END PRIVATE KEY-----\n"
+
+
+def get_my_ip():
+    s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    s1.connect(("8.8.8.8", 80))
+    my_ip = s1.getsockname()[0]
+    s1.close()
+    return my_ip
+
+
+def get_server_status(host_ip, port):
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    server_status = sock.connect_ex((host_ip, port))
+    sock.close()
+    if server_status == 0:
+        return True
+    return False
+
+
+def create_file(server_file, file_data):
+    with open(server_file, "w+") as file:
+        file.write(file_data)
+
+
+def get_ca_cert(ota_image_dir):
+    os.chdir(ota_image_dir)
+    server_file = os.path.join(ota_image_dir, "server_cert.pem")
+    create_file(server_file, server_cert)
+
+    key_file = os.path.join(ota_image_dir, "server_key.pem")
+    create_file(key_file, server_key)
+    return server_file, key_file
+
+
+def start_https_server(ota_image_dir, server_ip, server_port):
+    server_file, key_file = get_ca_cert(ota_image_dir)
+    httpd = BaseHTTPServer.HTTPServer((server_ip, server_port),
+                                      SimpleHTTPServer.SimpleHTTPRequestHandler)
+
+    httpd.socket = ssl.wrap_socket(httpd.socket,
+                                   keyfile=key_file,
+                                   certfile=server_file, server_side=True)
+    httpd.serve_forever()
+
+
+def start_chunked_server(ota_image_dir, server_port):
+    server_file, key_file = get_ca_cert(ota_image_dir)
+    chunked_server = subprocess.Popen(["openssl", "s_server", "-WWW", "-key", key_file, "-cert", server_file, "-port", str(server_port)])
+    return chunked_server
+
+
+def redirect_handler_factory(url):
+    """
+    Returns a request handler class that redirects to supplied `url`
+    """
+    class RedirectHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+        def do_GET(self):
+            print("Sending resp, URL: " + url)
+            self.send_response(301)
+            self.send_header('Location', url)
+            self.end_headers()
+
+    return RedirectHandler
+
+
+def start_redirect_server(ota_image_dir, server_ip, server_port, redirection_port):
+    os.chdir(ota_image_dir)
+    server_file, key_file = get_ca_cert(ota_image_dir)
+    redirectHandler = redirect_handler_factory("https://" + server_ip + ":" + str(redirection_port) + "/advanced_https_ota.bin")
+
+    httpd = BaseHTTPServer.HTTPServer((server_ip, server_port),
+                                      redirectHandler)
+
+    httpd.socket = ssl.wrap_socket(httpd.socket,
+                                   keyfile=key_file,
+                                   certfile=server_file, server_side=True)
+    httpd.serve_forever()
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_advanced_https_ota_example(env, extra_data):
+    """
+    This is a positive test case, which downloads complete binary file multiple number of times.
+    Number of iterations can be specified in variable iterations.
+    steps: |
+      1. join AP
+      2. Fetch OTA image over HTTPS
+      3. Reboot with the new OTA image
+    """
+    dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota")
+    # Number of iterations to validate OTA
+    iterations = 3
+    server_port = 8001
+    # File to be downloaded. This file is generated after compilation
+    bin_name = "advanced_https_ota.bin"
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    if (get_server_status(host_ip, server_port) is False):
+        thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
+        thread1.daemon = True
+        thread1.start()
+    dut1.start_app()
+    for i in range(iterations):
+        dut1.expect("Loaded app from partition at offset", timeout=30)
+        try:
+            ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+            print("Connected to AP with IP: {}".format(ip_address))
+        except DUT.ExpectTimeout:
+            raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+            thread1.close()
+        dut1.expect("Starting Advanced OTA example", timeout=30)
+
+        print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + bin_name))
+        dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + bin_name)
+        dut1.expect("Loaded app from partition at offset", timeout=60)
+        dut1.expect("Starting Advanced OTA example", timeout=30)
+        dut1.reset()
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_advanced_https_ota_example_truncated_bin(env, extra_data):
+    """
+    Working of OTA if binary file is truncated is validated in this test case.
+    Application should return with error message in this case.
+    steps: |
+      1. join AP
+      2. Generate truncated binary file
+      3. Fetch OTA image over HTTPS
+      4. Check working of code if bin is truncated
+    """
+    dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota")
+    server_port = 8001
+    # Original binary file generated after compilation
+    bin_name = "advanced_https_ota.bin"
+    # Truncated binary file to be generated from original binary file
+    truncated_bin_name = "truncated.bin"
+    # Size of truncated file to be grnerated. This value can range from 288 bytes (Image header size) to size of original binary file
+    # truncated_bin_size is set to 64000 to reduce consumed by the test case
+    truncated_bin_size = 64000
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    f = open(binary_file, "r+")
+    fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "w+")
+    fo.write(f.read(truncated_bin_size))
+    fo.close()
+    f.close()
+    binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    if (get_server_status(host_ip, server_port) is False):
+        thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
+        thread1.daemon = True
+        thread1.start()
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+    dut1.expect("Starting Advanced OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name))
+    dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name)
+    dut1.expect("Image validation failed, image is corrupted", timeout=30)
+    os.remove(binary_file)
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_advanced_https_ota_example_truncated_header(env, extra_data):
+    """
+    Working of OTA if headers of binary file are truncated is vaildated in this test case.
+    Application should return with error message in this case.
+    steps: |
+      1. join AP
+      2. Generate binary file with truncated headers
+      3. Fetch OTA image over HTTPS
+      4. Check working of code if headers are not sent completely
+    """
+    dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota")
+    server_port = 8001
+    # Original binary file generated after compilation
+    bin_name = "advanced_https_ota.bin"
+    # Truncated binary file to be generated from original binary file
+    truncated_bin_name = "truncated_header.bin"
+    # Size of truncated file to be grnerated. This value should be less than 288 bytes (Image header size)
+    truncated_bin_size = 180
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    f = open(binary_file, "r+")
+    fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "w+")
+    fo.write(f.read(truncated_bin_size))
+    fo.close()
+    f.close()
+    binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    if (get_server_status(host_ip, server_port) is False):
+        thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
+        thread1.daemon = True
+        thread1.start()
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+    dut1.expect("Starting Advanced OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name))
+    dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name)
+    dut1.expect("advanced_https_ota_example: esp_https_ota_read_img_desc failed", timeout=30)
+    os.remove(binary_file)
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_advanced_https_ota_example_random(env, extra_data):
+    """
+    Working of OTA if random data is added in binary file are validated in this test case.
+    Magic byte verification should fail in this case.
+    steps: |
+      1. join AP
+      2. Generate random binary image
+      3. Fetch OTA image over HTTPS
+      4. Check working of code for random binary file
+    """
+    dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota")
+    server_port = 8001
+    # Random binary file to be generated
+    random_bin_name = "random.bin"
+    # Size of random binary file. 32000 is choosen, to reduce the time required to run the test-case
+    random_bin_size = 32000
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, random_bin_name)
+    fo = open(binary_file, "w+")
+    # First byte of binary file is always set to zero. If first byte is generated randomly,
+    # in some cases it may generate 0xE9 which will result in failure of testcase.
+    fo.write(str(0))
+    for i in range(random_bin_size - 1):
+        fo.write(str(random.randrange(0,255,1)))
+    fo.close()
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    if (get_server_status(host_ip, server_port) is False):
+        thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
+        thread1.daemon = True
+        thread1.start()
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+    dut1.expect("Starting Advanced OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + random_bin_name))
+    dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + random_bin_name)
+    dut1.expect("esp_ota_ops: OTA image has invalid magic byte", timeout=10)
+    os.remove(binary_file)
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_advanced_https_ota_example_chunked(env, extra_data):
+    """
+    This is a positive test case, which downloads complete binary file multiple number of times.
+    Number of iterations can be specified in variable iterations.
+    steps: |
+      1. join AP
+      2. Fetch OTA image over HTTPS
+      3. Reboot with the new OTA image
+    """
+    dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota")
+    # File to be downloaded. This file is generated after compilation
+    bin_name = "advanced_https_ota.bin"
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    chunked_server = start_chunked_server(dut1.app.binary_path, 8070)
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+    dut1.expect("Starting Advanced OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":8070/" + bin_name))
+    dut1.write("https://" + host_ip + ":8070/" + bin_name)
+    dut1.expect("Loaded app from partition at offset", timeout=60)
+    dut1.expect("Starting Advanced OTA example", timeout=30)
+    chunked_server.kill()
+    os.remove(os.path.join(dut1.app.binary_path, "server_cert.pem"))
+    os.remove(os.path.join(dut1.app.binary_path, "server_key.pem"))
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_advanced_https_ota_example_redirect_url(env, extra_data):
+    """
+    This is a positive test case, which starts a server and a redirection server.
+    Redirection server redirects http_request to different port
+    Number of iterations can be specified in variable iterations.
+    steps: |
+      1. join AP
+      2. Fetch OTA image over HTTPS
+      3. Reboot with the new OTA image
+    """
+    dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota")
+    server_port = 8001
+    # Port to which the request should be redirecetd
+    redirection_server_port = 8081
+    # File to be downloaded. This file is generated after compilation
+    bin_name = "advanced_https_ota.bin"
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    if (get_server_status(host_ip, server_port) is False):
+        thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
+        thread1.daemon = True
+        thread1.start()
+    thread2 = Thread(target=start_redirect_server, args=(dut1.app.binary_path, host_ip, redirection_server_port, server_port))
+    thread2.daemon = True
+    thread2.start()
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+        thread1.close()
+        thread2.close()
+    dut1.expect("Starting Advanced OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":" + str(redirection_server_port) + "/" + bin_name))
+    dut1.write("https://" + host_ip + ":" + str(redirection_server_port) + "/" + bin_name)
+    dut1.expect("Loaded app from partition at offset", timeout=60)
+    dut1.expect("Starting Advanced OTA example", timeout=30)
+    dut1.reset()
+
+
+if __name__ == '__main__':
+    test_examples_protocol_advanced_https_ota_example()
+    test_examples_protocol_advanced_https_ota_example_chunked()
+    test_examples_protocol_advanced_https_ota_example_redirect_url()
+    test_examples_protocol_advanced_https_ota_example_truncated_bin()
+    test_examples_protocol_advanced_https_ota_example_truncated_header()
+    test_examples_protocol_advanced_https_ota_example_random()

+ 22 - 0
examples/system/ota/advanced_https_ota/main/Kconfig.projbuild

@@ -6,4 +6,26 @@ menu "Example Configuration"
         help
         help
             URL of server which hosts the firmware image.
             URL of server which hosts the firmware image.
 
 
+    config EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
+        bool
+        default y if EXAMPLE_FIRMWARE_UPGRADE_URL = "FROM_STDIN"
+
+    config EXAMPLE_SKIP_COMMON_NAME_CHECK
+        bool "Skip server certificate CN fieldcheck"
+        default n
+        help
+            This allows you to skip the validation of OTA server certificate CN field.
+
+    config EXAMPLE_SKIP_VERSION_CHECK
+        bool "Skip firmware version check"
+        default n
+        help
+            This allows you to skip the firmware version check.
+
+    config EXAMPLE_OTA_RECV_TIMEOUT
+        int "OTA Receive Timeout"
+        default 5000
+        help
+            Maximum time for reception
+
 endmenu
 endmenu

+ 45 - 5
examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c

@@ -21,10 +21,16 @@
 #include "nvs_flash.h"
 #include "nvs_flash.h"
 #include "protocol_examples_common.h"
 #include "protocol_examples_common.h"
 
 
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+#include "esp_wifi.h"
+#endif
+
 static const char *TAG = "advanced_https_ota_example";
 static const char *TAG = "advanced_https_ota_example";
 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
 extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
 extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
 
 
+#define OTA_URL_SIZE 256
+
 static esp_err_t validate_image_header(esp_app_desc_t *new_app_info)
 static esp_err_t validate_image_header(esp_app_desc_t *new_app_info)
 {
 {
     if (new_app_info == NULL) {
     if (new_app_info == NULL) {
@@ -37,10 +43,13 @@ static esp_err_t validate_image_header(esp_app_desc_t *new_app_info)
         ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
         ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
     }
     }
 
 
+#ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK
     if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) {
     if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) {
         ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
         ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
         return ESP_FAIL;
         return ESP_FAIL;
     }
     }
+#endif
+
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
@@ -52,8 +61,27 @@ void advanced_ota_example_task(void *pvParameter)
     esp_http_client_config_t config = {
     esp_http_client_config_t config = {
         .url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
         .url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
         .cert_pem = (char *)server_cert_pem_start,
         .cert_pem = (char *)server_cert_pem_start,
+        .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
     };
     };
 
 
+#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
+    char url_buf[OTA_URL_SIZE];
+    if (strcmp(config.url, "FROM_STDIN") == 0) {
+        example_configure_stdin_stdout();
+        fgets(url_buf, OTA_URL_SIZE, stdin);
+        int len = strlen(url_buf);
+        url_buf[len - 1] = '\0';
+        config.url = url_buf;
+    } else {
+        ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
+        abort();
+    }
+#endif
+
+#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
+    config.skip_cert_common_name_check = true;
+#endif
+
     esp_https_ota_config_t ota_config = {
     esp_https_ota_config_t ota_config = {
         .http_config = &config,
         .http_config = &config,
     };
     };
@@ -88,6 +116,11 @@ void advanced_ota_example_task(void *pvParameter)
         ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
         ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
     }
     }
 
 
+    if (esp_https_ota_is_complete_data_received(https_ota_handle) != true) {
+        // the OTA image was not completely received and user can customise the response to this situation.
+        ESP_LOGE(TAG, "Complete data was not received.");
+    }
+
 ota_end:
 ota_end:
     ota_finish_err = esp_https_ota_finish(https_ota_handle);
     ota_finish_err = esp_https_ota_finish(https_ota_handle);
     if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) {
     if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) {
@@ -95,11 +128,11 @@ ota_end:
         vTaskDelay(1000 / portTICK_PERIOD_MS);
         vTaskDelay(1000 / portTICK_PERIOD_MS);
         esp_restart();
         esp_restart();
     } else {
     } else {
-        ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed...");
-    }
-
-    while (1) {
-        vTaskDelay(1000 / portTICK_PERIOD_MS);
+        if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) {
+            ESP_LOGE(TAG, "Image validation failed, image is corrupted");
+        }
+        ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed %d", ota_finish_err);
+        vTaskDelete(NULL);
     }
     }
 }
 }
 
 
@@ -126,6 +159,13 @@ void app_main()
     */
     */
     ESP_ERROR_CHECK(example_connect());
     ESP_ERROR_CHECK(example_connect());
 
 
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+    /* Ensure to disable any WiFi power save mode, this allows best throughput
+     * and hence timings for overall OTA operation.
+     */
+    esp_wifi_set_ps(WIFI_PS_NONE);
+#endif // CONFIG_EXAMPLE_CONNECT_WIFI
+
     xTaskCreate(&advanced_ota_example_task, "advanced_ota_example_task", 1024 * 8, NULL, 5, NULL);
     xTaskCreate(&advanced_ota_example_task, "advanced_ota_example_task", 1024 * 8, NULL, 5, NULL);
 }
 }
 
 

+ 4 - 0
examples/system/ota/advanced_https_ota/sdkconfig.ci

@@ -0,0 +1,4 @@
+CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN"
+CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
+CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y
+CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=2000

+ 21 - 0
examples/system/ota/advanced_https_ota/server_certs/ca_cert.pem

@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0
+nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC
+9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA
+w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF
+3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M
+lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY
+IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd
+/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA
+lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl
+6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2
+fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu
+y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy
+hA==
+-----END CERTIFICATE-----

+ 337 - 0
examples/system/ota/native_ota_example/example_test.py

@@ -0,0 +1,337 @@
+import re
+import os
+import socket
+import BaseHTTPServer
+import SimpleHTTPServer
+from threading import Thread
+import ssl
+
+from tiny_test_fw import DUT
+import ttfw_idf
+import random
+import subprocess
+
+server_cert = "-----BEGIN CERTIFICATE-----\n" \
+              "MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\
+              "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"\
+              "aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF\n"\
+              "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"\
+              "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"\
+              "CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0\n"\
+              "nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC\n"\
+              "9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA\n"\
+              "w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF\n"\
+              "3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M\n"\
+              "lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY\n"\
+              "IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww\n"\
+              "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd\n"\
+              "/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA\n"\
+              "lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl\n"\
+              "6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2\n"\
+              "fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu\n"\
+              "y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy\n"\
+              "hA==\n"\
+              "-----END CERTIFICATE-----\n"
+
+server_key = "-----BEGIN PRIVATE KEY-----\n"\
+             "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXN8LK/eYi/tOU\n"\
+             "uQ5vG6cp8J2sn/OB0A2uzHREG2kQ+FXnrSFYnR8SKfScg4yklKIX0TpCw92vpD56\n"\
+             "iAfhec4xT0kT6Ibvjc3V098YTMm9GXJy38KTxKzAo8L35Veyc51mZTD3L/G0A1tR\n"\
+             "ED9Oym8/PPcC+/fzZ999+skaHuig6ZplIJkWVJgctkDD9eVGvSxJFsukUZjSBeNo\n"\
+             "BXyIceI8NgvLiRk56mNX1TnXGL4gawvjDvnO4yCwfIXecl4ZgeC0ZUGuQvRkobm5\n"\
+             "1jTBwGPKyO5sMkLiJKU2KrYcPd+GzuPoJl11Xa/zjkyVUo3E/SQ7hSPgPyv7lRJY\n"\
+             "Lwkp8DDFAgMBAAECggEAfBhAfQE7mUByNbxgAgI5fot9eaqR1Nf+QpJ6X2H3KPwC\n"\
+             "02sa0HOwieFwYfj6tB1doBoNq7i89mTc+QUlIn4pHgIowHO0OGawomeKz5BEhjCZ\n"\
+             "4XeLYGSoODary2+kNkf2xY8JTfFEcyvGBpJEwc4S2VyYgRRx+IgnumTSH+N5mIKZ\n"\
+             "SXWNdZIuHEmkwod+rPRXs6/r+PH0eVW6WfpINEbr4zVAGXJx2zXQwd2cuV1GTJWh\n"\
+             "cPVOXLu+XJ9im9B370cYN6GqUnR3fui13urYbnWnEf3syvoH/zuZkyrVChauoFf8\n"\
+             "8EGb74/HhXK7Q2s8NRakx2c7OxQifCbcy03liUMmyQKBgQDFAob5B/66N4Q2cq/N\n"\
+             "MWPf98kYBYoLaeEOhEJhLQlKk0pIFCTmtpmUbpoEes2kCUbH7RwczpYko8tlKyoB\n"\
+             "6Fn6RY4zQQ64KZJI6kQVsjkYpcP/ihnOY6rbds+3yyv+4uPX7Eh9sYZwZMggE19M\n"\
+             "CkFHkwAjiwqhiiSlUxe20sWmowKBgQDEfx4lxuFzA1PBPeZKGVBTxYPQf+DSLCre\n"\
+             "ZFg3ZmrxbCjRq1O7Lra4FXWD3dmRq7NDk79JofoW50yD8wD7I0B7opdDfXD2idO8\n"\
+             "0dBnWUKDr2CAXyoLEINce9kJPbx4kFBQRN9PiGF7VkDQxeQ3kfS8CvcErpTKCOdy\n"\
+             "5wOwBTwJdwKBgDiTFTeGeDv5nVoVbS67tDao7XKchJvqd9q3WGiXikeELJyuTDqE\n"\
+             "zW22pTwMF+m3UEAxcxVCrhMvhkUzNAkANHaOatuFHzj7lyqhO5QPbh4J3FMR0X9X\n"\
+             "V8VWRSg+jA/SECP9koOl6zlzd5Tee0tW1pA7QpryXscs6IEhb3ns5R2JAoGAIkzO\n"\
+             "RmnhEOKTzDex611f2D+yMsMfy5BKK2f4vjLymBH5TiBKDXKqEpgsW0huoi8Gq9Uu\n"\
+             "nvvXXAgkIyRYF36f0vUe0nkjLuYAQAWgC2pZYgNLJR13iVbol0xHJoXQUHtgiaJ8\n"\
+             "GLYFzjHQPqFMpSalQe3oELko39uOC1CoJCHFySECgYBeycUnRBikCO2n8DNhY4Eg\n"\
+             "9Y3oxcssRt6ea5BZwgW2eAYi7/XqKkmxoSoOykUt3MJx9+EkkrL17bxFSpkj1tvL\n"\
+             "qvxn7egtsKjjgGNAxwXC4MwCvhveyUQQxtQb8AqGrGqo4jEEN0L15cnP38i2x1Uo\n"\
+             "muhfskWf4MABV0yTUaKcGg==\n"\
+             "-----END PRIVATE KEY-----\n"
+
+
+def get_my_ip():
+    s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    s1.connect(("8.8.8.8", 80))
+    my_ip = s1.getsockname()[0]
+    s1.close()
+    return my_ip
+
+
+def start_https_server(ota_image_dir, server_ip, server_port):
+    os.chdir(ota_image_dir)
+
+    server_file = os.path.join(ota_image_dir, "server_cert.pem")
+    cert_file_handle = open(server_file, "w+")
+    cert_file_handle.write(server_cert)
+    cert_file_handle.close()
+
+    key_file = os.path.join(ota_image_dir, "server_key.pem")
+    key_file_handle = open("server_key.pem", "w+")
+    key_file_handle.write(server_key)
+    key_file_handle.close()
+
+    httpd = BaseHTTPServer.HTTPServer((server_ip, server_port),
+                                      SimpleHTTPServer.SimpleHTTPRequestHandler)
+
+    httpd.socket = ssl.wrap_socket(httpd.socket,
+                                   keyfile=key_file,
+                                   certfile=server_file, server_side=True)
+    httpd.serve_forever()
+
+
+def start_chunked_server(ota_image_dir, server_port):
+    os.chdir(ota_image_dir)
+
+    server_file = os.path.join(ota_image_dir, "server_cert.pem")
+    cert_file_handle = open(server_file, "w+")
+    cert_file_handle.write(server_cert)
+    cert_file_handle.close()
+
+    key_file = os.path.join(ota_image_dir, "server_key.pem")
+    key_file_handle = open("server_key.pem", "w+")
+    key_file_handle.write(server_key)
+    key_file_handle.close()
+    chunked_server = subprocess.Popen(["openssl", "s_server", "-WWW", "-key", key_file, "-cert", server_file, "-port", str(server_port)])
+    return chunked_server
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_native_ota_example(env, extra_data):
+    """
+    This is a positive test case, which downloads complete binary file multiple number of times.
+    Number of iterations can be specified in variable iterations.
+    steps: |
+      1. join AP
+      2. Fetch OTA image over HTTPS
+      3. Reboot with the new OTA image
+    """
+    dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example")
+    # No. of times working of application to be validated
+    iterations = 3
+    # File to be downloaded. This file is generated after compilation
+    bin_name = "native_ota.bin"
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, 8002))
+    thread1.daemon = True
+    thread1.start()
+    dut1.start_app()
+    for i in range(iterations):
+        dut1.expect("Loaded app from partition at offset", timeout=30)
+        try:
+            ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+            print("Connected to AP with IP: {}".format(ip_address))
+        except DUT.ExpectTimeout:
+            raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+            thread1.close()
+        dut1.expect("Starting OTA example", timeout=30)
+
+        print("writing to device: {}".format("https://" + host_ip + ":8002/" + bin_name))
+        dut1.write("https://" + host_ip + ":8002/" + bin_name)
+        dut1.expect("Loaded app from partition at offset", timeout=60)
+        dut1.expect("Starting OTA example", timeout=30)
+        dut1.reset()
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_native_ota_example_truncated_bin(env, extra_data):
+    """
+    Working of OTA if binary file is truncated is validated in this test case.
+    Application should return with error message in this case.
+    steps: |
+      1. join AP
+      2. Generate truncated binary file
+      3. Fetch OTA image over HTTPS
+      4. Check working of code if bin is truncated
+    """
+    dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example")
+    # Original binary file generated after compilation
+    bin_name = "native_ota.bin"
+    # Truncated binary file to be generated from original binary file
+    truncated_bin_name = "truncated.bin"
+    # Size of truncated file to be grnerated. This value can range from 288 bytes (Image header size) to size of original binary file
+    # truncated_bin_size is set to 64000 to reduce consumed by the test case
+    truncated_bin_size = 64000
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    f = open(binary_file, "r+")
+    fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "w+")
+    fo.write(f.read(truncated_bin_size))
+    fo.close()
+    f.close()
+    binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=60)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+    dut1.expect("Starting OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":8002/" + truncated_bin_name))
+    dut1.write("https://" + host_ip + ":8002/" + truncated_bin_name)
+    dut1.expect("native_ota_example: Image validation failed, image is corrupted", timeout=20)
+    os.remove(binary_file)
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_native_ota_example_truncated_header(env, extra_data):
+    """
+    Working of OTA if headers of binary file are truncated is vaildated in this test case.
+    Application should return with error message in this case.
+    steps: |
+      1. join AP
+      2. Generate binary file with truncated headers
+      3. Fetch OTA image over HTTPS
+      4. Check working of code if headers are not sent completely
+    """
+    dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example")
+    # Original binary file generated after compilation
+    bin_name = "native_ota.bin"
+    # Truncated binary file to be generated from original binary file
+    truncated_bin_name = "truncated_header.bin"
+    # Size of truncated file to be grnerated. This value should be less than 288 bytes (Image header size)
+    truncated_bin_size = 180
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    f = open(binary_file, "r+")
+    fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "w+")
+    fo.write(f.read(truncated_bin_size))
+    fo.close()
+    f.close()
+    binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=60)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+    dut1.expect("Starting OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":8002/" + truncated_bin_name))
+    dut1.write("https://" + host_ip + ":8002/" + truncated_bin_name)
+    dut1.expect("native_ota_example: received package is not fit len", timeout=20)
+    os.remove(binary_file)
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_native_ota_example_random(env, extra_data):
+    """
+    Working of OTA if random data is added in binary file are validated in this test case.
+    Magic byte verification should fail in this case.
+    steps: |
+      1. join AP
+      2. Generate random binary image
+      3. Fetch OTA image over HTTPS
+      4. Check working of code for random binary file
+    """
+    dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example")
+    # Random binary file to be generated
+    random_bin_name = "random.bin"
+    # Size of random binary file. 32000 is choosen, to reduce the time required to run the test-case
+    random_bin_size = 32000
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, random_bin_name)
+    fo = open(binary_file, "w+")
+    # First byte of binary file is always set to zero. If first byte is generated randomly,
+    # in some cases it may generate 0xE9 which will result in failure of testcase.
+    fo.write(str(0))
+    for i in range(random_bin_size - 1):
+        fo.write(str(random.randrange(0,255,1)))
+    fo.close()
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=60)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+    dut1.expect("Starting OTA example", timeout=30)
+
+    print("writing to device: {}".format("https://" + host_ip + ":8002/" + random_bin_name))
+    dut1.write("https://" + host_ip + ":8002/" + random_bin_name)
+    dut1.expect("esp_ota_ops: OTA image has invalid magic byte", timeout=20)
+    os.remove(binary_file)
+
+
+@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
+def test_examples_protocol_native_ota_example_chunked(env, extra_data):
+    """
+    This is a positive test case, which downloads complete binary file multiple number of times.
+    Number of iterations can be specified in variable iterations.
+    steps: |
+      1. join AP
+      2. Fetch OTA image over HTTPS
+      3. Reboot with the new OTA image
+    """
+    dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example")
+    # File to be downloaded. This file is generated after compilation
+    bin_name = "native_ota.bin"
+    # check and log bin size
+    binary_file = os.path.join(dut1.app.binary_path, bin_name)
+    bin_size = os.path.getsize(binary_file)
+    ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
+    ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024)
+    # start test
+    host_ip = get_my_ip()
+    chunked_server = start_chunked_server(dut1.app.binary_path, 8070)
+    dut1.start_app()
+    dut1.expect("Loaded app from partition at offset", timeout=30)
+    try:
+        ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
+        print("Connected to AP with IP: {}".format(ip_address))
+    except DUT.ExpectTimeout:
+        raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
+
+    dut1.expect("Starting OTA example", timeout=30)
+    print("writing to device: {}".format("https://" + host_ip + ":8070/" + bin_name))
+    dut1.write("https://" + host_ip + ":8070/" + bin_name)
+    dut1.expect("Loaded app from partition at offset", timeout=60)
+    dut1.expect("Starting OTA example", timeout=30)
+    chunked_server.kill()
+    os.remove(os.path.join(dut1.app.binary_path, "server_cert.pem"))
+    os.remove(os.path.join(dut1.app.binary_path, "server_key.pem"))
+
+
+if __name__ == '__main__':
+    test_examples_protocol_native_ota_example()
+    test_examples_protocol_native_ota_example_chunked()
+    test_examples_protocol_native_ota_example_truncated_bin()
+    test_examples_protocol_native_ota_example_truncated_header()
+    test_examples_protocol_native_ota_example_random()

+ 22 - 0
examples/system/ota/native_ota_example/main/Kconfig.projbuild

@@ -6,6 +6,22 @@ menu "Example Configuration"
         help
         help
             URL of server which hosts the firmware image.
             URL of server which hosts the firmware image.
 
 
+    config EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
+        bool
+        default y if EXAMPLE_FIRMWARE_UPG_URL = "FROM_STDIN"
+
+    config EXAMPLE_SKIP_COMMON_NAME_CHECK
+        bool "Skip server certificate CN fieldcheck"
+        default n
+        help
+            This allows you to skip the validation of OTA server certificate CN field.
+
+    config EXAMPLE_SKIP_VERSION_CHECK
+        bool "Skip firmware version check"
+        default n
+        help
+            This allows you to skip the firmware version check.
+
     config EXAMPLE_GPIO_DIAGNOSTIC
     config EXAMPLE_GPIO_DIAGNOSTIC
         int "Number of the GPIO input for diagnostic"
         int "Number of the GPIO input for diagnostic"
         range 0 39
         range 0 39
@@ -17,4 +33,10 @@ menu "Example Configuration"
             `Diagnostics (5 sec)...` which will be on first boot.
             `Diagnostics (5 sec)...` which will be on first boot.
             If GPIO is not pulled low then the operable of the app will be confirmed.
             If GPIO is not pulled low then the operable of the app will be confirmed.
 
 
+    config EXAMPLE_OTA_RECV_TIMEOUT
+        int "OTA Receive Timeout"
+        default 5000
+        help
+            Maximum time for reception
+
 endmenu
 endmenu

+ 60 - 6
examples/system/ota/native_ota_example/main/native_ota_example.c

@@ -21,6 +21,11 @@
 #include "nvs_flash.h"
 #include "nvs_flash.h"
 #include "driver/gpio.h"
 #include "driver/gpio.h"
 #include "protocol_examples_common.h"
 #include "protocol_examples_common.h"
+#include "errno.h"
+
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+#include "esp_wifi.h"
+#endif
 
 
 #define BUFFSIZE 1024
 #define BUFFSIZE 1024
 #define HASH_LEN 32 /* SHA-256 digest length */
 #define HASH_LEN 32 /* SHA-256 digest length */
@@ -31,6 +36,8 @@ static char ota_write_data[BUFFSIZE + 1] = { 0 };
 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
 extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
 extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
 
 
+#define OTA_URL_SIZE 256
+
 static void http_cleanup(esp_http_client_handle_t client)
 static void http_cleanup(esp_http_client_handle_t client)
 {
 {
     esp_http_client_close(client);
     esp_http_client_close(client);
@@ -90,7 +97,27 @@ static void ota_example_task(void *pvParameter)
     esp_http_client_config_t config = {
     esp_http_client_config_t config = {
         .url = CONFIG_EXAMPLE_FIRMWARE_UPG_URL,
         .url = CONFIG_EXAMPLE_FIRMWARE_UPG_URL,
         .cert_pem = (char *)server_cert_pem_start,
         .cert_pem = (char *)server_cert_pem_start,
+        .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
     };
     };
+
+#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
+    char url_buf[OTA_URL_SIZE];
+    if (strcmp(config.url, "FROM_STDIN") == 0) {
+        example_configure_stdin_stdout();
+        fgets(url_buf, OTA_URL_SIZE, stdin);
+        int len = strlen(url_buf);
+        url_buf[len - 1] = '\0';
+        config.url = url_buf;
+    } else {
+        ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
+        abort();
+    }
+#endif
+
+#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
+    config.skip_cert_common_name_check = true;
+#endif
+
     esp_http_client_handle_t client = esp_http_client_init(&config);
     esp_http_client_handle_t client = esp_http_client_init(&config);
     if (client == NULL) {
     if (client == NULL) {
         ESP_LOGE(TAG, "Failed to initialise HTTP connection");
         ESP_LOGE(TAG, "Failed to initialise HTTP connection");
@@ -147,12 +174,13 @@ static void ota_example_task(void *pvParameter)
                             infinite_loop();
                             infinite_loop();
                         }
                         }
                     }
                     }
-
+#ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK
                     if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
                     if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
                         ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
                         ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
                         http_cleanup(client);
                         http_cleanup(client);
                         infinite_loop();
                         infinite_loop();
                     }
                     }
+#endif
 
 
                     image_header_was_checked = true;
                     image_header_was_checked = true;
 
 
@@ -177,14 +205,33 @@ static void ota_example_task(void *pvParameter)
             binary_file_length += data_read;
             binary_file_length += data_read;
             ESP_LOGD(TAG, "Written image length %d", binary_file_length);
             ESP_LOGD(TAG, "Written image length %d", binary_file_length);
         } else if (data_read == 0) {
         } else if (data_read == 0) {
-            ESP_LOGI(TAG, "Connection closed,all data received");
-            break;
+           /*
+            * As esp_http_client_read never returns negative error code, we rely on
+            * `errno` to check for underlying transport connectivity closure if any
+            */
+            if (errno == ECONNRESET || errno == ENOTCONN) {
+                ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
+                break;
+            }
+            if (esp_http_client_is_complete_data_received(client) == true) {
+                ESP_LOGI(TAG, "Connection closed");
+                break;
+            }
         }
         }
     }
     }
-    ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length);
+    ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length);
+    if (esp_http_client_is_complete_data_received(client) != true) {
+        ESP_LOGE(TAG, "Error in receiving complete file");
+        http_cleanup(client);
+        task_fatal_error();
+    }
 
 
-    if (esp_ota_end(update_handle) != ESP_OK) {
-        ESP_LOGE(TAG, "esp_ota_end failed!");
+    err = esp_ota_end(update_handle);
+    if (err != ESP_OK) {
+        if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
+            ESP_LOGE(TAG, "Image validation failed, image is corrupted");
+        }
+        ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
         http_cleanup(client);
         http_cleanup(client);
         task_fatal_error();
         task_fatal_error();
     }
     }
@@ -278,5 +325,12 @@ void app_main()
      */
      */
     ESP_ERROR_CHECK(example_connect());
     ESP_ERROR_CHECK(example_connect());
 
 
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+    /* Ensure to disable any WiFi power save mode, this allows best throughput
+     * and hence timings for overall OTA operation.
+     */
+    esp_wifi_set_ps(WIFI_PS_NONE);
+#endif // CONFIG_EXAMPLE_CONNECT_WIFI
+
     xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
     xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
 }
 }

+ 4 - 0
examples/system/ota/native_ota_example/sdkconfig.ci

@@ -0,0 +1,4 @@
+CONFIG_EXAMPLE_FIRMWARE_UPG_URL="FROM_STDIN"
+CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
+CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y
+CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=2000

+ 21 - 0
examples/system/ota/native_ota_example/server_certs/ca_cert.pem

@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0
+nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC
+9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA
+w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF
+3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M
+lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY
+IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd
+/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA
+lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl
+6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2
+fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu
+y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy
+hA==
+-----END CERTIFICATE-----

+ 11 - 0
examples/system/ota/simple_ota_example/main/simple_ota_example.c

@@ -22,6 +22,10 @@
 #include "nvs_flash.h"
 #include "nvs_flash.h"
 #include "protocol_examples_common.h"
 #include "protocol_examples_common.h"
 
 
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+#include "esp_wifi.h"
+#endif
+
 static const char *TAG = "simple_ota_example";
 static const char *TAG = "simple_ota_example";
 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
 extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
 extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
 extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
@@ -118,5 +122,12 @@ void app_main()
      */
      */
     ESP_ERROR_CHECK(example_connect());
     ESP_ERROR_CHECK(example_connect());
 
 
+#if CONFIG_EXAMPLE_CONNECT_WIFI
+    /* Ensure to disable any WiFi power save mode, this allows best throughput
+     * and hence timings for overall OTA operation.
+     */
+    esp_wifi_set_ps(WIFI_PS_NONE);
+#endif // CONFIG_EXAMPLE_CONNECT_WIFI
+
     xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
     xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
 }
 }

+ 1 - 1
tools/ci/config/target-test.yml

@@ -147,7 +147,7 @@ test_weekend_network:
 
 
 example_test_001:
 example_test_001:
   extends: .example_test_template
   extends: .example_test_template
-  parallel: 2
+  parallel: 3
   tags:
   tags:
     - ESP32
     - ESP32
     - Example_WIFI
     - Example_WIFI