/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include <stdlib.h>
#include <stdio.h>

#include "iotcs.h"
#include "iotcs/iotcs_private.h"
#include "iotcs_port_system.h"
#include "protocol/protocol_request.h"
#include "util/util.h"
#include "util/util_memory.h"
#include "util/util_thread_private.h"
#include "trusted_assets_manager/iotcs_tam.h"
#include "iotcs_port_ssl.h"
#include "scs/storage_dispatcher_private.h"
#include "protocol/protocol_request.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_CORE
#include "log/log_template.h"

#define MAX_HOST_LEN 128
#define MAX_API_LEN 128
#define DEFAULT_SCS_PORT 443
#define CHUNK_OVERHEAD 8 // %04X + /r/n + /r/n

#ifndef IOTCS_RESPONSE_BUFFER_LENGTH
#define IOTCS_RESPONSE_BUFFER_LENGTH 2048
#endif

#define EXPECTED_RESPONSE_CODE_COUNT 8

static char g_storage_response_buffer[IOTCS_RESPONSE_BUFFER_LENGTH];
static char g_storage_host[MAX_HOST_LEN];
int g_storage_initialized = IOTCS_FALSE;
static iotcs_bool g_storage_reconfig_required = IOTCS_TRUE;
static int g_storage_port = 0;
static char g_storage_rest_api[MAX_API_LEN];
static char g_storage_auth_token[UTIL_TOKEN_STRING_MAX_LENGTH];
char *g_storage_container_url = NULL;

/*Should be guarded (or called from guarded methods) by UTIL_LOCK_STORAGE/UTIL_UNLOCK_STORAGE */
static iotcs_result scs_parse_auth_response(const char* body) {
    json_tokens_t tokens;
    char* value_str = "";
    int value_len = 0;
    char* tmp = NULL;
    char* next = NULL;
    char* server_scheme = NULL;
    char* server_uri = NULL;
    char* server_name = NULL;
    char* server_port = NULL;
    iotcs_result rv = IOTCS_RESULT_FAIL;

    GOTO_ERR_MSG(json_parse(body, strlen(body), &tokens) != IOTCS_RESULT_OK,
            "Cannot parse SCS auth JSON.");

    GOTO_ERR_MSG(json_get_string_value_by_key(&tokens, "storageContainerUrl", &value_str,
            &value_len) != IOTCS_RESULT_OK, "Cannot find storageContainerUrl in SCS auth JSON");

    GOTO_ERR_OOM(rv, tmp = util_safe_strncpy(value_str, value_len)); /*now storageContainerUrl null terminated*/
    if (!g_storage_container_url)
        GOTO_ERR_OOM(rv, g_storage_container_url = util_safe_strncpy(value_str, value_len));
    LOG_INFO("Storage Container Url %s", g_storage_container_url);
    next = tmp;

    /*
     * parse and check the storageContainerUrl
     * example https://host[:port]/v1/Storage-a210401/container 
     */

    GOTO_ERR_MSG(NULL == (server_scheme = util_get_next_token(&next, '/')),
            "Bad storageContainerUrl format. There is no server scheme.");
    GOTO_ERR_MSG((strcmp("https:", server_scheme) != 0) && (strcmp("http:", server_scheme) != 0),
            "Bad storageContainerUrl format. Unexpected server scheme.");
    /*empty string is expected because of //*/
    GOTO_ERR_MSG(NULL == util_get_next_token(&next, '/'),
            "Bad storageContainerUrl format.");
    /*host[:port]*/
    GOTO_ERR_MSG(NULL == (server_uri = util_get_next_token(&next, '/')),
            "Bad storageContainerUrl format. There is no storage cloud service server value in the uri.");

    /*store storageContainerUrl to global variable before checking because tmp str will be changed*/
    GOTO_ERR_MSG(0 > util_safe_snprintf(g_storage_rest_api, MAX_API_LEN, "%s", next),
            "scs_parse_auth_response method failed: storageContainerUrl size is longer then expected.");

    /*api version part - useless information*/
    GOTO_ERR_MSG(NULL == util_get_next_token(&next, '/'),
            "Bad storageContainerUrl format. There is no api version part in the uri.");
    /*storage service identity*/
    GOTO_ERR_MSG(NULL == util_get_next_token(&next, '/'),
            "Bad storageContainerUrl format. There is no storage service identity in the uri.");
    /*storage service container*/
    GOTO_ERR_MSG(NULL == util_get_next_token(&next, '/'),
            "Bad storageContainerUrl format. There is no storage service container in the uri.");

    /*if we are here then URi is correct*/

    /*parse host name and port*/
    next = server_uri;
    GOTO_ERR_MSG(NULL == (server_name = util_get_next_token(&next, ':')),
            "Bad storageContainerUrl format. There is no server name.");

    /*store scs_host to global variable before checking because tmp str will be changed*/
    GOTO_ERR_MSG(0 > util_safe_snprintf(g_storage_host, MAX_HOST_LEN, "%s", server_name),
            "scs_parse_auth_response method failed: storage server name size is longer then expected.");

    if (NULL != (server_port = util_get_next_token(&next, ':'))) {
        /*server port was stored to server_uri*/
        g_storage_port = atoi(server_port);
    } else {
        g_storage_port = DEFAULT_SCS_PORT;
    }
    LOG_INFO("Storage server %s port %d", g_storage_host, g_storage_port);

    tmp = NULL;
    next = NULL;

    /*take and save authToken*/
    GOTO_ERR_MSG(json_get_string_value_by_key(&tokens, "authToken", &value_str,
            &value_len) != IOTCS_RESULT_OK, "Cannot find authToken in SCS auth JSON");
    GOTO_ERR_OOM(rv, tmp = util_safe_strncpy(value_str, value_len)); /*now authToken null terminated*/

    /*store authToken to global variable*/
    GOTO_ERR_MSG(0 > util_safe_snprintf(g_storage_auth_token, UTIL_TOKEN_STRING_MAX_LENGTH, "%s", tmp),
            "scs_parse_auth_response method failed: authToken size is longer then expected.");

    /*clean up allocated memory*/
    util_free(tmp);
    tmp = NULL;

    return IOTCS_RESULT_OK;

error:
    util_free(tmp);
    return rv;
}

