From 20fbbf2f57425abdb476ba07fea8de588a54aebe Mon Sep 17 00:00:00 2001 From: Andrea Greco Date: Tue, 23 Jan 2018 00:02:35 +0100 Subject: [PATCH] Http Client OTA: Add extra for HTTP client OTA update Add extra for Http client perform OTA firmaware upgrage. --- extras/http_client_ota/component.mk | 10 + extras/http_client_ota/http_buffered_client.c | 219 +++++++++++++++ extras/http_client_ota/http_buffered_client.h | 29 ++ extras/http_client_ota/http_client_ota.c | 259 ++++++++++++++++++ extras/http_client_ota/http_client_ota.h | 56 ++++ 5 files changed, 573 insertions(+) create mode 100644 extras/http_client_ota/component.mk create mode 100644 extras/http_client_ota/http_buffered_client.c create mode 100644 extras/http_client_ota/http_buffered_client.h create mode 100644 extras/http_client_ota/http_client_ota.c create mode 100644 extras/http_client_ota/http_client_ota.h diff --git a/extras/http_client_ota/component.mk b/extras/http_client_ota/component.mk new file mode 100644 index 0000000..4cebd16 --- /dev/null +++ b/extras/http_client_ota/component.mk @@ -0,0 +1,10 @@ +# Component makefile for extras/http_client_ota + +# Expected anyone using http_client_ota includes it as 'http_client_ota/ota' +INC_DIRS += $(http_client_ota_ROOT) + +# args for passing into compile rule generation +http_client_ota_INC_DIR = $(http_client_ota_ROOT) +http_client_ota_SRC_DIR = $(http_client_ota_ROOT) + +$(eval $(call component_compile_rules,http_client_ota)) diff --git a/extras/http_client_ota/http_buffered_client.c b/extras/http_client_ota/http_buffered_client.c new file mode 100644 index 0000000..a0d79bf --- /dev/null +++ b/extras/http_client_ota/http_buffered_client.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include + +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" + +#include "http_buffered_client.h" + +#define MAX_REQUEST_SIZE (152 / sizeof(uint32_t)) +#define vTaskDelayMs(ms) vTaskDelay((ms) / portTICK_PERIOD_MS) + +typedef void (*handle_http_token)(char *); + +struct http_token_table { + char * token; + handle_http_token http_tock_cb; +}; + +// Response struct +struct HTTP_response { + unsigned int response_code; + unsigned int length; +}; + +/** + * \addtogroup http_buffer_client_internal + * Http Request + */ +const char *req = + "GET %s HTTP/1.1\r\n" + "Host: %s \r\n" + "User-Agent: esp-open-rtos/0.1 esp8266\r\n" + "Connection: close\r\n" + "\r\n"; + +static uint32_t request[MAX_REQUEST_SIZE]; + +static const struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, +}; + +static struct HTTP_response http_reponse; + +// HTTP Header Token, add here function and then register it in HTTP Table callback +static void http_handle_cb_ContentLength(char *token) +{ + token += 16; // strlen("Content-Length:"), skip useless part + while (*token) { + if (isdigit((int) *token)) + http_reponse.length = (unsigned int) strtol(token, &token, 10); + else + token++; + } +} + +static inline void parse_http_header_HTTP_STATUS(char *token) +{ + token += 8; // Skip HTTP/1.0 + + while (*token) { + if (isdigit((int) *token)) + http_reponse.response_code = (unsigned int) strtol(token, &token, 10); + else + token++; + } +} + +// HTTP Token Hanling callback +struct http_token_table HTTP_HEADER_TOKEN[] = { + { .token = "Content-Length", .http_tock_cb = http_handle_cb_ContentLength }, +}; + +static inline void parse_http_header(char *header) +{ + char *str1, *str2, *token, *subtoken, *saveptr1, *saveptr2; + const char line_split[] = "\r\n", sub_chart[] = ":"; + unsigned int j, i; + + for (j = 1, str1 = header;; j++, str1 = NULL) { + token = strtok_r(str1, line_split, &saveptr1); + if (token == NULL) + break; + + str2 = token; + subtoken = strtok_r(str2, sub_chart, &saveptr2); + if (subtoken == NULL) + break; + + if (j == 1) { + // Is HTTP Header, response, HTTP Version and status + parse_http_header_HTTP_STATUS(token); + continue; + } + + for (i = 0; i < sizeof(HTTP_HEADER_TOKEN) / sizeof(struct http_token_table); i++) + if (!strcmp(subtoken, HTTP_HEADER_TOKEN[i].token)) + HTTP_HEADER_TOKEN[i].http_tock_cb(token); + + } +} + +HTTP_Client_State HttpClient_dowload(Http_client_info *info) +{ + struct addrinfo *res; + unsigned int tot_http_pdu_rd, read_byte, full; + int err, sock; + char *wrt_ptr; + + err = getaddrinfo(info->server, info->port, &hints, &res); + + if (err != 0 || res == NULL) { + if (res) + freeaddrinfo(res); + return HTTP_DNS_LOOKUP_FALLIED; + } + + sock = socket(res->ai_family, res->ai_socktype, 0); + if (sock < 0) { + freeaddrinfo(res); + return HTTP_SOCKET_ALLOCATION_FALLIED; + } + + if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) { + close(sock); + freeaddrinfo(res); + return HTTP_SOCKET_CONNECTION_FALLIED; + } + + // Release address memory + freeaddrinfo(res); + + // Alloc memory for request + sprintf((char *) request, req, info->path, info->server); + if (write(sock, (char *) request, strlen((char *) request)) < 0) { + close(sock); + return HTTP_REQUEST_SEND_FALLIED; + } + + tot_http_pdu_rd = 0; + wrt_ptr = info->buffer; + full = 0; + + // Ping wdog + vTaskDelayMs(250); + do { + int free_buff_space; + + free_buff_space = info->buffer_size - full; + read_byte = read(sock, wrt_ptr, free_buff_space); + + // Update buffer property + wrt_ptr += read_byte; + full += read_byte; + + if (tot_http_pdu_rd == 0) { + // Is fist chunk, then it contains http header, parse it. + unsigned int header_len, pdu_size; + char *header, *pdu; + + pdu = strstr(info->buffer, "\r\n\r\n"); + + if (pdu != NULL) + pdu += 4; // Offset by 4 bytes to start of content + else // Not all HTTP Header has been read, then continue read + continue; + + header_len = pdu - info->buffer + 4; + + header = malloc(header_len + 1); // NULL string terminator + + memset(header, 0, header_len + 1); + memcpy(header, info->buffer, header_len); + + parse_http_header(header); + // Release useless memory + free(header); + + // Move memory + pdu_size = wrt_ptr - pdu; + + memmove(info->buffer, pdu, pdu_size); + wrt_ptr = (info->buffer + pdu_size); + + full = pdu_size; + tot_http_pdu_rd = pdu_size; + + if (http_reponse.response_code != HTTP_OK) + goto err_label; + + continue; + } + tot_http_pdu_rd += read_byte; + + if (full == info->buffer_size) { + info->buffer_full_cb(info->buffer, full); + + memset(info->buffer, 0, info->buffer_size); + wrt_ptr = info->buffer; + full = 0; + vTaskDelayMs(50); + } + } while (read_byte > 0); + + info->final_cb(info->buffer, full); + if (tot_http_pdu_rd != http_reponse.length) + http_reponse.response_code = HTTP_DOWLOAD_SIZE_NOT_MATCH; + +err_label: + close(sock); + return http_reponse.response_code; +} /* HttpClient_dowload */ diff --git a/extras/http_client_ota/http_buffered_client.h b/extras/http_client_ota/http_buffered_client.h new file mode 100644 index 0000000..cfeb8da --- /dev/null +++ b/extras/http_client_ota/http_buffered_client.h @@ -0,0 +1,29 @@ +#ifndef HTTP_BUFFERED_CLIENT +#define HTTP_BUFFERED_CLIENT + +typedef unsigned int (*http_final_cb)(char *buff, uint16_t size); + +typedef enum { + HTTP_DNS_LOOKUP_FALLIED = 1, + HTTP_SOCKET_ALLOCATION_FALLIED = 2, + HTTP_SOCKET_CONNECTION_FALLIED = 3, + HTTP_SHA_DONT_MATCH = 4, + HTTP_REQUEST_SEND_FALLIED = 5, + HTTP_DOWLOAD_SIZE_NOT_MATCH = 6, + HTTP_OK = 200, + HTTP_NOTFOUND = 404, +} HTTP_Client_State; + +typedef struct { + char * server; + char * port; + char * path; + char * buffer; + uint16_t buffer_size; + http_final_cb buffer_full_cb; + http_final_cb final_cb; +} Http_client_info; + +HTTP_Client_State HttpClient_dowload(Http_client_info *info); + +#endif // ifndef HTTP_BUFFERED_CLIENT diff --git a/extras/http_client_ota/http_client_ota.c b/extras/http_client_ota/http_client_ota.c new file mode 100644 index 0000000..f7a2be9 --- /dev/null +++ b/extras/http_client_ota/http_client_ota.c @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include +#include "arch/cc.h" +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" +#include +#include +#include +#include +#include "mbedtls/sha256.h" +#include "http_client_ota.h" +#include "rboot-api.h" +#include "rboot.h" +#define MODULE "OTA" + +#if defined(DEBUG) +# ifndef MODULE +# error "Module not define" +# endif + + # define DEBUG_PRINT(fmt, args ...) \ + printf("[%s]\t" fmt "\n", MODULE, ## args) +#else + # define DEBUG_PRINT(fmt, args ...) /* Don't do anything in release builds */ +#endif + +#define MAX_IMAGE_SIZE 0x100000 /*1MB images max at the moment */ +#define READ_BUFFER_LEN 512 + +#define SHA256_SIZE_BIN 32 +#define SHA256_SIZE_STR SHA256_SIZE_BIN * 2 +#define SHA256_CONV_STEP_SIZE 4 + +#if SECTOR_SIZE % READ_BUFFER_LEN != 0 +# error "Incompatible SECTOR SIZE, with you current READ_BUFFER" +#endif + +#define SECTOR_BUFFER_SIZE (SECTOR_SIZE) +#define vTaskDelayMs(ms) vTaskDelay((ms) / portTICK_PERIOD_MS) + +static ota_info *ota_inf; +static mbedtls_sha256_context *sha256_ctx; + +static uint32_t flash_offset; +static uint32_t flash_limits; + +static unsigned char *SHA256_output; +static uint16_t *SHA256_dowload; +static char *SHA256_str; +static char *SHA256_wrt_ptr; + +/** + * CallBack called from Http Buffered client, for ota firmaware + */ +static unsigned int ota_firmaware_dowload_callback(char *buf, uint16_t size) +{ + if (ota_inf->sha256_path != NULL) + mbedtls_sha256_update(sha256_ctx, (const unsigned char *) buf, size); + + if (flash_offset + size > flash_limits) { + DEBUG_PRINT("Flash Limits override"); + return -1; + } + + // Ready for flash device, the erase NANDFLASH Block + if (flash_offset % SECTOR_SIZE == 0) { + unsigned int sector; + + sector = flash_offset / SECTOR_SIZE; + sdk_spi_flash_erase_sector(sector); + } + + // Write into Flash + sdk_spi_flash_write(flash_offset, (uint32_t *) buf, size); + flash_offset += size; + return 1; +} + +static unsigned int SHA256_check_callback(char *buf, uint16_t size) +{ + int Current_SHA_Size; + + // Check that str does not contains other streing with SHA256 + if (size > SHA256_SIZE_STR) + size = SHA256_SIZE_STR; + + Current_SHA_Size = SHA256_wrt_ptr - (char *) SHA256_str; + + if (!(Current_SHA_Size > SHA256_SIZE_STR)) { + memcpy(SHA256_wrt_ptr, buf, size); + SHA256_wrt_ptr += size; + } + return 1; +} + +static void convert_SHA256_Str_TO_uint32(char *str, uint16_t *final_sha_bin) +{ + char tmp[SHA256_CONV_STEP_SIZE + 1]; + char *wrt_ptr; + int i; + + wrt_ptr = str; + for (i = 0; i < SHA256_SIZE_STR / SHA256_CONV_STEP_SIZE; i++) { + uint16_t val; + memset(tmp, 0, sizeof(tmp)); + memcpy(tmp, wrt_ptr, SHA256_CONV_STEP_SIZE); + + val = strtol(tmp, NULL, 16); + + final_sha_bin[i] = LWIP_PLATFORM_HTONS(val); + + wrt_ptr += SHA256_CONV_STEP_SIZE; + } +} + +OTA_err ota_update(ota_info *ota_info_par) +{ + Http_client_info http_inf; + rboot_config rboot_config; + HTTP_Client_State err; + int slot; + + ota_inf = ota_info_par; + + // Malloc memory for work + http_inf.buffer = malloc(SECTOR_BUFFER_SIZE); + http_inf.buffer_size = SECTOR_BUFFER_SIZE; + http_inf.server = ota_inf->server; + http_inf.port = ota_inf->port; + + // Check memory alignement, must be aligned + if ((unsigned int) http_inf.buffer % sizeof(unsigned int)) { + DEBUG_PRINT("Malloc return Unaligned memory"); + free(http_inf.buffer); + } + + if (ota_inf->sha256_path != NULL) { + sha256_ctx = malloc(sizeof(mbedtls_sha256_context)); + SHA256_output = malloc(SHA256_SIZE_BIN); + SHA256_dowload = malloc(SHA256_SIZE_BIN); + SHA256_str = malloc(SHA256_SIZE_STR + 1); + SHA256_wrt_ptr = SHA256_str; + SHA256_str[SHA256_SIZE_STR] = '\0'; + mbedtls_sha256_init(sha256_ctx); + } + + DEBUG_PRINT("HTTP client task starting"); + + rboot_config = rboot_get_config(); + slot = (rboot_config.current_rom + 1) % rboot_config.count; + + DEBUG_PRINT("Image will be saved in OTA slot %d", slot); + if (slot == rboot_config.current_rom) { + DEBUG_PRINT("Only one OTA slot is configured!"); + err = OTA_ONE_SLOT_ONLY; + goto dealloc_all; + } + + /* Validate the OTA slot parameter */ + if (rboot_config.current_rom == slot || rboot_config.count <= slot) + DEBUG_PRINT("Current rom set to unknow value:%d", rboot_config.current_rom); + + // Calculate room limits + flash_offset = rboot_config.roms[slot]; + flash_limits = flash_offset + MAX_IMAGE_SIZE; + + if (ota_inf->sha256_path != NULL) { + // Setup for dowload sha256 + http_inf.path = ota_inf->sha256_path; + http_inf.final_cb = SHA256_check_callback; + http_inf.buffer_full_cb = SHA256_check_callback; + + memset(SHA256_dowload, 0, SHA256_SIZE_BIN); + memset(SHA256_str, 0, SHA256_SIZE_STR); + + err = HttpClient_dowload(&http_inf); + + // Check if dowload success + if (err != HTTP_OK) + goto dealloc_all; + + convert_SHA256_Str_TO_uint32(SHA256_str, SHA256_dowload); + } + + // Ping Wdog + vTaskDelayMs(250); + + // Dowload Firmaware + http_inf.path = ota_inf->binary_path; + http_inf.final_cb = ota_firmaware_dowload_callback; + http_inf.buffer_full_cb = ota_firmaware_dowload_callback; + if (ota_inf->sha256_path != NULL) + mbedtls_sha256_starts(sha256_ctx, 0); // Start SHA256, not SHA224 + + err = HttpClient_dowload(&http_inf); + + if (err != HTTP_OK) + goto dealloc_all; + + if (ota_inf->sha256_path != NULL) { + char com_res; + mbedtls_sha256_finish(sha256_ctx, SHA256_output); + mbedtls_sha256_free(sha256_ctx); + + com_res = !memcmp((void *) SHA256_output, (void *) SHA256_dowload, SHA256_SIZE_BIN); + if (!com_res) { + DEBUG_PRINT("SHA256 is not equal"); + err = HTTP_SHA_DONT_MATCH; + goto dealloc_all; + } + } + // Ping watch DOG + vTaskDelayMs(500); + { + #define MESSAGE_MAX 120 + unsigned int Rboot_verified, boot_dimension; + char error_message[MESSAGE_MAX]; + + memset(error_message, 0, sizeof(error_message)); + // Start verify + Rboot_verified = rboot_verify_image(rboot_config.roms[slot], &boot_dimension, (const char **) &error_message); + if (Rboot_verified) { + // Rom OK, call final callback for let inform user that all is ready for switch and reset. + + vPortEnterCritical(); + if (!rboot_set_current_rom(slot)) { + vPortExitCritical(); + err = OTA_FAIL_SET_NEW_SLOT; + goto dealloc_all; + } + vPortExitCritical(); + + // Update success, software return HTTP_200 + err = OTA_UPDATE_DONE; + goto dealloc_all; + } else { + DEBUG_PRINT("%s", error_message); + err = OTA_IMAGE_VERIFY_FALLIED; + goto dealloc_all; + } + } + +dealloc_all: + free(http_inf.buffer); + + if (ota_inf->sha256_path != NULL) { + free(sha256_ctx); + free(SHA256_str); + free(SHA256_output); + free(SHA256_dowload); + } + return err; +} /* ota_update */ diff --git a/extras/http_client_ota/http_client_ota.h b/extras/http_client_ota/http_client_ota.h new file mode 100644 index 0000000..56c7ea0 --- /dev/null +++ b/extras/http_client_ota/http_client_ota.h @@ -0,0 +1,56 @@ +#ifndef HTTP_OTA_H +#define HTTP_OTA_H + +/** + * @file ota_update.h + * @author Andrea Greco + * @brief File containing struct and function ota update + * File containing struct and function ota update. + * Ota use remote HTTP server in internet, for dowload Firmaware and sha256 file. + * Firmaware is compiled file stripped, contained in folder firmaware. + * Sha256 is a file that contains a sha256, of Firmaware. + * If enabled 256 is checked during firmaware download. + */ +#include "http_buffered_client.h" + +typedef enum { + // Keep this aligned with \ref HTTP_Client_State + OTA_DNS_LOOKUP_FALLIED = HTTP_DNS_LOOKUP_FALLIED,/**< DNS lookup has fallied */ + OTA_SOCKET_ALLOCATION_FALLIED = HTTP_SOCKET_ALLOCATION_FALLIED,/**< Impossible allocate required socket */ + OTA_SOCKET_CONNECTION_FALLIED = HTTP_SOCKET_CONNECTION_FALLIED,/**< Server unreachable, impossible connect */ + OTA_SHA_DONT_MATCH = HTTP_SHA_DONT_MATCH,/** Sha256 sum does not fit downloaded sha256 */ + OTA_REQUEST_SEND_FALLIED = HTTP_REQUEST_SEND_FALLIED,/**< Impossible send HTTP request */ + OTA_DOWLOAD_SIZE_NOT_MATCH = HTTP_DOWLOAD_SIZE_NOT_MATCH, /**< Dowload size don't match with server declared size */ + + // Ota error + OTA_ONE_SLOT_ONLY = 20,/**< rboot has only one slot configured, impossible switch it */ + OTA_FAIL_SET_NEW_SLOT = 21,/**< rboot cannot switch between rom */ + OTA_IMAGE_VERIFY_FALLIED = 22,/**< Dowloaded image binary checsum is fallied */ + OTA_UPDATE_DONE = 23, /**< Ota has completed upgrade process, all ready for system software reset */ + + OTA_HTTP_OK = 200,/** HTTP server has response 200, Ok */ + OTA_HTTP_NOTFOUND = 404,/** HTTP server has response 404, file not found */ +} OTA_err; + + +/** + * \brief Create ota info. + * Struct that contains all info for start ota. + */ +typedef struct { + char *server; /**< Server domain */ + char *port; /**< Server port */ + char *binary_path; /**< Server Path dowload new update binary */ + char *sha256_path; /**< Server Path of SHA256 sum for check binary, could be NULL, check will be skipped */ +} ota_info; + +/** + * \brief Start ota update. + * Start Ota update. + * Ota_info contains all information about file to download, port, server. + * \param ota_info_par ptr to ota info. + * In case of success, and ota update is right done, \ref finish_cb is called before ESP8266 reset. + * \return http server return Code, check out \ref HTTP_Client_State for all code. + */ +OTA_err ota_update(ota_info *ota_info_par); +#endif // ifndef HTTP_OTA_H