/*
 * Copyright (c) 2015, 2018, 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 <stdio.h>
#include <string.h>
#include <time.h>
#include <limits.h>

#include "iotcs.h"
#include "iotcs/iotcs_private.h"
#include "util/util_thread_private.h"
#include "messaging/msg_private.h"
#include "json/json_helper.h"
#include "advanced/iotcs_messaging.h"
#include "util/util.h"
#include "util/util_array_queue.h"
#include "util/util_resource_private.h"
#include "trusted_assets_manager/iotcs_tam.h"
#include "device_model/device_model_capability.h"
#include "device_model/device_model_private.h"
#include "protocol/protocol_request.h"
#include "util/util_buffer.h"
#include "util/util_memory.h"
#include "scs/storage_dispatcher_private.h"
#include "iotcs_port_system.h"
#include "iotcs_port_mutex.h"
#include "iotcs_port_ssl.h"
#include "iotcs_port_queue.h"
#include "iotcs_port_crypto.h"
#include "iotcs_port_thread.h"
#ifdef IOTCS_MESSAGE_DISPATCHER
#include "policy/device_policy.h"
#endif

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

#ifdef IOTCS_USE_SERVER_TIME
#include <ctype.h>
#include <stdlib.h>
#endif

#ifndef IOTCS_POLLING_TIMEOUT_MS
#define IOTCS_POLLING_TIMEOUT_MS 5000
#endif
#ifdef IOTCS_STORAGE_SUPPORT
#include "scs/storage_dispatcher_private.h"
#include "advanced/iotcs_storage_object.h"
#define IOTCS_SYNC_WAIT_INTERVAL 100
#endif
#ifdef IOTCS_MESSAGE_PERSISTENCE
#include "messaging/message_persistence.h"
#endif

/*
 strlen("Bearer ") + TOKEN_MAX_LENGTH
 */

UTIL_DECL_LL_LOCK();
UTIL_DECL_RWSSL_LOCK();
#ifdef IOTCS_LONG_POLLING
#define CL_HALF_SECOND_VALUE_MS 500

UTIL_DECL_LP_LOCK();
#endif

#ifdef IOTCS_STORAGE_SUPPORT
UTIL_DECL_SCS_LOCK();
#endif

#ifdef IOTCS_VIRTUALIZATION_SUPPORT

static void dispatcher_send_callback(iotcs_message *message) {
    if (message->base->type == IOTCS_MESSAGE_DATA && message->user_data == IOTCS_OFFERED_MARK) {
        release_msg_copy(message);
    } else if (message->base->type == IOTCS_MESSAGE_ALERT && message->user_data == IOTCS_OFFERED_ALERT_MARK) {
        release_alert_msg_copy(message);
    } else {
        device_model_handle_send(message, IOTCS_RESULT_OK, NULL);
    }
}

static void dispatcher_error_callback(iotcs_message *message, iotcs_result result, const char *fail_reason) {
    if (message->base->type == IOTCS_MESSAGE_DATA && message->user_data == IOTCS_OFFERED_MARK) {
        release_msg_copy(message);
    } else if (message->base->type == IOTCS_MESSAGE_ALERT && message->user_data == IOTCS_OFFERED_ALERT_MARK) {
        release_alert_msg_copy(message);
    } else {
        device_model_handle_send(message, result, fail_reason);
    }
}
#endif


iotcs_port_request_queue* request_queue;

static const char CL_DEFAULT_MESSAGE_DIGEST_ALGORITHM[] = "HmacSHA256";
static const char DIRECT_ACTIVATION_CAPABILITY[] = "urn:oracle:iot:dcd:capability:direct_activation";
#ifdef IOTCS_GATEWAY
static const char INDIRECT_ACTIVATION_CAPABILITY[] = "urn:oracle:iot:dcd:capability:indirect_activation";
#endif
#define IOTCS_EXP_CLAIM_DELTA_SEC (15 * 60) // 15 minutes
//access tokens
static cl_access_token client_token;
cl_proceed_request_function cl_proceed_request;
cl_check_min_accept_bytes_function cl_check_min_accept_bytes;

#ifdef IOTCS_LONG_POLLING
static int g_is_long_polling_active = 0;
#endif

cl_access_token* cl_get_access_token_ptr(void) {
    return &client_token;
}

#ifdef IOTCS_LONG_POLLING
static iotcs_bool g_is_long_polling_required = IOTCS_TRUE;
#endif

static iotcs_result set_request_function_by_scheme() {
#ifdef IOTCS_LONG_POLLING
    g_is_long_polling_required = IOTCS_TRUE;
#endif
    iotcs_result result = IOTCS_RESULT_FAIL;
    const char* server_scheme = tam_get_server_scheme();
#ifndef IOTCS_DISABLE_HTTP
    if (strcmp(server_scheme, "https") == 0) {
        cl_proceed_request = http_proceed_request;
        cl_check_min_accept_bytes = http_check_min_accept_bytes;
        return IOTCS_RESULT_OK;
    } else
#endif
#ifndef IOTCS_DISABLE_MQTT
        if (strcmp(server_scheme, "mqtts") == 0) {
        cl_proceed_request = mqtt_proceed_request;
        cl_check_min_accept_bytes = mqtt_check_min_accept_bytes;
        result = IOTCS_RESULT_OK;
#ifdef IOTCS_LONG_POLLING
        g_is_long_polling_required = IOTCS_FALSE;
#endif
    } else
#endif
    {
        LOG_CRIT("unsupported server scheme: %s", server_scheme);
    }
    return result;
}

iotcs_result cl_process_message_from_json(char* json) {
    iotcs_result rv;
    UTIL_LOCK_LL();
    rv = cl_process_message_from_json_unsafe(json);
    UTIL_UNLOCK_LL();
    return rv;
}

iotcs_result cl_process_message_from_json_unsafe(char* json) {
    json_tokens_t tokens;

    if (!json) {
        return IOTCS_RESULT_FAIL;
    }

    CHECK_NO_OK(json_parse(json, strlen(json), &tokens));

    int array_cnt = json_child_items_count(&tokens);
    // array of message objects
    if (array_cnt > 0) {
        json_array_iterator_t iter;
        CHECK_NO_OK(json_get_array_iterator(&tokens, &iter));
        while (json_array_iterator_has_next(&iter)) {
            json_tokens_t filed_tok;
            CHECK_NO_OK(json_array_iterator_next(&iter, &filed_tok));
            iotcs_request_message* request_message = msg_request_fromJSON(&filed_tok);
            if (request_message) {

#ifdef IOTCS_MESSAGE_DISPATCHER
                //Until cl_is_settle_time_passed() is false keep one slot in request queue so requests without user handler could be processed later (when user finally set the handler).
                if (cl_is_settle_time_passed() == IOTCS_FALSE &&
                        ((IOTCS_PORT_REQUEST_QUEUE_SIZE - iotcs_port_request_queue_count(request_queue)) <= 1)) {
                    LOG_ERRS("Can't push the response to queue.");
                    cl_internal_request_message_free_unsafe(request_message);
                } else
#endif
                if (iotcs_port_request_queue_put(request_queue, request_message, 0) != IOTCS_RESULT_OK) {
                    LOG_ERRS("Can't push the response to queue.");
                    cl_internal_request_message_free_unsafe(request_message);
                }
            }
        }
    }
    return IOTCS_RESULT_OK;
}