/*Should be guarded (or called from guarded methods) by UTIL_LOCK_STORAGE/UTIL_UNLOCK_STORAGE */

/*Should be guarded (or called from guarded methods) by UTIL_LOCK_LL/UTIL_UNLOCK_LL */
static iotcs_result scs_provision_request_unsafe() {
    iotcs_result result = IOTCS_RESULT_FAIL;
    protocol_response response;

    protocol_request request = {
        .body = "",
        .url = "/iot/api/v2/provisioner/storage",
        .method = PROTOCOL_REQUEST_METHOD_GET,
        .headers =
        {
            .connection = "close",
            .accept = "application/json",
            .host = tam_get_server_host(),
            .x_endpointId = tam_get_endpoint_id()
        }
    };

    result = cl_proceed_request(request, &response);
    if (result == IOTCS_RESULT_CANNOT_AUTHORIZE) {
        if (cl_get_token() == IOTCS_RESULT_OK) {
            result = cl_proceed_request(request, &response);
        }
    }

    if (result == IOTCS_RESULT_OK) {
        if (response.status_code == PROTOCOL_RESPONSE_CODE_OK) {
            if (response.body == NULL) {
                result = IOTCS_RESULT_FAIL;
            } else {
                result = scs_parse_auth_response(response.body);
            }
        } else {
            result = IOTCS_RESULT_FAIL;
        }
    }

    return result;
}

/*Should be guarded (or called from guarded methods) by UTIL_LOCK_STORAGE/UTIL_UNLOCK_STORAGE */
/*Should be guarded (or called from guarded methods) by UTIL_LOCK_LL/UTIL_UNLOCK_LL */
static iotcs_result cl_internal_storage_reconfig_unsafe();

iotcs_result verify_storage_config() {
    if (g_storage_reconfig_required) {
        UTIL_LOCK_LL();
        if (IOTCS_RESULT_OK != cl_internal_storage_reconfig_unsafe()) {
            UTIL_UNLOCK_LL();
            return IOTCS_RESULT_CANNOT_AUTHORIZE;
        } else {
            UTIL_UNLOCK_LL();
            return IOTCS_RESULT_OK;
        }
    }
    return IOTCS_RESULT_OK;
}

static iotcs_result cl_internal_storage_reconfig_unsafe() {
    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> SCS Reconfiguration started");

    char old_storage_host[MAX_HOST_LEN];
    int old_port = g_storage_port;

    memcpy(old_storage_host, g_storage_host, strlen(g_storage_host));

    if (IOTCS_RESULT_OK != scs_provision_request_unsafe()) {
        LOG_ERRS("scs_provision_request_unsafe method failed");
        return IOTCS_RESULT_FAIL;
    }

    if (old_port != g_storage_port || strcmp(old_storage_host, g_storage_host) != 0) {
        iotcs_port_storage_ssl_finalize();

        iotcs_bool is_ssl = (strncmp("https", g_storage_container_url, strlen("https")) == 0) ? IOTCS_TRUE : IOTCS_FALSE;
        if (IOTCS_RESULT_OK != iotcs_port_storage_ssl_init(g_storage_host, g_storage_port, is_ssl)) {
            LOG_ERRS("iotcs_port_storage_ssl_init method failed");
            return IOTCS_RESULT_FAIL;
        }
    }

    g_storage_reconfig_required = IOTCS_FALSE;
    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> SCS Reconfiguration finished");
    return IOTCS_RESULT_OK;
}


