/* * Copyright (C) 2019-2020 Alibaba Group Holding Limited */ #include #if CONFIG_FOTA_USE_HTTPC == 1 #include #include #include #include #include #include "util/network.h" #define TAG "fota-httpc" typedef struct { http_client_handle_t http_client; const char *cert; const char *path; } httpc_priv_t; static int _http_event_handler(http_client_event_t *evt) { switch(evt->event_id) { case HTTP_EVENT_ERROR: LOGD(TAG, "HTTP_EVENT_ERROR"); break; case HTTP_EVENT_ON_CONNECTED: LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); break; case HTTP_EVENT_HEADER_SENT: LOGD(TAG, "HTTP_EVENT_HEADER_SENT"); break; case HTTP_EVENT_ON_HEADER: // LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value); break; case HTTP_EVENT_ON_DATA: // LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); break; case HTTP_EVENT_ON_FINISH: LOGD(TAG, "HTTP_EVENT_ON_FINISH"); break; case HTTP_EVENT_DISCONNECTED: LOGD(TAG, "HTTP_EVENT_DISCONNECTED"); break; } return 0; } static bool process_again(int status_code) { switch (status_code) { case HttpStatus_MovedPermanently: case HttpStatus_Found: case HttpStatus_TemporaryRedirect: case HttpStatus_Unauthorized: return true; default: return false; } return false; } static http_errors_t _http_handle_response_code(http_client_handle_t http_client, int status_code, char *buffer, int buf_size, int data_size) { http_errors_t err; if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found || status_code == HttpStatus_TemporaryRedirect) { err = http_client_set_redirection(http_client); if (err != HTTP_CLI_OK) { LOGE(TAG, "URL redirection Failed"); return err; } } else if (status_code == HttpStatus_Unauthorized) { return HTTP_CLI_FAIL; } else if(status_code == HttpStatus_NotFound || status_code == HttpStatus_Forbidden) { LOGE(TAG, "File not found(%d)", status_code); return HTTP_CLI_FAIL; } else if (status_code == HttpStatus_InternalError) { LOGE(TAG, "Server error occurred(%d)", status_code); return HTTP_CLI_FAIL; } // process_again() returns true only in case of redirection. if (data_size > 0 && process_again(status_code)) { /* * In case of redirection, http_client_read() is called * to clear the response buffer of http_client. */ int data_read; while (data_size > buf_size) { data_read = http_client_read(http_client, buffer, buf_size); if (data_read <= 0) { return HTTP_CLI_OK; } data_size -= buf_size; } data_read = http_client_read(http_client, buffer, data_size); if (data_read <= 0) { return HTTP_CLI_OK; } } return HTTP_CLI_OK; } static http_errors_t _http_connect(http_client_handle_t http_client, char *buffer, int buf_size) { #define MAX_REDIRECTION_COUNT 10 http_errors_t err = HTTP_CLI_FAIL; int status_code = 0, header_ret; int redirect_counter = 0; do { if (redirect_counter++ > MAX_REDIRECTION_COUNT) { LOGE(TAG, "redirect_counter is max"); return HTTP_CLI_FAIL; } if (process_again(status_code)) { LOGD(TAG, "process again,status code:%d", status_code); } err = http_client_open(http_client, 0); if (err != HTTP_CLI_OK) { LOGE(TAG, "Failed to open HTTP connection"); return err; } header_ret = http_client_fetch_headers(http_client); if (header_ret < 0) { LOGE(TAG, "header_ret:%d", header_ret); return header_ret; } LOGD(TAG, "header_ret:%d", header_ret); status_code = http_client_get_status_code(http_client); LOGD(TAG, "status code:%d", status_code); err = _http_handle_response_code(http_client, status_code, buffer, buf_size, header_ret); if (err != HTTP_CLI_OK) { LOGE(TAG, "e handle resp code:%d", err); return err; } } while (process_again(status_code)); return err; } static void _http_cleanup(http_client_handle_t client) { LOGD(TAG, "httpc cleanup..."); if (client) { http_client_cleanup(client); } } static int http_read(netio_t *io, uint8_t *buffer, int length, int timeoutms) { #define RANGE_BUF_SIZE 56 int read_len; long long time1ms; httpc_priv_t *priv = (httpc_priv_t *)io->private; http_client_handle_t client = priv->http_client; if (client == NULL) { int ret = 0; int statuscode; char *range = NULL; char *buffer = NULL; http_errors_t err; http_client_config_t config = {0}; config.method = HTTP_METHOD_GET; config.url = priv->path; config.timeout_ms = timeoutms; config.buffer_size = BUFFER_SIZE; config.cert_pem = priv->cert; config.event_handler = _http_event_handler; client = http_client_init(&config); if (!client) { LOGE(TAG, "Client init e"); ret = -1; goto exit; } LOGD(TAG, "http client init ok.[%s]", config.url); LOGD(TAG, "http read connecting........"); buffer = aos_zalloc(BUFFER_SIZE + 1); if (!buffer) { LOGE(TAG, "http open nomem."); ret = -ENOMEM; goto exit; } range = (void *)aos_zalloc(RANGE_BUF_SIZE); if (!range) { LOGE(TAG, "range malloc e"); ret = -ENOMEM; goto exit; } snprintf(range, RANGE_BUF_SIZE, "bytes=%d-", io->offset); LOGD(TAG, "range:%s", range); err = http_client_set_header(client, "Range", range); if (err != HTTP_CLI_OK) { LOGE(TAG, "set header Range e"); ret = -1; goto exit; } err = http_client_set_header(client, "Connection", "keep-alive"); if (err != HTTP_CLI_OK) { LOGE(TAG, "set header Connection e"); ret = -1; goto exit; } err = http_client_set_header(client, "Cache-Control", "no-cache"); if (err != HTTP_CLI_OK) { LOGE(TAG, "set header Cache-Control e"); ret = -1; goto exit; } err = _http_connect(client, buffer, BUFFER_SIZE); if (err != HTTP_CLI_OK) { LOGE(TAG, "Client connect e"); ret = -1; goto exit; } statuscode = http_client_get_status_code(client); if (statuscode != 206) { LOGE(TAG, "not 206 Partial Content"); ret = -1; goto exit; } io->size = http_client_get_content_length(client); io->size += io->offset; LOGD(TAG, "range_len: %d", io->size); priv->http_client = client; exit: if (buffer) aos_free(buffer); if (range) aos_free(range); if (ret != 0) { _http_cleanup(client); return ret; } } if (io->offset >= io->size) { LOGW(TAG, "http_read done: offset:%d tsize:%d", io->offset, io->size); return 0; } time1ms = aos_now_ms(); read_len = 0; while (read_len < length) { if (aos_now_ms() - time1ms > timeoutms) { break; } int data_read = http_client_read(client, (char *)buffer + read_len, length - read_len); if (data_read < 0) { LOGW(TAG, "http client read error:%d, errno:%d", data_read, errno); return data_read; } else if (data_read == 0) { LOGD(TAG, "http client read 0 size"); break; } read_len += data_read; } io->offset += read_len; return read_len; } static int http_open(netio_t *io, const char *path) { const char *cert; LOGD(TAG, "http open:%s", path); cert = (const char *)io->cls->private; io->size = 0; io->offset = 0; io->block_size = CONFIG_FOTA_BUFFER_SIZE; httpc_priv_t *priv = aos_zalloc_check(sizeof(httpc_priv_t)); if (!priv) { LOGE(TAG, "http open nomem e"); return -1; } priv->http_client = NULL; priv->path = path; priv->cert = cert; io->private = priv; #if 0 // just for test, mem leak... char *tempbuffer = (char *)aos_malloc(400); if (tempbuffer == NULL) { LOGE(TAG, "malloc tempbuffer failed"); return -1; } memset(tempbuffer, 0, 400); if (strstr(priv->path, "https://")) { snprintf(tempbuffer, 400, "http://%s", priv->path + strlen("https://")); } priv->path = tempbuffer; #endif #if 0 //just for test redirection priv->path = "http://192.168.1.102:8889"; #endif return 0; } static int http_seek(netio_t *io, size_t offset, int whence) { io->offset = offset; return 0; } static int http_close(netio_t *io) { httpc_priv_t *priv = (httpc_priv_t *)io->private; http_client_handle_t client = priv->http_client; _http_cleanup(client); aos_free(priv); return 0; } netio_cls_t httpc_cls = { .name = "http", .read = http_read, .seek = http_seek, .open = http_open, .close = http_close, }; int netio_register_httpc(const char *cert) { httpc_cls.private = (void *)cert; return netio_register(&httpc_cls); } #endif