static iotcs_result post_messages_process_response(const char* buffer, cl_fail_string_desc *fail_reason) {
    char* response = NULL;
    iotcs_result rv = IOTCS_RESULT_OK;

    if ((rv = cl_post_messages(buffer, &response, fail_reason)) != IOTCS_RESULT_OK) {
        return rv;
    }
    /* When we uses Long Polling accepted bytes for send messages is ZERO.
     * So we aren't waiting for any requests from server.
     */
#ifndef IOTCS_LONG_POLLING   
    if (response) {
        cl_process_message_from_json_unsafe(response);
    }
#endif

    return rv;
}

static iotcs_result get_url_string_with_accepted_bytes(char* output_url, int length) {
    return (0 > util_safe_snprintf(output_url, length, "/iot/api/v2/messages?acceptBytes=%d",
#ifdef IOTCS_LONG_POLLING
            g_is_long_polling_required ? 0 : IOTCS_MAX_ACCEPTED_BYTES
#else
            IOTCS_MAX_ACCEPTED_BYTES
#endif
            )) ? IOTCS_RESULT_FAIL : IOTCS_RESULT_OK;
}

static void modify_base64(char* base64) {
    unsigned int i;

    for (i = 0; i < strlen(base64); i++) {
        if (base64[i] == '+') {
            base64[i] = '-';
        } else if (base64[i] == '/') {
            base64[i] = '_';
        }
    }
}

void cl_reset_token(void) {
    client_token.value[0] = '\0';
    client_token.created = 0;
}

static iotcs_result iotcs_token_initialize(protocol_response* response) {
    char* value;
    int length;
    json_tokens_t tokens_t;

    GOTO_ERR_MSG(json_parse(response->body, strlen(response->body), &tokens_t) != IOTCS_RESULT_OK,
            "JSON initialization failed");

    //get token value
    GOTO_ERR_MSG(json_get_string_value_by_key(&tokens_t, "access_token", &value, &length)
            != IOTCS_RESULT_OK, "json_get_string_value_by_key() method failed.");
    GOTO_ERR_MSG(length > UTIL_TOKEN_MAX_LENGTH || length <= 0,
            "Unexpected token length!");
    memcpy(client_token.value, "Bearer ", UTIL_BEARER_LENGTH * sizeof (char));
    memcpy(client_token.value + UTIL_BEARER_LENGTH, value, length * sizeof (char));
    //get "expires_in" value
    GOTO_ERR_MSG(json_get_int_value_by_key(&tokens_t, "expires_in", &client_token.expires_in)
            != IOTCS_RESULT_OK, "json_get_int_value_by_key method failed.");
    client_token.created = iotcs_port_get_current_time_millis();

    return IOTCS_RESULT_OK;

error:
    return IOTCS_RESULT_CANNOT_AUTHORIZE;
}

static void result_to_reason(cl_fail_string_desc *fail_reason, iotcs_result result) {
    if (NULL == fail_reason)
        return;

    if (result & IOTCS_RESULT_OUT_OF_MEMORY) {
        strncpy(fail_reason->buf, "Out of memory", fail_reason->len);
    } else if (result & IOTCS_RESULT_FAIL) {
        strncpy(fail_reason->buf, "Transport(HTTPS) error", fail_reason->len);
    } else if (result & IOTCS_RESULT_CANNOT_AUTHORIZE) {
        strncpy(fail_reason->buf, "Can not authorize", fail_reason->len);
    } else {
        strncpy(fail_reason->buf, "Unknown error", fail_reason->len);
    }
}

static iotcs_result create_get_token_body(char *body) {
    char* header = util_get_token_header_buffer();
    char* claims = util_get_token_claims_buffer();
    char* signature = util_get_token_signature_buffer();
    char* base64_string = util_get_token_base64_string_buffer();
    size_t base64_string_length = UTIL_GET_TOKEN_B64_STRING_LENGTH;
    char* signature_string = util_get_token_signature_string_buffer();
    size_t signature_string_length = UTIL_GET_TOKEN_SIGNATURE_STRING_LENGTH;
    char* assertion = util_get_token_assertion_buffer();
    size_t assertion_length = UTIL_GET_TOKEN_ASSERTION_LENGTH;
    size_t assertion_cx = UTIL_GET_TOKEN_ASSERTION_LENGTH;
    size_t signature_length;
    char* algorithm = NULL;
    const char* id = NULL;
    int written;
    int64_t tmp;

    cl_reset_token();
    algorithm = tam_is_activated() ?
            "RS256" : "HS256";
    id = tam_is_activated() ?
            tam_get_endpoint_id() : tam_get_client_id();

    // Do base64 on header and append it to assertion
    GOTO_ERR(0 > (written = util_safe_snprintf(header, UTIL_GET_TOKEN_HEADER_LENGTH, "{"
            "\"typ\":\"JWT\","
            "\"alg\":\"%s\""
            "}", algorithm)));
    GOTO_ERR_MSG(iotcs_port_crypto_encode_base64(assertion, &assertion_length, header, strlen(header)) != IOTCS_RESULT_OK,
            "iotcs_port_crypto_encode_base64 method failed.");

    GOTO_ERR((assertion_cx -= assertion_length) <= 0);
    assertion[assertion_length] = '\0';

    // Append "." to assertion
    GOTO_ERR((assertion_cx -= 1) <= 0);
    strcat(assertion, ".");

    GOTO_ERR_MSG(LONG_MAX < (tmp = (IOTCS_MILLISEC_TO_SEC(iotcs_port_get_current_time_millis()) +
            IOTCS_EXP_CLAIM_DELTA_SEC)), "Loss of data found.");
    long expires_in_sec = tmp;
    // Do base64 on claims and append it to assertion
    GOTO_ERR(0 > (written = util_safe_snprintf(claims, UTIL_GET_TOKEN_CLAIMS_LENGTH, "{"
            "\"iss\":\"%s\""
            ", \"sub\":\"%s\""
            ", \"aud\":\"oracle/iot/oauth2/token\""
            ", \"exp\":%ld"
            "}", id,
            id,
            expires_in_sec)));

    GOTO_ERR_MSG(iotcs_port_crypto_encode_base64(base64_string, &base64_string_length, claims, strlen(claims)) != IOTCS_RESULT_OK,
            "iotcs_port_crypto_encode_base64 method failed.");

    GOTO_ERR((assertion_cx -= base64_string_length) <= 0);
    strncat(assertion, base64_string, base64_string_length);

    if (tam_is_activated()) {
        signature_length = tam_sign_with_private_key(assertion, strlen(assertion), "SHA256withRSA",
                signature, UTIL_GET_TOKEN_SIGNATURE_LENGTH);
    } else {
        signature_length = tam_sign_with_shared_secret(assertion, strlen(assertion), CL_DEFAULT_MESSAGE_DIGEST_ALGORITHM,
                signature, UTIL_GET_TOKEN_SIGNATURE_LENGTH, NULL);
    }

    GOTO_ERR_MSG(signature_length <= 0, "tam_sign method failed.");
    GOTO_ERR_MSG(iotcs_port_crypto_encode_base64(signature_string, &signature_string_length, (char*) signature, signature_length) != IOTCS_RESULT_OK,
            "iotcs_port_crypto_encode_base64 method failed");
    GOTO_ERR((assertion_cx -= 1) <= 0);
    strcat(assertion, ".");
    GOTO_ERR((assertion_cx -= signature_string_length) <= 0);
    strncat(assertion, signature_string, signature_string_length);
    modify_base64(assertion);

    GOTO_ERR(0 > (written = util_safe_snprintf(body, UTIL_PAYLOAD_BUFFER_SIZE, "grant_type=client_credentials"
            "&client_assertion_type=urn%%3Aietf%%3Aparams%%3Aoauth%%3Aclient-assertion-type%%3Ajwt-bearer" // url encoded "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
            "&client_assertion=%s"
            "&scope=%s",
            assertion,
            tam_is_activated() ? "" : "oracle/iot/activation")));

    GOTO_ERR(written >= IOTCS_DEVICE_MODEL_URL_SIZE);

    return IOTCS_RESULT_OK;

error:
    return IOTCS_RESULT_FAIL;
}