/*Should be guarded (or called from guarded methods) by UTIL_LOCK_LL/UTIL_UNLOCK_LL */
iotcs_result cl_internal_storage_init() {
    iotcs_result result = IOTCS_RESULT_OK;

    UTIL_LOCK_STORAGE();
    /*send /iot/api/v2/provisioner/storage request to server*/
    GOTO_ERR_MSG((result = scs_provision_request_unsafe()) != IOTCS_RESULT_OK,
            "scs_provision_request method was failed.");
    /*Configurate addition sll connection fot storage server*/
    iotcs_bool is_ssl = (strncmp("https", g_storage_container_url, strlen("https")) == 0) ? IOTCS_TRUE : IOTCS_FALSE;
    GOTO_ERR_MSG((result = iotcs_port_storage_ssl_init(g_storage_host, g_storage_port, is_ssl)) != IOTCS_RESULT_OK,
            "iotcs_port_storage_ssl_init method was failed.");
    /*Configure async dispatcher*/
    GOTO_ERR_MSG((result = cl_internal_storage_async_dispatcher_init()) != IOTCS_RESULT_OK,
            "cl_internal_storage_async_dispatcher_init method was failed.");

    g_storage_reconfig_required = IOTCS_FALSE;
    g_storage_initialized = IOTCS_TRUE;
error:
    UTIL_UNLOCK_STORAGE();
    return result;
}

/*safe*/
void cl_internal_storage_finalize() {
    UTIL_LOCK_STORAGE();
    if (g_storage_initialized) {
        cl_internal_storage_async_dispatcher_finalize();
        iotcs_port_storage_ssl_finalize();
        g_storage_initialized = IOTCS_FALSE;
        g_storage_reconfig_required = IOTCS_TRUE;
        g_storage_port = 0;
        util_free(g_storage_container_url);
        g_storage_container_url = NULL;
    }
    UTIL_UNLOCK_STORAGE();
}

