Http client OTA (#553)

This commit is contained in:
Andrea Greco 2018-03-01 01:03:44 +01:00 committed by Ruslan V. Uss
parent 33082ba2c9
commit b77380bad1
9 changed files with 712 additions and 0 deletions

View file

@ -0,0 +1,4 @@
PROGRAM=http_ota
EXTRA_COMPONENTS=extras/rboot-ota extras/mbedtls extras/http_client_ota
include ../../common.mk

View file

@ -0,0 +1,134 @@
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "http_client_ota.h"
#include "ssid_config.h"
#define vTaskDelayMs(ms) vTaskDelay((ms) / portTICK_PERIOD_MS)
/*
* How to test
* cd test_file
* python -m SimpleHTTPServer 8080
* fill missing define SERVER and PORT, in your private_ssid_config.h
* Ready for test.
*/
#define BINARY_PATH "/blink.bin"
#define SHA256_PATH "/blink.sha256"
// Default
#define SERVER "192.168.1.30"
#define PORT "8080"
#ifndef SERVER
#error "Server address is not defined define it:`192.168.X.X`"
#endif
#ifndef PORT
#error "Port is not defined example:`8080`"
#endif
static inline void ota_error_handling(OTA_err err) {
printf("Error:");
switch(err) {
case OTA_DNS_LOOKUP_FALLIED:
printf("DNS lookup has fallied\n");
break;
case OTA_SOCKET_ALLOCATION_FALLIED:
printf("Impossible allocate required socket\n");
break;
case OTA_SOCKET_CONNECTION_FALLIED:
printf("Server unreachable, impossible connect\n");
break;
case OTA_SHA_DONT_MATCH:
printf("Sha256 sum does not fit downloaded sha256\n");
break;
case OTA_REQUEST_SEND_FALLIED:
printf("Impossible send HTTP request\n");
break;
case OTA_DOWLOAD_SIZE_NOT_MATCH:
printf("Dowload size don't match with server declared size\n");
break;
case OTA_ONE_SLOT_ONLY:
printf("rboot has only one slot configured, impossible switch it\n");
break;
case OTA_FAIL_SET_NEW_SLOT:
printf("rboot cannot switch between rom\n");
break;
case OTA_IMAGE_VERIFY_FALLIED:
printf("Dowloaded image binary checsum is fallied\n");
break;
case OTA_UPDATE_DONE:
printf("Ota has completed upgrade process, all ready for system software reset\n");
break;
case OTA_HTTP_OK:
printf("HTTP server has response 200, Ok\n");
break;
case OTA_HTTP_NOTFOUND:
printf("HTTP server has response 404, file not found\n");
break;
}
}
static void ota_task(void *PvParameter)
{
// Wait until we have joined AP and are assigned an IP *
while (sdk_wifi_station_get_connect_status() != STATION_GOT_IP)
vTaskDelayMs(100);
while (1) {
OTA_err err;
// Remake this task until ota work
err = ota_update((ota_info *) PvParameter);
ota_error_handling(err);
if(err != OTA_UPDATE_DONE) {
vTaskDelayMs(1000);
printf("\n\n\n");
continue;
}
vTaskDelayMs(1000);
printf("Delay 1\n");
vTaskDelayMs(1000);
printf("Delay 2\n");
vTaskDelayMs(1000);
printf("Delay 3\n");
printf("Reset\n");
sdk_system_restart();
}
}
static ota_info info = {
.server = SERVER,
.port = PORT,
.binary_path = BINARY_PATH,
.sha256_path = SHA256_PATH,
};
void user_init(void)
{
uart_set_baud(0, 115200);
printf("SDK version:%s\n", sdk_system_get_sdk_version());
struct sdk_station_config config = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
};
/* required to call wifi_set_opmode before station_set_config */
sdk_wifi_set_opmode(STATION_MODE);
sdk_wifi_station_set_config(&config);
xTaskCreate(ota_task, "get_task", 4096, &info, 2, NULL);
}

Binary file not shown.

View file

@ -0,0 +1 @@
57dda900027355de85f0de9e6c966e3c4c16741d8eed134d209c0fb6304cf852

View file

@ -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))

View file

@ -0,0 +1,219 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#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 */

View file

@ -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

View file

@ -0,0 +1,259 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#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 <espressif/spi_flash.h>
#include <espressif/esp_system.h>
#include <espressif/esp_common.h>
#include <espressif/esp_system.h>
#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 */

View file

@ -0,0 +1,56 @@
#ifndef HTTP_OTA_H
#define HTTP_OTA_H
/**
* @file ota_update.h
* @author Andrea Greco <a.greco@4sigma.it>
* @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