/**
 * \file gatekeeper_common.c
**/

#include "gatekeeper_common.h"
#include "gatekeeper_records.h"
#include "gatekeeper_log.h"
#include "verify_auth_tocken_cmd.h"
#include "hal/gatekeeper_hal.h"
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>

#define PASSWORD_AUTHENTICATOR_ID 0
#define HANDLE_FLAG_THROTTLE_SECURE 1

#define km_htonl(x)                           \
    (((((uint32_t)(x) & 0x000000FFU) << 24) | \
      (((uint32_t)(x) & 0x0000FF00U) <<  8) | \
      (((uint32_t)(x) & 0x00FF0000U) >>  8) | \
      (((uint32_t)(x) & 0xFF000000U) >> 24)))
#define km_htobe64(x)                                            \
    (((uint64_t)(km_htonl((uint32_t)((x << 32) >> 32))) << 32) | \
     (km_htonl(((uint32_t)(x >> 32)))))

bool gk_is_empty(const sized_buffer *buffer)
{
    if ((buffer->buffer == NULL) || (buffer->size == 0)) {
        return true;
    }

    return false;
}

bool gk_cp_to_sized_buffer(const uint8_t* data, uint32_t len, sized_buffer* out)
{
    if (out == NULL) {
        return false;
    }

    out->size = len;
    if (len) {
        out->buffer = hal_mem_malloc(len);
        if (out->buffer == NULL) {
            return false;
        }

        hal_mem_move(out->buffer, data, len);
    } else {
        out->buffer = NULL;
    }

    return true;
}

void gk_free_sized_buffer(sized_buffer *obj)
{
    hal_mem_free(obj->buffer);
    obj->buffer = NULL;
    obj->size = 0;
}


static gk_err_t
gk_ver_curr_pwd(uint32_t uid,
                const sized_buffer *handle,
                const sized_buffer *enr_pwd,
                uint32_t *response_time,
                fail_record_t *failure_record)
{
    passw_handle_t *pwd_handle = (passw_handle_t *)handle->buffer;
    LOGD("previous handle is not null\n");
    if (pwd_handle->version > HANDLE_VERSION) {
        LOGE("pwd_handle version wrong\n");
        return ERR_INVALID;
    }
    uint64_t remaining_time = 0;
#if (ANDROID_VERSION >= 7)

    if (gk_check_throttle(pwd_handle->user_id, failure_record, &remaining_time)) { /* check remained throttling time with previous failure counter */
        LOGE("security issue mistmatch pwd_handle\n");
        return ERR_INVALID;
    }

    if (remaining_time > 0) {
        LOGE("throttle remaining_time = %"PRIu64" \n", remaining_time);
        *response_time = remaining_time;
        return ERR_RETRY;
    }

    if (!gk_inc_fails(uid, pwd_handle->user_id, failure_record)) { /* Increase fail counter first */
            LOGE("failed to increase fail counter\n");
            return ERR_UNKNOWN;
    }

    if (!gk_verify_hndl(uid, pwd_handle, enr_pwd)) {
        if (gk_check_throttle(pwd_handle->user_id, failure_record, &remaining_time)) {  /* If password check failed, calculate new throttling time again */
            LOGE("security issue mistmatch pwd_handle\n");
            return ERR_INVALID;
        }
        if (remaining_time > 0) {
            LOGE("throttle remaining_time = %"PRIu64" \n", remaining_time);
            *response_time = remaining_time;
            return ERR_RETRY;
        }

        return ERR_INVALID;
    }

#elif (ANDROID_VERSION == 6)
    if (gk_check_throttle(pwd_handle->user_id, failure_record, &remaining_time)) {
        LOGE("security issue mistmatch pwd_handle\n");
        return ERR_INVALID;
    }
    if (remaining_time > 0) {
        LOGE("throttle remaining_time = %"PRIu64" \n", remaining_time);
        *response_time = remaining_time;
        return ERR_RETRY;
    }

    if (!gk_verify_hndl(uid, pwd_handle, enr_pwd)) {
        if (!gk_inc_fails(uid, pwd_handle->user_id, failure_record)) {
            LOGE("failed to increase fail counter\n");
            return ERR_UNKNOWN;
        }
        return ERR_INVALID;
    }
#endif
    return ERR_NONE;
}

static secure_id_t gk_get_user_id(const sized_buffer *handle)
{
     if (handle->buffer == NULL) {
        return 0;
     }

     return ((passw_handle_t*)handle->buffer)->user_id;

}