/*Should be guarded (or called from guarded methods) by UTIL_LOCK_STORAGE/UTIL_UNLOCK_STORAGE*/
iotcs_result cl_internal_send_upload_request_unsafe(iotcs_storage_request* request, protocol_response* response) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    int written_bytes = 0;
    int cx = 0;
    int64_t bytes_total = 0;
    FILE* hInput = NULL;
    char *tmp = NULL;
    struct sd_storage_object_t *storage_object = (struct sd_storage_object_t *)request->storage->object;

    /*check cancellation mark*/
    if (cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL) == IOTCS_TRUE) {
        goto cancel;
    }

    hInput = fopen(storage_object->input_path, "rb");
    if (hInput == NULL) {
        LOG_ERR("Cannot open file %s", storage_object->input_path);
        storage_object->progress_state = IOTCS_FAILED;
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    storage_object->progress_state = IOTCS_IN_PROGRESS;

    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> Upload file to SCS");
    GOTO_ERR(iotcs_port_storage_ssl_connect() != IOTCS_RESULT_OK);

    /*write header*/

    /*API header*/
    if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
        GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, "%s /%s/%s/%s HTTP/1.1\r\n",
                protocol_request_methods_to_string(PROTOCOL_REQUEST_METHOD_PUT), g_storage_rest_api, storage_object->endpoint_id, storage_object->name)), REQUEST_STRING_LENGTH_ERR_MSG);
    } else {
        GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, "%s /%s/%s HTTP/1.1\r\n",
                protocol_request_methods_to_string(PROTOCOL_REQUEST_METHOD_PUT), g_storage_rest_api, storage_object->name)), REQUEST_STRING_LENGTH_ERR_MSG);
    }
    cx += written_bytes;
    if (!storage_object->uri) {
        int expected_size = strlen(g_storage_container_url)+strlen(storage_object->name)+2;
        if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
            expected_size += strlen(storage_object->endpoint_id) + 1;
        }
        char *tmp_buffer;
        GOTO_ERR_OOM(result, tmp_buffer = (char*)util_calloc(expected_size, sizeof(char)));
        if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
            util_safe_snprintf(tmp_buffer, expected_size, "%s/%s/%s", g_storage_container_url, storage_object->endpoint_id, storage_object->name);
        } else {
            util_safe_snprintf(tmp_buffer, expected_size, "%s/%s", g_storage_container_url, storage_object->name);
        }
        storage_object->uri = tmp_buffer;
    }

    /*Host*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Host: %s\r\n", g_storage_host)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*X-Auth-Token*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "X-Auth-Token: %s\r\n", g_storage_auth_token)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*Content-Type*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Content-Type: %s\r\n", storage_object->content_type)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*Connection*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Connection: close\r\n")), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*Transfer-Encoding*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Transfer-Encoding: chunked\r\n\r\n")), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    LOG_INFOSN(g_storage_response_buffer, cx);
    GOTO_ERR(iotcs_port_storage_ssl_write(g_storage_response_buffer, cx) != IOTCS_RESULT_OK);

    char* chunk_buffer = &g_storage_response_buffer[0];
    char* tmp_buffer = &g_storage_response_buffer[IOTCS_RESPONSE_BUFFER_LENGTH / 2 + CHUNK_OVERHEAD + 1]; //NULL terminated symbols

    /*write chunked data*/
    while (0 < (written_bytes = fread(tmp_buffer, 1, IOTCS_RESPONSE_BUFFER_LENGTH / 2 - CHUNK_OVERHEAD - 2, hInput))) {
        int tmp_bytes;
        tmp_buffer[written_bytes] = '\0'; //add null terminated symbol

        GOTO_ERR_MSG(0 > (tmp_bytes = util_safe_snprintf(chunk_buffer, IOTCS_RESPONSE_BUFFER_LENGTH / 2 + CHUNK_OVERHEAD + 1,
                "%04X\r\n", written_bytes)), REQUEST_STRING_LENGTH_ERR_MSG);
        GOTO_ERR(iotcs_port_storage_ssl_write(chunk_buffer, tmp_bytes) != IOTCS_RESULT_OK);
        bytes_total += tmp_bytes;

        GOTO_ERR(iotcs_port_storage_ssl_write(tmp_buffer, written_bytes) != IOTCS_RESULT_OK);
        bytes_total += written_bytes;

        GOTO_ERR(iotcs_port_storage_ssl_write("\r\n", strlen("\r\n")) != IOTCS_RESULT_OK);
        bytes_total += strlen("\r\n");

        if (cl_storage_progress_cb_h(request->storage,
                storage_object->progress_state, bytes_total, NULL) == IOTCS_TRUE) {
            goto cancel;
        }
    }

    /*write empty chunk*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(chunk_buffer, IOTCS_RESPONSE_BUFFER_LENGTH / 2 + CHUNK_OVERHEAD + 1,
            "0\r\n\r\n")), REQUEST_STRING_LENGTH_ERR_MSG);
    GOTO_ERR(iotcs_port_storage_ssl_write(chunk_buffer, strlen(chunk_buffer)) != IOTCS_RESULT_OK);

    /*read the response*/
    GOTO_ERR(iotcs_port_storage_ssl_read(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, &written_bytes) != IOTCS_RESULT_OK);
    LOG_INFOSN(g_storage_response_buffer, written_bytes);

    /*disconnect the socket*/
    GOTO_ERR(iotcs_port_storage_ssl_disconnect() != IOTCS_RESULT_OK);

    /*parse the response buffer*/
    GOTO_ERR(http_parse(response, g_storage_response_buffer, written_bytes) != IOTCS_RESULT_OK);

    fclose(hInput);
    hInput = NULL;
    LOG_INFOSN(g_storage_response_buffer, written_bytes);
    tmp = strstr(g_storage_response_buffer, "X-Last-Modified-Timestamp:");
    if (tmp != NULL) {
        tmp += strlen("X-Last-Modified-Timestamp:");
        while (*tmp == ' ') {
            tmp++;
        }
        int last_modify = atoi(tmp);
        storage_object->content_date = IOTCS_SEC_TO_MILLISEC(last_modify);
    }

    result = IOTCS_RESULT_OK;
    if (response->status_code == PROTOCOL_RESPONSE_CODE_CREATED) {
        storage_object->progress_state = IOTCS_COMPLETED;
    } else {
        storage_object->progress_state = IOTCS_FAILED;
        result = IOTCS_RESULT_FAIL;
    }
    cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL);

    return result;
error:
    storage_object->progress_state = IOTCS_FAILED;
    cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL);
    if (hInput) {
        fclose(hInput);
    }
    iotcs_port_storage_ssl_disconnect();
    return result;

cancel:
    storage_object->progress_state = IOTCS_CANCELED;
    cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL);
    if (hInput) {
        fclose(hInput);
    }
    iotcs_port_storage_ssl_disconnect();
    return IOTCS_RESULT_OPERATION_CANCELED;
}

