/*
 * Copyright (c) 2015, 2016, 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 <stdlib.h>
#include <assert.h>

#include "iotcs.h"
#include "util/util.h"
#include "util/util_memory.h"
#include "util/util_buffer.h"
#include "trusted_assets_manager/iotcs_tam.h"

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

#include "iotcs_port_crypto.h"
#include "iotcs_port_system.h"
#include "iotcs_port_tam.h"

/* One byte for tag + two bytes for item's length */
#define TAM_ITEM_HEADER_SIZE 3

/*
 * By default = 127 + '/0'
 * We get it from tam store.
 */
#ifndef IOTCS_SERVER_HOST_BUFFER_LENGTH
#define IOTCS_SERVER_HOST_BUFFER_LENGTH 128
#endif

/*
 * By default = 18(SYSTEM_DEFAULT_CA + '/0')
 * We get it from tam store.
 */
#ifndef IOTCS_TRUST_STORE_BUFFER_LENGTH
#define IOTCS_TRUST_STORE_BUFFER_LENGTH 18
#endif

/*
 * By default = 20 + '/0'
 * We get it from tam store.
 */
#ifndef IOTCS_CLIENT_ID_BUFFER_LENGTH
#define IOTCS_CLIENT_ID_BUFFER_LENGTH 21
#endif

/*
 * By default = 50 + '/0'
 * We get it from tam store in base 64.
 */
#ifndef IOTCS_SHARED_SECRET_BUFFER_LENGTH
#define IOTCS_SHARED_SECRET_BUFFER_LENGTH 51
#endif

/*
 * By default = 256
 * We get it from tam store.
 */
#ifndef IOTCS_ASSETS_SIGNATURE_BUFFER_LENGTH
#define IOTCS_ASSETS_SIGNATURE_BUFFER_LENGTH 256
#endif

/*
 * By default = 350. It's enough for public key with length 2048 bits.
 * We get be generated by port_crypto. Used activation policy parameters.
 */
#ifndef IOTCS_PUBLIC_KEY_BUFFER_LENGTH
#define IOTCS_PUBLIC_KEY_BUFFER_LENGTH 350
#endif

/*
 * Max lenth = 12 (server.port=) + 5(max port value - 65535) + 1('/n') + 1('/0') = 19
 */
#ifndef IOTCS_SERVER_PORT_LENGTH
#define IOTCS_SERVER_PORT_LENGTH 19
#endif

/*
 * Max lenth = 10
 */
#ifndef IOTCS_SERVER_SCHEME_LENGTH
#define IOTCS_SERVER_SCHEME_LENGTH 9
#endif

/*
 * Max trusted assets path legnth = 128
 */
#ifndef IOTCS_TRUSTED_ASSETS_STORE_PATH_LENGTH
#define IOTCS_TRUSTED_ASSETS_STORE_PATH_LENGTH 128
#endif

/*
 * if "trustAnchors" property is equal to this string then system default CA store
 * must be used for host certificate validation */
#define SYSTEM_DEFAULT_CA "SYSTEM_DEFAULT_CA"

/*
 * if "trustAnchors" property is equal to this string then CA store is in the same
 * file below between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----
 */
#define IN_PLACE_CA "IN_PLACE_CA"

/*
 * To have a simple 1 byte version and be readable outside of the blob,
 * the first version number will be first printable ASCII character 33 (!).
 *
 * Since the last printable char is 126 (~), we can have 94 versions.
 */
#define TAM_DEFAULT_FORMAT_VERSION 33

/* max port = 65535 */
#define TAM_MAX_PORT_LENGTH 5
#define TAM_DEFAULT_HTTPS_PORT 443
#define TAM_DEFAULT_MQTTS_PORT 8883

#define TAM_SERVER_HOST_PROP "server.host"

static char tam_server_scheme[IOTCS_SERVER_SCHEME_LENGTH];
static char tam_server_host[IOTCS_SERVER_HOST_BUFFER_LENGTH];
static int tam_server_port;
static char tam_trust_store_type[IOTCS_TRUST_STORE_BUFFER_LENGTH];
static char tam_client_id[IOTCS_CLIENT_ID_BUFFER_LENGTH];
static char encrypted_shared_secret[IOTCS_SHARED_SECRET_BUFFER_LENGTH];
static char tam_shared_secret[IOTCS_SHARED_SECRET_BUFFER_LENGTH];
static unsigned char tam_device_public_key[IOTCS_PUBLIC_KEY_BUFFER_LENGTH];
static size_t tam_device_public_key_len = IOTCS_PUBLIC_KEY_BUFFER_LENGTH;
static char tam_endpoint_id[IOTCS_CLIENT_ID_BUFFER_LENGTH];
static size_t tam_endpoint_id_length = IOTCS_CLIENT_ID_BUFFER_LENGTH;
static int tam_is_initialized = 0;
static iotcs_bool is_unified_tas = IOTCS_FALSE;
static int format_version = 0;
static char tas_path[IOTCS_TRUSTED_ASSETS_STORE_PATH_LENGTH];

