/*
 * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Created in Samsung Ukraine R&D Center (SRK) under a contract between
 * LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine)
 * and "Samsung Electronics Co", Ltd (Seoul, Republic of Korea)
 *
 * Created on: Dec 15, 2017
 * Author: Kostiantyn Volobuiev <k.volobuyev@samsung.com>
 * Brief: Module that responds for SFS (Secure File System) operations.
 */

#include "TzwStorage.h"

#include <limits.h>

#include "TzwMacro.h"
#include "TzwMemory.h"
#include "TzwString.h"

#if defined(TZ_MODEL_QCOM)
    #include <qsee_fs.h>
    #include <qsee_sfs.h>
    #include <IxErrno.h>
#endif

struct __TzwSfsObject {
#if defined(TZ_MODEL_BLOWFISH)
    TEE_ObjectHandle handle;
#elif defined(TZ_MODEL_QCOM)
    uint32_t fd;
    char* path;
    TEE_ObjectInfo objInfo;
#endif
};

struct __TzwSfsObjectList {
#if defined(TZ_MODEL_BLOWFISH)
    TEE_ObjectEnumHandle objectEnumerator;
#elif defined(TZ_MODEL_QCOM)
    sfs_file_entry* list;
    uint32_t listSize;
    uint32_t currentIndx;
#endif
};

#if defined(TZ_MODEL_QCOM)
    // function ensures that path to object is c-string, ends with '\0'
    static char* createPathToObject(const char* oid, size_t oidlen) {
        const size_t pathToObjectLength = oidlen + 1;
        char* path = (char*) tzwMalloc(pathToObjectLength);
        if (NULL == path) {
            LOG_E("Failed to allocate memory for path");
            return path;
        }
        tzwMemMove((void*) path, (void*) oid, oidlen);

        return path;
    }

    static int toQseeAccessFlags(uint32_t flags) {
        int res = O_RDONLY;
        if ((flags & TEE_DATA_FLAG_ACCESS_WRITE) && (flags & TEE_DATA_FLAG_ACCESS_READ)) {
            res |= O_RDWR;
        }

        if (flags & TEE_DATA_FLAG_ACCESS_WRITE) {
            res |= O_WRONLY;
        }

        if (flags & TEE_DATA_FLAG_CREATE) {
            res |= O_CREAT;
        }

        return res;
    }

    static int toQseeWhence(TEE_Whence whence) {
        int qseeWhence;
        switch (whence) {
        case TEE_DATA_SEEK_SET:
            qseeWhence = SEEK_SET;
            break;
        case TEE_DATA_SEEK_END:
            qseeWhence = SEEK_END;
            break;
        case TEE_DATA_SEEK_CUR:
        default:
            qseeWhence = SEEK_CUR;
            break;
        }
        return qseeWhence;
    }
#endif

TzwErrorCode_t tzwCreateSfsObject(void* objectID, size_t objectIDLen, uint32_t flags, TzwSfsObject_t* handle) {
    LOG_FUNC_BEGIN;

    CHECK_FUNCTION_ARGUMENT_RETURN(NULL != objectID);
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != handle);

    TzwErrorCode_t status = TEE_SUCCESS;
    TzwSfsObject_t lHandle = TEE_HANDLE_NULL;
    char* path = NULL;

    do {
        lHandle = tzwMalloc(sizeof(struct __TzwSfsObject));
        CHECK_BUFFER_ALLOCATED_BREAK(lHandle);

#if defined(TZ_MODEL_BLOWFISH)
        lHandle->handle = TEE_HANDLE_NULL;
        status = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE,
                                            objectID, objectIDLen, flags,
                                            TEE_HANDLE_NULL, NULL, 0,
                                            &lHandle->handle);
        CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_CreatePersistentObject");