/*public/safe*/
iotcs_result cl_internal_storage_file_upload(iotcs_storage_request* request) {
    iotcs_result result;
    protocol_response response;
    struct sd_storage_object_t *storage_object = (struct sd_storage_object_t *)request->storage->object;

    UTIL_LOCK_STORAGE();

    if (IOTCS_RESULT_OK != verify_storage_config()) {
        UTIL_UNLOCK_STORAGE();
        storage_object->progress_state = IOTCS_FAILED;
        return IOTCS_RESULT_CANNOT_AUTHORIZE;
    }

    result = cl_internal_send_upload_request_unsafe(request, &response);
    switch (response.status_code) {
        case PROTOCOL_RESPONSE_CODE_CREATED:
            storage_object->progress_state = IOTCS_COMPLETED;
            break;
        case PROTOCOL_RESPONSE_CODE_UNAUTHORIZED:
            g_storage_reconfig_required = IOTCS_TRUE;
            if (IOTCS_RESULT_OK != verify_storage_config()) {
                storage_object->progress_state = IOTCS_FAILED;
                LOG_ERRS("scs_reconfig_unsafe method was failed");
                result = IOTCS_RESULT_CANNOT_AUTHORIZE;
            }
            result = cl_internal_send_upload_request_unsafe(request, &response);
            break;
        default:
            storage_object->progress_state = IOTCS_FAILED;
            result = IOTCS_RESULT_FAIL;
    }

    UTIL_UNLOCK_STORAGE();
    return result;
}

/*Should be guarded (or called from guarded methods) by UTIL_LOCK_STORAGE/UTIL_UNLOCK_STORAGE*/
iotcs_result cl_internal_send_download_request_unsafe(iotcs_storage_request* request, protocol_response* response) {
    iotcs_result result;
    int written_bytes = 0;
    int cx = 0;
    char* tmp = NULL;
    char* header_str = NULL;
    char* body_str = NULL;
    int64_t bytes_total = 0;
    FILE* hInput = NULL;
    struct sd_storage_object_t *storage_object = (struct sd_storage_object_t *)request->storage->object;

    /*check cancellation mark*/
    if (cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL) == IOTCS_TRUE) {
        goto cancel;
    }
    response->status_code = PROTOCOL_RESPONSE_CODE_UNKNOWN_CODE;

    hInput = fopen(storage_object->output_path, "wb");
    if (hInput == NULL) {
        LOG_ERR("Cannot open file %s", storage_object->output_path);
        storage_object->progress_state = IOTCS_FAILED;
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> Download file to SCS");
    GOTO_ERR(iotcs_port_storage_ssl_connect() != IOTCS_RESULT_OK);
    storage_object->progress_state = IOTCS_IN_PROGRESS;

    /*write header*/

    /*API header*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, "%s /%s/%s HTTP/1.1\r\n",
            protocol_request_methods_to_string(PROTOCOL_REQUEST_METHOD_GET), g_storage_rest_api, storage_object->name)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*Host*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Host: %s\r\n", g_storage_host)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*X-Auth-Token*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "X-Auth-Token: %s\r\n", g_storage_auth_token)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*Connection*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Connection: close\r\n\r\n")), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    LOG_INFOSN(g_storage_response_buffer, cx);
    GOTO_ERR(iotcs_port_storage_ssl_write(g_storage_response_buffer, cx) != IOTCS_RESULT_OK);

    /*read first part*/
    GOTO_ERR(iotcs_port_storage_ssl_read(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, &written_bytes) != IOTCS_RESULT_OK);

    /*modify the response buffer to find headers*/
    GOTO_ERR_MSG(NULL == (tmp = strstr(g_storage_response_buffer, "\r\n\r\n")), "Bad format of the http response");
    strncpy(tmp, "\0\0\0\0", 4);

    header_str = &g_storage_response_buffer[0];
    body_str = tmp + 4;
    written_bytes -= strlen(header_str) + 4;
    LOG_INFOS(header_str);

    /*prepare the protocol_response*/
    response->body = NULL;
    response->min_accept_bytes = 0;

    LOG_INFOSN(g_storage_response_buffer, written_bytes);
    /*parse response code*/
    /* Drop "HTTP/1.1 " */
    tmp = &header_str[9];
    int code = atoi(tmp);
    switch (code) {
        case 200:
        case 202:
        case 206:
            response->status_code = PROTOCOL_RESPONSE_CODE_OK;
            break;
        case 400:
            response->status_code = PROTOCOL_RESPONSE_CODE_BAD_REQUEST;
            break;
        case 401:
            response->status_code = PROTOCOL_RESPONSE_CODE_UNAUTHORIZED;
            break;
        case 404:
            response->status_code = PROTOCOL_RESPONSE_CODE_NOT_FOUND;
            break;
        case 409:
            response->status_code = PROTOCOL_RESPONSE_CODE_CONFLICT;
            break;
        case 500:
        default:
            response->status_code = PROTOCOL_RESPONSE_CODE_INTERNAL_SERVER_ERROR;
            break;
    }

    GOTO_ERR(response->status_code != PROTOCOL_RESPONSE_CODE_OK);

    /*TODO: Think how to support chunked data*/
    GOTO_ERR_MSG(NULL != (tmp = strstr(header_str, "chunked")), "Chunked data is unsupported yet");

    if (written_bytes > 0) {
        GOTO_ERR(0 > fwrite(body_str, sizeof (char), written_bytes, hInput));
        bytes_total += written_bytes;
        if (cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL) == IOTCS_TRUE) {
            goto cancel;
        }
    }

    do {
        GOTO_ERR(iotcs_port_storage_ssl_read(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, &written_bytes) != IOTCS_RESULT_OK);
        if (written_bytes > 0) {
            GOTO_ERR(0 > fwrite(g_storage_response_buffer, sizeof (char), written_bytes, hInput));
        }
        bytes_total += written_bytes;
        if (cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL) == IOTCS_TRUE) {
            goto cancel;
        }
   } while (written_bytes > 0);

    /*disconnect the socket*/
    GOTO_ERR(iotcs_port_storage_ssl_disconnect() != IOTCS_RESULT_OK);

    fclose(hInput);
    hInput = NULL;
    result = IOTCS_RESULT_OK;
    LOG_INFOS(">>>>>>>>>>>>>Disconnected");
    LOG_INFOSN(g_storage_response_buffer, written_bytes);
    tmp = strstr(g_storage_response_buffer, "X-Last-Modified-Timestamp:");
    if (tmp != NULL) {
        tmp += strlen("X-Last-Modified-Timestamp:");
        while (*tmp == ' ') {
            tmp++;
        }
        int last_modify = atoi(tmp);
        storage_object->content_date = IOTCS_SEC_TO_MILLISEC(last_modify);
    }

    if (response->status_code == PROTOCOL_RESPONSE_CODE_OK ||
        response->status_code == PROTOCOL_RESPONSE_CODE_PARTIAL) {
        storage_object->progress_state = IOTCS_COMPLETED;
    } else {
        storage_object->progress_state = IOTCS_FAILED;
        result = IOTCS_RESULT_FAIL;
    }
    cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL);
    return result;
