/**
 * Copyright(c) 2011 Trusted Logic.   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name Trusted Logic nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Implementation Notes:
 *
 * The PKCS11 session handle is directly mapped on the
 * Trusted Foundations Software session handle (S_HANDLE).
 */

#include "pkcs11_internal.h"


/* ------------------------------------------------------------------------
                          Public Functions
------------------------------------------------------------------------- */


CK_RV PKCS11_EXPORT C_OpenSession(CK_SLOT_ID              slotID,        /* the slot's ID */
                                    CK_FLAGS              flags,         /* defined in CK_SESSION_INFO */
                                    CK_VOID_PTR           pApplication,  /* pointer passed to callback */
                                    CK_NOTIFY             Notify,        /* notification callback function */
                                    CK_SESSION_HANDLE*    phSession)     /* receives new session handle */
{
   CK_RV                   nErrorCode = CKR_OK;
   uint32_t                nErrorOrigin = TEEC_ORIGIN_API;
   TEEC_Result             nTeeError;
   TEEC_Operation          sOperation;
   PPKCS11_PRIMARY_SESSION_CONTEXT   pSession = NULL;
   PPKCS11_SECONDARY_SESSION_CONTEXT pSecondarySession = NULL;
   uint32_t                nLoginType;
   uint32_t                nLoginData = 0;
   void*                   pLoginData = NULL;
   bool                    bIsPrimarySession;
   char*                   pSignatureFile = NULL;
   uint32_t                nSignatureFileLen = 0;
   uint8_t                 nParamType3 = TEEC_NONE;

   /* Prevent the compiler from complaining about unused parameters */
   do{(void)pApplication;}while(0);
   do{(void)Notify;}while(0);

   if (phSession == NULL)
   {
      return CKR_ARGUMENTS_BAD;
   }

      /* Check Cryptoki is initialized */
   if (!g_bCryptokiInitialized)
   {
      return CKR_CRYPTOKI_NOT_INITIALIZED;
   }

   if ((flags & CKVF_OPEN_SUB_SESSION) == 0)
   {
      *phSession = CK_INVALID_HANDLE;

      /*
      * Allocate the session context
      */
      pSession = (PPKCS11_PRIMARY_SESSION_CONTEXT)malloc(sizeof(PKCS11_PRIMARY_SESSION_CONTEXT));
      if (pSession == NULL)
      {
         return CKR_DEVICE_MEMORY;
      }

      pSession->sHeader.nMagicWord  = PKCS11_SESSION_MAGIC;
      pSession->sHeader.nSessionTag = PKCS11_PRIMARY_SESSION_TAG;
      memset(&pSession->sSession, 0, sizeof(TEEC_Session));
      pSession->sSecondarySessionTable.pRoot = NULL_PTR;

      /* The structure must be initialized first (in a portable manner)
         to make it work on Win32 */
      memset(&pSession->sSecondarySessionTableMutex, 0,
               sizeof(pSession->sSecondarySessionTableMutex));
      libMutexInit(&pSession->sSecondarySessionTableMutex);

      switch (slotID)
      {
      case CKV_TOKEN_SYSTEM_SHARED:
      case CKV_TOKEN_USER_SHARED:
         nLoginType = TEEC_LOGIN_PUBLIC;
         break;

      case CKV_TOKEN_SYSTEM:
      case CKV_TOKEN_USER:
      default:
         nLoginType = TEEC_LOGIN_AUTHENTICATION;
         break;
      }

      /* Group tokens */
      if ((slotID >= 0x00010000) && (slotID <= 0x0002FFFF))
      {
         nLoginType = TEEC_LOGIN_GROUP;

         /* The 16 lower-order bits encode the group identifier */
         nLoginData = (uint32_t)slotID & 0x0000FFFF;
         pLoginData = (void*)&nLoginData;

         /* Update the slotID for the system / PKCS11 service */
         if ((slotID >= 0x00010000) && (slotID <= 0x0001FFFF))
         {
            /* System group token */
            slotID = 3;       /* CKV_TOKEN_SYSTEM_GROUP */
         }
         else  /* ((slotID >= 0x00020000) && (slotID <= 0x0002FFFF)) */
         {
            /* User group token */
            slotID = 0x4014;  /* CKV_TOKEN_USER_GROUP */
         }
      }

retry:
      memset(&sOperation, 0, sizeof(TEEC_Operation));

      if (nLoginType == TEEC_LOGIN_AUTHENTICATION)
      {
          nTeeError = TEEC_ReadSignatureFile((void **)&pSignatureFile, &nSignatureFileLen);
          if (nTeeError != TEEC_ERROR_ITEM_NOT_FOUND)
          {
              if (nTeeError != TEEC_SUCCESS)
              {
                  goto error;
              }

              sOperation.params[3].tmpref.buffer = pSignatureFile;
              sOperation.params[3].tmpref.size   = nSignatureFileLen;
              nParamType3 = TEEC_MEMREF_TEMP_INPUT;
          }
          else
          {
              /* No signature file found.
              * Should use LOGIN_APPLICATION for now
              * Can not use TEEC_LOGIN_AUTHENTICATION as this means that all .exe wil need a signature file
              * - a bit annoying for when passing the tests
              */
              nLoginType = TEEC_LOGIN_USER_APPLICATION;
          }
      }

      sOperation.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE, TEEC_NONE, nParamType3);
      nTeeError = TEEC_OpenSession(&g_sContext,
                                &pSession->sSession,        /* OUT session */
                                &SERVICE_UUID,              /* destination UUID */
                                nLoginType,                 /* connectionMethod */
                                pLoginData,                 /* connectionData */
                                &sOperation,                /* IN OUT operation */
                                NULL                        /* OUT returnOrigin, optional */
                                );
      if (nTeeError != TEEC_SUCCESS)
      {
         /* No need of the returnOrigin as this is not specific to P11 */

         if (  (nTeeError == TEEC_ERROR_NOT_SUPPORTED) &&
               (nLoginType == TEEC_LOGIN_AUTHENTICATION))
         {
            /* We could not open a session with the login TEEC_LOGIN_AUTHENTICATION */
            /* If it is not supported by the product, */
            /* retry with fallback to TEEC_LOGIN_USER_APPLICATION */
            nLoginType = TEEC_LOGIN_USER_APPLICATION;
            goto retry;
         }

         /* The ERROR_ACCESS_DENIED, if returned, will be converted into CKR_TOKEN_NOT_PRESENT
          * For the External Cryptographic API, this means that the authentication
          * of the calling application fails.
          */
         goto error;
      }

      memset(&sOperation, 0, sizeof(TEEC_Operation));
      sOperation.params[0].value.a = slotID;
      sOperation.params[0].value.b = flags;  /* access flags */
      sOperation.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INOUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
      nTeeError = TEEC_InvokeCommand(&pSession->sSession,
                                  SERVICE_SYSTEM_PKCS11_C_OPEN_SESSION_COMMAND_ID & 0x00007FFF,
                                  &sOperation,              /* IN OUT operation */
                                  &nErrorOrigin             /* OUT returnOrigin, optional */
                                 );
      if (nTeeError != TEEC_SUCCESS)
      {
         goto error;
      }

      *phSession = (CK_SESSION_HANDLE)pSession;
      pSession->hCryptoSession = sOperation.params[0].value.a;

      return CKR_OK;
   }
   else
   {
      bool bResult;

      /* Check that {*phSession} is a valid primary session handle */
      if ((!ckInternalSessionIsOpenedEx(*phSession, &bIsPrimarySession)) ||
         (!bIsPrimarySession))
      {
         return CKR_SESSION_HANDLE_INVALID;
      }

      pSession = (PPKCS11_PRIMARY_SESSION_CONTEXT)(*phSession);

      /* allocate the secondary session context */
      pSecondarySession = (PKCS11_SECONDARY_SESSION_CONTEXT*)malloc(sizeof(PKCS11_SECONDARY_SESSION_CONTEXT));
      if (pSecondarySession == NULL)
      {
         return CKR_DEVICE_MEMORY;
      }
      pSecondarySession->sHeader.nMagicWord  = PKCS11_SESSION_MAGIC;
      pSecondarySession->sHeader.nSessionTag = PKCS11_SECONDARY_SESSION_TAG;
      pSecondarySession->pPrimarySession = pSession;

      libMutexLock(&pSession->sSecondarySessionTableMutex);
      bResult = libObjectHandle16Add(&pSession->sSecondarySessionTable,
                                    &pSecondarySession->sSecondarySessionNode);
      libMutexUnlock(&pSession->sSecondarySessionTableMutex);
      if (bResult == false)
      {
         free(pSecondarySession);
         return CKR_DEVICE_MEMORY;
      }

      memset(&sOperation, 0, sizeof(TEEC_Operation));
      sOperation.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INOUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
      nTeeError = TEEC_InvokeCommand(&pSession->sSession,
                                  (pSession->hCryptoSession << 16) |
                                    (1 << 15) |
                                    (SERVICE_SYSTEM_PKCS11_C_OPEN_SESSION_COMMAND_ID & 0x00007FFF),
                                  &sOperation,                 /* IN OUT operation */
                                  &nErrorOrigin                /* OUT returnOrigin, optional */
                                 );
      if (nTeeError != TEEC_SUCCESS)
      {
         goto error;
      }

      *phSession = (CK_SESSION_HANDLE)pSecondarySession;
      pSecondarySession->hSecondaryCryptoSession = sOperation.params[0].value.a;

      return CKR_OK;
   }

