/*
 * src/teesl/tee_persistent_time.c
 *
 * Copyright (C) 2014, Samsung Electronics Co., Ltd.
 *
 * TEE Internal API Time functions
 */

#include <tee_internal_api.h>

#include <errno.h>
#include <stdio.h>
#include <time.h>

///////////////////////////////////////////////////////////////////////
//             Helpers and local definitions
//////////////////////////////////////////////////////////////////////
#define NUM_MILLIS_IN_SEC                 (1000)
#define NUM_NANOS_IN_MILLI                (1000000L)
#define TA_PERSISTENT_TIME_OFFSET_OBJECT  "PersistentTime"

typedef struct {
  bool isLeading;
  TEE_Time offset;
} RelativeTime;

static bool TAPersistentTimeResetNeeded(bool isSet, bool newVal) {
  static bool isResetNeeded = false;

  if (isSet) {
    isResetNeeded = newVal;
  }
  return isResetNeeded;
}


///////////////////////////////////////////////////////////////////////
//             TEE Internal API Time Functions
//////////////////////////////////////////////////////////////////////

TEE_Result TEE_GetTAPersistentTime(TEE_Time *time) {
  char             *objectID = TA_PERSISTENT_TIME_OFFSET_OBJECT;
  uint32_t objectIDSize = sizeof(TA_PERSISTENT_TIME_OFFSET_OBJECT) - 1;
  TEE_ObjectHandle objectHandle;
  TEE_Result result = TEE_SUCCESS;
  TEE_Time currTime = { 0U, 0U };
  TEE_Time persistentTime = { 0U, 0U };
  RelativeTime TATime = { false, { 0U, 0U } };

  if (!time) {
    return TEE_ERROR_BAD_PARAMETERS;
  }
  if (TAPersistentTimeResetNeeded(false, false)) {
    return TEE_ERROR_TIME_NEEDS_RESET;
  }

  result = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE,
                                    (void *)objectID, objectIDSize,
                                    (TEE_DATA_FLAG_ACCESS_READ |
                                     TEE_DATA_FLAG_ACCESS_WRITE),
                                    &objectHandle);
  if (TEE_SUCCESS == result) {
    uint32_t count;
    result = TEE_ReadObjectData(objectHandle, (void *)&TATime, sizeof(TATime),
                                &count);
    if ((TEE_SUCCESS == result) && (count != sizeof(TATime))) {
      (void)TAPersistentTimeResetNeeded(true, true);
      result = TEE_ERROR_TIME_NEEDS_RESET;
    } else {
      TEE_GetREETime(&currTime);
      if (TEE_ERROR_CORRUPT_OBJECT == result) {
        TEE_CloseAndDeletePersistentObject1(objectHandle);
        (void)TAPersistentTimeResetNeeded(true, true);
        return TEE_ERROR_TIME_NEEDS_RESET;
      }
      if (TATime.isLeading) {
        if ((UINT32_MAX - currTime.seconds) < TATime.offset.seconds) {
          result = TEE_ERROR_OVERFLOW;
        } else {
          persistentTime.seconds = currTime.seconds + TATime.offset.seconds;

          uint32_t tmp = currTime.millis + TATime.offset.millis;
          persistentTime.seconds += tmp / NUM_MILLIS_IN_SEC;
          persistentTime.millis = tmp % NUM_MILLIS_IN_SEC;
        }
      } else {
        if ((currTime.seconds < TATime.offset.seconds) ||
            ((currTime.seconds == TATime.offset.seconds) &&
             (TATime.offset.millis > currTime.millis))) {
          /* Persistent time of TA can not be negetive */
          (void)TAPersistentTimeResetNeeded(true, true);
          result = TEE_ERROR_TIME_NEEDS_RESET;
        } else {
          persistentTime.seconds = currTime.seconds - TATime.offset.seconds - 1;

          uint32_t tmp = currTime.millis + NUM_MILLIS_IN_SEC -
                         TATime.offset.millis;
          persistentTime.seconds += tmp / NUM_MILLIS_IN_SEC;
          persistentTime.millis = tmp % NUM_MILLIS_IN_SEC;
        }
      }
    }
    TEE_CloseObject(objectHandle);
    *time = persistentTime;
    return result;
  } else if (TEE_ERROR_ITEM_NOT_FOUND == result) {
    result = TEE_ERROR_TIME_NOT_SET;
  }

  return result;
}

TEE_Result TEE_SetTAPersistentTime(TEE_Time *time) {

  char              *objectID = TA_PERSISTENT_TIME_OFFSET_OBJECT;
  uint32_t objectIDSize = sizeof(TA_PERSISTENT_TIME_OFFSET_OBJECT) - 1;
  TEE_ObjectHandle objectHandle;
  TEE_Result result = TEE_SUCCESS;
  TEE_Time currTime = { 0U, 0U };

  RelativeTime TATime = { false, { 0U, 0U } };

  if (!time) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  TEE_GetREETime(&currTime);

  if ((currTime.seconds > time->seconds) ||
      ((currTime.seconds == time->seconds) &&
       (currTime.millis >= time->millis))) {
    TATime.isLeading = false;
    if (currTime.millis >= time->millis) {
      TATime.offset.millis = currTime.millis - time->millis;
      TATime.offset.seconds = currTime.seconds - time->seconds;
    } else {
      TATime.offset.millis = currTime.millis + NUM_MILLIS_IN_SEC - time->millis;
      TATime.offset.seconds = currTime.seconds - time->seconds - 1;
    }
  } else {
    TATime.isLeading = true;
    if (time->millis >= currTime.millis) {
      TATime.offset.millis = time->millis - currTime.millis;
      TATime.offset.seconds = time->seconds - currTime.seconds;
    } else {
      TATime.offset.millis = time->millis + NUM_MILLIS_IN_SEC - currTime.millis;
      TATime.offset.seconds = time->seconds - currTime.seconds - 1;

    }
  }

  result = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE,
                                    (void *)objectID, objectIDSize,
                                    (TEE_DATA_FLAG_ACCESS_READ |
                                     TEE_DATA_FLAG_ACCESS_WRITE),
                                    &objectHandle);
  if (TEE_SUCCESS == result) {
    result = TEE_WriteObjectData(objectHandle, (void *)&TATime, sizeof(TATime));
    if (result == TEE_SUCCESS) {
      (void)TAPersistentTimeResetNeeded(true, false);
    }
    if (TEE_ERROR_CORRUPT_OBJECT == result) {
        objectHandle = TEE_HANDLE_NULL;
        result = TEE_ERROR_GENERIC;
    }
    TEE_CloseObject(objectHandle);
    return result;
  } else if (TEE_ERROR_ITEM_NOT_FOUND == result) {
    result = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE,
                                        (void *)objectID, objectIDSize,
                                        (TEE_DATA_FLAG_ACCESS_READ |
                                         TEE_DATA_FLAG_ACCESS_WRITE),
                                        TEE_HANDLE_NULL, (void *)&TATime,
                                        sizeof(TATime),
                                        &objectHandle);

    if (TEE_SUCCESS == result) {
      (void)TAPersistentTimeResetNeeded(true, false);
      TEE_CloseObject(objectHandle);
      return result;
    }
  }

  return result;
}