iotcs_result cl_get_token() {
    char *body = util_get_payload_buffer();
    protocol_response response;
    iotcs_result result = IOTCS_RESULT_OK;

    /* Commented out because function call means that we want to renew token no matter what */
    /* if (client_token.value[0] != '\0' &&
            client_token.expires_in - (iotcs_port_get_current_time_millis() - client_token.created) > 0) {
        return IOTCS_RESULT_OK;
    } */
    cl_reset_token();

    GOTO_ERR_NO_OK(result, create_get_token_body(body));

    protocol_request request = {
        .body = body,
        .url = "/iot/api/v2/oauth2/token",
        .method = PROTOCOL_REQUEST_METHOD_POST,
        .headers =
        {
            .accept = "application/json",
            .connection = "close",
            .content_type = "application/x-www-form-urlencoded",
            .host = tam_get_server_host()
        }
    };

    GOTO_ERR_NO_OK(result, cl_proceed_request(request, &response));

    if (response.status_code == PROTOCOL_RESPONSE_CODE_OK) {
        GOTO_ERR_NO_OK(result, iotcs_token_initialize(&response));
    } else {
#ifdef IOTCS_USE_SERVER_TIME
        static int server_time_setted = 0;
        if (server_time_setted == 0) {
            server_time_setted = 1;
            char* value = NULL;
            int output_length, i;
            json_tokens_t time_tok;

            GOTO_ERR_NO_OK(result, json_parse(response.body, strlen(response.body), &time_tok));
            GOTO_ERR_NO_OK(result, json_get_string_value_by_key(&time_tok, "currentTime", &value, &output_length));
            for (i = 0; isdigit((int) value[i]); i++) {
            }
            value[i - 3] = '\0'; // value / 1000
            unsigned long cur_time = strtoul(value, NULL, 10);
            iotcs_port_set_current_time(cur_time);
            cl_reset_token();
            GOTO_ERR_NO_OK(result, create_get_token_body(body));
            GOTO_ERR_NO_OK(result, cl_proceed_request(request, &response)); // Try one more time with setted time
            if (response.status_code == PROTOCOL_RESPONSE_CODE_OK) {
                GOTO_ERR_NO_OK(result, iotcs_token_initialize(&response));
            }
        }
#else
        GOTO_ERR_NO_OK(result, IOTCS_RESULT_CANNOT_AUTHORIZE);
#endif //IOTCS_USE_SERVER_TIME
    }

    return result;

error:
    // TODO: strange logic
    if (!result) {
        result = IOTCS_RESULT_FAIL;
    }
    return result;
}
extern UTIL_DECL_RWSSL_LOCK();

iotcs_result cl_internal_init(const char* path, const char* password) {
    iotcs_result result;

    UTIL_INIT_LL_LOCK();
    UTIL_INIT_RWSSL_LOCK();
#ifdef IOTCS_LONG_POLLING
    UTIL_INIT_LP_LOCK();
#endif

#ifdef IOTCS_STORAGE_SUPPORT
    UTIL_INIT_SCS_LOCK();
#endif

    if ((result = tam_init(path, password)) != IOTCS_RESULT_OK) {
        LOG_CRITS("Trusted assets manager initialization failed");
        return result;
    }

    if ((result = set_request_function_by_scheme()) != IOTCS_RESULT_OK) {
        LOG_CRITS("Invalid server scheme. Initialization failed");
        return result;
    }

    if (iotcs_port_ssl_init(tam_get_server_host(), (unsigned short) tam_get_server_port())
            != IOTCS_RESULT_OK) {
        LOG_CRITS("iotcs_port_ssl_init method failed.");
        iotcs_port_ssl_finalize();
        tam_finalize();
        return IOTCS_RESULT_FAIL;
    }

    request_queue = iotcs_port_request_queue_create();

    if (!request_queue) {
        iotcs_port_ssl_finalize();
        tam_finalize();
        return IOTCS_RESULT_FAIL;
    }
    cl_reset_token();

    UTIL_LOCK_LL();
    result = cl_get_token();

    if (result != IOTCS_RESULT_OK) {
        UTIL_UNLOCK_LL();
        cl_internal_finalize();
        return result;
    }
#ifdef IOTCS_MESSAGE_DISPATCHER
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
    dm_init_policy();
#endif
#endif
    if (tam_is_activated()) {
        if (IOTCS_RESULT_OK != (result = cl_post_activation())) {
            UTIL_UNLOCK_LL();
            cl_internal_finalize();
            return result;
        }
#ifdef IOTCS_MESSAGE_DISPATCHER
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
        dm_load_local_policy_unsafe(NULL, NULL, IOTCS_FALSE);
#endif
#endif
    }

    UTIL_UNLOCK_LL();
#if  defined(IOTCS_MESSAGE_DISPATCHER) && (IOTCS_REQUEST_HANDLER_THREAD_POOL_SIZE > 1)
    cl_internal_request_handler_thread_pool_init();
#endif    

    return IOTCS_RESULT_OK;
}

void cl_internal_finalize(void) {
    UTIL_LOCK_LL();
#ifdef IOTCS_STORAGE_SUPPORT 
    /*should be called before iotcs_port_ssl_finalize*/
    cl_internal_storage_finalize();
#endif
    iotcs_port_ssl_finalize();
    tam_finalize();

#if  defined(IOTCS_MESSAGE_DISPATCHER) && (IOTCS_REQUEST_HANDLER_THREAD_POOL_SIZE > 1)
    cl_internal_request_handler_dispatcher_finalize();
#endif    
    iotcs_port_request_queue_destroy(request_queue);
    request_queue = NULL;

    UTIL_UNLOCK_LL();
    UTIL_FINIT_LL_LOCK();
    UTIL_FINIT_RWSSL_LOCK();
#ifdef IOTCS_LONG_POLLING
    UTIL_FINIT_LP_LOCK();
#endif

#ifdef IOTCS_STORAGE_SUPPORT
    UTIL_FINIT_SCS_LOCK();
#endif
}

