/*
 * app_getDeviceStatus.c
 */

#include <string.h>

#include "app_main.h"
#include "app_core.h"
#include "app_drk.h"
#include "app_json.h"
#include "app_readData.h"
#include "app_getDeviceStatus.h"

#include "icccOperations_v4.h"

uint32_t append_to_result_msg(char *result_message, uint32_t *offset, uint32_t result_message_len, uint8_t type, const char *flag_name, const uint8_t flag_size)
{
    const char insecure_msg_preffix[] = "The value of ";
    const char read_error_msg_preffix[] = "The value of ";
    const char insecure_msg_suffix[] = " is insecure. ";
    const char read_error_msg_suffix[] = " could not be read. ";
    uint8_t insecure_msg_size = 0;
    uint8_t read_error_msg_size = 0;
    uint32_t init_offset = *offset;

    switch(type) {
        case INSECURE_MSG_TYPE:
            if ((sizeof(insecure_msg_preffix)) <= UINT8_MAX) {
                insecure_msg_size = sizeof(insecure_msg_preffix);
                if ((sizeof(insecure_msg_suffix)) > 0 && (insecure_msg_size <= (UINT8_MAX-(sizeof(insecure_msg_suffix))))) {
                    insecure_msg_size += (sizeof(insecure_msg_suffix));
                    if (flag_size > 0 && (insecure_msg_size <= UINT8_MAX - flag_size)) {
                        insecure_msg_size = insecure_msg_size + flag_size - 2;
                        if ((*offset + insecure_msg_size) < result_message_len) {
                            memcpy(result_message + *offset, insecure_msg_preffix, sizeof(insecure_msg_preffix) - 1);
                            *offset += sizeof(insecure_msg_preffix) - 1;
                            memcpy(result_message + *offset, flag_name, flag_size);
                            *offset += flag_size;
                            memcpy(result_message + *offset, insecure_msg_suffix, sizeof(insecure_msg_suffix) - 1);
                            *offset += sizeof(insecure_msg_suffix) - 1;
                            *offset -= init_offset;
                        }
                    }
                }
            }
            break;
        case FLAG_NOT_READ:
            if ((sizeof(read_error_msg_preffix)) <= UINT8_MAX) {
                read_error_msg_size = sizeof(read_error_msg_preffix);
                if ((sizeof(read_error_msg_suffix)) > 0 && (read_error_msg_size <= (UINT8_MAX-(sizeof(read_error_msg_suffix))))) {
                    read_error_msg_size += (sizeof(read_error_msg_suffix));
                    if (flag_size > 0 && (read_error_msg_size <= UINT8_MAX - flag_size)) {
                        read_error_msg_size = read_error_msg_size + flag_size - 2;
                        if ((*offset + read_error_msg_size) < result_message_len) {
                            memcpy(result_message + *offset, read_error_msg_preffix, sizeof(read_error_msg_preffix) - 1);
                            *offset += sizeof(read_error_msg_preffix) - 1;
                            memcpy(result_message + *offset, flag_name, flag_size);
                            *offset += flag_size;
                            memcpy(result_message + *offset, read_error_msg_suffix, sizeof(read_error_msg_suffix) - 1);
                            *offset += sizeof(read_error_msg_suffix) - 1;
                            *offset -= init_offset;
                        }
                    }
                }
            }
            break;
    }

    return *offset;
}

uint32_t check_flag(char *result_message, uint32_t *offset, const iccc_status_flags_t *flag)
{
    uint32_t value;
    uint32_t read_result;
    uint32_t ret = ICCC_STATUS_RETURN_SECURE;

    read_result = Iccc_Core_ReadData_TA(flag->type, &value);
    if (read_result == ICCC_SUCCESS) {
        if (flag->is_secure(value)) {
        // The Secure message is printed later.
        ret = ICCC_STATUS_RETURN_SECURE;
        } else {
        append_to_result_msg(result_message, offset, ICCC_STATUS_MAX_RESULT_MESSAGE, INSECURE_MSG_TYPE, flag->name, flag->name_size);
        ret = ICCC_STATUS_RETURN_NOT_SECURE;
        }
    } else {
        append_to_result_msg(result_message, offset, ICCC_STATUS_MAX_RESULT_MESSAGE, FLAG_NOT_READ, flag->name, flag->name_size);
        ret = ICCC_STATUS_RETURN_FAILURE_TO_READ;
    }
    return ret;
}

