/**
 * \file
 * \brief PKCS11 Library Object Handling Base
 *
 * \copyright (c) 2017 Microchip Technology Inc. and its subsidiaries.
 *            You may use this software and any derivatives exclusively with
 *            Microchip products.
 *
 * \page License
 *
 * (c) 2017 Microchip Technology Inc. and its subsidiaries. You may use this
 * software and any derivatives exclusively with Microchip products.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
 * EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
 * PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION
 * WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION.
 *
 * IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
 * INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
 * WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS
 * BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE
 * FULLEST EXTENT ALLOWED BY LAW, MICROCHIPS TOTAL LIABILITY ON ALL CLAIMS IN
 * ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
 * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 *
 * MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE
 * TERMS.
 */

#include "cryptoki.h"
#include "pkcs11_config.h"
#include "pkcs11_debug.h"
#include "pkcs11_init.h"
#include "pkcs11_session.h"
#include "pkcs11_util.h"
#include "pkcs11_object.h"
#include "pkcs11_find.h"
#include "pkcs11_key.h"
#include "pkcs11_cert.h"
#include "cryptoauthlib.h"

/**
 * \defgroup pkcs11 Object (pkcs11_object_)
   @{ */

#if PKCS11_USE_STATIC_MEMORY
static pkcs11_object pkcs11_object_store[PKCS11_MAX_OBJECTS_ALLOWED];
#endif

pkcs11_object_cache_t pkcs11_object_cache[PKCS11_MAX_OBJECTS_ALLOWED];

/** For object handle tracking */
static CK_OBJECT_HANDLE pkcs11_object_alloc_handle(void)
{
    static CK_OBJECT_HANDLE pkcs11_object_last_handle = 1;

    if (pkcs11_object_last_handle)
    {
        pkcs11_object_last_handle++;
    }

    return pkcs11_object_last_handle;
}

/**
 * CKA_CLASS == CKO_HW_FEATURE_TYPE
 * CKA_HW_FEATURE_TYPE == CKH_MONOTONIC_COUNTER
 */
const pkcs11_attrib_model pkcs11_object_monotonic_attributes[] = {
    /** Object Class - CK_OBJECT_CLASS */
    { CKA_CLASS,           pkcs11_object_get_class                                            },
    /** Hardware Feature Type - CK_HW_FEATURE_TYPE */
    { CKA_HW_FEATURE_TYPE, pkcs11_object_get_type                                             },
    /** Counter will reset to a previously returned value if the token is
        initialized using C_InitToken. */
    { CKA_RESET_ON_INIT,   pkcs11_attrib_false                                                },
    /** Counter has been reset at least once at some point in time. */
    { CKA_HAS_RESET,       pkcs11_attrib_false                                                },
    /** Current value of the monotonic counter. Big endian order. */
    { CKA_VALUE,           NULL_PTR                                                           },
};

const CK_ULONG pkcs11_object_monotonic_attributes_count = PKCS11_UTIL_ARRAY_SIZE(pkcs11_object_monotonic_attributes);

///**
// * Mandatory Object Identification Fields for All objects not identified
// * as CKA_CLASS == CKO_HW_FEATURE
// */
//const pkcs11_attrib_model const pkcs11_object_storage_attributes[] = {
//    /** Object Class - CK_OBJECT_CLASS */
//    { CKA_CLASS,                        pkcs11_object_get_class },
//    /** CK_TRUE if object is a token object; CK_FALSE if object is a session object. Default is CK_FALSE. */
//    { CKA_TOKEN,                        pkcs11_attrib_true },
//    /** CK_TRUE if object is a private object; CK_FALSE if object is a public object. */
//    { CKA_PRIVATE,                      pkcs11_key_get_access_type },
//    /** CK_TRUE if object can be modified. Default is CK_TRUE. */
//    { CKA_MODIFIABLE,                   NULL_PTR },
//    /** Description of the object(default empty). */
//    { CKA_LABEL,                        pkcs11_object_get_name },
//    /** CK_TRUE if object can be copied using C_CopyObject.Defaults to CK_TRUE. */
//    { CKA_COPYABLE,                     pkcs11_attrib_false },
//    /** CK_TRUE if the object can be destroyed using C_DestroyObject. Default is CK_TRUE. */
//    { CKA_DESTROYABLE,                  pkcs11_attrib_false },
//};