error:
    if (hInput) {
        fclose(hInput);
    }
    iotcs_port_storage_ssl_disconnect();
    if (response->status_code == PROTOCOL_RESPONSE_CODE_UNAUTHORIZED) {
        return IOTCS_RESULT_OK;
    }
    storage_object->progress_state = IOTCS_FAILED;
    cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL);

    return IOTCS_RESULT_FAIL;

cancel:
    storage_object->progress_state = IOTCS_CANCELED;
    cl_storage_progress_cb_h(request->storage, storage_object->progress_state, bytes_total, NULL);

    if (hInput) {
        fclose(hInput);
    }
    iotcs_port_storage_ssl_disconnect();
    return IOTCS_RESULT_OPERATION_CANCELED;
}

/*public/safe*/
iotcs_result cl_internal_storage_file_download(iotcs_storage_request* request) {
    iotcs_result result;
    protocol_response response;
    struct sd_storage_object_t *storage_object = (struct sd_storage_object_t *)request->storage->object;

    UTIL_LOCK_STORAGE();

    if (IOTCS_RESULT_OK != verify_storage_config()) {
        storage_object->progress_state = IOTCS_FAILED;
        UTIL_UNLOCK_STORAGE();
        return IOTCS_RESULT_CANNOT_AUTHORIZE;
    }

    result = cl_internal_send_download_request_unsafe(request, &response);

    switch (response.status_code) {
        case PROTOCOL_RESPONSE_CODE_OK:
        case PROTOCOL_RESPONSE_CODE_PARTIAL:
            storage_object->progress_state = IOTCS_COMPLETED;
            break;
        case PROTOCOL_RESPONSE_CODE_UNAUTHORIZED:
            g_storage_reconfig_required = IOTCS_TRUE;
            if (IOTCS_RESULT_OK != verify_storage_config()) {
                LOG_ERRS("scs_reconfig_unsafe method was failed");
                storage_object->progress_state = IOTCS_FAILED;
                result = IOTCS_RESULT_CANNOT_AUTHORIZE;
            }
            result = cl_internal_send_download_request_unsafe(request, &response);
            break;
        default:
            storage_object->progress_state = IOTCS_FAILED;
            result = IOTCS_RESULT_FAIL;
    }
    UTIL_UNLOCK_STORAGE();

    return result;
}