#elif defined(TZ_MODEL_QCOM)
        path = createPathToObject((char*) objectID, objectIDLen);
        CHECK_BUFFER_ALLOCATED_BREAK(path);

        flags |= TEE_DATA_FLAG_CREATE;

        qsee_sfs_rm(path);

        lHandle->path = path;
        lHandle->fd = qsee_sfs_open(lHandle->path, toQseeAccessFlags(flags));
        if (0 == lHandle->fd) {
            status = TEE_ERROR_ACCESS_DENIED;
            LOG_E("qsee_sfs_open failed");
            break;
        }
        path = NULL;
        lHandle->objInfo.handleFlags |= TEE_HANDLE_FLAG_PERSISTENT;
        lHandle->objInfo.dataSize = 0;
        lHandle->objInfo.dataPosition = 0;

#endif
        *handle = lHandle;
        lHandle = TEE_HANDLE_NULL;
    } while (0);

    if (TEE_SUCCESS != status) {
        tzwFree(path);
        tzwFree(lHandle);
    }
    LOG_FUNC_END;
    return status;
}

TzwErrorCode_t tzwOpenSfsObject(void* objectID, size_t objectIDLen, uint32_t flags, TzwSfsObject_t* handle) {
    CHECK_FUNCTION_ARGUMENT_RETURN(NULL != objectID);
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != handle);

    TEE_Result status = TEE_SUCCESS;
    char* path = NULL;
    TzwSfsObject_t lHandle = TEE_HANDLE_NULL;

    do {
        lHandle = tzwMalloc(sizeof(struct __TzwSfsObject));
        CHECK_BUFFER_ALLOCATED_BREAK(lHandle);

#if defined(TZ_MODEL_BLOWFISH)
        lHandle->handle = TEE_HANDLE_NULL;
        status = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, objectID, objectIDLen, flags, &lHandle->handle);
        if (TEE_SUCCESS != status) {
            break;
        }
#elif defined(TZ_MODEL_QCOM)
        path = createPathToObject((char*) objectID, objectIDLen);
        CHECK_BUFFER_ALLOCATED_BREAK(path);

        lHandle->path = path;
        lHandle->fd = qsee_sfs_open(lHandle->path, toQseeAccessFlags(flags));
        if (0 == lHandle->fd) {
            status = TEE_ERROR_ITEM_NOT_FOUND;
            break;
        }

        int ret = qsee_sfs_seek(lHandle->fd, 0, SEEK_SET);
        if (ret < 0) {
            LOG_E("Failed seek to %d", ret);
            return TEE_ERROR_CORRUPT_OBJECT;
        }

        uint32_t filesize = 0;
        qsee_sfs_getSize(lHandle->fd, &filesize);

        path = NULL;
        lHandle->objInfo.dataPosition = 0;
        lHandle->objInfo.dataSize = filesize;
        lHandle->objInfo.handleFlags |= TEE_HANDLE_FLAG_PERSISTENT;
#endif
        *handle = lHandle;
        lHandle = TEE_HANDLE_NULL;
    } while (0);

    if (TEE_SUCCESS != status) {
        tzwFree(path);
        tzwFree(lHandle);
    }
    return status;
}

void tzwCloseSfsObject(TzwSfsObject_t handle) {
    if (TEE_HANDLE_NULL == handle) {
        return;
    }
#if defined(TZ_MODEL_BLOWFISH)
    TEE_CloseObject(handle->handle);
#elif defined(TZ_MODEL_QCOM)
    int ret = qsee_sfs_close(handle->fd);
    if (E_FAILURE == ret) {
        int sfs_errno = qsee_sfs_error(handle->fd);
        LOG_E("qsee_sfs_close failed! sfs_errno = %d", sfs_errno);
    }
    tzwFree(handle->path);
#endif
    tzwFree(handle);
}

void tzwCloseAndDeleteSfsObject(TzwSfsObject_t handle) {
    if (TEE_HANDLE_NULL == handle) {
        LOG_E("Handle is null");
        return;
    }
#if defined(TZ_MODEL_BLOWFISH)
    TEE_CloseAndDeletePersistentObject(handle->handle);
#elif defined(TZ_MODEL_QCOM)
    char* path = NULL;
    do {
        const size_t sz = strlen(handle->path) + 1;
        path = (char*) tzwMalloc(sz);
        if (NULL == path) {
            tzwCloseSfsObject(handle);
            break;
        }
        tzwMemMove(path, handle->path, sz);
        tzwCloseSfsObject(handle);
        int ret = qsee_sfs_rm(path);
        if (E_SUCCESS != ret) {
            LOG_E("Failed to delete object %.*s", (int)sz, path);
        }
    } while (0);

    tzwFree(path);
#endif
}

