/** ****************************************************************************** * @file ota_server.c * @author QQ ding * @version V1.0.0 * @date 19-Oct-2016 * @brief Create a OTA server thread, download update bin file. Reboot system * if download success. ****************************************************************************** * * The MIT License * Copyright (c) 2016 MXCHIP Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is furnished * to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************** */ #include "mico.h" #include "HTTPUtils.h" #include "SocketUtils.h" #include "ota_server.h" #include "url.h" #if OTA_DEBUG #define ota_server_log(M, ...) custom_log("OTA", M, ##__VA_ARGS__) #else #define ota_server_log(M, ...) #endif static ota_server_context_t *ota_server_context = NULL; static HTTPHeader_t *httpHeader = NULL; static CRC16_Context crc_context; static md5_context md5; static uint32_t offset = 0; static OSStatus onReceivedData(struct _HTTPHeader_t * httpHeader, uint32_t pos, uint8_t *data, size_t len, void * userContext); static void hex2str(char *hex, int hex_len, char *str) { int i = 0; for(i=0; i= 'A') && (*(str+i) <= 'Z')){ *(str+i) += 32; } } } static int ota_server_send(char *data, int datalen) { int res = 0; if(ota_server_context->download_url.HTTP_SECURITY == HTTP_SECURITY_HTTP){ res = send(ota_server_context->download_url.ota_fd, data, datalen, 0); } #if OTA_USE_HTTPS if(ota_server_context->download_url.HTTP_SECURITY == HTTP_SECURITY_HTTPS){ res = ssl_send(ota_server_context->download_url.ota_ssl, data, datalen); } #endif return res; } static OSStatus ota_server_connect(struct sockaddr_in *addr, socklen_t addrlen) { OSStatus err = kNoErr; #if OTA_USE_HTTPS int ssl_errno = 0; #endif err = connect(ota_server_context->download_url.ota_fd, (struct sockaddr *)addr, addrlen); require_noerr_string(err, exit, "ERROR: connect ota server failed"); #if OTA_USE_HTTPS if(ota_server_context->download_url.HTTP_SECURITY == HTTP_SECURITY_HTTPS){ ota_server_context->download_url.ota_ssl = ssl_connect(ota_server_context->download_url.ota_fd, 0, NULL, &ssl_errno); require_action_string(ota_server_context->download_url.ota_ssl != NULL, exit, err = kConnectionErr,"ERROR: ssl disconnect"); } #endif exit: return err; } static int ota_server_read_header(HTTPHeader_t *httpHeader) { int res = 0; if(ota_server_context->download_url.HTTP_SECURITY == HTTP_SECURITY_HTTP){ res = SocketReadHTTPHeader(ota_server_context->download_url.ota_fd, httpHeader); } if(ota_server_context->download_url.HTTP_SECURITY == HTTP_SECURITY_HTTPS){ #if OTA_USE_HTTPS res = SocketReadHTTPSHeader(ota_server_context->download_url.ota_ssl, httpHeader); #endif } return res; } static int ota_server_read_body(HTTPHeader_t *httpHeader) { int res = 0; if(ota_server_context->download_url.HTTP_SECURITY == HTTP_SECURITY_HTTP){ res = SocketReadHTTPBody(ota_server_context->download_url.ota_fd, httpHeader); } if(ota_server_context->download_url.HTTP_SECURITY == HTTP_SECURITY_HTTPS){ #if OTA_USE_HTTPS res = SocketReadHTTPSBody(ota_server_context->download_url.ota_ssl, httpHeader); #endif } return res; } static int ota_server_send_header(void) { char *header = NULL; int j = 0; int ret = 0; header = malloc(OTA_SEND_HEAD_SIZE); memset(header, 0x00, OTA_SEND_HEAD_SIZE); j = sprintf(header, "GET "); j += sprintf(header + j, "/%s HTTP/1.1\r\n", ota_server_context->download_url.url); if (ota_server_context->download_url.port == 0) { j += sprintf(header + j, "Host: %s\r\n", ota_server_context->download_url.host); } else { j += sprintf(header + j, "Host: %s:%d\r\n", ota_server_context->download_url.host, ota_server_context->download_url.port); } j += sprintf(header + j, "Connection: close\r\n"); //Keep-Alive close //Range: bytes=start-end if (ota_server_context->download_state.download_begin_pos > 0) { if (ota_server_context->download_state.download_end_pos > 0) { j += sprintf(header + j, "Range: bytes=%d-%d\r\n", ota_server_context->download_state.download_begin_pos, ota_server_context->download_state.download_end_pos); } else { j += sprintf(header + j, "Range: bytes=%d-\r\n", ota_server_context->download_state.download_begin_pos); } } j += sprintf(header + j, "\r\n"); ret = ota_server_send((char *) header, strlen(header)); // ota_server_log("send: %d\r\n%s", strlen(header), header); if (header != NULL) free(header); return ret; } static void ota_server_socket_close(void) { #if OTA_USE_HTTPS if (ota_server_context->download_url.ota_ssl) ssl_close(ota_server_context->download_url.ota_ssl); #endif SocketClose(&(ota_server_context->download_url.ota_fd)); ota_server_context->download_url.ota_fd = -1; } static int ota_server_connect_server(struct in_addr in_addr) { int err = 0; struct sockaddr_in server_address; if (ota_server_context->download_url.port == 0) { if (ota_server_context->download_url.HTTP_SECURITY == HTTP_SECURITY_HTTP) { server_address.sin_port = htons(80); } else { server_address.sin_port = htons(443); } } else { server_address.sin_port = htons(ota_server_context->download_url.port); } server_address.sin_family = AF_INET; server_address.sin_addr = in_addr; err = ota_server_connect(&server_address, sizeof(server_address)); if (err != 0) { mico_thread_sleep(1); return -1; } ota_server_log("ota server connected!"); return 0; } static void ota_server_progress_set(OTA_STATE_E state) { float progress = 0.00; progress =(float) ota_server_context->download_state.download_begin_pos / ota_server_context->download_state.download_len; progress = progress*100; if(ota_server_context->ota_server_cb != NULL) ota_server_context->ota_server_cb(state, progress); } static void ota_server_thread(mico_thread_arg_t arg) { OSStatus err; uint16_t crc16 = 0; char md5_value[16] = {0}; char md5_value_string[33] = {0}; fd_set readfds; struct hostent* hostent_content = NULL; char **pptr = NULL; struct in_addr in_addr; mico_logic_partition_t* ota_partition = MicoFlashGetInfo(MICO_PARTITION_OTA_TEMP); ota_server_context->ota_control = OTA_CONTROL_START; hostent_content = gethostbyname(ota_server_context->download_url.host); require_action_quiet(hostent_content != NULL, DELETE, ota_server_progress_set(OTA_FAIL)); pptr=hostent_content->h_addr_list; in_addr.s_addr = *(uint32_t *)(*pptr); strcpy(ota_server_context->download_url.ip, inet_ntoa(in_addr)); ota_server_log("OTA server address: %s, host ip: %s", ota_server_context->download_url.host, ota_server_context->download_url.ip); offset = 0; MicoFlashErase(MICO_PARTITION_OTA_TEMP, 0x0, ota_partition->partition_length); CRC16_Init(&crc_context); if(ota_server_context->ota_check.is_md5 == true){ InitMd5(&md5); } httpHeader = HTTPHeaderCreateWithCallback(1024, onReceivedData, NULL, NULL); require_action(httpHeader, DELETE, ota_server_progress_set(OTA_FAIL)); while (1) { if (ota_server_context->ota_control == OTA_CONTROL_PAUSE){ mico_thread_sleep(1); continue; }else if(ota_server_context->ota_control == OTA_CONTROL_STOP){ goto DELETE; } ota_server_context->download_url.ota_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); err = ota_server_connect_server(in_addr); require_noerr_action(err, RECONNECTED, ota_server_progress_set(OTA_FAIL)); /* Send HTTP Request */ ota_server_send_header(); FD_ZERO(&readfds); FD_SET(ota_server_context->download_url.ota_fd, &readfds); select(ota_server_context->download_url.ota_fd + 1, &readfds, NULL, NULL, NULL); if (FD_ISSET(ota_server_context->download_url.ota_fd, &readfds)) { /*parse header*/ err = ota_server_read_header(httpHeader); if (ota_server_context->ota_control == OTA_CONTROL_START) { ota_server_context->download_state.download_len = httpHeader->contentLength; ota_server_context->ota_control = OTA_CONTROL_CONTINUE; } switch (err) { case kNoErr: #if OTA_DEBUG PrintHTTPHeader(httpHeader); #endif err = ota_server_read_body(httpHeader);/*get body data*/ require_noerr(err, RECONNECTED); /*get data and print*/ break; case EWOULDBLOCK: case kNoSpaceErr: case kConnectionErr: default: ota_server_log("ERROR: HTTP Header parse error: %d", err); break; } } if (ota_server_context->download_state.download_len == ota_server_context->download_state.download_begin_pos) { if(httpHeader->statusCode != 200){ ota_server_progress_set(OTA_FAIL); goto DELETE; } CRC16_Final(&crc_context, &crc16); if(ota_server_context->ota_check.is_md5 == true){ Md5Final(&md5, (unsigned char *) md5_value); hex2str((char *)md5_value, 16, md5_value_string); } if (memcmp(md5_value_string, ota_server_context->ota_check.md5, OTA_MD5_LENTH) == 0){ ota_server_progress_set(OTA_SUCCE); mico_ota_switch_to_new_fw(ota_server_context->download_state.download_len, crc16); mico_system_power_perform(mico_system_context_get(), eState_Software_Reset); }else{ ota_server_log("OTA md5 check err, Calculation:%s, Get:%s", md5_value_string, ota_server_context->ota_check.md5); ota_server_progress_set(OTA_FAIL); } goto DELETE; } RECONNECTED: ota_server_socket_close(); mico_thread_sleep(2); continue; } DELETE: HTTPHeaderDestory(&httpHeader); ota_server_socket_close(); if(ota_server_context != NULL){ if(ota_server_context->download_url.url != NULL){ free(ota_server_context->download_url.url); ota_server_context->download_url.url = NULL; } free(ota_server_context); ota_server_context = NULL; } ota_server_log("ota server thread will delete"); mico_rtos_delete_thread(NULL); } /*one request may receive multi reply*/ static OSStatus onReceivedData(struct _HTTPHeader_t * inHeader, uint32_t inPos, uint8_t * inData, size_t inLen, void * inUserContext) { OSStatus err = kNoErr; if (inLen == 0) return err; ota_server_context->download_state.download_begin_pos += inLen; CRC16_Update(&crc_context, inData, inLen); if(ota_server_context->ota_check.is_md5 == true){ Md5Update(&md5, inData, inLen); } MicoFlashWrite(MICO_PARTITION_OTA_TEMP, &offset, inData, inLen); ota_server_progress_set(OTA_LOADING); if(ota_server_context->ota_control == OTA_CONTROL_PAUSE){ while(1){ if(ota_server_context->ota_control != OTA_CONTROL_PAUSE) break; mico_thread_msleep(100); } } if(ota_server_context->ota_control == OTA_CONTROL_STOP){ err = kUnsupportedErr; } return err; } static OSStatus ota_server_set_url(char *url) { OSStatus err = kNoErr; url_field_t *url_t; char *pos = NULL; url_t = url_parse(url); require_action(url, exit, err = kParamErr); #if OTA_DEBUG url_field_print(url_t); #endif if (!strcmp(url_t->schema, "https")) { ota_server_context->download_url.HTTP_SECURITY = HTTP_SECURITY_HTTPS; } else { ota_server_context->download_url.HTTP_SECURITY = HTTP_SECURITY_HTTP; } strcpy(ota_server_context->download_url.host, url_t->host); ota_server_context->download_url.port = atoi(url_t->port); pos = strstr(url, url_t->path); if (pos == NULL) { strcpy(ota_server_context->download_url.url, ""); } else { strcpy(ota_server_context->download_url.url, pos); } exit: url_free(url_t); return err; } OSStatus ota_server_start(char *url, char *md5, ota_server_cb_fn call_back) { OSStatus err = kNoErr; require_action(url, exit, err = kParamErr); if(ota_server_context != NULL){ if(ota_server_context->download_url.url != NULL){ free(ota_server_context->download_url.url); ota_server_context->download_url.url = NULL; } free(ota_server_context); ota_server_context = NULL; } ota_server_context = malloc(sizeof(ota_server_context_t)); require_action(ota_server_context, exit, err = kNoMemoryErr); memset(ota_server_context, 0x00, sizeof(ota_server_context_t)); ota_server_context->download_url.url = malloc(strlen(url)); require_action(ota_server_context->download_url.url, exit, err = kNoMemoryErr); memset(ota_server_context->download_url.url, 0x00, strlen(url)); err = ota_server_set_url(url); require_noerr(err, exit); if(md5 != NULL){ ota_server_context->ota_check.is_md5 = true; memcpy(ota_server_context->ota_check.md5, md5, OTA_MD5_LENTH); upper2lower(ota_server_context->ota_check.md5, OTA_MD5_LENTH); } ota_server_context->ota_server_cb = call_back; err = mico_rtos_create_thread(NULL, MICO_APPLICATION_PRIORITY, "OTA", ota_server_thread, OTA_SERVER_THREAD_STACK_SIZE, 0); exit: return err; } void ota_server_pause(void) { ota_server_context->ota_control = OTA_CONTROL_PAUSE; } void ota_server_continue(void) { ota_server_context->ota_control = OTA_CONTROL_CONTINUE; } void ota_server_stop(void) { ota_server_context->ota_control = OTA_CONTROL_STOP; } OTA_CONTROL_E ota_server_get(void) { return ota_server_context->ota_control; }