/*Should be guarded (or called from guarded methods) by UTIL_LOCK_STORAGE/UTIL_UNLOCK_STORAGE*/
static iotcs_result send_file_information_request_unsafe(struct iotcs_uri_object_t *locator, protocol_response* response) {
    int written_bytes = 0;
    int cx = 0;
    char *tmp = NULL;
    struct sd_storage_object_t *storage_object = (struct sd_storage_object_t *)locator->object;

    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> Download file info from SCS");
    GOTO_ERR(iotcs_port_storage_ssl_connect() != IOTCS_RESULT_OK);

    /*write header*/

    /*API header*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, "%s /%s/%s HTTP/1.1\r\n",
            protocol_request_methods_to_string(PROTOCOL_REQUEST_METHOD_HEAD), g_storage_rest_api, storage_object->name)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*Host*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Host: %s\r\n", g_storage_host)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*X-Auth-Token*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "X-Auth-Token: %s\r\n", g_storage_auth_token)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    /*Connection*/
    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Connection: close\r\n\r\n")), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    LOG_INFOSN(g_storage_response_buffer, cx);
    GOTO_ERR(iotcs_port_storage_ssl_write(g_storage_response_buffer, cx) != IOTCS_RESULT_OK);

    /*read first part*/
    GOTO_ERR(iotcs_port_storage_ssl_read(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, &written_bytes) != IOTCS_RESULT_OK);
    LOG_INFOSN(g_storage_response_buffer, written_bytes);

    /*disconnect the socket*/
    GOTO_ERR(iotcs_port_storage_ssl_disconnect() != IOTCS_RESULT_OK);

    tmp = strstr(g_storage_response_buffer, "X-Last-Modified-Timestamp:");
    if (tmp != NULL) {
        tmp += strlen("X-Last-Modified-Timestamp:");
        while (*tmp == ' ') {
            tmp++;
        }
        int last_modify = atoi(tmp);
        storage_object->content_date = IOTCS_SEC_TO_MILLISEC(last_modify);
    }

    /*parse the response buffer*/
    GOTO_ERR(http_parse(response, g_storage_response_buffer, written_bytes) != IOTCS_RESULT_OK);

    return IOTCS_RESULT_OK;
error:
    iotcs_port_storage_ssl_disconnect();
    return IOTCS_RESULT_FAIL;
}

/*public/safe*/
iotcs_result iotcs_storage_object_fill_file_info(struct iotcs_uri_object_t *locator) {
    iotcs_result result;
    protocol_response response;
    struct sd_storage_object_t *storage_object = (struct sd_storage_object_t *)locator->object;

    UTIL_LOCK_STORAGE();
    if (IOTCS_RESULT_OK != verify_storage_config()) {
        UTIL_UNLOCK_STORAGE();
        return IOTCS_RESULT_CANNOT_AUTHORIZE;
    }

    result = send_file_information_request_unsafe(locator, &response);

    switch (response.status_code) {
        case PROTOCOL_RESPONSE_CODE_ACCEPTED:
        case PROTOCOL_RESPONSE_CODE_OK:
        case PROTOCOL_RESPONSE_CODE_CREATED:
            break;
        case PROTOCOL_RESPONSE_CODE_UNAUTHORIZED:
            g_storage_reconfig_required = IOTCS_TRUE;
            if (IOTCS_RESULT_OK != verify_storage_config()) {
                UTIL_UNLOCK_STORAGE();
                LOG_ERRS("scs_reconfig_unsafe method was failed");
                return IOTCS_RESULT_CANNOT_AUTHORIZE;
            }
            result = send_file_information_request_unsafe(locator, &response);
            break;
        default:
            UTIL_UNLOCK_STORAGE();
            return IOTCS_RESULT_FAIL;
    }

    if (result == IOTCS_RESULT_OK &&
            (response.status_code == PROTOCOL_RESPONSE_CODE_ACCEPTED ||
            response.status_code == PROTOCOL_RESPONSE_CODE_OK ||
            response.status_code == PROTOCOL_RESPONSE_CODE_CREATED)) {
        /*update locator*/
        storage_object->content_length = atoi(response.content_length);
        storage_object->content_type = strdup(response.content_type);
        if ((!storage_object->uri) && g_storage_container_url) {
            int expected_size = strlen(g_storage_container_url)+strlen(storage_object->name)+2;
            if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
                expected_size += strlen(storage_object->endpoint_id) + 1;
            }
            char *tmp_buffer = (char*)util_calloc(expected_size, sizeof(char));
            if (tmp_buffer) {
                if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
                    util_safe_snprintf(tmp_buffer, expected_size, "%s/%s/%s", g_storage_container_url, storage_object->endpoint_id, storage_object->name);
                } else {
                    util_safe_snprintf(tmp_buffer, expected_size, "%s/%s", g_storage_container_url, storage_object->name);
                }
                storage_object->uri = tmp_buffer;
            } else {
                UTIL_UNLOCK_STORAGE();
                return IOTCS_RESULT_OUT_OF_MEMORY;
            }
        }
        UTIL_UNLOCK_STORAGE();
        return IOTCS_RESULT_OK;
    }
    UTIL_UNLOCK_STORAGE();

    return result;
}