///**
// * CKO_DATA - Object Attribute Model
// * Other than providing access to it, Cryptoki does not attach any special meaning to a data object.
// */
//const pkcs11_attrib_model pkcs11_object_data_attributes[] = {
//    /** Description of the application that manages the object(default empty) */
//    { CKA_APPLICATION,                  NULL_PTR },
//    /** DER - encoding of the object identifier indicating the data object type(default empty) */
//    { CKA_OBJECT_ID,                    NULL_PTR },
//    /** Value of the object(default empty) */
//    { CKA_VALUE,                        NULL_PTR }
//
//};

CK_RV pkcs11_object_alloc(pkcs11_object_ptr * ppObject)
{
    CK_ULONG i;
    CK_RV rv = CKR_OK;

    if (!ppObject)
    {
        rv = CKR_ARGUMENTS_BAD;
    }
    else
    {
        *ppObject = NULL;
    }

    for (i = 0; i < PKCS11_MAX_OBJECTS_ALLOWED && CKR_OK == rv; i++)
    {
        if (!pkcs11_object_cache[i].object)
        {
#if PKCS11_USE_STATIC_MEMORY
            *ppObject = &pkcs11_object_store[i];
#else
            /* Use dynamic memory functions from OS abstraction layer */
#endif
            if (*ppObject)
            {
                memset(*ppObject, 0, sizeof(pkcs11_object));
                pkcs11_object_cache[i].handle = pkcs11_object_alloc_handle();
                pkcs11_object_cache[i].object = *ppObject;
            }
            break;
        }
    }

    if (PKCS11_MAX_OBJECTS_ALLOWED == i)
    {
        rv = CKR_HOST_MEMORY;
    }

    return rv;
}

CK_RV pkcs11_object_free(pkcs11_object_ptr pObject)
{
    CK_ULONG i;

    for (i = 0; i < PKCS11_MAX_OBJECTS_ALLOWED; i++)
    {
        if (pObject == pkcs11_object_cache[i].object)
        {
            /* Delink it */
            pkcs11_object_cache[i].object = NULL_PTR;
            pkcs11_object_cache[i].handle = 0;
        }
    }

    if (pObject)
    {
#if PKCS11_USE_STATIC_MEMORY
        memset(pObject, 0, sizeof(pkcs11_object));
#else
        if (pObject->data && (pObject->flags & PKCS11_OBJECT_FLAG_DYNAMIC))
        {
            pkcs11_os_free(pObject->data);
        }
        /* Use dynamic memory functions from OS abstraction layer */
#endif
    }

    return CKR_OK;
}

CK_RV pkcs11_object_check(pkcs11_object_ptr * ppObject, CK_OBJECT_HANDLE hObject)
{
    CK_ULONG i;

    if (!hObject)
    {
        return CKR_OBJECT_HANDLE_INVALID;
    }

    for (i = 0; i < PKCS11_MAX_OBJECTS_ALLOWED; i++)
    {
        if (hObject == pkcs11_object_cache[i].handle)
        {
            break;
        }
    }
    if (i == PKCS11_MAX_OBJECTS_ALLOWED)
    {
        return CKR_OBJECT_HANDLE_INVALID;
    }
    else if (ppObject)
    {
        *ppObject = pkcs11_object_cache[i].object;
    }

    return CKR_OK;
}

CK_RV pkcs11_object_get_handle(pkcs11_object_ptr pObject, CK_OBJECT_HANDLE_PTR phObject)
{
    CK_ULONG i;

    if (!phObject || !pObject)
    {
        return CKR_ARGUMENTS_BAD;
    }

    for (i = 0; i < PKCS11_MAX_OBJECTS_ALLOWED; i++)
    {
        if (pObject == pkcs11_object_cache[i].object)
        {
            *phObject = pkcs11_object_cache[i].handle;
            break;
        }
    }
    if (i == PKCS11_MAX_OBJECTS_ALLOWED)
    {
        return CKR_OBJECT_HANDLE_INVALID;
    }

    return CKR_OK;
}