uint32_t ICCC_device_status_check_flags(uint32_t comp_type, uint32_t *result_code, char *result_message)
{
    uint32_t result_check;
    uint32_t offset = 0;
    const char device_secure_msg[] = "Device Secure.";
    uint32_t i = 0;

    *result_code = ICCC_STATUS_RETURN_SECURE;
    for (i = 0; i < (sizeof(iccc_status_flags) / sizeof(iccc_status_flags_t)); i++) {
        if (iccc_status_flags[i].comp_type_array[comp_type - 1]) { // tz_iccc_device_status_comp_type_t starts on 1
            ICCC_LOG("TZ_ICCC: Checking flag %d for component %d", i, comp_type);
            result_check = check_flag(result_message, &offset, &iccc_status_flags[i]);
            if(result_check != ICCC_STATUS_RETURN_SECURE) {
                *result_code |= iccc_status_flags[i].type_bitwise;
            }
        }
    }

    if (*result_code == ICCC_STATUS_RETURN_SECURE) {
        memcpy(result_message, device_secure_msg, strlen(device_secure_msg));
    } else {
        // Remove the trailing space
        result_message[offset - 1] = '\0';
    }

    return ICCC_SUCCESS;
}

uint32_t Iccc_Core_DeviceStatus_TA(uint32_t comp_type, uint8_t *resp_msg_buf, uint32_t resp_msg_buf_size, uint32_t *resp_msg_len, uint32_t *result_code)
{
    uint32_t ret = ICCC_ERROR_DEVICE_STATUS_FAILED;

    if (resp_msg_buf == NULL) {
        ICCC_LOG("TZ_ICCC: Iccc_Core_DeviceStatus_TA: resp_msg_buf NULL");
        return ret;
    }

    if (resp_msg_buf_size < ICCC_STATUS_MAX_RESULT_MESSAGE) {
        ICCC_LOG("TZ_ICCC: Iccc_Core_DeviceStatus_TA: resp_msg_buf_size < ICCC_STATUS_MAX_RESULT_MESSAGE");
        return ret;
    }
    if (comp_type < ICCC_STATUS_COMP_TYPE_HARD_INTEGRITY || comp_type > ICCC_STATUS_COMP_TYPE_SOFT_INTEGRITY) {
        ICCC_LOG("TZ_ICCC: Iccc_Core_DeviceStatus_TA: comp_type %d", comp_type);
        return ret;
    }

    memset(resp_msg_buf, 0, resp_msg_buf_size); // invalid resp_msg_buf log

    ret = ICCC_device_status_check_flags(comp_type, result_code, (char *)resp_msg_buf);
    if (ret != ICCC_SUCCESS) {
        ICCC_LOG("TZ_ICCC: error checking flags");
        return ret;
    }

    (*resp_msg_len) = strlen((char *)resp_msg_buf);

    ICCC_LOG("TZ_ICCC: Device Status comp_type = %d ret = %d", comp_type, ret);
    ICCC_LOG("TZ_ICCC: Device Status resp_msg_buf = %s resp_msg_len = %d", (char *)resp_msg_buf, *resp_msg_len);
    return ret;
}