iotcs_result cl_get_activation_policy(cl_activation_policy* policy) {
    protocol_response response;
    iotcs_result rv = IOTCS_RESULT_OK;
    char* url_buffer = util_get_url_buffer();
    const char* os_name;
    const char* os_version;
    int tmp_cx, cx = 0;

    GOTO_ERR(0 > (tmp_cx = util_safe_snprintf(url_buffer, UTIL_REST_API_URL_MAX_LENGTH, "/iot/api/v2/activation/policy?OSName=")));
    cx += tmp_cx;

    GOTO_ERR_MSG((os_name = iotcs_port_get_os_name()) == NULL, "IOTCS_OS_NAME env isn't set");
    GOTO_ERR(0 > (tmp_cx = util_urlencode(os_name, url_buffer + cx, UTIL_REST_API_URL_MAX_LENGTH - cx)));
    cx += tmp_cx;

    GOTO_ERR(0 > (tmp_cx = util_safe_snprintf(url_buffer + cx, UTIL_REST_API_URL_MAX_LENGTH - cx, "&OSVersion=")));
    cx += tmp_cx;

    GOTO_ERR_MSG((os_version = iotcs_port_get_os_version()) == NULL, "IOTCS_OS_VERSION env isn't set");
    GOTO_ERR(0 > (tmp_cx = util_urlencode(os_version, url_buffer + cx, UTIL_REST_API_URL_MAX_LENGTH - cx)));

    protocol_request request = {
        .body = "",
        .url = url_buffer,
        .method = PROTOCOL_REQUEST_METHOD_GET,
        .headers =
        {
            .accept = "application/json",
            .connection = "close",
            .content_type = "application/json",
            .host = tam_get_server_host(),
            .x_activationId = tam_get_client_id()
        }
    };

    GOTO_ERR_NO_OK(rv, cl_proceed_request(request, &response));

    if (response.status_code == PROTOCOL_RESPONSE_CODE_ACCEPTED || response.status_code == PROTOCOL_RESPONSE_CODE_OK || response.status_code == PROTOCOL_RESPONSE_CODE_CREATED) {
        char* value = NULL;
        int output_length;
        json_tokens_t tokens_t;

        GOTO_ERR_NO_OK(rv, json_parse(response.body, strlen(response.body), &tokens_t));

        GOTO_ERR_NO_OK(rv, json_get_string_value_by_key(&tokens_t, "keyType", &value, &output_length));
        CHECK_OOM(policy->key_type = util_safe_strncpy(value, output_length));

        GOTO_ERR_NO_OK(rv, json_get_string_value_by_key(&tokens_t, "hashAlgorithm", &value, &output_length));
        CHECK_OOM(policy->hash_algorithm = util_safe_strncpy(value, output_length));

        GOTO_ERR_NO_OK(rv, json_get_int_value_by_key(&tokens_t, "keySize", &policy->key_size));
    }

    return IOTCS_RESULT_OK;

error:
    // policy will be free on higher lvl
    return IOTCS_RESULT_FAIL;
}

static char* iotcsp_generate_activation_request_body(const char *device_models[], const unsigned char* public_key,
        size_t public_key_length, cl_activation_policy* policy) {
    int digest_length;
    char* signature = util_get_activation_signature_buffer();
    char* signature_string = util_get_activation_signature_string_buffer();
    size_t signature_string_length = UTIL_ACTIVATION_SIGNATURE_STRING_LENGTH;
    char* content = util_get_activation_content_buffer();
    char* device_models_str = util_get_activation_device_models_str_buffer();
    size_t signature_length;
    char* hash_code = util_get_activation_hash_code_buffer();
    char* public_key_string = util_get_activation_public_key_string_buffer();
    size_t public_key_string_length = UTIL_ACTIVATION_PUBLIC_KEY_STR_LENGTH;
    char* digest = util_get_activation_digest_buffer();
    char* body = util_get_payload_buffer();
    size_t hash_code_length;
    iotcs_result result = IOTCS_RESULT_OK;
    int i;
    int cx;
    int written;

    GOTO_ERR_MSG((result = iotcs_port_crypto_encode_base64(public_key_string, &public_key_string_length, (char*) public_key, public_key_length))
            != IOTCS_RESULT_OK, "iotcs_port_crypto_encode_base64 method failed");

    //generating of the hash_code that include proof of possession of the device's shared secret

    GOTO_ERR(0 > (written = util_safe_snprintf(content, UTIL_ACTIVATION_CONTENT_LENGTH, "%s", tam_get_client_id())));
    hash_code_length = tam_sign_with_shared_secret(content, strlen(content),
            "HmacSHA256", hash_code, UTIL_ACTIVATION_HASH_CODE_LENGTH, NULL);
    GOTO_ERR_MSG(hash_code_length <= 0, "tam_sign method failed.");
    GOTO_ERR(0 > (digest_length = util_safe_snprintf(digest, UTIL_ACTIVATION_DIGEST_LENGTH, "%s\n%s\n%s\n%s\n",
            tam_get_client_id(),
            policy->key_type,
            "X.509",
            CL_DEFAULT_MESSAGE_DIGEST_ALGORITHM)));

    digest_length = strlen(digest);
    memcpy(digest + digest_length, hash_code, hash_code_length);
    digest_length += hash_code_length;
    memcpy(digest + digest_length, public_key, public_key_length);
    digest_length += public_key_length;

    signature_length = tam_sign_with_private_key(digest, digest_length,
            policy->hash_algorithm, signature, UTIL_ACTIVATION_SIGNATURE_LENGTH);
    //signing of the digest
    GOTO_ERR_MSG(signature_length <= 0, "tam_sign_with_private_key method failed");
    GOTO_ERR_MSG((result = iotcs_port_crypto_encode_base64(signature_string,
            &signature_string_length, (char*) signature, signature_length))
            != IOTCS_RESULT_OK, "iotcs_port_crypto_encode_base64 method failed");
    GOTO_ERR_MSG(result != IOTCS_RESULT_OK, "Failed to create http_request in activation");

    cx = 0;
    GOTO_ERR(0 > (cx += util_safe_snprintf(device_models_str, UTIL_ACTIVATION_DM_STR_LENGTH, "\"deviceModels\":[")));
    GOTO_ERR(0 > (cx += util_safe_snprintf(device_models_str + cx, UTIL_ACTIVATION_DM_STR_LENGTH - cx,
            "\"%s\",", DIRECT_ACTIVATION_CAPABILITY)));
#ifdef IOTCS_GATEWAY
    GOTO_ERR(0 > (cx += util_safe_snprintf(device_models_str + cx, UTIL_ACTIVATION_DM_STR_LENGTH - cx,
            "\"%s\",", INDIRECT_ACTIVATION_CAPABILITY)));
#endif
#ifdef IOTCS_MESSAGE_DISPATCHER
    GOTO_ERR(0 > (cx += util_safe_snprintf(device_models_str + cx, UTIL_ACTIVATION_DM_STR_LENGTH - cx,
            "\"%s\",", "urn:oracle:iot:dcd:capability:message_dispatcher")));
#endif
    GOTO_ERR(0 > (cx += util_safe_snprintf(device_models_str + cx, UTIL_ACTIVATION_DM_STR_LENGTH - cx,
            "\"%s\",", "urn:oracle:iot:dcd:capability:diagnostics")));
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
    GOTO_ERR(0 > (cx += util_safe_snprintf(device_models_str + cx, UTIL_ACTIVATION_DM_STR_LENGTH - cx,
            "\"%s\",", "urn:oracle:iot:dcd:capability:device_policy")));
#endif
    for (i = 0; device_models[i]; i++) {
        GOTO_ERR(0 > (cx += util_safe_snprintf(device_models_str + cx, UTIL_ACTIVATION_DM_STR_LENGTH - cx,
                "\"%s\",", device_models[i])));
    }
    // delete last comma
    cx -= 1;
    // add last bracket
    GOTO_ERR(0 > util_safe_snprintf(device_models_str + cx, UTIL_ACTIVATION_DM_STR_LENGTH - cx, "]"));
    GOTO_ERR(0 > util_safe_snprintf(body, UTIL_PAYLOAD_BUFFER_SIZE, "{"
            "%s,"
            "\"certificationRequestInfo\":{"
            "\"subject\":\"%s\","
            "\"subjectPublicKeyInfo\":{"
            "\"algorithm\":\"%s\","
            "\"publicKey\":\"%.*s\","
            "\"format\":\"X.509\","
            "\"secretHashAlgorithm\":\"%s\""
            "},"
            "\"attributes\":{}"
            "},"
            "\"signatureAlgorithm\":\"%s\","
            "\"signature\":\"%.*s\""
            "}", device_models_str,
            tam_get_client_id(),
            policy->key_type,
            (int) public_key_string_length,
            public_key_string,
            CL_DEFAULT_MESSAGE_DIGEST_ALGORITHM,
            policy->hash_algorithm,
            (int) signature_string_length,
            signature_string));

    return body;

error:
    return NULL;
}