/**
 * @struct tam_icd_list
 * @brief Contains activation ids and shared secrets for indirect connected devices.
 */
typedef struct {
    char* activation_id;
    unsigned char* shared_secret;
    void* next;
} tam_icd_list;

static tam_icd_list* icd_list = NULL;

static unsigned char* get_shared_secret_from_icd_list(const char* activation_id) {
    tam_icd_list* current = icd_list;
    while (current) {
        if (strcmp(current->activation_id, activation_id) == 0) {
            return current->shared_secret;
        }
        current = current->next;
    }

    return NULL;
}

static iotcs_result add_new_icd(const char* activation_id, const unsigned char* shared_secret) {
    //add first element
    tam_icd_list* new_icd;
    CHECK_OOM(new_icd = (tam_icd_list*) util_malloc(sizeof (tam_icd_list)));
    CHECK_OOM(new_icd->activation_id = util_safe_strcpy(activation_id));
    CHECK_OOM(new_icd->shared_secret = (unsigned char*) util_safe_strcpy((const char*) shared_secret));

    new_icd->next = icd_list;
    icd_list = new_icd;

    return IOTCS_RESULT_OK;
}

static void release_icd_list() {
    tam_icd_list* current = icd_list;
    while (current) {
        tam_icd_list* next = current->next;
        util_free(current->activation_id);
        util_free(current->shared_secret);
        util_free(current);
        current = next;
    }
    icd_list = NULL;
}

typedef struct {
    FILE *fp;
    char *buf;
    size_t buf_len;
    int pos;
} tam_prop_file_desc;

static int read_string_property(tam_prop_file_desc *desc, const char *prop_name, char *prop_value_buf, size_t prop_value_buf_len) {
#define PROP_FORMAT_SIZE 128
    char *line_buf = desc->buf + PROP_FORMAT_SIZE;
    GOTO_ERR(0 >= util_safe_snprintf(desc->buf, PROP_FORMAT_SIZE, "%s=%%%us", prop_name, prop_value_buf_len - 1));
    GOTO_ERR(NULL == fgets(line_buf, desc->buf_len - PROP_FORMAT_SIZE, desc->fp));
    if (sscanf(line_buf, desc->buf, prop_value_buf) != 1) {
        LOG_INFO("Could not find %s property value", prop_name);
        fseek(desc->fp, desc->pos, SEEK_SET);
        return -1;
    }
    desc->pos += strlen(line_buf);
    LOG_INFO("%s=%s", prop_name, prop_value_buf);
    return 0;
error:
    LOG_ERR("Failed to read %s property value", prop_name);
    return -1;
}

static int read_int_property(tam_prop_file_desc *desc, const char *prop_name, int *prop_value) {
#define PROP_FORMAT_SIZE 128
    char *line_buf = desc->buf + PROP_FORMAT_SIZE;
    GOTO_ERR(0 >= util_safe_snprintf(desc->buf, PROP_FORMAT_SIZE, "%s=%%d", prop_name));
    GOTO_ERR(NULL == fgets(line_buf, desc->buf_len - PROP_FORMAT_SIZE, desc->fp));
    desc->pos += strlen(line_buf);
    GOTO_ERR(sscanf(line_buf, desc->buf, prop_value) != 1);
    LOG_INFO("%s=%d", prop_name, *prop_value);
    return 0;
error:
    LOG_ERR("Failed to read %s property value", prop_name);
    return -1;
}

static void fill_iv(unsigned char *iv) {
    int64_t mills = iotcs_port_get_current_time_millis();
    int seed, i;

    if (sizeof (int) != sizeof (int64_t)) {
        seed = (int) mills;
        seed ^= (int) (mills >> 32);
    } else {
        seed = (int) mills;
    }
    srand(seed);

    i = TAM_AES_CBC_128_IV_LENGTH;
    while (i--) {
        *iv++ = rand();
    }
}

static int write_item(unsigned char **buf, int *buf_len, int tag, const unsigned char *item, int item_len) {
    int bytes_to_write = TAM_ITEM_HEADER_SIZE + item_len;
    unsigned char *p = *buf;

    if (*buf_len < bytes_to_write) {
        return -1;
    }
    *p++ = tag;
    *p++ = item_len >> 8;
    *p++ = item_len;
    memcpy(p, item, item_len);

    *buf_len -= bytes_to_write;
    *buf += bytes_to_write;

    return bytes_to_write;
}