error:
   nErrorCode = (nErrorOrigin == TEEC_ORIGIN_TRUSTED_APP ?
                  nTeeError :
                  ckInternalTeeErrorToCKError(nTeeError));

   if ((flags & CKVF_OPEN_SUB_SESSION) == 0)
   {
      libMutexDestroy(&pSession->sSecondarySessionTableMutex);
      free(pSession);
   }
   else
   {
      libMutexLock(&pSession->sSecondarySessionTableMutex);
      libObjectHandle16Remove(&pSession->sSecondarySessionTable,&pSecondarySession->sSecondarySessionNode);
      libMutexUnlock(&pSession->sSecondarySessionTableMutex);
      free(pSecondarySession);
   }

   return nErrorCode;
}

CK_RV PKCS11_EXPORT C_CloseSession(CK_SESSION_HANDLE hSession) /* the session's handle */
{
   CK_RV                   nErrorCode = CKR_OK;
   uint32_t                nErrorOrigin = TEEC_ORIGIN_API;
   TEEC_Result             nTeeError;
   TEEC_Operation          sOperation;
   bool                    bIsPrimarySession;

   /* Check Cryptoki is initialized */
   if (!g_bCryptokiInitialized)
   {
      return CKR_CRYPTOKI_NOT_INITIALIZED;
   }

   if (!ckInternalSessionIsOpenedEx(hSession, &bIsPrimarySession))
   {
      return CKR_SESSION_HANDLE_INVALID;
   }

   if (bIsPrimarySession)
   {
      LIB_OBJECT_NODE_HANDLE16*           pObject;
      PPKCS11_SECONDARY_SESSION_CONTEXT   pSecSession;
      PPKCS11_PRIMARY_SESSION_CONTEXT     pSession = (PPKCS11_PRIMARY_SESSION_CONTEXT)hSession;

      hSession = pSession->hCryptoSession;

      memset(&sOperation, 0, sizeof(TEEC_Operation));
      sOperation.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE, TEEC_NONE, TEEC_NONE);
      nTeeError = TEEC_InvokeCommand(&pSession->sSession,
                                  (pSession->hCryptoSession << 16 ) |
                                    (SERVICE_SYSTEM_PKCS11_C_CLOSE_SESSION_COMMAND_ID & 0x00007FFF),
                                  &sOperation,                 /* IN OUT operation */
                                  &nErrorOrigin                /* OUT returnOrigin, optional */
                                 );
      if (nTeeError != TEEC_SUCCESS)
      {
         goto end;
      }

      TEEC_CloseSession(&pSession->sSession);
      memset(&pSession->sSession, 0, sizeof(TEEC_Session));

      /* Free all secondary session contexts */
      libMutexLock(&pSession->sSecondarySessionTableMutex);
      pObject = libObjectHandle16RemoveOne(&pSession->sSecondarySessionTable);
      while (pObject != NULL)
      {
         /* find all secondary session contexts,
            and release associated resources */

         pSecSession = LIB_OBJECT_CONTAINER_OF(pObject, //ptr
                                               PKCS11_SECONDARY_SESSION_CONTEXT,//type
                                               sSecondarySessionNode);//member

         /* free secondary session context */
         free(pSecSession);

         pObject = libObjectHandle16RemoveOne(&pSession->sSecondarySessionTable);
      }
      libMutexUnlock(&pSession->sSecondarySessionTableMutex);

      libMutexDestroy(&pSession->sSecondarySessionTableMutex);

      /* free primary session context */
      free(pSession);
   }
   else
   {
      PPKCS11_SECONDARY_SESSION_CONTEXT pSecSession = (PPKCS11_SECONDARY_SESSION_CONTEXT)hSession;
      PPKCS11_PRIMARY_SESSION_CONTEXT   pSession;

      uint32_t nCommandID = ( (pSecSession->hSecondaryCryptoSession & 0xFFFF) << 16 ) |
                              (1 << 15) |
                              (SERVICE_SYSTEM_PKCS11_C_CLOSE_SESSION_COMMAND_ID & 0x00007FFF);

      /* every pre-check are fine, then, update the local handles */
      hSession = pSecSession->pPrimarySession->hCryptoSession;
      pSession = (PPKCS11_PRIMARY_SESSION_CONTEXT)(pSecSession->pPrimarySession);

      memset(&sOperation, 0, sizeof(TEEC_Operation));
      sOperation.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE, TEEC_NONE, TEEC_NONE);
      nTeeError = TEEC_InvokeCommand(&pSession->sSession,
                                  nCommandID,
                                  &sOperation,                 /* IN OUT operation */
                                  &nErrorOrigin                /* OUT returnOrigin, optional */
                                 );
      if (nTeeError != TEEC_SUCCESS)
      {
         goto end;
      }

      /* remove the object from the table */
      libMutexLock(&pSession->sSecondarySessionTableMutex);
      libObjectHandle16Remove(&pSecSession->pPrimarySession->sSecondarySessionTable, &pSecSession->sSecondarySessionNode);
      libMutexUnlock(&pSession->sSecondarySessionTableMutex);

      /* free secondary session context */
      free(pSecSession);
   }

end:
   nErrorCode = (nErrorOrigin == TEEC_ORIGIN_TRUSTED_APP ?
                  nTeeError :
                  ckInternalTeeErrorToCKError(nTeeError));
   return nErrorCode;
}


CK_RV PKCS11_EXPORT C_Login(CK_SESSION_HANDLE hSession,  /* the session's handle */
                              CK_USER_TYPE      userType,  /* the user type */
                              const CK_UTF8CHAR*   pPin,      /* the user's PIN */
                              CK_ULONG          ulPinLen)  /* the length of the PIN */
{
   /* Prevent the compiler from complaining about unused variables */
   do{(void)hSession;}while(0);
   do{(void)userType;}while(0);
   do{(void)pPin;}while(0);
   do{(void)ulPinLen;}while(0);

   return CKR_OK;
}

CK_RV PKCS11_EXPORT C_Logout(CK_SESSION_HANDLE hSession) /* the session's handle */
{
   do{(void)hSession;}while(0);

   return CKR_OK;
}