static void iotcs_free_activation_policy(cl_activation_policy* policy) {
    util_free((char*) policy->hash_algorithm);
    util_free((char*) policy->key_type);
}

iotcs_result iotcs_activate(const char *device_models[]) {
    protocol_response response;
    iotcs_result rv = IOTCS_RESULT_OK;
    cl_activation_policy policy;
    int call_post_activation = 0;

    const unsigned char* public_key;
    size_t public_key_length;

    /* default policy initialization */
    policy.hash_algorithm = NULL;
    policy.key_type = NULL;

    if (!device_models || !device_models[0]) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    UTIL_LOCK_LL();

    // device already activated
    if (tam_is_activated()) {
        UTIL_UNLOCK_LL();
        return IOTCS_RESULT_FAIL;
    }

    GOTO_ERR_MSG((rv = cl_get_activation_policy(&policy)) != IOTCS_RESULT_OK,
            "cl_get_activation_policy() method returns NULL");

    GOTO_ERR_MSG(SIZE_MAX < policy.key_size, "Loss of data found.");
    GOTO_ERR_MSG((rv = tam_generate_key_pair(policy.key_type, (size_t) policy.key_size)) != IOTCS_RESULT_OK,
            "tam_generate_key_pair method failed");

    public_key = tam_get_device_public_key(&public_key_length);

    protocol_request request = {
#ifdef IOTCS_USE_DRAFT_DEVICE_MODELS
        .url = "/iot/api/v2/activation/direct", // default
#else
        .url = "/iot/api/v2/activation/direct?createDraft=false",
#endif
        .method = PROTOCOL_REQUEST_METHOD_POST,
        .headers =
        {
            .accept = "application/json",
            .connection = "close",
            .content_type = "application/json",
            .host = tam_get_server_host(),
            .x_activationId = tam_get_client_id()
        }
    };

    GOTO_ERR_MSG(NULL == (request.body =
            iotcsp_generate_activation_request_body(device_models, public_key, public_key_length, &policy)),
            "iotcsp_generate_activation_request_body() method returns NULL");
    rv = cl_proceed_request(request, &response);

    /*
     If not authorize then try to get token again.
     */
    if (rv == IOTCS_RESULT_CANNOT_AUTHORIZE) {
        GOTO_ERR_MSG(IOTCS_RESULT_OK != cl_get_token(),
                "cl_get_token method failed");
        GOTO_ERR_MSG(NULL == (request.body =
                iotcsp_generate_activation_request_body(device_models, public_key, public_key_length, &policy)),
                "iotcsp_generate_activation_request_body() method returns NULL");
        rv = cl_proceed_request(request, &response);
    }

    if (rv == IOTCS_RESULT_OK) {
        if (response.status_code == PROTOCOL_RESPONSE_CODE_ACCEPTED || response.status_code == PROTOCOL_RESPONSE_CODE_OK || response.status_code == PROTOCOL_RESPONSE_CODE_CREATED) {
            char* value = NULL;
            int output_length;
            json_tokens_t tokens_t;

            GOTO_ERR_NO_OK(rv, json_parse(response.body, strlen(response.body), &tokens_t));
            GOTO_ERR_NO_OK(rv, json_get_string_value_by_key(&tokens_t, "endpointId", &value, &output_length));
            value[output_length] = '\0';
            GOTO_ERR_NO_OK(rv, tam_set_endpoint_credentials(value, output_length));
            cl_reset_token(); // activation token is illegal now
            cl_get_token(); // get the new one
            call_post_activation = 1;
        } else {
            rv = IOTCS_RESULT_FAIL;
        }
    }

    iotcs_free_activation_policy(&policy);

    if (call_post_activation == 1) {
        GOTO_ERR_NO_OK(rv, cl_post_activation());
    }
    UTIL_UNLOCK_LL();
#ifdef IOTCS_MESSAGE_DISPATCHER
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
    int i;
    for (i = 0; device_models[i]; i++) {
        dm_get_policy(tam_get_endpoint_id(), device_models[i]);
    }
#endif
#endif

    return rv;
error:
    UTIL_UNLOCK_LL();
    iotcs_free_activation_policy(&policy);
    return (rv == IOTCS_RESULT_OK) ? IOTCS_RESULT_FAIL : rv;
}

#ifdef IOTCS_LONG_POLLING
#define LONG_POLLING_RUNNING_STATE 0x0
#define LONG_POLLING_STOP_STATE 0x1
#define LONG_POLLING_INTERRUPTED_STATE 0x2

static int g_long_polling_receive_state = LONG_POLLING_RUNNING_STATE;
#endif

iotcs_result cl_post_activation(void) {
#ifdef IOTCS_STORAGE_SUPPORT     
    if (cl_internal_storage_init() != IOTCS_RESULT_OK) {
        LOG_CRITS("iotcs_get_storage_init method failed.");
    }
#endif
#ifdef IOTCS_LONG_POLLING
    g_long_polling_receive_state = LONG_POLLING_RUNNING_STATE;
#endif
#ifdef IOTCS_MESSAGE_DISPATCHER
    if (cl_internal_async_message_dispatcher_init(
#ifdef IOTCS_VIRTUALIZATION_SUPPORT
            dispatcher_send_callback,
            dispatcher_error_callback,
#else
            NULL,
            NULL,
#endif
            IOTCS_POLLING_TIMEOUT_MS,
#ifdef IOTCS_LONG_POLLING
            g_is_long_polling_required
#else
            IOTCS_FALSE
#endif
            ) != IOTCS_RESULT_OK) {
        return IOTCS_RESULT_FAIL;
    }
#endif /* #ifdef IOTCS_MESSAGE_DISPATCHER */
#ifdef IOTCS_MESSAGE_PERSISTENCE
    iotcs_message_persistence_init();
#endif
    device_model_capability_initilize();
    return IOTCS_RESULT_OK;
}

int iotcs_is_activated() {
    int rv;
    UTIL_LOCK_LL();
    rv = tam_is_activated();
    UTIL_UNLOCK_LL();
    return rv;
}

#ifdef IOTCS_LONG_POLLING

void cl_long_polling_receive_should_be_interrupted() {
    UTIL_LOCK_LP();
    if (g_long_polling_receive_state == LONG_POLLING_RUNNING_STATE) {
        g_long_polling_receive_state = LONG_POLLING_INTERRUPTED_STATE;
    }
    UTIL_UNLOCK_LP();
}

iotcs_bool cl_is_long_polling_receive_should_be_interrupted() {
    int rv = IOTCS_FALSE;
    UTIL_LOCK_LP();
    if (g_long_polling_receive_state == LONG_POLLING_INTERRUPTED_STATE) {
        g_long_polling_receive_state = LONG_POLLING_RUNNING_STATE;
        rv = IOTCS_TRUE;
    }
    UTIL_UNLOCK_LP();
    return rv;
}

void cl_long_polling_receive_should_be_stopped() {
    g_long_polling_receive_state = LONG_POLLING_STOP_STATE;
}

iotcs_bool cl_is_long_polling_receive_should_be_stopped() {
    int rv = IOTCS_FALSE;
    UTIL_LOCK_LP();
    if (g_long_polling_receive_state == LONG_POLLING_STOP_STATE) {
        rv = IOTCS_TRUE;
    }
    UTIL_UNLOCK_LP();
    return rv;
}