static int write_credentials(unsigned char *buf, int len, unsigned char *tmp_buf, int tmp_buf_len) {
    int written, initial_len = len;

    /* Server URI */
    GOTO_ERR_MSG(0 > (written = util_safe_snprintf((char *) tmp_buf, tmp_buf_len, "%s://%s:%d", tam_server_scheme, tam_server_host, tam_server_port)), "failed to form server URL");
    GOTO_ERR_MSG(0 > write_item(&buf, &len, TAM_SERVER_URI_TAG, tmp_buf, written), "failed to write server URL string");

    /* Client ID */
    GOTO_ERR_MSG(0 > write_item(&buf, &len, TAM_CLIENT_ID_TAG, (unsigned char *) tam_get_client_id(), strlen(tam_get_client_id())), "failed to write client ID");

    /* Shared secret */
    GOTO_ERR_MSG(0 > write_item(&buf, &len, TAM_SHARED_SECRET_TAG, (unsigned char *) tam_shared_secret, strlen(tam_shared_secret)), "failed to write shared secret");

    /* Endpoint ID */
    GOTO_ERR_MSG(0 > write_item(&buf, &len, TAM_ENDPOINT_ID_TAG, (unsigned char *) tam_get_endpoint_id(), strlen(tam_get_endpoint_id())), "failed to write shared secret");

    /* trusted anchors */
    {
        size_t certs = tam_get_trust_anchors_count();
        unsigned char *cert_buf = tmp_buf;
        const char *cert;
        int cert_buf_len;
        size_t cert_len, i;

        cert_buf_len = tmp_buf_len;
        for (i = 0; i < certs; i++) {
            cert = tam_get_trust_anchor_certificate(i, &cert_len);
            GOTO_ERR(cert == NULL);
            GOTO_ERR_MSG(util_size_t_to_int(cert_len) < 0, "Loss of data found");
            GOTO_ERR(cert_buf_len < (int) cert_len);
            memcpy(cert_buf, cert, cert_len);
            cert_buf += (int) cert_len;
            cert_buf_len -= (int) cert_len;
        }

        GOTO_ERR_MSG(0 > write_item(&buf, &len, TAM_TRUST_ANCHOR_TAG, tmp_buf, tmp_buf_len - cert_buf_len), "failed to write CA");
    }

    /* private key */
    {
        size_t written_len;
        GOTO_ERR(IOTCS_RESULT_OK != iotcs_port_crypto_serialize_private_key(tmp_buf, tmp_buf_len, &written_len));
        GOTO_ERR_MSG(0 > write_item(&buf, &len, TAM_PRIVATE_KEY_TAG, tmp_buf, (int) written_len), "failed to write key");
    }

    /*connected devices*/
    {
        tam_icd_list* current = icd_list;
        while (current) {
            int icd_data_len = strlen(current->activation_id) + strlen((const char*) current->shared_secret) + 2 * TAM_ITEM_HEADER_SIZE;
            GOTO_ERR(len < TAM_ITEM_HEADER_SIZE);
            *buf++ = TAM_CONNECTED_DEVICE_TAG;
            *buf++ = icd_data_len >> 8;
            *buf++ = icd_data_len;
            len -= TAM_ITEM_HEADER_SIZE;
            //icd id
            GOTO_ERR_MSG(0 > write_item(&buf, &len, TAM_CLIENT_ID_TAG, (unsigned char *) current->activation_id, strlen(current->activation_id)), "failed to write ICD ID");
            //icd shared secret
            GOTO_ERR_MSG(0 > write_item(&buf, &len, TAM_SHARED_SECRET_TAG, (unsigned char *) current->shared_secret, strlen((const char*) current->shared_secret)), "failed to write ICD shared secret");
            current = current->next;
        };
    }

    return initial_len - len;
error:
    return -1;
}