CK_RV pkcs11_object_get_name(CK_VOID_PTR pObject, CK_ATTRIBUTE_PTR pAttribute)
{
    pkcs11_object_ptr obj_ptr = (pkcs11_object_ptr)pObject;

    if (!obj_ptr)
    {
        return CKR_ARGUMENTS_BAD;
    }

    if (obj_ptr->name)
    {
        return pkcs11_attrib_fill(pAttribute, (const CK_VOID_PTR)obj_ptr->name, strlen((char*)obj_ptr->name));
    }
    else
    {
        return pkcs11_attrib_fill(pAttribute, NULL_PTR, 0);
    }
}

CK_RV pkcs11_object_get_class(CK_VOID_PTR pObject, CK_ATTRIBUTE_PTR pAttribute)
{
    pkcs11_object_ptr obj_ptr = (pkcs11_object_ptr)pObject;

    if (!obj_ptr)
    {
        return CKR_ARGUMENTS_BAD;
    }

    return pkcs11_attrib_fill(pAttribute, &obj_ptr->class_id, sizeof(obj_ptr->class_id));
}

CK_RV pkcs11_object_get_type(CK_VOID_PTR pObject, CK_ATTRIBUTE_PTR pAttribute)
{
    pkcs11_object_ptr obj_ptr = (pkcs11_object_ptr)pObject;

    if (!obj_ptr)
    {
        return CKR_ARGUMENTS_BAD;
    }

    return pkcs11_attrib_fill(pAttribute, &obj_ptr->class_type, sizeof(obj_ptr->class_type));
}

CK_RV pkcs11_object_get_destroyable(CK_VOID_PTR pObject, CK_ATTRIBUTE_PTR pAttribute)
{
    pkcs11_object_ptr obj_ptr = (pkcs11_object_ptr)pObject;

    if (!obj_ptr)
    {
        return CKR_ARGUMENTS_BAD;
    }

    if (obj_ptr->flags & PKCS11_OBJECT_FLAG_DESTROYABLE)
    {
        return pkcs11_attrib_true(pObject, pAttribute);
    }
    else
    {
        return pkcs11_attrib_false(pObject, pAttribute);
    }
}

CK_RV pkcs11_object_get_size(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, CK_ULONG_PTR pulSize)
{
    pkcs11_object_ptr pObject;
    CK_RV rv;

    rv = pkcs11_init_check(NULL, FALSE);
    if (rv)
    {
        return rv;
    }

    if (!pulSize)
    {
        return CKR_ARGUMENTS_BAD;
    }

    rv = pkcs11_session_check(NULL_PTR, hSession);
    if (rv)
    {
        return rv;
    }

    rv = pkcs11_object_check(&pObject, hObject);
    if (rv)
    {
        return rv;
    }

    *pulSize = pObject->size;

    return CKR_OK;
}