iotcs_result cl_post_long_polling_request(char** response_buf, cl_fail_string_desc *fail_reason, int32_t timeout_ms) {
    iotcs_result result = IOTCS_RESULT_OK;
    protocol_response response;
    char* url_body = util_get_url_buffer_lp();
    int32_t cur_timeout_ms = (timeout_ms < 0) ? IOTCS_LP_BIG_TIMEOUT_MS : timeout_ms;
    int32_t cur_timeout_sec;

    /* User set whole number of seconds in timeout for receive method (because it's a requirement). 
     * But we can setup IOTCS_LP_SMALL_TIMEOUT_MS in ms for more flexibility.
     */
    if (cur_timeout_ms == 0) {
        cur_timeout_sec = 0;
        GOTO_ERR(0 > util_safe_snprintf(url_body, UTIL_REST_API_URL_MAX_LENGTH, "/iot/api/v2/messages?acceptBytes=%d",
                IOTCS_LONG_POLLING_BUFFER_SIZE));
    } else {
        cur_timeout_sec = (cur_timeout_ms + CL_HALF_SECOND_VALUE_MS) / 1000;
        if (cur_timeout_sec == 0) {
            cur_timeout_sec = 1;
        }
        GOTO_ERR(0 > util_safe_snprintf(url_body, UTIL_REST_API_URL_MAX_LENGTH, "/iot/api/v2/messages?acceptBytes=%d&iot.sync&iot.timeout=%d",
                IOTCS_LONG_POLLING_BUFFER_SIZE, cur_timeout_sec));
    }

    protocol_request request = {
        .body = "",
        .url = url_body,
        .method = PROTOCOL_REQUEST_METHOD_POST,
        .headers =
        {
            .connection = "close",
            .content_type = "application/json",
            .host = tam_get_server_host(),
            .x_endpointId = tam_get_endpoint_id()
        }
    };

    result = http_proceed_request_lp(request, &response, cur_timeout_ms);

    if (result == IOTCS_RESULT_OK) {
        if (response.status_code == PROTOCOL_RESPONSE_CODE_ACCEPTED) {
            cl_check_min_accept_bytes(&response);
            result = IOTCS_RESULT_OK;
            *response_buf = response.body;
        } else {
            result = IOTCS_RESULT_FAIL;
            /* server returned not-OK status code, copy body with fail reason */
            if (fail_reason) {
                strncpy(fail_reason->buf, response.body, fail_reason->len);
            }
        }
    } else {
        result_to_reason(fail_reason, result);
    }
    //Free resources
    return result;

error:
    return IOTCS_RESULT_FAIL;
}
#endif

#define INCR_ARRAY_LEN 12
#ifndef BACKOFF_DELAY
#define BACKOFF_DELAY 1000
#endif
static int incr_timeout[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};

iotcs_result cl_post_messages(const char* buffer, char** response_buf, cl_fail_string_desc *fail_reason) {
    iotcs_result result = IOTCS_RESULT_OK;
    protocol_response response;
    char* url_body = util_get_url_buffer();
    int incr_timeout_iter = 0;

    if (buffer == NULL) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    GOTO_ERR(get_url_string_with_accepted_bytes(url_body, UTIL_REST_API_URL_MAX_LENGTH) != IOTCS_RESULT_OK);

    protocol_request request = {
        .body = buffer,
        .url = url_body,
        .method = PROTOCOL_REQUEST_METHOD_POST,
        .headers =
        {
            .connection = "close",
            .content_type = "application/json",
            .host = tam_get_server_host(),
            .x_endpointId = tam_get_endpoint_id()
        }
    };

    do {
        result = cl_proceed_request(request, &response);

        if (result == IOTCS_RESULT_OK) {
            if (response.status_code == PROTOCOL_RESPONSE_CODE_ACCEPTED) {
#ifndef IOTCS_LONG_POLLING
                cl_check_min_accept_bytes(&response);
#endif
                result = IOTCS_RESULT_OK;
                *response_buf = response.body;
            } else {
                result = IOTCS_RESULT_FAIL;
                /* server returned not-OK status code, copy body with fail reason */
                if (fail_reason && response.body) {
                    strncpy(fail_reason->buf, response.body, fail_reason->len);
                }
            }
        } else {
            result_to_reason(fail_reason, result);
        }

        // First timeout is zero
        iotcs_port_sleep_millis(incr_timeout[incr_timeout_iter] * BACKOFF_DELAY);
        if (incr_timeout_iter < INCR_ARRAY_LEN - 1) {
            incr_timeout_iter++;
        }
    } while (result == IOTCS_RESULT_OK &&
             response.status_code == PROTOCOL_RESPONSE_CODE_SERVICE_UNAVAILABLE);
    //Free resources
    return result;

error:
    util_free((char*) buffer);
    return IOTCS_RESULT_FAIL;
}

const char* iotcs_get_endpoint_id() {
    const char* endpoint;
    UTIL_LOCK_LL();
    endpoint = tam_get_endpoint_id();
    UTIL_UNLOCK_LL();
    return endpoint;
}

const char* cl_get_server_host() {
    const char* host;
    UTIL_LOCK_LL();
    host = tam_get_server_host();
    UTIL_UNLOCK_LL();
    return host;
}
int g_message_uid = 1;

iotcs_result cl_serialize_messages(iotcs_message messages[], size_t count, int first, int *serialized, int *guaranted_uid, int **loaded_uids) {
    int total_written = 0;
    int serialized_cnt = 0;
    int start_index = first;
    int loaded_data_size = 0;
    int ad_uid = -1;
    int batch_uid = -1;
    iotcs_message *tmp;
    char* tmp_message_buffer = util_get_payload_buffer();
    int written = 0;
    int *current_uid = NULL;
    // Null terminated Array of UID that was loaded from DB
    *loaded_uids = NULL;

    tmp_message_buffer[total_written++] = '[';
#ifdef IOTCS_MESSAGE_PERSISTENCE
    iotcs_message_persistence_load_messages(tam_get_endpoint_id(), tmp_message_buffer + total_written, &loaded_data_size, &ad_uid, UTIL_PAYLOAD_BUFFER_SIZE - total_written - 1);
    if (loaded_data_size > 0) {
        total_written += loaded_data_size;
        *guaranted_uid = ad_uid;
    }
#endif

    for (serialized_cnt = start_index; serialized_cnt < (int) count; (serialized_cnt)++) {
#ifdef IOTCS_MESSAGE_PERSISTENCE
        if (serialized_cnt < IOTCS_MAX_MESSAGES_FOR_SEND) {
#endif
            written = msg_toJSON(&messages[serialized_cnt], tmp_message_buffer + total_written,
                    UTIL_PAYLOAD_BUFFER_SIZE - total_written - 1 /* for closing ']'*/);
#ifdef IOTCS_MESSAGE_PERSISTENCE
            // Save message from array, because other was saved in msg_dispatcher.c
            tmp = &messages[serialized_cnt];
            if (tmp->base && tmp->base->reliability == IOTCS_MESSAGE_RELIABILITY_GUARANTED_DELIVERY &&
                (tmp->base->type == IOTCS_MESSAGE_DATA ||
                 tmp->base->type == IOTCS_MESSAGE_ALERT)) {
                char *json = util_safe_strncpy(tmp_message_buffer + total_written, written);
                char *endpoint = tam_get_endpoint_id();
                iotcs_message_persistence_save_message(tmp, json, endpoint);
                util_free(json);
            }
        } else {
            if (!current_uid) {
                *loaded_uids = util_malloc(sizeof(int) * (count - start_index + 1));
                current_uid = *loaded_uids;
            }
            iotcs_batch_by_load_messages(tmp_message_buffer + total_written, &loaded_data_size, &batch_uid, UTIL_PAYLOAD_BUFFER_SIZE - total_written - 1);
            if (loaded_data_size > 0) {
                *current_uid = batch_uid;
                current_uid++;
                written = loaded_data_size + 1;
            } else {
                written = 0;
            }
        }
#endif

        /* if written is negative it is equal to -(iotcs_result) */
        if (written <= 0) {
            break;
        }

        total_written += written;
        /* separate different jsons [{},{},{}...] by overwriting '\0' symbol */
        tmp_message_buffer[total_written - 1] = ',';
    }
    if (current_uid) {
        *current_uid = 0;
    }

    if (total_written > 1 /* '[' is always in the buffer */) {
        tmp_message_buffer[total_written - 1] = ']'; /* overwrite last comma */
        tmp_message_buffer[total_written] = '\0'; /* extra increment to get an exact number of bytes */
    }

    *serialized = *serialized + serialized_cnt - start_index;

    return written < 0 ? (iotcs_result) - written : IOTCS_RESULT_OK;
}