TzwErrorCode_t tzwWriteToSfsObject(TzwSfsObject_t handle, void* buf, size_t buflen) {
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != handle);

    LOG_FUNC_BEGIN
#if defined(TZ_MODEL_BLOWFISH)
    return TEE_WriteObjectData(handle->handle, buf, buflen);
#elif defined(TZ_MODEL_QCOM)
    int ret = qsee_sfs_write(handle->fd, (const char*) buf, buflen); // returns number of written bytes
    if (ret < 0) {
        LOG_E("Failed to write to object!");
        return TEE_ERROR_CORRUPT_OBJECT;
    }
    if ((size_t) ret != buflen) {
        LOG_E("Failed to write to file! 'ret != buflen' ");
        return TEE_ERROR_GENERIC;
    }
    handle->objInfo.dataPosition += ret;
    handle->objInfo.dataSize += ret;

#endif
    LOG_FUNC_END
    return TEE_SUCCESS;
}

TzwErrorCode_t tzwReadFromSfsObject(TzwSfsObject_t handle, void* buf, size_t buflen, uint32_t* outCount) {
    CHECK_FUNCTION_ARGUMENT_RETURN(NULL != outCount);
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != handle);

#if defined(TZ_MODEL_BLOWFISH)
    return TEE_ReadObjectData(handle->handle, buf, buflen, outCount);
#elif defined(TZ_MODEL_QCOM)
    TEE_Result status = TEE_SUCCESS;
    int ret = qsee_sfs_read(handle->fd, (char*) buf, buflen);
    if (ret < 0) {
        *outCount = 0;
        status = TEE_ERROR_CORRUPT_OBJECT;
        int sfs_errno = qsee_sfs_error(handle->fd);
        LOG_E("Failed to read from object! sfserrno = %d", sfs_errno);
    } else {
        *outCount = ret;
        handle->objInfo.dataPosition += ret;
    }
    return status;
#endif
}

TzwErrorCode_t tzwSeekIntoSfsObject(TzwSfsObject_t handle, int32_t offset, TEE_Whence whence) {
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != handle);
#if defined(TZ_MODEL_BLOWFISH)
    return TEE_SeekObjectData(handle->handle, offset, whence);
#elif defined(TZ_MODEL_QCOM)
    int ret = qsee_sfs_seek(handle->fd, offset, toQseeWhence(whence)); // returns current location on success
    if (ret < 0) {
        LOG_E("Failed seek to %d", offset);
        return TEE_ERROR_CORRUPT_OBJECT;
    }

    handle->objInfo.dataPosition = ret;
    return TEE_SUCCESS;
#endif
}

TzwErrorCode_t tzwGetSfsObjectInfo(TzwSfsObject_t object, TEE_ObjectInfo *objectInfo) {
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != object);
#if defined(TZ_MODEL_BLOWFISH)
    TEE_GetObjectInfo(object->handle, objectInfo);
    return TEE_SUCCESS;
#elif defined(TZ_MODEL_QCOM)
    tzwMemMove(objectInfo, &(object->objInfo), sizeof(TEE_ObjectInfo));
    return TEE_SUCCESS;
#endif
}

TzwErrorCode_t tzwTruncateSfsObjectData(TzwSfsObject_t object, uint32_t size) {
    LOG_FUNC_BEGIN;
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != object);

#if defined(TZ_MODEL_BLOWFISH)
    LOG_FUNC_END;
    return TEE_TruncateObjectData(object->handle, size);