static iotcs_result store_credentials(void) {
    if (is_unified_tas) {
        FILE *f = NULL;
        size_t output_len;
        unsigned char *buf1 = util_get_load_tam_decrypted_buffer();
        unsigned char *buf2 = util_get_load_tam_encrypted_buffer();
        unsigned char *iv = buf1;
        size_t credentials_len;

        /* put IV to beginning of buf 1 : buf1 IV | buf2 --- */
        fill_iv(iv);

        /* put credentials beginning of buf 2 :  buf1 IV | buf2 credentials */
        GOTO_ERR(0 >= (credentials_len = write_credentials(buf2, UTIL_READ_TAS_BUFFER_LENGTH, buf1 + TAM_AES_CBC_128_IV_LENGTH, UTIL_READ_TAS_BUFFER_LENGTH - TAM_AES_CBC_128_IV_LENGTH)));

        /* put encoded credentials into buf 1 straight after IV : buf1 IV/credentials | buf2 credentials */
        output_len = UTIL_READ_TAS_BUFFER_LENGTH - TAM_AES_CBC_128_IV_LENGTH;
        GOTO_ERR(IOTCS_RESULT_OK != iotcs_port_tam_unified_credentials_encrypt(buf1 + TAM_AES_CBC_128_IV_LENGTH, &output_len, buf2, (size_t) credentials_len, iv));

        /* put base 64 encoded encoded credentials + IV into buf 2 straight after IV : buf1 IV/credentials | buf2 base64(IV/credentials) */
        credentials_len = UTIL_READ_TAS_BUFFER_LENGTH;
        if (IOTCS_RESULT_OK != iotcs_port_crypto_encode_base64((char*) buf2, &credentials_len, (const char*) buf1, output_len + TAM_AES_CBC_128_IV_LENGTH)) {
            LOG_CRITS("The default value of the IOTCS_PAYLOAD_BUFFER_SIZE value should be increased.");
            goto error;
        }


        GOTO_ERR((f = fopen(tas_path, "wb")) == NULL);
        GOTO_ERR(EOF == fputc(TAM_DEFAULT_FORMAT_VERSION, f));
        GOTO_ERR(credentials_len != fwrite(buf2, 1, credentials_len, f));
        GOTO_ERR(0 > util_safe_snprintf((char *) buf1, UTIL_READ_TAS_BUFFER_LENGTH, "%s://%s:%d", tam_server_scheme, tam_server_host, tam_server_port));
        GOTO_ERR(0 > fprintf(f, "\n#serverUri:%s", buf1));
        GOTO_ERR(0 > fprintf(f, "\n#clientId:%s", tam_get_client_id()));
        fclose(f);
        return IOTCS_RESULT_OK;
error:
        if (f != NULL) {
            fclose(f);
        }
        return IOTCS_RESULT_FAIL;
    }
    return iotcs_port_tam_credentials_store();
}

static char* find_sym(char *buf, int len, char sym) {
    while (len && *buf != sym) {
        buf++;
        len--;
    }

    if (!len) {
        return NULL;
    }
    return buf;
}

static int parse_server_uri(char *buf, int len) {
    char *end_ptr;
    int str_len;
    iotcs_bool use_default_port = IOTCS_FALSE;
    /*
     * example <scheme>://<host>[:<port>]
     */

    if (NULL == (end_ptr = find_sym(buf, len, ':'))) {
        return -1;
    }
    str_len = end_ptr - buf;
    GOTO_ERR(IOTCS_SERVER_SCHEME_LENGTH <= str_len);
    memcpy(tam_server_scheme, buf, str_len);
    tam_server_scheme[str_len] = '\0';

    if (strcmp("https", tam_server_scheme) == 0) {
        tam_server_port = TAM_DEFAULT_HTTPS_PORT;
    } else if (strcmp("mqtts", tam_server_scheme) == 0) {
        tam_server_port = TAM_DEFAULT_MQTTS_PORT;
    } else {
        LOG_ERR("Unsupported server scheme: %s", tam_server_scheme);
        return -1;
    }
    LOG_INFO("server_scheme=%s", tam_server_scheme);
    buf += str_len + 1;
    len -= str_len + 1;

    GOTO_ERR(len < 2 || buf[0] != '/' || buf[1] != '/');
    buf += 2;
    len -= 2;

    // no port set, so use default for the server scheme
    if (NULL == (end_ptr = find_sym(buf, len, ':'))) {
        use_default_port = IOTCS_TRUE;
        end_ptr = buf + len;
    }
    str_len = end_ptr - buf;
    GOTO_ERR(IOTCS_SERVER_HOST_BUFFER_LENGTH <= str_len);
    memcpy(tam_server_host, buf, str_len);
    tam_server_host[str_len] = '\0';
    LOG_INFO("server_host=%s", tam_server_host);

    if (use_default_port) {
        LOG_INFO("Use default server port for \"%s\" server scheme", tam_server_scheme);
    } else {
        buf += str_len + 1;
        len -= str_len + 1;
        // TAM_MAX_PORT_LENGTH + '\0'
        char port_str[TAM_MAX_PORT_LENGTH + 1];
        str_len = len < TAM_MAX_PORT_LENGTH ? len : TAM_MAX_PORT_LENGTH;
        memcpy(port_str, buf, str_len);
        port_str[str_len] = '\0';
        tam_server_port = atoi(port_str);
    }
    LOG_INFO("server_port=%d", tam_server_port);

    return 0;
error:
    return -1;
}