#ifndef IOTCS_MESSAGE_DISPATCHER
iotcs_result iotcs_send(iotcs_message messages[], size_t len) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    cl_fail_string_desc desc = {NULL, 0};
    char* payload_buffer = util_get_payload_buffer();
    int serialized = 0;
    int retry_cnt = 1; /* retry one time if authorization fail */
    int i;

    if (!messages || len <= 0) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    UTIL_LOCK_LL();
#if defined IOTCS_STORAGE_SUPPORT
    for (i = 0; i < len; i++) {
        if (messages[i].base->type != IOTCS_MESSAGE_DATA &&
            messages[i].base->type != IOTCS_MESSAGE_ALERT) {
            continue;
        }
        iotcs_value *values;
        const iotcs_data_item_desc *desc;
        if (messages[i].base->type == IOTCS_MESSAGE_DATA) {
            values = messages[i].u.data.items_value;
            desc = messages[i].u.data.items_desc;
        } else {
            values = messages[i].u.alert.items_value;
            desc = messages[i].u.alert.items_desc;
        }
        if (!values || !desc) {
            continue;
        }
        while (desc->key) {
            if (desc->type == IOTCS_VALUE_TYPE_URI) {
                if (IS_STORAGE_OBJECT(values->uri_object)) {
                    sd_storage_object *storage_object = (sd_storage_object *)values->uri_object->object;
                    while (storage_object->progress_state != IOTCS_COMPLETED) {
                        if (storage_object->progress_state == IOTCS_IN_PROGRESS ||
                            storage_object->progress_state == IOTCS_QUEUED) {

                            iotcs_port_sleep_millis(IOTCS_SYNC_WAIT_INTERVAL);
                        } else {
                            if (storage_object->progress_state == IOTCS_CANCELED) {
                                LOG_ERRS("Syncronization for storage object was canceled");
                            } else {
                                LOG_ERRS("Storage object can not be syncronized");
                            }
                            UTIL_UNLOCK_LL();
                            return IOTCS_RESULT_FAIL;
                        } 
                    }
                }
            }
            values++;
            desc++;
        }
    }
#endif

    do {
        int uid = 0, j, *batch_arr = NULL, *iter;

        if (IOTCS_RESULT_OK != (result = cl_serialize_messages(messages, len, 0, &serialized, &uid, &batch_arr))) {
            break;
        }

        if (serialized != (int) len) {
            result = IOTCS_RESULT_OUT_OF_MEMORY;
            break;
        }

        if (IOTCS_RESULT_OK == (result = post_messages_process_response(payload_buffer, &desc))) {
#ifdef IOTCS_MESSAGE_PERSISTENCE
            iotcs_bool anything_for_delete = IOTCS_FALSE;
            if (!uid) {
                for (j = 0; j < serialized; j++) {
                    if (messages[j].uid != 0) {
                        anything_for_delete = IOTCS_TRUE;
                    }
                }
            } else {
                anything_for_delete = IOTCS_TRUE;
            }
            if (anything_for_delete) {
                iotcs_message_persistence_delete_messages(messages, serialized, uid);
            }

            iter = batch_arr;
            if (iter) {
                while (*iter) {
                    iotcs_batch_by_delete_message(*iter);
                    iter++;
                }
            }
            util_free(batch_arr);
#endif
            break;
        }

        if (result != IOTCS_RESULT_CANNOT_AUTHORIZE) {
            break;
        }

        /* not authorized - try to get token again */
        if (IOTCS_RESULT_OK != cl_get_token()) {
            break;
        }
        serialized = 0;
    } while (retry_cnt--);

    {
        int i;
        for (i = 0; i < (int) len; i++) {
            cl_internal_response_message_free_unsafe(&messages[i]);
        }
    }
    UTIL_UNLOCK_LL();

    return result;
}

iotcs_request_message* iotcs_receive(int32_t timeout) {
    return cl_internal_receive(timeout);
}
#endif

iotcs_result cl_internal_send(iotcs_message messages[], int count, int* posted, cl_fail_string_desc *fail_reason) {
    iotcs_result result;
    UTIL_LOCK_LL();
    result = cl_internal_send_unsafe(messages, count, posted, fail_reason);
    UTIL_UNLOCK_LL();
    return result;
}

