/** * @copyright * * Tencent is pleased to support the open source community by making IoT Hub available. * Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved. * * Licensed under the MIT License(the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://opensource.org/licenses/MIT * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. * * @file ota_downloader.c * @brief * @author fancyxu (fancyxu@tencent.com) * @version 1.0 * @date 2021-10-20 * * @par Change Log: * *
Date Version Author Description *
2021-10-20 1.0 fancyxu first commit *
*/ #include "ota_downloader.h" #include "utils_log.h" #include "utils_md5.h" #define OTA_HTTP_TIMEOUT_MS 5000 #define OTA_HTTP_BUF_SIZE 1024 #define MAX_SIZE_OF_DOWNLOAD_URL 512 /** * @brief Break point info. * */ typedef struct { OTAFirmwareInfo file_id; uint32_t downloaded_size; } OTADownloadInfo; /** * @brief OTA downloader handle. * */ typedef struct { void* cos_download; void* mqtt_client; void* downloader; OTAFirmwareInfo download_now; OTADownloadInfo break_point; char download_url[MAX_SIZE_OF_DOWNLOAD_URL]; uint8_t download_buff[OTA_HTTP_BUF_SIZE]; uint32_t download_size; IotMd5Context download_md5_ctx; OTADownloaderStatus status; } OTADownloaderHandle; /** * @brief Handle. * */ static OTADownloaderHandle sg_ota_downloader_handle = {0}; // ---------------------------------------------------------------------------- // Downloader function // ---------------------------------------------------------------------------- // break point function /** * @brief Read break point from file. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_break_point_init(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; sg_ota_downloader_handle.cos_download = NULL; utils_md5_reset(&handle->download_md5_ctx); memset(&handle->break_point, 0, sizeof(handle->break_point)); size_t len = HAL_File_Read(OTA_BREAK_POINT_FILE_PATH, (uint8_t*)&handle->break_point, sizeof(handle->break_point), 0); if (len != sizeof(handle->break_point)) { Log_w("open break point file fail!"); } return 0; } /** * @brief Memset break point. * * @param[in,out] usr_data @see OTADownloaderHandle */ static void _ota_break_point_deinit(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; memset(&handle->break_point, 0, sizeof(handle->break_point)); } /** * @brief Set break point using download now info. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_break_point_set(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; // read break point info from flash memset(&handle->break_point, 0, sizeof(handle->break_point)); memcpy(&handle->break_point.file_id, &handle->download_now, sizeof(handle->break_point.file_id)); return 0; } /** * @brief Update break point and save. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_break_point_save(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; // update md5 sum & download_size handle->break_point.downloaded_size += handle->download_size; utils_md5_update(&handle->download_md5_ctx, handle->download_buff, handle->download_size); // report progress char buf[256]; int buf_len = sizeof(buf); IOT_OTA_ReportProgress(handle->mqtt_client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, handle->break_point.downloaded_size * 100 / handle->download_now.file_size, handle->break_point.file_id.version); // write to local, only write downloaded size is ok size_t write_size = HAL_File_Write(OTA_BREAK_POINT_FILE_PATH, (uint8_t*)&handle->break_point, sizeof(OTADownloadInfo), 0); return write_size != sizeof(OTADownloadInfo); } /** * @brief Check if break point matches download now info. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_break_point_check(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; // version should be the same, download size should not bigger than file size Log_d("download now:%s,%d,%s", handle->download_now.version, handle->download_now.file_size, handle->download_now.md5sum); Log_d("break point:%s,%d,%d,%s", handle->break_point.file_id.version, handle->break_point.file_id.file_size, handle->break_point.downloaded_size, handle->break_point.file_id.md5sum); return strncmp(handle->break_point.file_id.version, handle->download_now.version, MAX_SIZE_OF_FW_VERSION) || handle->break_point.file_id.file_size != handle->download_now.file_size || handle->break_point.downloaded_size > handle->download_now.file_size || strncmp(handle->break_point.file_id.md5sum, handle->download_now.md5sum, 32); } /** * @brief Calculate md5 sum according break point. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_break_point_restore(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; // update md5 according downloaded data size_t rlen, total_read = 0, size = 0; size = handle->break_point.downloaded_size; while (size > 0) { rlen = (size > OTA_HTTP_BUF_SIZE) ? OTA_HTTP_BUF_SIZE : size; if (!HAL_File_Read(OTA_FILE_PATH, handle->download_buff, rlen, total_read)) { Log_e("read data failed"); handle->break_point.downloaded_size = 0; break; } utils_md5_update(&handle->download_md5_ctx, handle->download_buff, rlen); size -= rlen; total_read += rlen; } return 0; } // data download function /** * @brief Init cos downloader. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_data_download_init(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; IotCosDownloadParams params = { .url = handle->download_url, .offset = handle->break_point.downloaded_size, .file_size = handle->break_point.file_id.file_size, .is_fragmentation = false, .is_https_enabled = false, }; handle->cos_download = IOT_COS_DownloadInit(¶ms); return handle->cos_download ? 0 : -1; } /** * @brief Deinit cos downloader. * * @param[in,out] usr_data @see OTADownloaderHandle */ static void _ota_data_download_deinit(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; IOT_COS_DownloadDeinit(handle->cos_download); } /** * @brief Check if download finished. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_data_download_is_over(void* usr_data) { // check download is over OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; return handle->break_point.downloaded_size == handle->download_now.file_size; } /** * @brief Download from cos server. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_data_download_recv(void* usr_data) { // download data using http OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; // TODO: https download handle->download_size = IOT_COS_DownloadFetch(handle->cos_download, handle->download_buff, OTA_HTTP_BUF_SIZE, OTA_HTTP_TIMEOUT_MS); return handle->download_size; } /** * @brief Sava firmware to file. * * @param[in,out] usr_data @see OTADownloaderHandle * @return 0 for success */ static int _ota_data_download_save(void* usr_data) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; return handle->download_size > 0 ? HAL_File_Write(OTA_FILE_PATH, handle->download_buff, handle->download_size, handle->break_point.downloaded_size) != handle->download_size : 0; } /** * @brief Process download result. * * @param[in,out] usr_data @see OTADownloaderHandle * @param[in] status status when finish download, @see UtilsDownloaderStatus * @return 0 for success */ static int _ota_data_download_finish(void* usr_data, UtilsDownloaderStatus status) { OTADownloaderHandle* handle = (OTADownloaderHandle*)usr_data; int rc = 0; char buf[256]; int buf_len = sizeof(buf); switch (status) { case UTILS_DOWNLOADER_STATUS_SUCCESS: utils_md5_finish(&handle->download_md5_ctx); // do something when ota finish; int valid = !utils_md5_compare(&handle->download_md5_ctx, handle->download_now.md5sum); if (!valid) { memset(&handle->break_point, 0, sizeof(handle->break_point)); HAL_File_Write(OTA_BREAK_POINT_FILE_PATH, (uint8_t*)&handle->break_point, sizeof(handle->break_point), 0); } rc = valid ? IOT_OTA_ReportProgress(handle->mqtt_client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS, 0, handle->download_now.version) : IOT_OTA_ReportProgress(handle->mqtt_client, buf, buf_len, IOT_OTA_REPORT_TYPE_MD5_NOT_MATCH, 0, handle->download_now.version); break; case UTILS_DOWNLOADER_STATUS_NETWORK_FAILED: rc = IOT_OTA_ReportProgress(handle->mqtt_client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOAD_TIMEOUT, 0, handle->download_now.version); break; case UTILS_DOWNLOADER_STATUS_BREAK_POINT_FAILED: case UTILS_DOWNLOADER_STATUS_DATA_DOWNLOAD_FAILED: rc = IOT_OTA_ReportProgress(handle->mqtt_client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_FAIL, 0, handle->download_now.version); break; default: break; } handle->status = OTA_DOWNLOADER_STATUS_FINISHED; return rc; } // ---------------------------------------------------------------------------- // API // ---------------------------------------------------------------------------- /** * @brief Init ota downloader. * * @param[in,out] client pointer to mqtt client * @return 0 for success. */ int ota_downloader_init(void* client) { // downloader init UtilsDownloaderFunction ota_callback = { .downloader_malloc = HAL_Malloc, .downloader_free = HAL_Free, // break point .break_point_init = _ota_break_point_init, .break_point_deinit = _ota_break_point_deinit, .break_point_set = _ota_break_point_set, .break_point_save = _ota_break_point_save, .break_point_check = _ota_break_point_check, .break_point_restore = _ota_break_point_restore, // data download .data_download_init = _ota_data_download_init, .data_download_deinit = _ota_data_download_deinit, .data_download_is_over = _ota_data_download_is_over, .data_download_recv = _ota_data_download_recv, .data_download_save = _ota_data_download_save, .data_download_finish = _ota_data_download_finish, }; sg_ota_downloader_handle.downloader = utils_downloader_init(ota_callback, &sg_ota_downloader_handle); if (!sg_ota_downloader_handle.downloader) { Log_e("initialize downloaded failed"); return -1; } sg_ota_downloader_handle.mqtt_client = client; sg_ota_downloader_handle.status = OTA_DOWNLOADER_STATUS_INITTED; return 0; } /** * @brief Set download info of ota firmware. * * @param[in] firmware_info pointer to firmware info * @param[in] url url of cos download * @param[in] url_len download length */ void ota_downloader_info_set(OTAFirmwareInfo* firmware_info, const char* url, int url_len) { if (OTA_DOWNLOADER_STATUS_DOWNLOADING != sg_ota_downloader_handle.status) { memcpy(&sg_ota_downloader_handle.download_now, firmware_info, sizeof(OTAFirmwareInfo)); strncpy(sg_ota_downloader_handle.download_url, url, url_len); sg_ota_downloader_handle.download_url[url_len] = '\0'; sg_ota_downloader_handle.status = OTA_DOWNLOADER_STATUS_DOWNLOADING; } } /** * @brief Process ota download. * * @return @see OTADownloaderStatus */ OTADownloaderStatus ota_downloader_process(void) { if (OTA_DOWNLOADER_STATUS_DOWNLOADING == sg_ota_downloader_handle.status) { utils_downloader_process(sg_ota_downloader_handle.downloader); } return sg_ota_downloader_handle.status; } /** * @brief Deinit ota downloader. * */ void ota_downloader_deinit(void) { utils_downloader_deinit(sg_ota_downloader_handle.downloader); memset(&sg_ota_downloader_handle, 0, sizeof(sg_ota_downloader_handle)); }