/*
 * Copyright (c) 2018 Qualcomm Technologies, Inc.
 * All Rights Reserved.
 * Confidential and Proprietary - Qualcomm Technologies, Inc.
 */

#include <stdint.h>

#include "CHash.h"
#include "IHash.h"
#include "cdefs.h"
#include "hash.h"
#include "hash_handler.h"
#include "object.h"
#include "qsee_env.h"
#include "qsee_hash.h"
#include "qsee_log.h"
#include "qsee_services.h"

static uint32_t hash_result_size(enum hash_alg alg)
{
  switch (alg) {
    case HASH_SHA1:
      return 160 / 8;
    case HASH_SHA256:
      return 256 / 8;
    case HASH_SHA384:
      return 384 / 8;
    case HASH_SHA512:
      return 512 / 8;
    default:
      return 0;
  }
}

static uint32_t get_qsee_hash_alg(enum hash_alg alg)
{
  switch (alg) {
    case HASH_SHA1:
      return QSEE_HASH_SHA1;

    case HASH_SHA256:
      return QSEE_HASH_SHA256;

    case HASH_SHA384:
      return QSEE_HASH_SHA384;

    case HASH_SHA512:
      return QSEE_HASH_SHA512;

    default:
      return QSEE_HASH_INVALID;
  }
}

static uint32_t get_hash_service_id(enum hash_alg alg)
{
  switch (alg) {
    case HASH_SHA1:
      return CHashSHA1_UID;

    case HASH_SHA256:
      return CHashSHA256_UID;

    case HASH_SHA384:
      return CHashSHA384_UID;

    case HASH_SHA512:
      return CHashSHA512_UID;

    default:
      return QSEE_OPEN_INVALID_ID;
  }
}

/* Hash using qsee API */
static int qsee_hash_run(const struct hash_req *req, struct hash_rsp *rsp)
{
  int error;

  rsp->hash_size = hash_result_size(req->alg);

  error = qsee_hash(
    get_qsee_hash_alg(req->alg),
    req->msg_buffer,
    req->msg_len,
    rsp->hash_result_buffer,
    rsp->hash_size);

  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "qsee_hash failed with error: %d", error);
  }

  return error;
}

static int hash_example_prepare_buffer(void *addr, size_t size)
{
  int error;

  error = qsee_register_shared_buffer(addr, size);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "qsee_register_shared_buffer failed with error: %d", error);

    return error;
  }

  error = qsee_prepare_shared_buf_for_secure_read(addr, size);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR,
             "qsee_prepare_shared_buf_for_secure_read failed with error: %d",
             error);

    (void)qsee_deregister_shared_buffer(addr);
  }

  return error;
}

static int hash_example_cleanup_buffer(void *addr, size_t size)
{
  int error;

  error = qsee_prepare_shared_buf_for_nosecure_read(addr, size);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR,
             "qsee_prepare_shared_buf_for_nosecure_read failed with error: %d",
             error);

    return error;
  }

  error = qsee_deregister_shared_buffer(addr);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "qsee_deregister_shared_buffer failed with error: %d", error);
  }

  return error;
}

/* Hash using qsee API, from a QSEECom_send_modified_cmd call. */
static int qsee_hash_modified_cmd_run(const struct hash_req *req, struct hash_rsp *rsp)
{
  int error;
  int cleanup_error;

  error = hash_example_prepare_buffer(req->msg_ptr, req->msg_len);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "Failed to prepare hash input buffer.");
    return error;
  }

  rsp->hash_size = hash_result_size(req->alg);

  error = hash_example_prepare_buffer(req->hash_result_ptr, rsp->hash_size);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "Failed to prepare hash output buffer.");

    (void)hash_example_cleanup_buffer(req->msg_ptr, req->msg_len);

    return error;
  }

  error = qsee_hash(
    get_qsee_hash_alg(req->alg), req->msg_ptr, req->msg_len, req->hash_result_ptr, rsp->hash_size);

  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "qsee_hash failed with error: %d", error);
  }

  cleanup_error = hash_example_cleanup_buffer(req->msg_ptr, req->msg_len);
  if (cleanup_error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "Failed to cleanup hash input buffer.");
    error = cleanup_error;
  }

  cleanup_error = hash_example_cleanup_buffer(req->hash_result_ptr, rsp->hash_size);
  if (cleanup_error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "Failed to cleanup hash output buffer.");
    error = cleanup_error;
  }
  
  return error;
}

/* Hash using invoke API */
static int invoke_hash_run(const struct hash_req *req, struct hash_rsp *rsp)
{
  int error;

  Object hash_obj;

  error = qsee_open(get_hash_service_id(req->alg), &hash_obj);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "invoke_hash_run: qsee_open failed with error: %d", error);
    return error;
  }

  error = IHash_update(hash_obj, req->msg_buffer, req->msg_len);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "invoke_hash_run: IHash_update failed with error: %d", error);
    goto out;
  }

  rsp->hash_size = hash_result_size(req->alg);
  size_t out_len = 0;

  error = IHash_final(hash_obj, rsp->hash_result_buffer, rsp->hash_size, &out_len);
  if (error) {
    qsee_log(QSEE_LOG_MSG_ERROR, "invoke_hash_run: IHash_final failed with error: %d", error);
    goto out;
  }

  if (out_len != rsp->hash_size) {
    error = INVOKE_HASH_FAILURE;
    qsee_log(QSEE_LOG_MSG_ERROR, "Hash digest length was different than expected");
  }

out:
  Object_ASSIGN_NULL(hash_obj);
  return error;
}

void hash_handle_cmd(const struct hash_req *req, struct hash_rsp *rsp)
{
  int error;

  switch (req->cmd) {
    case QSEE_HASH:
      error = qsee_hash_run(req, rsp);
      if (error) {
        rsp->status = HASH_FAILURE;
      } else {
        rsp->status = HASH_SUCCESS;
      }
      break;

    case QSEE_HASH_MODIFIED_CMD:
      error = qsee_hash_modified_cmd_run(req, rsp);
      if (error) {
        rsp->status = HASH_FAILURE;
      } else {
        rsp->status = HASH_SUCCESS;
      }
      break;

    case INVOKE_HASH:
      error = invoke_hash_run(req, rsp);
      if (error) {
        rsp->status = INVOKE_HASH_FAILURE;
      } else {
        rsp->status = HASH_SUCCESS;
      }
      break;

    default:
      rsp->status = HASH_FAILURE;
      break;
  }
}