iotcs_result cl_internal_send_unsafe(iotcs_message messages[], int count, int* posted, cl_fail_string_desc *fail_reason) {
    iotcs_result rv = IOTCS_RESULT_FAIL;
    int serialized = 0;
    char* payload_buff = util_get_payload_buffer();
    int retry_cnt = 1; /* retry one time if authorization fail */
    int i;
    int first = 0;
#if defined IOTCS_STORAGE_SUPPORT
    if (posted) {
        first = *posted;
    }
    for (i = first; i < count; i++) {
        if (i >= IOTCS_MAX_MESSAGES_FOR_SEND) {
            break;
        }
		if (messages[i].base &&
			messages[i].base->type != IOTCS_MESSAGE_DATA &&
            messages[i].base->type != IOTCS_MESSAGE_ALERT) {
            continue;
        }
        iotcs_value *values;
        const iotcs_data_item_desc *desc;
        if (messages[i].base->type == IOTCS_MESSAGE_DATA) {
            values = messages[i].u.data.items_value;
            desc = messages[i].u.data.items_desc;
        } else {
            values = messages[i].u.alert.items_value;
            desc = messages[i].u.alert.items_desc;
        }
        if (!values || !desc) {
            continue;
        }
        while (desc->key) {
            if (desc->type == IOTCS_VALUE_TYPE_URI) {
                if (IS_STORAGE_OBJECT(values->uri_object)) {
                    sd_storage_object *storage_object = (sd_storage_object *)values->uri_object->object;
                    while (storage_object->progress_state != IOTCS_COMPLETED) {
                        if (storage_object->progress_state == IOTCS_IN_PROGRESS ||
                            storage_object->progress_state == IOTCS_QUEUED) {

                            iotcs_port_sleep_millis(IOTCS_SYNC_WAIT_INTERVAL);
                        } else {
                            if (storage_object->progress_state == IOTCS_CANCELED) {
                                strncpy(fail_reason->buf, "Syncronization for storage object was canceled", fail_reason->len);
                            } else {
                                strncpy(fail_reason->buf, "Storage object can not be syncronized", fail_reason->len);
                            }
                            return IOTCS_RESULT_FAIL;
                        } 
                    }
                }
            }
            values++;
            desc++;
        }
    }
#endif
    do {
        int uid = 0, j, *batch_arr = NULL, *iter;

        rv = cl_serialize_messages(messages, count, first, &serialized, &uid, &batch_arr);

        if (posted) {
            *posted = (serialized == 0) ? *posted : *posted + serialized; //when serialized == 0 then current msg is too large or invalid
        }

        if (rv != IOTCS_RESULT_OK) {
            break;
        }

        if (serialized <= 0) {
            strncpy(fail_reason->buf, "Message can't be serialized", fail_reason->len);
            break;
        }

        if (IOTCS_RESULT_OK == (rv = post_messages_process_response(payload_buff, fail_reason))) {
#ifdef IOTCS_MESSAGE_PERSISTENCE
            iotcs_bool anything_for_delete = IOTCS_FALSE;
            if (!uid) {
                first = 0;
                if (posted) {
                    first = *posted;
                }
                if (first < IOTCS_MAX_MESSAGES_FOR_SEND) {
                    for (j = first; j < serialized; j++) {
                        if (j >= IOTCS_MAX_MESSAGES_FOR_SEND) {
                            break;
                        }
                        if (messages[j].base->reliability == IOTCS_MESSAGE_RELIABILITY_GUARANTED_DELIVERY &&
                            (messages[j].base->type == IOTCS_MESSAGE_DATA ||
                             messages[j].base->type == IOTCS_MESSAGE_ALERT)) {
                            if (messages[j].uid != 0) {
                                anything_for_delete = IOTCS_TRUE;
                            }
                        }
                    }
                }
            } else {
                anything_for_delete = IOTCS_TRUE;
            }
            first = 0;
            if (posted) {
                first = *posted;
            }
            if (anything_for_delete) {
                if (first < IOTCS_MAX_MESSAGES_FOR_SEND) {
                    iotcs_message_persistence_delete_messages(messages, serialized, uid);
                } else {
                    iotcs_message_persistence_delete_messages(NULL, 0, uid);
                }
            }

            iter = batch_arr;
            if (iter) {
                while (*iter) {
                    iotcs_batch_by_delete_message(*iter);
                    iter++;
                }
            }
            util_free(batch_arr);
#endif
            break;
        }

        if (rv != IOTCS_RESULT_CANNOT_AUTHORIZE) {
            break;
        }

        /* not authorized - try to get token again */
        if (posted) {
            *posted = *posted - serialized; // Rollback serialized
        }
        if (IOTCS_RESULT_OK != cl_get_token()) {
            break;
        }
        serialized = 0;
    } while (retry_cnt--);
    {
        int i;
        int first = 0;
        if (posted) {
            first = *posted;
        }
        for (i = first; i < serialized; i++) {
            if (i >= IOTCS_MAX_MESSAGES_FOR_SEND) {
                break;
            }
            cl_internal_response_message_free_unsafe(&messages[i]);
        }
    }

    return rv;
}

static iotcs_result poll_server_for_requests(char** out_response_request) {
    iotcs_result result = IOTCS_RESULT_OK;
    protocol_response response;
    char* url_body = util_get_url_buffer();

    /* We won't be able to get responses if device isn't activated */
    if (!tam_is_activated()) {
        return IOTCS_RESULT_OK;
    }

    CHECK_ERR(get_url_string_with_accepted_bytes(url_body, UTIL_REST_API_URL_MAX_LENGTH));

    protocol_request request = {
        .body = "",
        .url = url_body,
        .method = PROTOCOL_REQUEST_METHOD_POST,
        .headers =
        {
            .connection = "close",
            .content_type = "application/json",
            .host = tam_get_server_host(),
            .x_endpointId = tam_get_endpoint_id()
        }
    };

    result = cl_proceed_request(request, &response);

    if (result == IOTCS_RESULT_OK) {
        result = response.status_code == PROTOCOL_RESPONSE_CODE_ACCEPTED ? IOTCS_RESULT_OK : IOTCS_RESULT_FAIL;
    }

    if (result == IOTCS_RESULT_OK) {
        cl_check_min_accept_bytes(&response);
        *out_response_request = response.body;
    }

    return result;
}

static void cl_get_requests_messages_unsafe(void) {
    char* response_buf = NULL;
    iotcs_result rv = poll_server_for_requests(&response_buf);

    if (rv == IOTCS_RESULT_CANNOT_AUTHORIZE) {
        if (IOTCS_RESULT_OK != cl_get_token()) {
            LOG_ERRS("Method cl_get_token failed.");
            return;
        }

        if (poll_server_for_requests(&response_buf) != IOTCS_RESULT_OK) {
            LOG_ERRS("Method poll_server_for_requests failed.");
            return;
        }
    }

    if (response_buf) {
        cl_process_message_from_json_unsafe(response_buf);
    }
}

void cl_get_requests_messages(void) {
    UTIL_LOCK_LL();
    cl_get_requests_messages_unsafe();
    UTIL_UNLOCK_LL();
}

void cl_put_special_request_message(iotcs_request_message *request, int32_t timeout) {
    iotcs_port_request_queue_put(request_queue, request, timeout);
}

iotcs_request_message* cl_get_request_message_blocking(int32_t timeout) {
    iotcs_request_message* rv = NULL;
    iotcs_port_request_queue_get(request_queue, &rv, timeout);
    return rv;
}

iotcs_request_message* cl_internal_receive(int32_t timeout) {
    iotcs_request_message* response_request;

#ifdef IOTCS_LONG_POLLING
    if (!g_is_long_polling_required)
#endif
    {
        do {
            UTIL_LOCK_LL();
            response_request = NULL;
            /* poll server only if no pending requests and timeout is non-zero */
            if (IOTCS_RESULT_OK != iotcs_port_request_queue_get(request_queue, &response_request, 0) &&
                    timeout) {
                timeout = 0; /* don't poll more that once */
                cl_get_requests_messages_unsafe();
                iotcs_port_request_queue_get(request_queue, &response_request, 0);
            }
            UTIL_UNLOCK_LL();
            /* redo if it's valid capability request */
        } while (response_request != NULL && IOTCS_RESULT_OK == device_model_process_capability_request(response_request));

        return response_request;
    }
#ifdef IOTCS_LONG_POLLING
    else {
        char* response = NULL;

        UTIL_LOCK_LP();
        if (IOTCS_RESULT_OK == iotcs_port_request_queue_get(request_queue, &response_request, 0)) {
            UTIL_UNLOCK_LP();
            return response_request;
        }

        if (g_is_long_polling_active) {
            UTIL_UNLOCK_LP();
            return NULL;
        }

        g_is_long_polling_active = 1;
        UTIL_UNLOCK_LP();

        cl_post_long_polling_request(&response, NULL, timeout);

        UTIL_LOCK_LP();
        UTIL_LOCK_LL();
        if (response) {
            cl_process_message_from_json_unsafe(response);
        }
        UTIL_UNLOCK_LL();

        response_request = NULL;
        iotcs_port_request_queue_get(request_queue, &response_request, 0);

        /*Receive method returns NULL if there was capability request*/
        if (response_request && IOTCS_RESULT_OK == device_model_process_capability_request(response_request)) {
            response_request = NULL;
        }

        g_is_long_polling_active = 0;
        UTIL_UNLOCK_LP();

        return response_request;
    }
#endif
}