#elif defined(TZ_MODEL_QCOM)
    uint8_t* tmpBuff = NULL;
    TzwErrorCode_t status = TEE_SUCCESS;
    int ret = E_SUCCESS;
    do {
        if (object->objInfo.dataSize == size) {
            LOG_D("Not needed to truncate!");
            break;
        } else if (object->objInfo.dataSize > size) {

            const uint32_t NEED_READ_BYTES = size > object->objInfo.dataSize ?  object->objInfo.dataSize : size;

            LOG_D("Allocate buffer for object data... %d", NEED_READ_BYTES);
            tmpBuff = tzwMalloc(NEED_READ_BYTES);
            CHECK_BUFFER_ALLOCATED_RETURN(tmpBuff);

            LOG_D("seek to object begining...");
            ret = qsee_sfs_seek(object->fd, 0, SEEK_SET);
            if (ret < 0 ) {
                LOG_E("Failed seek to the begining of file");
                status = TEE_ERROR_CORRUPT_OBJECT;
                break;
            }

            LOG_D("Reading data from object...");
            ret = qsee_sfs_read(object->fd, (char*) tmpBuff, NEED_READ_BYTES);
            if (ret < 0) {
                LOG_E("Failed read from origin object!");
                status = TEE_ERROR_CORRUPT_OBJECT;
                break;
            }

            LOG_D("Close object...");
            ret = qsee_sfs_close(object->fd);
            if (E_SUCCESS != ret) {
                LOG_D("Failed to close object!");
                status = TEE_ERROR_GENERIC;
                break;
            }

            LOG_D("Truncate object...");
            object->fd = qsee_sfs_open(object->path, O_TRUNC | O_RDWR);
            if (0 == object->fd) {
                LOG_E("Failed to reopen file with Truncate flag");
                status = TEE_ERROR_ITEM_NOT_FOUND;
                break;
            }

            LOG_D("Write to object %u bytes...", NEED_READ_BYTES);
            ret = qsee_sfs_write(object->fd, (const char*)tmpBuff, NEED_READ_BYTES); // returns number of written bytes
            if (ret < 0) {
                LOG_E("Failed to write to object!");
                status = TEE_ERROR_CORRUPT_OBJECT;
                break;
            }
            if ((uint32_t) ret != NEED_READ_BYTES) {
                LOG_E("Not all data has been writted!, exp. %u, act. %d", NEED_READ_BYTES, ret);
                status = TEE_ERROR_CORRUPT_OBJECT;
                break;
            }
            object->objInfo.dataPosition = ret;
            object->objInfo.dataSize = ret;

        } else if (size > object->objInfo.dataSize){
            const uint32_t LACKS_BYTES = size - object->objInfo.dataSize;
                // If size is greater than the current size of the data stream
                // then the data stream is extended by adding zero bytes at the end of the stream.
            LOG_D("Allocate buffer for object data... %d", LACKS_BYTES);
            tmpBuff = tzwMalloc(LACKS_BYTES);
            CHECK_BUFFER_ALLOCATED_RETURN(tmpBuff);

            LOG_D("Write to object zero %u bytes...", LACKS_BYTES);
            ret = qsee_sfs_write(object->fd, (const char*)tmpBuff, LACKS_BYTES); // returns number of written bytes
            if (ret < 0) {
                LOG_E("Failed to write to object!");
                status = TEE_ERROR_CORRUPT_OBJECT;
                break;
            }
            if ((uint32_t) ret != LACKS_BYTES) {
                LOG_E("Not all data has been writted!, exp. %u, act. %d", LACKS_BYTES, ret);
                status = TEE_ERROR_CORRUPT_OBJECT;
                break;
            }
            object->objInfo.dataPosition += ret;
            object->objInfo.dataSize += ret;

        }
        LOG_D("Truncate success!");
        status = TEE_SUCCESS;

    } while(0);

    tzwFree(tmpBuff);
    if (status != TEE_SUCCESS) {
        object->objInfo.dataPosition = 0;
        object->objInfo.dataSize = 0;
    }

    LOG_FUNC_END;
    return status;
#endif
}