uint32_t Iccc_DeviceStatus_App(ta_iccc_status_app_req_t *request)
{
    /* Aux */
    uint32_t ret = ICCC_ERROR_DEVICE_STATUS_FAILED;
    uint8_t ta_status_msg[ICCC_STATUS_MAX_RESULT_MESSAGE];
    uint32_t ta_status_msg_len = 0, ta_status_result_code;
    ta_iccc_status_app_payload_t payload;
    uint32_t prev_buf_len = 0, this_buf_len = 0;

    /* HEADER */
    ICCC_LOG("TZ_ICCC: Iccc_DeviceStatus_App: Start Header, buffer_len: %d", *request->resp_msg_len);
    ret = generate_header(&request->resp_msg_buf[0], request->resp_msg_len, request->resp_msg_buf_size);
    if (ret != ICCC_SUCCESS || *request->resp_msg_len >= request->resp_msg_buf_size) {
        ICCC_LOG("TZ_ICCC: Iccc_DeviceStatus_App: header is too long: %d", *request->resp_msg_len);
        goto error;
    }
    ret = encode_base64_in_place(&request->resp_msg_buf[0], request->resp_msg_len, request->resp_msg_buf_size);
    if (ret != ICCC_SUCCESS) {
        goto error;
    }
    ret = append_char_to_json(&request->resp_msg_buf[0], request->resp_msg_buf_size, request->resp_msg_len, '.');
    if (ret != ICCC_SUCCESS) {
        goto error;
    }
    prev_buf_len = *request->resp_msg_len;

    /* PAYLOAD */
    ICCC_LOG("TZ_ICCC: Iccc_DeviceStatus_App: Start Payload, buffer_len: %d", *request->resp_msg_len);
    if (request->comp_type == ICCC_STATUS_COMP_TYPE_DIR_INTEGRITY) {
        /* For Hard and Soft Types the function Iccc_Core_DeviceStatus_TA would return
         * a string and a int with a "full" representation of the device status.
         * But for DIR Type we need to generate the string and the int "manually".
         * The string is in fact a separate JSON, and the int is always 0
         * (which guarantees ICCC Response will be read). */
        ret = prepare_DIR_values();
        if (ret != ICCC_SUCCESS) {
            goto error;
        }
        // The values used for DIR string should now be in struct payload_DIR.
        ret = generate_DIR_message(ta_status_msg, sizeof(ta_status_msg), &ta_status_msg_len);
        if (ret != ICCC_SUCCESS) {
            goto error;
        }
        // We base64-encode it so that the NW don't try to parse it too soon.
        ret = encode_base64_in_place(ta_status_msg, &ta_status_msg_len, ICCC_STATUS_MAX_RESULT_MESSAGE);
        if (ret != ICCC_SUCCESS) {
            goto error;
        }
        ICCC_LOG("TZ_ICCC: DIR String: %s", ta_status_msg);
        ta_status_result_code = 0;
    } else { /* Hard and Soft Types */
        ret = Iccc_Core_DeviceStatus_TA(request->comp_type, ta_status_msg, sizeof(ta_status_msg), &ta_status_msg_len, &ta_status_result_code);
        if (ret != ICCC_SUCCESS) {
            ICCC_LOG("TZ_ICCC: Iccc_Core_DeviceStatus_TA failed");
            ret = ICCC_ERROR_DEVICE_STATUS_FAILED;
            goto error;
        }
    }
    if (ta_status_msg_len >= sizeof(ta_status_msg)) {
        ICCC_LOG("TZ_ICCC: Iccc_Core_DeviceStatus_TA returned a buffer too long: %d", ta_status_msg_len);
        ret = ICCC_ERROR_DEVICE_STATUS_FAILED;
        goto error;
    }
    ret = prepare_payload(request, ta_status_result_code, ta_status_msg, &payload);
    if (ret != ICCC_SUCCESS) {
        goto error;
    }
    ret = generate_payload(&request->resp_msg_buf[0], request->resp_msg_buf_size, request->resp_msg_len, &payload);
    if (ret != ICCC_SUCCESS) {
        ICCC_LOG("TZ_ICCC: Payload failure");
        goto error;
    }
    this_buf_len = *request->resp_msg_len - prev_buf_len;
    ret = encode_base64_in_place(&request->resp_msg_buf[prev_buf_len], &this_buf_len, ICCC_STATUS_APP_MAX_RESPONSE);
    if (ret != ICCC_SUCCESS) {
        goto error;
    }
    *request->resp_msg_len = prev_buf_len + this_buf_len;
    ret = append_char_to_json(&request->resp_msg_buf[0], request->resp_msg_buf_size, request->resp_msg_len, '.');
    if (ret != ICCC_SUCCESS) {
        goto error;
    }
    prev_buf_len = *request->resp_msg_len;

    /* SIGNATURE */
    ICCC_LOG("TZ_ICCC: Iccc_DeviceStatus_App: Start Signature, buffer_len: %d", *request->resp_msg_len);
    ret = generate_signature(&request->resp_msg_buf[0], request->resp_msg_len, request->key);
    if (ret != ICCC_SUCCESS) {
        goto error;
    }
    this_buf_len = *request->resp_msg_len - prev_buf_len;
    ret = encode_base64_in_place(&request->resp_msg_buf[prev_buf_len], &this_buf_len, ICCC_STATUS_APP_MAX_RESPONSE);
    if (ret != ICCC_SUCCESS) {
        goto error;
    }
    *request->resp_msg_len = prev_buf_len + this_buf_len;

error:
    clearBuffer(request->key, sizeof(tz_iccc_rsakey_t));
    clearBuffer(ta_status_msg, sizeof(ta_status_msg));
    ICCC_LOG("TZ_ICCC: Iccc_DeviceStatus_App: End, buffer_len: %d", *request->resp_msg_len);
    return ret;
}