CK_RV pkcs11_object_find(pkcs11_object_ptr * ppObject, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{
    int i;
    CK_ATTRIBUTE_PTR pName = NULL;
    CK_OBJECT_CLASS class = CKO_PRIVATE_KEY;    /* Unless specified assume private key object */

    if (!ppObject || !pTemplate || !ulCount)
    {
        return CKR_ARGUMENTS_BAD;
    }

    /* Match Name and Class */
    for (i = 0; i < ulCount; i++, pTemplate++)
    {
        switch (pTemplate->type)
        {
        case CKA_LABEL:
            pName = pTemplate;
            break;
        case CKA_CLASS:
            class = *((CK_OBJECT_CLASS_PTR)pTemplate->pValue);
            break;
        default:
            break;
        }
    }

    if (pName)
    {
        for (i = 0; i < PKCS11_MAX_OBJECTS_ALLOWED; i++)
        {
            pkcs11_object_ptr pObj = pkcs11_object_cache[i].object;
            if (pObj)
            {
                if ((pObj->class_id == class) && (strlen(pObj->name) == pName->ulValueLen))
                {
                    if (!memcmp(pObj->name, pName->pValue, pName->ulValueLen))
                    {
                        *ppObject = pObj;
                        break;
                    }
                }
            }
        }
    }

    return CKR_OK;
}

/**
 * \brief Create a new object on the token in the specified session using the given attribute template
 */
CK_RV pkcs11_object_create
(
    CK_SESSION_HANDLE    hSession,
    CK_ATTRIBUTE_PTR     pTemplate,
    CK_ULONG             ulCount,
    CK_OBJECT_HANDLE_PTR phObject
)
{
    CK_RV rv;
    pkcs11_object_ptr pObject;
    CK_ATTRIBUTE_PTR pLabel = NULL;
    CK_OBJECT_CLASS_PTR pClass = NULL;
    CK_ATTRIBUTE_PTR pData = NULL;
    int i;
    pkcs11_lib_ctx_ptr pLibCtx = NULL;
    pkcs11_session_ctx_ptr pSession = NULL;

    rv = pkcs11_init_check(&pLibCtx, FALSE);
    if (rv)
    {
        return rv;
    }

    rv = pkcs11_session_check(&pSession, hSession);
    if (rv)
    {
        return rv;
    }

    /* Look for supported/mandatory attributes */
    for (i = 0; i < ulCount; i++)
    {
        switch (pTemplate[i].type)
        {
        case CKA_LABEL:
            pLabel = &pTemplate[i];
            break;
        case CKA_CLASS:
            pClass = pTemplate[i].pValue;
            break;
        case CKA_VALUE:
        /* fall-through */
        case CKA_EC_POINT:
            pData = &pTemplate[i];
            break;
        default:
            break;
        }
    }

    rv = pkcs11_object_alloc(&pObject);
    if (pObject)
    {
        switch (*pClass)
        {
        case CKO_CERTIFICATE:
            rv = pkcs11_config_cert(pLibCtx, pSession->slot, pObject, pLabel);
            if (CKR_OK == rv)
            {
                rv = pkcs11_cert_x509_write(pObject, pData);
            }
            break;
        case CKO_PUBLIC_KEY:
            pObject->class_id = CKO_PUBLIC_KEY;
            if (CKR_OK == (rv = pkcs11_config_key(pLibCtx, pSession->slot, pObject, pLabel)))
            {
                rv = pkcs11_key_write(pSession, pObject, pData);
            }
            break;
        case CKO_PRIVATE_KEY:
            pObject->class_id = CKO_PRIVATE_KEY;
            if (CKR_OK == (rv = pkcs11_config_key(pLibCtx, pSession->slot, pObject, pLabel)))
            {
                rv = pkcs11_key_write(pSession, pObject, pData);
            }
            break;
        default:
            break;
        }
        if (CKR_OK == rv)
        {
            rv = pkcs11_object_get_handle(pObject, phObject);
        }
    }

    return rv;
}

/**
 * \brief Destroy the specified object
 */
CK_RV pkcs11_object_destroy(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject)
{
    pkcs11_object_ptr pObject;
    CK_RV rv;
    pkcs11_lib_ctx_ptr pLibCtx = NULL;
    pkcs11_session_ctx_ptr pSession = NULL;

    rv = pkcs11_init_check(&pLibCtx, FALSE);
    if (rv)
    {
        return rv;
    }

    rv = pkcs11_session_check(&pSession, hSession);
    if (rv)
    {
        return rv;
    }

    rv = pkcs11_object_check(&pObject, hObject);
    if (rv)
    {
        return rv;
    }

    if (pObject->flags & PKCS11_OBJECT_FLAG_DESTROYABLE)
    {
#if !PKCS11_USE_STATIC_CONFIG
        pkcs11_config_remove_object(pLibCtx, pSession->slot, pObject);
#endif
        return pkcs11_object_free(pObject);
    }
    else
    {
        return CKR_ACTION_PROHIBITED;
    }
}

/* Interal function to clean up resources */
CK_RV pkcs11_object_deinit(pkcs11_lib_ctx_ptr pContext)
{
    CK_RV rv = CKR_OK;
    int i;

    for (i = 0; i < PKCS11_MAX_OBJECTS_ALLOWED; i++)
    {
        pkcs11_object_ptr pObj = pkcs11_object_cache[i].object;
        if (pObj)
        {
            CK_RV tmp = pkcs11_object_free(pObj);
            if (!rv)
            {
                rv = tmp;
            }
        }
    }
    return rv;
}



/** @} */