/*public*/
iotcs_result iotcs_storage_dispatcher_queue(struct iotcs_uri_object_t *object) {
    iotcs_storage_request* request = (iotcs_storage_request*)util_calloc(1, sizeof(iotcs_storage_request));
    struct sd_storage_object_t *storage_object = (struct sd_storage_object_t *)object->object;
    if (storage_object->progress_state == IOTCS_COMPLETED) {
        return IOTCS_RESULT_OK;
    }
    
    if (!storage_object->input_path && !storage_object->output_path && IS_EXTERNAL_OBJECT(object)) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if ((!storage_object->uri) && g_storage_container_url) {
        int expected_size = strlen(g_storage_container_url)+strlen(storage_object->name)+2;
        if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
            expected_size += strlen(storage_object->endpoint_id) + 1;
        }
        char *tmp_buffer = (char*)util_calloc(expected_size, sizeof(char));
        if (tmp_buffer) {
            if (storage_object->endpoint_id && !DISABLE_STORAGE_OBJECT_PREFIX) {
                util_safe_snprintf(tmp_buffer, expected_size, "%s/%s/%s", g_storage_container_url, storage_object->endpoint_id, storage_object->name);
            } else {
                util_safe_snprintf(tmp_buffer, expected_size, "%s/%s", g_storage_container_url, storage_object->name);
            }
            storage_object->uri = tmp_buffer;
        } else {
            util_free((void*)request);
            return IOTCS_RESULT_OUT_OF_MEMORY;
        }
    }
    if (storage_object->progress_state == IOTCS_IN_PROGRESS || storage_object->progress_state == IOTCS_QUEUED) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }
    storage_object->progress_state = IOTCS_QUEUED;
    request->storage = object;
    object->ref_cnt++;
    iotcs_result rv = cl_internal_storage_dispatcher_queue_safe(request);
    if (rv != IOTCS_RESULT_OK) {
        storage_object->progress_state = IOTCS_FAILED;
        util_free(request);
    }
    return rv;
}

void iotcs_storage_dispatcher_set_callback(iotcs_storage_dispatcher_progress_callback callback) {
    if (callback == NULL) {
        progress_callback = cl_storage_progress_cb;
    } else {
        progress_callback = callback;
    }
}

/*only for MD auth - should be replaced to more smart method that used default library MD in correct way*/
iotcs_result cl_internal_send_reconfig_request_md() {
    protocol_response response;
    int written_bytes;
    int cx = 0;

    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> Auth request from SCS dispatcher");

    iotcs_port_storage_ssl_finalize();
    // Only ssl here
    GOTO_ERR(iotcs_port_storage_ssl_init(tam_get_server_host(), tam_get_server_port(), IOTCS_TRUE) != IOTCS_RESULT_OK);
    GOTO_ERR(iotcs_port_storage_ssl_connect() != IOTCS_RESULT_OK);

    /*write header*/

    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, "GET /iot/api/v2/provisioner/storage HTTP/1.1\r\n")),
            REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Host: %s\r\n", tam_get_server_host())), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Connection: close\r\n")), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Authorization: %s\r\n", cl_get_access_token_ptr()->value)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "X-EndpointId: %s\r\n", tam_get_endpoint_id())), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    GOTO_ERR_MSG(0 > (written_bytes = util_safe_snprintf(g_storage_response_buffer + cx, IOTCS_RESPONSE_BUFFER_LENGTH - cx,
            "Content-Type: application/json\r\n\r\n")), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += written_bytes;

    LOG_INFOSN(g_storage_response_buffer, cx);
    GOTO_ERR(iotcs_port_storage_ssl_write(g_storage_response_buffer, cx) != IOTCS_RESULT_OK);


    /*read the response*/
    GOTO_ERR(iotcs_port_storage_ssl_read(g_storage_response_buffer, IOTCS_RESPONSE_BUFFER_LENGTH, &written_bytes) != IOTCS_RESULT_OK);
    LOG_INFOSN(g_storage_response_buffer, written_bytes);

    /*disconnect the socket*/
    GOTO_ERR(iotcs_port_storage_ssl_disconnect() != IOTCS_RESULT_OK);

    /*parse the response buffer*/
    GOTO_ERR(http_parse(&response, g_storage_response_buffer, written_bytes) != IOTCS_RESULT_OK);

    GOTO_ERR(response.body != NULL);
    GOTO_ERR(scs_parse_auth_response(response.body) != IOTCS_RESULT_OK);

    iotcs_port_storage_ssl_disconnect();
    iotcs_port_storage_ssl_finalize();
    iotcs_bool is_ssl = (strncmp("https", g_storage_container_url, strlen("https")) == 0) ? IOTCS_TRUE : IOTCS_FALSE;
    GOTO_ERR(iotcs_port_storage_ssl_init(g_storage_host, g_storage_port, is_ssl) != IOTCS_RESULT_OK);

    return IOTCS_RESULT_OK;
error:

    iotcs_port_storage_ssl_disconnect();
    return IOTCS_RESULT_FAIL;
}