static iotcs_result iotcsp_read_from_unified_tam_store(FILE *prop_file, size_t* signed_data_length) {
    iotcs_result rv = IOTCS_RESULT_FAIL;
    unsigned char *input = util_get_load_tam_encrypted_buffer();
    unsigned char *output = util_get_load_tam_decrypted_buffer();
    size_t output_length = UTIL_READ_TAS_BUFFER_LENGTH;
    int entry_count;

    size_t len_read = fread(input, 1, UTIL_READ_TAS_BUFFER_LENGTH, prop_file);

    if (!feof(prop_file) || ferror(prop_file) || len_read <= 0) {
        LOG_ERRS("failed to read assets file");
        return IOTCS_RESULT_FAIL;
    }

    /* find string before first comment - required encrypted data */
    GOTO_ERR_MSG(NULL == (input = (unsigned char *) util_get_next_token((char **) &input, '#')),
            "failed to read assets file");

    size_t data_len = strlen((char*) input);

    input = (unsigned char *) util_remove_symbol_from_str((char**) &input, '\n', &entry_count);
    data_len -= entry_count;
    input = (unsigned char *) util_remove_symbol_from_str((char**) &input, '\r', &entry_count);
    data_len -= entry_count;

    /* check that there are no corruption and string is NULL terminated */
    assert(strlen((char*) input) == data_len);

    /* Do in place base64 decoding and make sure that we have more than
     * TAM_AES_CBC_128_IV_LENGTH bytes in output */
    GOTO_ERR_MSG(IOTCS_RESULT_OK != (rv = iotcs_port_crypto_decode_base64((char*) input, &data_len, (const char*) input, data_len) ||
            data_len <= TAM_AES_CBC_128_IV_LENGTH), "failed to decode base64");

    {
        unsigned char *iv = input;
        input += TAM_AES_CBC_128_IV_LENGTH;
        data_len -= TAM_AES_CBC_128_IV_LENGTH;

        GOTO_ERR(IOTCS_RESULT_OK != (rv = iotcs_port_tam_unified_credentials_decrypt(output, &output_length,
                input, data_len, iv)));
    }

    /*How information is encoded:
     *First byte - field tag:
     *    01 - server uri
     *    02 - client id
     *    03 - shared secret
     *    04 - endpoint id
     *    05 - trust store anchor
     *    06 - private key
     *    07 - public key
     *    08 - indirect connected device
     * Second byte - high byte of the length
     * Third byte - low byte of the length
     * N bytes of the value
     * 
     * Example:
     * 
     *  01 00 09 69 6f 74 73 65 72 76 65 72 - "iotserver"  
     */

    rv = IOTCS_RESULT_FAIL;
    while (output_length > TAM_ITEM_HEADER_SIZE) {
        char tag = output[0];
        int value_length = (output[1] << 8) | output[2];
        output += TAM_ITEM_HEADER_SIZE;
        output_length -= TAM_ITEM_HEADER_SIZE;
        GOTO_ERR_MSG(util_size_t_to_int(output_length) < 0, "Loss of data found");
        GOTO_ERR_MSG(value_length != 0 && (int) output_length < value_length, "Asset file is inconsistent");
        switch (tag) {
            case TAM_SERVER_URI_TAG:
                GOTO_ERR(parse_server_uri((char *) output, value_length));
                break;
            case TAM_CLIENT_ID_TAG:
                GOTO_ERR_MSG(IOTCS_CLIENT_ID_BUFFER_LENGTH <= value_length, "Client id is too long");
                memcpy(tam_client_id, output, value_length);
                tam_client_id[value_length] = '\0';
                LOG_INFO("client_id=%s", tam_client_id);
                break;
            case TAM_SHARED_SECRET_TAG:
                GOTO_ERR_MSG(IOTCS_SHARED_SECRET_BUFFER_LENGTH <= value_length, "Shared secret is too long");
                memcpy(tam_shared_secret, output, value_length);
                tam_shared_secret[value_length] = '\0';
                LOG_INFO("shared_secret=%s", tam_shared_secret);
                break;
            case TAM_ENDPOINT_ID_TAG:
                GOTO_ERR_MSG(IOTCS_CLIENT_ID_BUFFER_LENGTH <= value_length, "Endpoint id is too long");
                memcpy(tam_endpoint_id, output, value_length);
                tam_endpoint_id[value_length] = '\0';
                LOG_INFO("endpoint_id=%s", tam_endpoint_id);
                break;
            case TAM_TRUST_ANCHOR_TAG:
                GOTO_ERR_MSG(iotcs_port_tam_load_trust_store_achors(output, value_length), "Failed to load CA roots");
                break;
            case TAM_PRIVATE_KEY_TAG:
                GOTO_ERR_MSG((IOTCS_RESULT_OK != iotcs_port_crypto_deserialize_private_key((unsigned char*) output, value_length)),
                        "iotcs_port_crypto_deserialize_private_key method failed");
                break;
            case TAM_PUBLIC_KEY_TAG:
                GOTO_ERR_MSG(1, "Getting private key isn't implemented!");
                break;
            case TAM_CONNECTED_DEVICE_TAG:
            {
                char* icd_activation_id = NULL;
                unsigned char* icd_shared_secret = NULL;
                int icd_activation_id_length, icd_shared_secret_length;
                input = util_get_load_tam_encrypted_buffer();

                GOTO_ERR(TAM_CLIENT_ID_TAG != output[0]);
                GOTO_ERR(UTIL_RESPONSE_BUFFER_LENGTH / 2 < (icd_activation_id_length = (output[1] << 8) | output[2]));
                output += TAM_ITEM_HEADER_SIZE;
                output_length -= TAM_ITEM_HEADER_SIZE;

                memcpy(input, output, icd_activation_id_length);
                input[icd_activation_id_length] = '\0';

                icd_activation_id = (char*) input;
                input += icd_activation_id_length + 1;

                output += icd_activation_id_length;
                output_length -= icd_activation_id_length;

                GOTO_ERR(TAM_SHARED_SECRET_TAG != output[0]);
                GOTO_ERR((UTIL_RESPONSE_BUFFER_LENGTH / 2 - icd_activation_id_length) <
                        (icd_shared_secret_length = (output[1] << 8) | output[2]));
                output += TAM_ITEM_HEADER_SIZE;
                output_length -= TAM_ITEM_HEADER_SIZE;

                memcpy(input, output, icd_shared_secret_length);
                input[icd_shared_secret_length] = '\0';

                icd_shared_secret = input;

                output += icd_shared_secret_length;
                output_length -= icd_shared_secret_length;

                value_length = 0;

                LOG_INFO("icd_activation_id=%s", icd_activation_id);
                LOG_INFO("icd_shared_secret=%s", icd_shared_secret);
                GOTO_ERR(IOTCS_RESULT_OK != add_new_icd(icd_activation_id, icd_shared_secret));
                break;
            }
            default:
                GOTO_ERR_MSG(1, "unexpected TAG in assets file");
        }
        output += value_length;
        output_length -= value_length;
    }
    rv = IOTCS_RESULT_OK;

error:

    return rv;
}