TzwErrorCode_t tzwAllocateSfsObjectList(TzwSfsObjectList_t* objectEnumerator) {
    TzwErrorCode_t status = TEE_SUCCESS;
    TzwSfsObjectList_t lenumerator = TEE_HANDLE_NULL;
    do {
        lenumerator = tzwMalloc(sizeof(struct __TzwSfsObjectList));
        CHECK_BUFFER_ALLOCATED_BREAK(lenumerator);

#if defined(TZ_MODEL_BLOWFISH)
        status = TEE_AllocatePersistentObjectEnumerator(&(lenumerator->objectEnumerator));
        CHECK_TEE_STATUS_SUCCESS_BREAK("TEE_AllocatePersistentObjectEnumerator()");
#elif defined(TZ_MODEL_QCOM)
        lenumerator->list = NULL;
        lenumerator->listSize = 0;
        lenumerator->currentIndx = 0;
#endif
        *objectEnumerator = lenumerator;
        lenumerator = TEE_HANDLE_NULL;
    } while (0);

    if (TEE_SUCCESS != status) {
        tzwFree(lenumerator);
    }
    return status;
}

TzwErrorCode_t tzwStartSfsObjectList(TzwSfsObjectList_t objectEnumerator) {
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != objectEnumerator);

    TzwErrorCode_t status = TEE_SUCCESS;
#if defined(TZ_MODEL_BLOWFISH)
    return TEE_StartPersistentObjectEnumerator(objectEnumerator->objectEnumerator, TEE_STORAGE_PRIVATE);
#elif defined(TZ_MODEL_QCOM)
    do {
        uint32_t listLen = 0;

        int ret = qsee_sfs_get_file_list(&(objectEnumerator->list), &listLen);
        if (0 != ret) {
            status = TEE_ERROR_ITEM_NOT_FOUND;
            break;
        }

        if (listLen == 0) {
            status = TEE_ERROR_ITEM_NOT_FOUND;
            break;
        } else {
            LOG_D("Items count is %d", listLen);

            objectEnumerator->listSize = listLen;
            objectEnumerator->currentIndx = 0;
            status = TEE_SUCCESS;
        }
    } while(0);
#endif
    return status;
}

TzwErrorCode_t tzwGetNextSfsObject(TzwSfsObjectList_t objectEnumerator,
                                   void* objectID, uint32_t* objectIDLen) {
    CHECK_FUNCTION_ARGUMENT_RETURN(TEE_HANDLE_NULL != objectEnumerator);
    CHECK_FUNCTION_ARGUMENT_RETURN(NULL != objectID);
    CHECK_FUNCTION_ARGUMENT_RETURN(NULL != objectIDLen);

    TzwErrorCode_t status = TEE_SUCCESS;
#if defined(TZ_MODEL_BLOWFISH)
    return TEE_GetNextPersistentObject(objectEnumerator->objectEnumerator, NULL, objectID, (uint32_t*) objectIDLen);
#elif defined(TZ_MODEL_QCOM)
    if (objectEnumerator->currentIndx < objectEnumerator->listSize) {
        sfs_file_entry* entry = &(objectEnumerator->list[objectEnumerator->currentIndx]);

        size_t objectIDLenTemp = strlen(entry->file_name);
        CHECK_CONDITION_RETURN(objectIDLenTemp < UINT_MAX);

        tzwMemMove(objectID, entry->file_name, strlen(entry->file_name));
        *objectIDLen = (uint32_t) objectIDLenTemp;
        objectEnumerator->currentIndx++;
    } else {
        status = TEE_ERROR_ITEM_NOT_FOUND;
    }
#endif
    return status;
}

void tzwFreeSfsObjectList(TzwSfsObjectList_t objectEnumerator) {
#if defined(TZ_MODEL_BLOWFISH)
    TEE_FreePersistentObjectEnumerator(objectEnumerator->objectEnumerator);
#elif defined(TZ_MODEL_QCOM)
    if (objectEnumerator != TEE_HANDLE_NULL) {
        if (objectEnumerator->list != NULL) {
            qsee_sfs_clean_file_list(objectEnumerator->list);
        }
    }
#endif
    tzwFree(objectEnumerator);
}