uint32_t validate_nonce(uint8_t *nonce) {
    uint32_t ret = ICCC_SUCCESS;
    uint32_t i;

    for (i = 0; i < ICCC_NONCE_SIZE; i++) {
        if (!((nonce[i] >= '0' && nonce[i] <= '9') ||
              (nonce[i] >= 'a' && nonce[i] <= 'f') ||
              (nonce[i] >= 'A' && nonce[i] <= 'F'))) {
            ret = ICCC_NONCE_ERROR;
            break;
        }
    }

    if (ret == ICCC_SUCCESS) {
        nonce[ICCC_NONCE_SIZE] = '\0';
    }

    return ret;
}

uint32_t ICCC_device_status(tz_iccc_status_payload_t *sendmsg, tz_iccc_status_payload_t *respmsg)
{
    uint8_t *nonce = NULL;
    uint8_t *key = NULL;
    uint32_t key_len = 0;
    ta_iccc_status_app_req_t request;
    uint32_t ret = ICCC_SUCCESS;
    uint32_t unwrap_len = ICCC_MAX_KEY_BUF;
    int i;

    request.comp_type = sendmsg->content.iccc_req.comp_type;
    nonce = sendmsg->content.iccc_req.nonce;
    key = sendmsg->content.iccc_req.key_buffer;
    key_len = sendmsg->content.iccc_req.key_buffer_len;

    request.nonce = nonce;
    ret = validate_nonce(nonce);
    if (ret != ICCC_SUCCESS) {
        ICCC_LOG("TZ_ICCC: Nonce is not valid");
        goto error;
    }
    if (key_len == 0 || key_len > sizeof(dsttmpData)) {
        ICCC_LOG_DEBUG("TZ_ICCC: ICCC_device_status: Abnormal key length");
        ret = ICCC_ERROR_DEVICE_STATUS_FAILED_KEY_LENGTH;
        goto error;
    }
    if (unwrap(key, key_len, &unwrap_len) != ICCC_SUCCESS) {
        ICCC_LOG("TZ_ICCC: unwrap failed")
        ICCC_LOG_DEBUG("TZ_ICCC: ICCC_device_status: unwrap ICCC key failed");
        ret = ICCC_ERROR_DEVICE_STATUS_FAILED_UNWRAP;
        goto error;
    }
    if (load_iccc_key(dsttmpData, unwrap_len)) {
        ICCC_LOG("TZ_ICCC: load ICCC key failed");
        ICCC_LOG_DEBUG("TZ_ICCC: ICCC_device_status: Load ICCC key failed");
        ret = ICCC_LOAD_KEY_ERROR;
        goto error;
    }

    request.key = &rsakey;
    for (i = 0; i < iccc_certs_num; i++) {
        request.certs[i] = iccc_certs[i];
        request.certs_len[i] = iccc_certs_len[i];
    }
    request.certs_num = iccc_certs_num;

    respmsg->content.iccc_rsp.buflen = 0; /* initialize buffer len */
    request.resp_msg_buf = respmsg->content.iccc_rsp.buffer;
    request.resp_msg_buf_size = ICCC_STATUS_APP_MAX_RESPONSE;
    request.resp_msg_len = &respmsg->content.iccc_rsp.buflen;

    ret = Iccc_DeviceStatus_App(&request);
    ICCC_LOG_DEBUG("TZ_ICCC: Iccc_DeviceStatus_App return: %d", ret);
    if (ret != ICCC_SUCCESS) {
        ICCC_LOG("TZ_ICCC: Device Status App error");
        ICCC_LOG_DEBUG("TZ_ICCC: Iccc_DeviceStatus_App error: %d", ret);
        ret = ICCC_ERROR_DEVICE_STATUS_FAILED;
    } else if (*(request.resp_msg_len) >= ICCC_STATUS_APP_MAX_RESPONSE) {
        ICCC_LOG_DEBUG("TZ_ICCC: Iccc_DeviceStatus_App returned a buffer too long: %d", *(request.resp_msg_len));
        ret = ICCC_ERROR_DEVICE_STATUS_FAILED;
    } else {
        ret = ICCC_SUCCESS;
    }

error:
    memset(dsttmpData, 0, sizeof(dsttmpData));
    respmsg->content.iccc_rsp.ret = ret;
    respmsg->content.iccc_rsp.cmd_id = CMD_ICCC_DEVICE_STATUS;
    ICCC_LOG("TZ_ICCC: ICCC_device_status: End. ret: %d", ret);
    return ret;
}