void gk_enroll(const enroll_request *request, enroll_responce_t *response, fail_record_t *failure_record)
{
    if (request == NULL || response == NULL) {
        return;
    }

    secure_id_t user_id = gk_get_user_id(&request->pwd_hndl);
    LOGD("user_id in : %"PRIu64"\n", user_id);
    if (request->pwd.buffer == NULL) {
         LOGE("provided_password is NULL \n");
         response->result = ERR_INVALID;
         return;
    }

    /* Current password and current password handle go together */
    if (gk_is_empty(&request->pwd_hndl) || gk_is_empty(&request->enr_pwd) ) {
        do {
            hal_gen_rand((uint8_t*)&user_id, sizeof(user_id));
        } while(user_id == 0); /* secure user_id shouldn't use 0, 0 is tmp vaule */
    } else {
        response->result = gk_ver_curr_pwd(response->uid,
                                           &request->pwd_hndl,
                                           &request->enr_pwd,
                                           &response->retry_timeout,
                                           failure_record);
        if (response->result != ERR_NONE) {
            return;
        }
    }

    response->handle.version = HANDLE_VERSION;
    hal_gen_rand((uint8_t *)&response->handle.salt, sizeof(response->handle.salt));
    response->handle.user_id = user_id;
    response->handle.flags = HANDLE_FLAG_THROTTLE_SECURE;
    response->handle.hw_backed = true;

    if (!gk_pwd_hndl(response->uid,
                     &response->handle,
                     &request->pwd,
                     sizeof(response->handle.signature),
                     response->handle.signature)) {
        LOGE("create_password_handle fail\n");
        response->result = ERR_INVALID;
        return;
    }

    if (!gk_clear_fails(response->uid, user_id, failure_record)) {
        LOGE("failed to clear fail counter\n");
        response->result = ERR_UNKNOWN;
        clear_signature(sizeof(response->handle.signature), response->handle.signature);
        return;
    }

    response->result = ERR_NONE;

    return;
}

static gk_err_t
gk_create_auth_token(hw_auth_token_t *token, uint64_t timestamp, secure_id_t user_id,
                  secure_id_t auth_id, uint64_t challenge)
{
    gk_err_t ret = ERR_UNKNOWN;
    if (token == NULL) {
        return ret;
    }

    token->version = HW_AUTH_TOKEN_VERSION;
    token->challenge = challenge;
    token->user_id = user_id;
    token->authenticator_id = km_htobe64(auth_id);
    token->authenticator_type = km_htonl(HW_AUTH_PASSWORD);
    token->timestamp = km_htobe64(timestamp);
    uint8_t hmac[sizeof(token->hmac)];
    uint8_t password_key[32];
    if (gk_cp_sess_key(password_key, sizeof(password_key))) {
        uint32_t hash_len = sizeof(*token) - sizeof(token->hmac);
        if(gk_create_sign(hmac,
                          sizeof(hmac),
                          password_key,
                          sizeof(password_key),
                          (const uint8_t *)token,
                          hash_len)) {
            ret = ERR_NONE;
        }
    }

    if (ret == ERR_NONE) {
        hal_mem_move(token->hmac, hmac, sizeof(token->hmac));
    }

    return ret;
}

void gk_verify(const verify_request *request, verify_responce_t *response, fail_record_t *failure_record)
{
    if (request == NULL || response == NULL) {
        return;
    }

    passw_handle_t *exp_hndl = (passw_handle_t*)request->pwd_hndl.buffer;
    if (exp_hndl->version > HANDLE_VERSION) {
        response->result = ERR_INVALID;
        return;
    }

    if (request->pwd.buffer == NULL) {
         LOGE("provided_password is NULL\n");
         response->result = ERR_INVALID;
         return;
    }

    secure_id_t user_id = gk_get_user_id(&request->pwd_hndl);
    response->result = gk_ver_curr_pwd(response->uid,
                                       &request->pwd_hndl,
                                       &request->pwd,
                                       &response->retry_timeout,
                                       failure_record);
    if (response->result != ERR_NONE) {
        return;
    }

    response->result = gk_create_auth_token(&response->token,
                                            hal_get_time(),
                                            user_id,
                                            PASSWORD_AUTHENTICATOR_ID,
                                            response->challenge);
    if (!gk_clear_fails(response->uid, user_id, failure_record)) {
        LOGE("failed to clear fail counter\n");
        response->result = ERR_UNKNOWN;
        return;
    }

    response->request_reenroll = !(exp_hndl->version >= HANDLE_VERSION_THROTTLE);
}

#ifdef DEBUG
void gk_ver_auth_token(void *cmd_data)
{
    verify_auth_token_cmd_t *cmd = (verify_auth_token_cmd_t *)cmd_data;
    cmd->result = VR_FAIL;
    hw_auth_token_t *token = &cmd->auth_token;
    uint8_t *auth_token_key = NULL;
    uint8_t password_key[32];
    if (gk_cp_sess_key(password_key, sizeof(password_key))) {
        uint32_t hash_len = sizeof(*token) - sizeof(token->hmac);
        uint8_t hmac[sizeof(token->hmac)];
        if (gk_create_sign(hmac, sizeof(hmac), password_key,
                              sizeof(password_key), (const uint8_t *)token, hash_len)
                && (hal_mem_cmp(hmac, token->hmac, sizeof(hmac)) == 0)) {
            cmd->result = VR_SUCCESS;
        }

        hal_mem_free(auth_token_key);
    }
}
#endif /* DEBUG */
