/*
 * app_getDeviceStatus.c
 */

#include <string.h>

#include <qsee_log.h> // qsee_log (get_alt_rot_distname)
#include <qsee_message.h> // qsee_decapsulate_inter_app_message
#include <qsee_cfg_prop.h> // qsee_cfg_getpropval

#include "app_main.h"
#include "app_core.h"
#include "app_drk.h"
#include "app_json.h"
#include "app_cipher.h" // TZ_wrap_persist_data_aes_cbc_256, TZ_unwrap_persist_data_aes_cbc_256
#include "app_allowlist.h"
#include "app_readData.h"
#include "app_getDeviceStatus.h"

#include "tz_iccc_comdef.h"

void get_alt_rot_distname(char *i_appname, char *o_distname)
{
    const char *prop_name = "alt_rot_domain_name_dot";
    uint32_t ret = 0;
    uint32_t ret_size = 0;
    size_t len = 0;
    qsee_cfg_propvar_t *ptr = NULL;
    uint32_t prop[2 + (MAX_DISTNAME_PREFIX_SZ / sizeof(uint32_t))] = {0};
    char distname_prefix[MAX_DISTNAME_PREFIX_SZ + 1] = {0};

    ret = qsee_cfg_getpropval(prop_name,
                              strlen(prop_name) + 1, 0,
                              (qsee_cfg_propvar_t *)&prop,
                              sizeof(prop), &ret_size);
    if (QSEE_CFG_SUCCESS != ret) {
        qsee_log(QSEE_LOG_MSG_ERROR, "'alt_rot_domain_name_dot' read failed %d, using legacy appname", ret);
        ret_size = strlcpy(o_distname, i_appname, MAX_TANAME_SZ);
        return;
    }
    ptr = (qsee_cfg_propvar_t *)prop;
    /* len = ret_size - sizeof(qsee_cfg_propvar_t) + padding */
    len = ret_size - sizeof(*ptr) + 2 * sizeof(ptr->val) + 1;
    if (len > MAX_DISTNAME_PREFIX_SZ) {
        qsee_log(QSEE_LOG_MSG_ERROR, "'alt_rot_domain_name_dot' len invalid, using legacy appname");
        strlcpy(o_distname, i_appname, MAX_TANAME_SZ);
        return;
    }
    /* remove the quotes only when read from devcfg */
    memcpy(distname_prefix, &ptr->val[1], len - 1);
    distname_prefix[len] = '\0';
    /* finalize fully qualified distname */
    strlcpy(o_distname, distname_prefix, MAX_DISTNAME_PREFIX_SZ);
    strlcat(o_distname, i_appname, MAX_FULLNAME_SZ);
}

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 += (uint32_t)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 += (uint32_t)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 += (uint32_t)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 += (uint32_t)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 < ((uint32_t)sizeof(iccc_status_flags) / (uint32_t)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) = (uint32_t)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(QSEE_RSA_KEY));
    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;
    uint32_t key_len = 0;
    ta_iccc_status_app_req_t request;
    uint32_t ret = ICCC_SUCCESS;
    int i;
    uint32_t wrap_ret;
    uint32_t wrapped_keylen = 0;
    char skm_app_name[TA_NAME_SIZE] = "skm";
    char full_distname[MAX_DISTNAME_PREFIX_SZ + MAX_TANAME_SZ + 1] = {0}; // skm + RoT
    char src_app[MAX_DISTNAME_PREFIX_SZ + MAX_TANAME_SZ + 1] = {0};
    uint32_t temp_buf;
    uint8_t temp_key_buffer[ICCC_MAX_KEY_BUF];
    QSEE_RSA_KEY rsa_key;

    if (respmsg == NULL || sendmsg == NULL) {
        ICCC_LOG("TZ_ICCC: sendmsg or respmsg is NULL ");
        return ICCC_FAILURE;
    }

    request.comp_type = sendmsg->content.iccc_req.comp_type;
    nonce = sendmsg->content.iccc_req.nonce;
    key_len = sendmsg->content.iccc_req.key_buffer_len;

    memcpy(temp_key_buffer, sendmsg->content.iccc_req.key_buffer, ICCC_MAX_KEY_BUF);

    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 > ICCC_MAX_KEY_BUF) {
        ICCC_LOG("TZ_ICCC: ICCC_device_status: Abnormal key length");
        ret = ICCC_ERROR_DEVICE_STATUS_FAILED_KEY_LENGTH;
        goto error;
    }

    key_file_buffer_len = sizeof(key_file_buffer);
    if (sendmsg->content.iccc_req.is_wrapped_key == 0) {
        ICCC_LOG("TZ_ICCC: ICCC_device_status: Before decapsulate: key_len: %d key_file_buffer_len: %d", key_len, key_file_buffer_len);
        get_alt_rot_distname(skm_app_name, full_distname);
        ret = qsee_decapsulate_inter_app_message(src_app,
                                                 temp_key_buffer,
                                                 key_len,
                                                 key_file_buffer,
                                                 &key_file_buffer_len);
        ICCC_LOG("TZ_ICCC: ICCC_device_status: After decapsulate: ret: %d src_app: %s key_len: %d key_file_buffer_len: %d", ret, src_app, key_len, key_file_buffer_len);
        if (ret != QSEE_MESSAGE_SUCESS) {
            ICCC_LOG("TZ_ICCC: ICCC_device_status: qsee_decapsulate_inter_app_message() FAILED : %x", ret);
            ret = ICCC_ERROR_DEVICE_STATUS_FAILED_DECAPSULATE;
            goto error;
        } else if (strncmp(src_app, full_distname, strlen(full_distname) + 1)) {
            ICCC_LOG("TZ_ICCC: ICCC_device_status: source app name \"%s\" was not \"%s\"", src_app, full_distname);
            ret = ICCC_ERROR_DEVICE_STATUS_FAILED_APP_NAME;
            goto error;
        } else {
            ICCC_LOG("TZ_ICCC: ICCC_device_status: Decapsulate successful, proceeding to wrap");
            wrapped_keylen = MAX_WRAPPED_KEY_LEN;
            ICCC_LOG("TZ_ICCC: ICCC_device_status: Before wrap: key_file_buffer_len: %d wrapped_keylen: %d", key_file_buffer_len, wrapped_keylen);
            wrap_ret = TZ_wrap_persist_data_aes_cbc_256((uint8_t *)"tz_iccc",
                                                        strlen("tz_iccc"),
                                                        key_file_buffer,
                                                        key_file_buffer_len,
                                                        respmsg->content.iccc_rsp.wrapped_key.wrapped_keybuf,
                                                        &wrapped_keylen);
            ICCC_LOG("TZ_ICCC: ICCC_device_status: After wrap: key_file_buffer_len: %d wrapped_keylen: %d", key_file_buffer_len, wrapped_keylen);
            if (wrap_ret != TZ_API_OK) {
                ICCC_LOG("TZ_ICCC: ICCC_device_status: TZ_wrap_data FAILED: %d", wrap_ret);
                ret = ICCC_ERROR_DEVICE_STATUS_FAILED_WRAP;
                goto error;
            }
            respmsg->content.iccc_rsp.wrapped_key.wrapped_keylen = wrapped_keylen;
        }
    } else if (sendmsg->content.iccc_req.is_wrapped_key == 1) {
        ICCC_LOG("TZ_ICCC: ICCC_device_status: Before unwrap: key_len: %d key_file_buffer_len: %d", key_len, key_file_buffer_len);
        wrap_ret = TZ_unwrap_persist_data_aes_cbc_256((uint8_t *)"tz_iccc",
                                                      strlen("tz_iccc"),
                                                      temp_key_buffer,
                                                      key_len,
                                                      key_file_buffer,
                                                      &key_file_buffer_len);
        ICCC_LOG("TZ_ICCC: ICCC_device_status: After unwrap: key_len: %d key_file_buffer_len: %d", key_len, key_file_buffer_len);
        if (wrap_ret == TZ_API_ERROR_NO_KEY) {
            ICCC_LOG("TZ_ICCC: ICCC_device_status: TZ_unwrap_data FAILED with no key: %d", wrap_ret);
            ret = ICCC_ERROR_DEVICE_STATUS_FAILED_NO_KEY;
            goto error;
        } else if(wrap_ret == TZ_API_ERROR_VERSION_MISMATCH) {
            ICCC_LOG("TZ_ICCC: ICCC_device_status: TZ_unwrap_data FAILED with version mismatch error: %d", wrap_ret);
            ret = ICCC_ERROR_DEVICE_STATUS_FAILED_VERSION_MISMATCH;
            goto error;
        }
        if (wrap_ret != TZ_API_OK) {
            ICCC_LOG("TZ_ICCC: ICCC_device_status: TZ_unwrap_data FAILED: %d", wrap_ret);
            ret = ICCC_ERROR_DEVICE_STATUS_FAILED_UNWRAP;
            goto error;
        }
    } else {
        ICCC_LOG("TZ_ICCC: ICCC_device_status: is_wrapped_key has strange values");
        ret = ICCC_ERROR_DEVICE_STATUS_FAILED_UNKNOWN_WRAP;
        goto error;
    }

    if (load_iccc_key(&rsa_key) != ICCC_SUCCESS) {
        ICCC_LOG("TZ_ICCC: ICCC_device_status: Load ICCC key failed");
        ret = ICCC_LOAD_KEY_ERROR;
        free_key(&rsa_key);
        goto error;
    }

    request.key = &rsa_key;
    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;

    request.resp_msg_buf = respmsg->content.iccc_rsp.buffer;
    request.resp_msg_buf_size = ICCC_STATUS_APP_MAX_RESPONSE;
    temp_buf = respmsg->content.iccc_rsp.buflen;
    request.resp_msg_len = &(temp_buf);

    ret = Iccc_DeviceStatus_App(&request);
    ICCC_LOG("TZ_ICCC: Iccc_DeviceStatus_App return: %d", ret);
    if (ret != ICCC_SUCCESS) {
        ICCC_LOG("TZ_ICCC: Iccc_DeviceStatus_App error: %d", ret);
    } else if (*(request.resp_msg_len) >= ICCC_STATUS_APP_MAX_RESPONSE) {
        ICCC_LOG("TZ_ICCC: Iccc_DeviceStatus_App returned a buffer too long: %d", *(request.resp_msg_len));
        ret = ICCC_ERROR_DEVICE_STATUS_FAILED_BUFFER;
    } else {
        respmsg->content.iccc_rsp.buflen = temp_buf;
        ret = ICCC_SUCCESS;
    }

    free_key(&rsa_key);

error:
    memset(key_file_buffer, 0, key_file_buffer_len);
    memset(temp_key_buffer, 0, ICCC_MAX_KEY_BUF);
    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;
}