const char* tam_get_server_scheme(void) {
    return tam_server_scheme;
}

static iotcs_result iotcsp_read_from_custom_tam_store(FILE *prop_file, size_t* signed_data_length) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    tam_prop_file_desc desc = {prop_file, (char *) util_get_load_tam_decrypted_buffer(), UTIL_READ_TAS_BUFFER_LENGTH, 0};
    char* icd_id = (char *) util_get_load_tam_encrypted_buffer();

    GOTO_ERR(read_string_property(&desc, "client.id", tam_client_id, sizeof (tam_client_id)));
    GOTO_ERR(read_string_property(&desc, "client.secret", encrypted_shared_secret, sizeof (encrypted_shared_secret)));
    GOTO_ERR(read_string_property(&desc, "trustAnchors", tam_trust_store_type, sizeof (tam_trust_store_type)));

    if (0 == strcmp(tam_trust_store_type, IN_PLACE_CA)) {
        char *line_buf = desc.buf + PROP_FORMAT_SIZE;
        GOTO_ERR(0 >= util_safe_snprintf(desc.buf, PROP_FORMAT_SIZE, "%s=%%%us", TAM_SERVER_HOST_PROP, sizeof (tam_server_host) - 1));

        /* skip cert - everything between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- */
        do {
            GOTO_ERR_MSG(NULL == fgets(line_buf, desc.buf_len - PROP_FORMAT_SIZE, desc.fp), "Failed to read IN_PLACE_CA from tam store file");
            desc.pos += strlen(line_buf);
        } while (0 != strncmp(line_buf, TAM_SERVER_HOST_PROP, strlen(TAM_SERVER_HOST_PROP)));
        GOTO_ERR(sscanf(line_buf, desc.buf, tam_server_host) != 1);
        LOG_INFO(TAM_SERVER_HOST_PROP "=%s", tam_server_host);
    } else {
        GOTO_ERR_MSG(0 != strcmp(tam_trust_store_type, SYSTEM_DEFAULT_CA), "'trustAnchors' property has invalid value: expected " SYSTEM_DEFAULT_CA " or " IN_PLACE_CA);
        GOTO_ERR(read_string_property(&desc, TAM_SERVER_HOST_PROP, tam_server_host, sizeof (tam_server_host)));
    }

    GOTO_ERR(read_int_property(&desc, "server.port", &tam_server_port));

    GOTO_ERR(read_string_property(&desc, "server.scheme", tam_server_scheme, sizeof (tam_server_scheme)));

    while (read_string_property(&desc, "icd.client.id", (char *) icd_id, UTIL_READ_TAS_BUFFER_LENGTH) == 0) {
        char* icd_encrypted_shared_secret = icd_id + strlen(icd_id) + 1;
        GOTO_ERR(read_string_property(&desc, "icd.shared.secret", (char *) icd_encrypted_shared_secret, UTIL_READ_TAS_BUFFER_LENGTH - strlen(icd_id) + 1));
        GOTO_ERR(IOTCS_RESULT_OK != iotcs_port_tam_decode_shared_secret(icd_encrypted_shared_secret,
                tam_shared_secret, IOTCS_SHARED_SECRET_BUFFER_LENGTH));
        GOTO_ERR(IOTCS_RESULT_OK != add_new_icd(icd_id, (const unsigned char*) icd_encrypted_shared_secret));
        icd_id = (char*) util_get_load_tam_encrypted_buffer();
    };

    *signed_data_length = desc.pos;

    GOTO_ERR(read_string_property(&desc, "signature", (char *) util_get_load_tam_encrypted_buffer(), UTIL_READ_TAS_BUFFER_LENGTH));

    if (0 == strcmp(tam_trust_store_type, SYSTEM_DEFAULT_CA)) {
        result = iotcs_port_tam_load_trust_store(NULL);
    } else {
        rewind(prop_file);
        result = iotcs_port_tam_load_trust_store(prop_file);
    }
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Failed to load trust store.");
        goto error;
    }

    result = iotcs_port_tam_decode_shared_secret(encrypted_shared_secret, tam_shared_secret, sizeof (encrypted_shared_secret));
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Failed to decode shared secret.");
        goto error;
    }

    // signature verification for client.properties
    // it's in assets_signature and signed content length is signed_data_length
    rewind(prop_file);
    result = iotcs_port_tam_check_file_signature(prop_file, *signed_data_length, (char *) util_get_load_tam_encrypted_buffer());
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Failed to verify properties file signature.");
        goto error;
    }

    // try to load endpoint ID & private key
    result = iotcs_port_tam_credentials_load(tam_client_id, tam_endpoint_id, &tam_endpoint_id_length, tam_device_public_key, &tam_device_public_key_len);
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("invalid activation data.");
        goto error;
    }

    result = IOTCS_RESULT_OK;
error:
    return result;
}

static iotcs_result iotcsp_read_from_tam_store(const char* path, size_t* signed_data_length) {
    FILE *prop_file = NULL;
    iotcs_result result = IOTCS_RESULT_FAIL;
    int version;

    GOTO_ERR_MSG(path == NULL, "No path value to Trusted Assets Manager was provided.");
    LOG_INFOS("Opening TAM file at:");
    LOG_INFOS(path);
    GOTO_ERR_MSG((prop_file = fopen(path, "r")) == NULL,
            "No configuration file found for Trusted Assets Manager");

    GOTO_ERR_MSG(EOF == (version = fgetc(prop_file)),
            "No configuration file is empty");

    if (version == TAM_DEFAULT_FORMAT_VERSION) {
        int path_length = strlen(path);
        if (path_length > IOTCS_TRUSTED_ASSETS_STORE_PATH_LENGTH) {
            LOG_ERRS("Path to trusted assets store is too long. Please increase value of the IOTCS_TRUSTED_ASSETS_STORE_PATH_LENGTH define option.");
            goto error;
        }
        /*store path to file*/
        memcpy(tas_path, path, path_length);

        is_unified_tas = IOTCS_TRUE;
        format_version = version;
        result = iotcsp_read_from_unified_tam_store(prop_file, signed_data_length);
    } else {
        rewind(prop_file); /* put back first character */
        result = iotcsp_read_from_custom_tam_store(prop_file, signed_data_length);
    }
error:
    if (prop_file != NULL) {
        fclose(prop_file);
    }
    return result;
}

iotcs_result tam_init(const char* path, const char* password) {
    iotcs_result result = IOTCS_RESULT_OK;
    size_t signed_data_length = 0;

    if (!path || !password) {
        LOG_ERRS("Trusted assets manager get invalid arguments.");
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    if (tam_is_initialized) {
        LOG_ERRS("Trusted assets manager is already initialized.");
        return IOTCS_RESULT_FAIL;
    }

    result = iotcs_port_crypto_init();
    if (result != IOTCS_RESULT_OK) {
        LOG_ERRS("Failed to initialize crypto.");
        goto done;
    }

    if ((result = iotcs_port_tam_init()) != IOTCS_RESULT_OK) {
        LOG_ERRS("iotcs_port_tam_init method failed");
        goto done;
    }

    if ((result = iotcs_port_tam_set_ts_password(password)) != IOTCS_RESULT_OK) {
        LOG_ERRS("iotcs_port_tam_set_ts_password method failed");
        goto done;
    }


    if ((result = iotcsp_read_from_tam_store(path, &signed_data_length)) != IOTCS_RESULT_OK) {
        LOG_ERRS("Failed to read from tam store.");
        goto done;
    }

    tam_is_initialized = 1;
done:
    if (result != IOTCS_RESULT_OK) {
        tam_finalize();
    }
    return result;
}

const char* tam_get_server_host(void) {
    return tam_server_host;
}

int tam_get_server_port(void) {
    return tam_server_port;
}

const char* tam_get_client_id(void) {
    return tam_client_id;
}

const char* tam_get_endpoint_id(void) {
    return tam_endpoint_id;
}

int tam_is_activated(void) {
    return tam_is_initialized && strlen(tam_endpoint_id) > 0;
}

const unsigned char *tam_get_device_public_key(size_t *len) {
    if (len != NULL) {
        *len = tam_device_public_key_len;
    }
    return tam_device_public_key;
}

iotcs_result tam_set_endpoint_credentials(const char* endpoint_id, int length) {
    if (!endpoint_id) {
        LOG_ERRS("Some of the input parameters are invalid.");
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    memcpy(tam_endpoint_id, endpoint_id, length);
    return store_credentials();
}

iotcs_result tam_generate_key_pair(const char* algorithm, size_t key_size) {
    if (!tam_is_initialized) {
        LOG_ERRS("Trusted assets manager isn't initialized.");
        return IOTCS_RESULT_FAIL;
    }

    if (!algorithm || key_size == 0) {
        LOG_ERRS("Some of the input parameters are invalid.");
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    // TODO: add enum of supported algs
    if (strcmp(algorithm, "RSA") != 0) {
        LOG_ERRS("Unsupported algorithm.");
        return IOTCS_RESULT_FAIL;
    }

    tam_device_public_key_len = IOTCS_PUBLIC_KEY_BUFFER_LENGTH;
    if (iotcs_port_tam_generate_keypair(key_size, tam_device_public_key, &tam_device_public_key_len) != IOTCS_RESULT_OK) {
        LOG_ERRS("Keypair generation failed.");
        return IOTCS_RESULT_FAIL;
    }

    return IOTCS_RESULT_OK;
}

size_t tam_sign_with_private_key(const char* data, size_t datalen, const char* algorithm, char* signature, size_t maxlen) {
    size_t signature_length;
    if (!tam_is_initialized) {
        LOG_ERRS("Trusted assets manager isn't initialized");
        return 0;
    }

    if (!data || datalen <= 0 || !algorithm || maxlen <= 0) {
        LOG_ERRS("Some of the input parameters are invalid");
        return 0;
    }

    IOTCS_TYPE_SHA local_alg;
    if (strcmp(algorithm, "SHA1withRSA") == 0) {
        local_alg = IOTCS_TYPE_SHA1;
    } else if (strcmp(algorithm, "SHA256withRSA") == 0) {
        local_alg = IOTCS_TYPE_SHA256;
    } else if (strcmp(algorithm, "SHA512withRSA") == 0) {
        local_alg = IOTCS_TYPE_SHA512;
    } else {
        LOG_ERRS("Unsupported algorithm.");
        return 0;
    }

    signature_length = iotcs_port_tam_sign_with_private_key(local_alg, (unsigned char*) data, datalen, signature, maxlen);
    if (signature_length == 0) {
        LOG_ERRS("iotcs_port_tam_sign_with_private_key method failed.");
    }
    return signature_length;
}

size_t tam_sign_with_shared_secret(const char* data, size_t dlen, const char* algorithm, char* hash, size_t maxsize,
        const char* hardware_id) {
    if (!tam_is_initialized) {
        LOG_ERRS("Trusted assets manager isn't initialized.");
        return 0;
    }

    if (!data || dlen <= 0 || !algorithm || maxsize <= 0) {
        LOG_ERRS("Some of the input parameters are invalid.");
        return 0;
    }

    IOTCS_TYPE_SHA local_alg;
    if (strcmp(algorithm, "HmacSHA1") == 0) {
        local_alg = IOTCS_TYPE_SHA1;
    } else if (strcmp(algorithm, "HmacSHA256") == 0) {
        local_alg = IOTCS_TYPE_SHA256;
    } else if (strcmp(algorithm, "HmacSHA512") == 0) {
        local_alg = IOTCS_TYPE_SHA512;
    } else {
        LOG_ERRS("Unsupported algorithm.");
        return 0;
    }

    if (hardware_id) {
        unsigned char* tmp_secret = get_shared_secret_from_icd_list(hardware_id);
        return tmp_secret != NULL ? iotcs_port_tam_secret_hash(local_alg, (unsigned char*) tmp_secret,
                strlen((char*) tmp_secret), (unsigned char*) data, dlen, (unsigned char*) hash, maxsize) : 0;
    }

    return iotcs_port_tam_secret_hash(local_alg, (unsigned char*) tam_shared_secret,
            strlen(tam_shared_secret), (unsigned char*) data, dlen, (unsigned char*) hash, maxsize);
}

iotcs_result tam_reset(void) {
    // Unified TAM doesn't support reset operation
    return IOTCS_RESULT_FAIL;
}

void tam_finalize() {
    tam_endpoint_id[0] = '\0';
    tam_endpoint_id_length = IOTCS_CLIENT_ID_BUFFER_LENGTH;
    tam_server_port = 0;
    release_icd_list();
    iotcs_port_tam_finalize();
    iotcs_port_crypto_finalize();

    tam_is_initialized = 0;
}

size_t tam_get_trust_anchors_count(void) {
    return iotcs_port_tam_get_trust_anchors_count();
}

extern const char *tam_get_trust_anchor_certificate(size_t index, size_t *len) {
    return iotcs_port_crypto_get_trust_anchor_certificate(index, len);
}
