/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * Copyright (C) 2010 Stefan Walter
 * Copyright (C) 2011 Collabora Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General  License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General  License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, see
 * <http://www.gnu.org/licenses/>.
 *
 * In addition, when the library is used with OpenSSL, a special
 * exception applies. Refer to the LICENSE_EXCEPTION file for details.
 */

#include "config.h"

#include "mock-pkcs11.h"

#include <p11-kit/pkcs11.h>

#include <glib.h>

#include <string.h>

/*
 * This is *NOT* how you'd want to implement a PKCS#11 module. This
 * fake module simply provides enough for gnutls-pkcs11 backend to test against.
 * It doesn't pass any tests, or behave as expected from a PKCS#11 module.
 */

static gboolean initialized = FALSE;
static gchar *the_pin = NULL;
static gulong n_the_pin = 0;

static gboolean logged_in = FALSE;
static CK_USER_TYPE user_type = 0;
static CK_FUNCTION_LIST functionList;

typedef enum
{
  OP_FIND = 1,
  OP_CRYPTO
} Operation;

typedef struct
{
  CK_SESSION_HANDLE handle;
  CK_SESSION_INFO info;
  GHashTable *objects;

  Operation operation;

  /* For find operations */
  GList *matches;

  /* For crypto operations */
  CK_OBJECT_HANDLE crypto_key;
  CK_ATTRIBUTE_TYPE crypto_method;
  CK_MECHANISM_TYPE crypto_mechanism;
  CK_BBOOL want_context_login;
} Session;

static guint unique_identifier = 100;
static GHashTable *the_sessions = NULL;
static GHashTable *the_objects = NULL;

static void
free_session (gpointer data)
{
  Session *sess = (Session *)data;
  if (sess)
    g_hash_table_destroy (sess->objects);
  g_free (sess);
}

static GPkcs11Array *
lookup_object (Session *session,
               CK_OBJECT_HANDLE hObject)
{
  GPkcs11Array *attrs;
  attrs = g_hash_table_lookup (the_objects, GUINT_TO_POINTER (hObject));
  if (!attrs)
    attrs = g_hash_table_lookup (session->objects, GUINT_TO_POINTER (hObject));
  return attrs;
}

CK_OBJECT_HANDLE
mock_module_take_object (GPkcs11Array *attrs)
{
  gboolean token;
  guint handle;

  g_return_val_if_fail (the_objects, 0);

  if (g_pkcs11_array_find_boolean (attrs, CKA_TOKEN, &token))
    g_return_val_if_fail (token == TRUE, 0);

  handle = ++unique_identifier;
  g_pkcs11_array_add_boolean (attrs, CKA_TOKEN, TRUE);
  g_hash_table_insert (the_objects, GUINT_TO_POINTER (handle), attrs);
  return handle;
}

void
mock_module_enumerate_objects (CK_SESSION_HANDLE handle,
                               MockEnumerator func,
                               gpointer user_data)
{
  GHashTableIter iter;
  gpointer key;
  gpointer value;
  Session *session;
  gboolean private;

  g_assert (the_objects);
  g_assert (func);

  /* Token objects */
  g_hash_table_iter_init (&iter, the_objects);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      /* Don't include private objects when not logged in */
      if (!logged_in)
        {
          if (g_pkcs11_array_find_boolean (value, CKA_PRIVATE, &private) && private == TRUE)
            continue;
        }

      if (!(func) (GPOINTER_TO_UINT (key), value, user_data))
        return;
    }

  /* session objects */
  if (handle)
    {
      session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (handle));
      if (session)
        {
          g_hash_table_iter_init (&iter, session->objects);
          while (g_hash_table_iter_next (&iter, &key, &value))
            {
              /* Don't include private objects when not logged in */
              if (!logged_in)
                {
                  if (g_pkcs11_array_find_boolean (value, CKA_PRIVATE, &private) && private == TRUE)
                    continue;
                }

              if (!(func) (GPOINTER_TO_UINT (key), value, user_data))
                return;
            }
        }
    }
}

typedef struct {
  CK_ATTRIBUTE_PTR attrs;
  CK_ULONG n_attrs;
  CK_OBJECT_HANDLE object;
} FindObject;

static gboolean
enumerate_and_find_object (CK_OBJECT_HANDLE object,
                           GPkcs11Array *attrs,
                           gpointer user_data)
{
  FindObject *ctx = user_data;
  const CK_ATTRIBUTE *match;
  const CK_ATTRIBUTE *attr;
  CK_ULONG i;

  for (i = 0; i < ctx->n_attrs; ++i)
    {
      match = ctx->attrs + i;
      attr = g_pkcs11_array_find (attrs, match->type);
      if (!attr)
        return TRUE; /* Continue */

      if (attr->ulValueLen != match->ulValueLen ||
          memcmp (attr->pValue, match->pValue, attr->ulValueLen) != 0)
        return TRUE; /* Continue */
    }

  ctx->object = object;
  return FALSE; /* Stop iteration */
}

CK_OBJECT_HANDLE
mock_module_find_object (CK_SESSION_HANDLE session,
                         CK_ATTRIBUTE_PTR attrs,
                         CK_ULONG n_attrs)
{
  FindObject ctx;

  ctx.attrs = attrs;
  ctx.n_attrs = n_attrs;
  ctx.object = 0;

  mock_module_enumerate_objects (session, enumerate_and_find_object, &ctx);
  return ctx.object;
}

static gboolean
enumerate_and_count_objects (CK_OBJECT_HANDLE object,
                             GPkcs11Array *attrs,
                             gpointer user_data)
{
  guint *n_objects = user_data;
  ++(*n_objects);
  return TRUE; /* Continue */
}

guint
mock_module_count_objects (CK_SESSION_HANDLE session)
{
  guint n_objects = 0;
  mock_module_enumerate_objects (session, enumerate_and_count_objects, &n_objects);
  return n_objects;
}

void
mock_module_set_object (CK_OBJECT_HANDLE object,
                        CK_ATTRIBUTE_PTR attrs,
                        CK_ULONG n_attrs)
{
  CK_ULONG i;
  GPkcs11Array *atts;

  g_return_if_fail (object != 0);
  g_return_if_fail (the_objects);

  atts = g_hash_table_lookup (the_objects, GUINT_TO_POINTER (object));
  g_return_if_fail (atts);

  for (i = 0; i < n_attrs; ++i)
    g_pkcs11_array_set (atts, &attrs[i]);
}

void
mock_module_set_pin (const gchar *password)
{
  g_free (the_pin);
  the_pin = g_strdup (password);
  n_the_pin = strlen (password);
}

CK_RV
mock_C_Initialize (CK_VOID_PTR pInitArgs)
{
  GPkcs11Array *attrs;
  CK_C_INITIALIZE_ARGS_PTR args;

  g_return_val_if_fail (initialized == FALSE, CKR_CRYPTOKI_ALREADY_INITIALIZED);

  args = (CK_C_INITIALIZE_ARGS_PTR)pInitArgs;
  if (args)
    {
      g_return_val_if_fail(
          (args->CreateMutex == NULL && args->DestroyMutex == NULL &&
           args->LockMutex == NULL && args->UnlockMutex == NULL) ||
          (args->CreateMutex != NULL && args->DestroyMutex != NULL &&
           args->LockMutex != NULL && args->UnlockMutex != NULL),
          CKR_ARGUMENTS_BAD);

      /* Flags should allow OS locking and os threads */
      g_return_val_if_fail ((args->flags & CKF_OS_LOCKING_OK), CKR_CANT_LOCK);
      g_return_val_if_fail ((args->flags & CKF_LIBRARY_CANT_CREATE_OS_THREADS) == 0, CKR_NEED_TO_CREATE_THREADS);
    }

  the_pin = g_strdup (MOCK_SLOT_ONE_PIN);
  n_the_pin = strlen (the_pin);
  the_sessions = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, free_session);
  the_objects = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_pkcs11_array_unref);

  /* Our first token object */
  attrs = g_pkcs11_array_new ();
  g_pkcs11_array_add_ulong (attrs, CKA_CLASS, CKO_DATA);
  g_pkcs11_array_add_value (attrs, CKA_LABEL, "TEST LABEL", -1);
  g_pkcs11_array_add_boolean (attrs, CKA_TOKEN, TRUE);
  g_hash_table_insert (the_objects, GUINT_TO_POINTER (2), attrs);

  /* Our second token object */
  attrs = g_pkcs11_array_new ();
  g_pkcs11_array_add_ulong (attrs, CKA_CLASS, CKO_DATA);
  g_pkcs11_array_add_value (attrs, CKA_LABEL, "LABEL TWO", -1);
  g_pkcs11_array_add_boolean (attrs, CKA_TOKEN, TRUE);
  g_hash_table_insert (the_objects, GUINT_TO_POINTER (3), attrs);

  /* A private object */
  attrs = g_pkcs11_array_new ();
  g_pkcs11_array_add_ulong (attrs, CKA_CLASS, CKO_DATA);
  g_pkcs11_array_add_value (attrs, CKA_LABEL, "PRIVATE", -1);
  g_pkcs11_array_add_boolean (attrs, CKA_PRIVATE, TRUE);
  g_pkcs11_array_add_boolean (attrs, CKA_TOKEN, TRUE);
  g_hash_table_insert (the_objects, GUINT_TO_POINTER (4), attrs);

  initialized = TRUE;
  return CKR_OK;
}

CK_RV
mock_validate_and_C_Initialize (CK_VOID_PTR pInitArgs)
{
  CK_C_INITIALIZE_ARGS_PTR args;
  void *mutex;
  CK_RV rv;

  args = (CK_C_INITIALIZE_ARGS_PTR)pInitArgs;
  if (args)
    {
      g_assert ((args->CreateMutex) (NULL) == CKR_ARGUMENTS_BAD && "CreateMutex succeeded wrong");
      g_assert ((args->DestroyMutex) (NULL) == CKR_MUTEX_BAD && "DestroyMutex succeeded wrong");
      g_assert ((args->LockMutex) (NULL) == CKR_MUTEX_BAD && "LockMutex succeeded wrong");
      g_assert ((args->UnlockMutex) (NULL) == CKR_MUTEX_BAD && "UnlockMutex succeeded wrong");

      /* Try to create an actual mutex */
      rv = (args->CreateMutex) (&mutex);
      g_assert (rv == CKR_OK && "CreateMutex g_assert_not_reacheded");
      g_assert (mutex != NULL && "CreateMutex created null mutex");

      /* Try and lock the mutex */
      rv = (args->LockMutex) (mutex);
      g_assert (rv == CKR_OK && "LockMutex g_assert_not_reacheded");

      /* Try and unlock the mutex */
      rv = (args->UnlockMutex) (mutex);
      g_assert (rv == CKR_OK && "UnlockMutex g_assert_not_reacheded");

      /* Try and destroy the mutex */
      rv = (args->DestroyMutex) (mutex);
      g_assert (rv == CKR_OK && "DestroyMutex g_assert_not_reacheded");
    }

  return mock_C_Initialize (pInitArgs);
}

CK_RV
mock_C_Finalize (CK_VOID_PTR pReserved)
{
  g_return_val_if_fail (pReserved == NULL, CKR_ARGUMENTS_BAD);
  g_return_val_if_fail (initialized == TRUE, CKR_CRYPTOKI_NOT_INITIALIZED);

  initialized = FALSE;
  logged_in = FALSE;
  g_hash_table_destroy (the_objects);
  the_objects = NULL;

  g_hash_table_destroy (the_sessions);
  the_sessions = NULL;

  g_free (the_pin);
  return CKR_OK;
}

static const CK_INFO TEST_INFO = {
  { CRYPTOKI_VERSION_MAJOR, CRYPTOKI_VERSION_MINOR },
  "TEST MANUFACTURER              ",
  0,
  "TEST LIBRARY                   ",
  { 45, 145 }
};

CK_RV
mock_C_GetInfo (CK_INFO_PTR pInfo)
{
  g_return_val_if_fail (pInfo, CKR_ARGUMENTS_BAD);
  memcpy (pInfo, &TEST_INFO, sizeof (*pInfo));
  return CKR_OK;
}

CK_RV
mock_C_GetFunctionList (CK_FUNCTION_LIST_PTR_PTR list)
{
  g_return_val_if_fail (list, CKR_ARGUMENTS_BAD);
  *list = &functionList;
  return CKR_OK;
}

/*
 * Two slots
 *  ONE: token present
 *  TWO: token not present
 */

CK_RV
mock_C_GetSlotList (CK_BBOOL tokenPresent,
                    CK_SLOT_ID_PTR pSlotList, CK_ULONG_PTR pulCount)
{
  CK_ULONG count;

  g_return_val_if_fail (pulCount, CKR_ARGUMENTS_BAD);

  count = tokenPresent ? 1 : 2;

  /* Application only wants to know the number of slots. */
  if (pSlotList == NULL)
    {
      *pulCount = count;
      return CKR_OK;
    }

  if (*pulCount < count)
    g_return_val_if_reached (CKR_BUFFER_TOO_SMALL);

  *pulCount = count;
  pSlotList[0] = MOCK_SLOT_ONE_ID;
  if (!tokenPresent)
    pSlotList[1] = MOCK_SLOT_TWO_ID;

  return CKR_OK;
}

/* Update mock-pkcs11.h URIs when updating this */

static const CK_SLOT_INFO TEST_INFO_ONE = {
  "TEST SLOT                                                       ",
  "TEST MANUFACTURER              ",
  CKF_TOKEN_PRESENT | CKF_REMOVABLE_DEVICE,
  { 55, 155 },
  { 65, 165 },
};

/* Update mock-pkcs11.h URIs when updating this */

static const CK_SLOT_INFO TEST_INFO_TWO = {
  "TEST SLOT                                                       ",
  "TEST MANUFACTURER              ",
  CKF_REMOVABLE_DEVICE,
  { 55, 155 },
  { 65, 165 },
};

CK_RV
mock_C_GetSlotInfo (CK_SLOT_ID slotID,
                    CK_SLOT_INFO_PTR pInfo)
{
  g_return_val_if_fail (pInfo, CKR_ARGUMENTS_BAD);

  if (slotID == MOCK_SLOT_ONE_ID)
    {
      memcpy (pInfo, &TEST_INFO_ONE, sizeof (*pInfo));
      return CKR_OK;
    }
  else if (slotID == MOCK_SLOT_TWO_ID)
    {
      memcpy (pInfo, &TEST_INFO_TWO, sizeof (*pInfo));
      return CKR_OK;
    }
  else
    {
      g_return_val_if_reached (CKR_SLOT_ID_INVALID);
    }
}

/* Update mock-pkcs11.h URIs when updating this */

static const CK_TOKEN_INFO TEST_TOKEN_ONE = {
  "TEST LABEL                      ",
  "TEST MANUFACTURER               ",
  "TEST MODEL      ",
  "TEST SERIAL     ",
  CKF_LOGIN_REQUIRED | CKF_USER_PIN_INITIALIZED | CKF_CLOCK_ON_TOKEN | CKF_TOKEN_INITIALIZED,
  1,
  2,
  3,
  4,
  5,
  6,
  7,
  8,
  9,
  10,
  { 75, 175 },
  { 85, 185 },
  { '1', '9', '9', '9', '0', '5', '2', '5', '0', '9', '1', '9', '5', '9', '0', '0' }
};

CK_RV
mock_C_GetTokenInfo (CK_SLOT_ID slotID,
                     CK_TOKEN_INFO_PTR pInfo)
{
  g_return_val_if_fail (pInfo != NULL, CKR_ARGUMENTS_BAD);

  if (slotID == MOCK_SLOT_ONE_ID)
    {
      memcpy (pInfo, &TEST_TOKEN_ONE, sizeof (*pInfo));
      return CKR_OK;
    }
  else if (slotID == MOCK_SLOT_TWO_ID)
    {
      return CKR_TOKEN_NOT_PRESENT;
    }
  else
    {
      g_return_val_if_reached (CKR_SLOT_ID_INVALID);
    }
}

CK_RV
mock_fail_C_GetTokenInfo (CK_SLOT_ID slotID,
                          CK_TOKEN_INFO_PTR pInfo)
{
  return CKR_GENERAL_ERROR;
}

/*
 * TWO mechanisms:
 *  CKM_MOCK_CAPITALIZE
 *  CKM_MOCK_PREFIX
 */

CK_RV
mock_C_GetMechanismList (CK_SLOT_ID slotID,
                         CK_MECHANISM_TYPE_PTR pMechanismList,
                         CK_ULONG_PTR pulCount)
{
  g_return_val_if_fail (slotID == MOCK_SLOT_ONE_ID, CKR_SLOT_ID_INVALID);
  g_return_val_if_fail (pulCount, CKR_ARGUMENTS_BAD);

  /* Application only wants to know the number of slots. */
  if (pMechanismList == NULL)
    {
      *pulCount = 0;
      return CKR_OK;
    }

  return CKR_OK;
}

CK_RV
mock_C_GetMechanismInfo (CK_SLOT_ID slotID,
                         CK_MECHANISM_TYPE type,
                         CK_MECHANISM_INFO_PTR pInfo)
{
  g_return_val_if_fail (slotID == MOCK_SLOT_ONE_ID, CKR_SLOT_ID_INVALID);
  g_return_val_if_fail (pInfo, CKR_ARGUMENTS_BAD);

  g_return_val_if_reached (CKR_MECHANISM_INVALID);
}

CK_RV
mock_specific_args_C_InitToken (CK_SLOT_ID slotID,
                                CK_UTF8CHAR_PTR pPin,
                                CK_ULONG ulPinLen,
                                CK_UTF8CHAR_PTR pLabel)
{
  g_return_val_if_fail (slotID == MOCK_SLOT_ONE_ID, CKR_SLOT_ID_INVALID);

  g_return_val_if_fail (pPin, CKR_PIN_INVALID);
  g_return_val_if_fail (strlen ("TEST PIN") == ulPinLen, CKR_PIN_INVALID);
  g_return_val_if_fail (strncmp ((gchar *)pPin, "TEST PIN", ulPinLen) == 0, CKR_PIN_INVALID);
  g_return_val_if_fail (pLabel != NULL, CKR_PIN_INVALID);
  g_return_val_if_fail (strcmp ((gchar *)pPin, "TEST LABEL") == 0, CKR_PIN_INVALID);

  g_free (the_pin);
  the_pin = g_strndup ((gchar *)pPin, ulPinLen);
  n_the_pin = ulPinLen;
  return CKR_OK;
}

CK_RV
mock_unsupported_C_WaitForSlotEvent (CK_FLAGS flags,
                                     CK_SLOT_ID_PTR pSlot,
                                     CK_VOID_PTR pReserved)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_C_OpenSession (CK_SLOT_ID slotID,
                    CK_FLAGS flags,
                    CK_VOID_PTR pApplication,
                    CK_NOTIFY Notify,
                    CK_SESSION_HANDLE_PTR phSession)
{
  Session *sess;

  g_return_val_if_fail (slotID == MOCK_SLOT_ONE_ID || slotID == MOCK_SLOT_TWO_ID, CKR_SLOT_ID_INVALID);
  g_return_val_if_fail (phSession != NULL, CKR_ARGUMENTS_BAD);
  g_return_val_if_fail ((flags & CKF_SERIAL_SESSION) == CKF_SERIAL_SESSION, CKR_SESSION_PARALLEL_NOT_SUPPORTED);

  if (slotID == MOCK_SLOT_TWO_ID)
    return CKR_TOKEN_NOT_PRESENT;

  sess = g_new0 (Session, 1);
  sess->handle = ++unique_identifier;
  sess->info.flags = flags;
  sess->info.slotID = slotID;
  sess->info.state = 0;
  sess->info.ulDeviceError = 1414;
  sess->objects = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_pkcs11_array_unref);
  *phSession = sess->handle;

  g_hash_table_replace (the_sessions, GUINT_TO_POINTER (sess->handle), sess);
  return CKR_OK;
}

CK_RV
mock_fail_C_OpenSession (CK_SLOT_ID slotID,
                         CK_FLAGS flags,
                         CK_VOID_PTR pApplication,
                         CK_NOTIFY Notify,
                         CK_SESSION_HANDLE_PTR phSession)
{
  return CKR_GENERAL_ERROR;
}

CK_RV
mock_C_CloseSession (CK_SESSION_HANDLE hSession)
{
  Session *session;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);

  g_hash_table_remove (the_sessions, GUINT_TO_POINTER (hSession));
  return CKR_OK;
}

CK_RV
mock_C_CloseAllSessions (CK_SLOT_ID slotID)
{
  g_return_val_if_fail (slotID == MOCK_SLOT_ONE_ID, CKR_SLOT_ID_INVALID);

  g_hash_table_remove_all (the_sessions);
  return CKR_OK;
}

CK_RV
mock_C_GetFunctionStatus (CK_SESSION_HANDLE hSession)
{
  return CKR_FUNCTION_NOT_PARALLEL;
}

CK_RV
mock_C_CancelFunction (CK_SESSION_HANDLE hSession)
{
  return CKR_FUNCTION_NOT_PARALLEL;
}

CK_RV
mock_C_GetSessionInfo (CK_SESSION_HANDLE hSession,
                       CK_SESSION_INFO_PTR pInfo)
{
  Session *session;

  g_return_val_if_fail (pInfo != NULL, CKR_ARGUMENTS_BAD);

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_assert (session != NULL && "No such session found");
  if (!session)
    return CKR_SESSION_HANDLE_INVALID;

  if (logged_in)
    {
      if (session->info.flags & CKF_RW_SESSION)
        session->info.state = CKS_RW_USER_FUNCTIONS;
      else
        session->info.state = CKS_RO_USER_FUNCTIONS;
    }
  else
    {
      if (session->info.flags & CKF_RW_SESSION)
        session->info.state = CKS_RW_PUBLIC_SESSION;
      else
        session->info.state = CKS_RO_PUBLIC_SESSION;
    }

  memcpy (pInfo, &session->info, sizeof (*pInfo));
  return CKR_OK;
}

CK_RV
mock_fail_C_GetSessionInfo (CK_SESSION_HANDLE hSession,
                            CK_SESSION_INFO_PTR pInfo)
{
  return CKR_GENERAL_ERROR;
}

CK_RV
mock_C_InitPIN (CK_SESSION_HANDLE hSession, CK_UTF8CHAR_PTR pPin,
                    CK_ULONG ulPinLen)
{
  Session *session;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);

  g_free (the_pin);
  the_pin = g_strndup ((gchar *)pPin, ulPinLen);
  n_the_pin = ulPinLen;
  return CKR_OK;
}

CK_RV
mock_C_SetPIN (CK_SESSION_HANDLE hSession,
               CK_UTF8CHAR_PTR pOldPin,
               CK_ULONG ulOldLen,
               CK_UTF8CHAR_PTR pNewPin,
               CK_ULONG ulNewLen)
{
  Session *session;
  gchar *old;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);

  old = g_strndup ((gchar *)pOldPin, ulOldLen);
  if (!old || !g_str_equal (old, the_pin))
    return CKR_PIN_INCORRECT;

  g_free (the_pin);
  the_pin = g_strndup ((gchar *)pNewPin, ulNewLen);
  n_the_pin = ulNewLen;
  return CKR_OK;
}

CK_RV
mock_unsupported_C_GetOperationState (CK_SESSION_HANDLE hSession,
                                      CK_BYTE_PTR pOperationState,
                                      CK_ULONG_PTR pulOperationStateLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_SetOperationState (CK_SESSION_HANDLE hSession,
                                      CK_BYTE_PTR pOperationState,
                                      CK_ULONG ulOperationStateLen,
                                      CK_OBJECT_HANDLE hEncryptionKey,
                                      CK_OBJECT_HANDLE hAuthenticationKey)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_C_Login (CK_SESSION_HANDLE hSession,
              CK_USER_TYPE userType,
              CK_UTF8CHAR_PTR pPin,
              CK_ULONG pPinLen)
{
  Session *session;

  g_return_val_if_fail (userType == CKU_SO ||
                        userType == CKU_USER ||
                        userType == CKU_CONTEXT_SPECIFIC,
                        CKR_USER_TYPE_INVALID);

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);
  g_return_val_if_fail (logged_in == FALSE, CKR_USER_ALREADY_LOGGED_IN);

  if (!pPin)
    return CKR_PIN_INCORRECT;

  if (pPinLen != strlen (the_pin))
    return CKR_PIN_INCORRECT;
  if (strncmp ((gchar *)pPin, the_pin, pPinLen) != 0)
    return CKR_PIN_INCORRECT;

  if (userType == CKU_CONTEXT_SPECIFIC)
    {
      g_return_val_if_fail (session->want_context_login == TRUE, CKR_OPERATION_NOT_INITIALIZED);
      session->want_context_login = CK_FALSE;
    }
  else
    {
      logged_in = TRUE;
      user_type = userType;
    }

  return CKR_OK;
}

CK_RV
mock_C_Logout (CK_SESSION_HANDLE hSession)
{
  Session *session;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_assert (session != NULL && "No such session found");
  if (!session)
    return CKR_SESSION_HANDLE_INVALID;

  g_assert (logged_in && "Not logged in");
  logged_in = FALSE;
  user_type = 0;
  return CKR_OK;
}

CK_RV
mock_C_CreateObject (CK_SESSION_HANDLE hSession,
                     CK_ATTRIBUTE_PTR pTemplate,
                     CK_ULONG ulCount,
                     CK_OBJECT_HANDLE_PTR phObject)
{
  GPkcs11Array *attrs;
  Session *session;
  gboolean token, priv;
  CK_ULONG i;

  g_return_val_if_fail (phObject, CKR_ARGUMENTS_BAD);

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);

  attrs = g_pkcs11_array_new ();
  for (i = 0; i < ulCount; ++i)
    g_pkcs11_array_add_value (attrs, pTemplate[i].type, pTemplate[i].pValue, pTemplate[i].ulValueLen);

  if (g_pkcs11_array_find_boolean (attrs, CKA_PRIVATE, &priv) && priv)
    {
      if (!logged_in)
        {
          g_pkcs11_array_unref (attrs);
          return CKR_USER_NOT_LOGGED_IN;
        }
    }

  *phObject = ++unique_identifier;
  if (g_pkcs11_array_find_boolean (attrs, CKA_TOKEN, &token) && token)
    g_hash_table_insert (the_objects, GUINT_TO_POINTER (*phObject), attrs);
  else
    g_hash_table_insert (session->objects, GUINT_TO_POINTER (*phObject), attrs);

  return CKR_OK;
}

CK_RV
mock_fail_C_CreateObject (CK_SESSION_HANDLE hSession,
                          CK_ATTRIBUTE_PTR pTemplate,
                          CK_ULONG ulCount,
                          CK_OBJECT_HANDLE_PTR phObject)
{
  /* Always fails */
  return CKR_FUNCTION_FAILED;
}

CK_RV
mock_unsupported_C_CopyObject (CK_SESSION_HANDLE hSession,
                               CK_OBJECT_HANDLE hObject,
                               CK_ATTRIBUTE_PTR pTemplate,
                               CK_ULONG ulCount,
                               CK_OBJECT_HANDLE_PTR phNewObject)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_C_DestroyObject (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject)
{
  GPkcs11Array *attrs;
  Session *session;
  gboolean priv;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);

  attrs = lookup_object (session, hObject);
  g_return_val_if_fail (attrs, CKR_OBJECT_HANDLE_INVALID);

  if (g_pkcs11_array_find_boolean (attrs, CKA_PRIVATE, &priv) && priv)
    {
      if (!logged_in)
        return CKR_USER_NOT_LOGGED_IN;
    }

  g_hash_table_remove (the_objects, GUINT_TO_POINTER (hObject));
  g_hash_table_remove (session->objects, GUINT_TO_POINTER (hObject));

  return CKR_OK;
}

CK_RV
mock_unsupported_C_GetObjectSize (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
                                      CK_ULONG_PTR pulSize)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_C_GetAttributeValue (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
                              CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{
  CK_ATTRIBUTE_PTR result;
  CK_RV ret = CKR_OK;
  GPkcs11Array *attrs;
  const CK_ATTRIBUTE *attr;
  Session *session;
  CK_ULONG i;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);

  attrs = lookup_object (session, hObject);
  if (!attrs)
    {
      g_assert_not_reached (); /* "invalid object handle passed" */
      return CKR_OBJECT_HANDLE_INVALID;
    }

  for (i = 0; i < ulCount; ++i)
    {
      result = pTemplate + i;
      attr = g_pkcs11_array_find (attrs, result->type);
      if (!attr)
        {
          result->ulValueLen = (CK_ULONG)-1;
          ret = CKR_ATTRIBUTE_TYPE_INVALID;
          continue;
        }

      if (!result->pValue)
        {
          result->ulValueLen = attr->ulValueLen;
          continue;
        }

      if (result->ulValueLen >= attr->ulValueLen)
        {
          memcpy (result->pValue, attr->pValue, attr->ulValueLen);
          continue;
        }

      result->ulValueLen = (CK_ULONG)-1;
      ret = CKR_BUFFER_TOO_SMALL;
    }

  return ret;
}

CK_RV
mock_fail_C_GetAttributeValue (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
                                   CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{
  return CKR_FUNCTION_FAILED;
}

CK_RV
mock_C_SetAttributeValue (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject,
                              CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{
  Session *session;
  GPkcs11Array *attrs;
  CK_ULONG i;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);

  attrs = lookup_object (session, hObject);
  g_return_val_if_fail (attrs, CKR_OBJECT_HANDLE_INVALID);

  for (i = 0; i < ulCount; ++i)
    g_pkcs11_array_set (attrs, pTemplate + i);

  return CKR_OK;
}

typedef struct
{
  CK_ATTRIBUTE_PTR template;
  CK_ULONG count;
  Session *session;
} FindObjects;

static gboolean
enumerate_and_find_objects (CK_OBJECT_HANDLE object,
                            GPkcs11Array *attrs,
                            gpointer user_data)
{
  FindObjects *ctx = user_data;
  CK_ATTRIBUTE_PTR match;
  const CK_ATTRIBUTE *attr;
  CK_ULONG i;

  for (i = 0; i < ctx->count; ++i)
    {
      match = ctx->template + i;
      attr = g_pkcs11_array_find (attrs, match->type);
      if (!attr)
        return TRUE; /* Continue */

      if (attr->ulValueLen != match->ulValueLen ||
          memcmp (attr->pValue, match->pValue, attr->ulValueLen) != 0)
        return TRUE; /* Continue */
    }

  ctx->session->matches = g_list_prepend (ctx->session->matches, GUINT_TO_POINTER (object));
  return TRUE; /* Continue */
}

CK_RV
mock_C_FindObjectsInit (CK_SESSION_HANDLE hSession,
                        CK_ATTRIBUTE_PTR pTemplate,
                        CK_ULONG ulCount)
{
  Session *session;
  FindObjects ctx;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);

  /* Starting an operation, cancels any previous one */
  if (session->operation != 0)
    session->operation = 0;

  session->operation = OP_FIND;

  ctx.template = pTemplate;
  ctx.count = ulCount;
  ctx.session = session;

  mock_module_enumerate_objects (hSession, enumerate_and_find_objects, &ctx);
  return CKR_OK;
}

CK_RV
mock_fail_C_FindObjects (CK_SESSION_HANDLE hSession,
                         CK_OBJECT_HANDLE_PTR phObject,
                         CK_ULONG ulMaxObjectCount,
                         CK_ULONG_PTR pulObjectCount)
{
  /* Always fails */
  return CKR_FUNCTION_FAILED;
}

CK_RV
mock_C_FindObjects (CK_SESSION_HANDLE hSession,
                    CK_OBJECT_HANDLE_PTR phObject,
                    CK_ULONG ulMaxObjectCount,
                    CK_ULONG_PTR pulObjectCount)
{
  Session *session;

  g_return_val_if_fail (phObject, CKR_ARGUMENTS_BAD);
  g_return_val_if_fail (pulObjectCount, CKR_ARGUMENTS_BAD);
  g_return_val_if_fail (ulMaxObjectCount != 0, CKR_ARGUMENTS_BAD);

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);
  g_return_val_if_fail (session->operation == OP_FIND, CKR_OPERATION_NOT_INITIALIZED);

  *pulObjectCount = 0;
  while (ulMaxObjectCount > 0 && session->matches)
    {
      *phObject = GPOINTER_TO_UINT (session->matches->data);
      ++phObject;
      --ulMaxObjectCount;
      ++(*pulObjectCount);
      session->matches = g_list_remove (session->matches, session->matches->data);
    }

  return CKR_OK;
}

CK_RV
mock_C_FindObjectsFinal (CK_SESSION_HANDLE hSession)
{
  Session *session;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);
  g_return_val_if_fail (session->operation == OP_FIND, CKR_OPERATION_NOT_INITIALIZED);

  session->operation = 0;
  g_list_free (session->matches);
  session->matches = NULL;

  return CKR_OK;
}

CK_RV
mock_no_mechanisms_C_EncryptInit (CK_SESSION_HANDLE hSession,
                                  CK_MECHANISM_PTR pMechanism,
                                  CK_OBJECT_HANDLE hKey)
{
  Session *session;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);

  return CKR_MECHANISM_INVALID;
}

CK_RV
mock_not_initialized_C_Encrypt (CK_SESSION_HANDLE hSession,
                                CK_BYTE_PTR pData,
                                CK_ULONG ulDataLen,
                                CK_BYTE_PTR pEncryptedData,
                                CK_ULONG_PTR pulEncryptedDataLen)
{
  return CKR_OPERATION_NOT_INITIALIZED;
}

CK_RV
mock_unsupported_C_EncryptUpdate (CK_SESSION_HANDLE hSession,
                                  CK_BYTE_PTR pPart,
                                  CK_ULONG ulPartLen,
                                  CK_BYTE_PTR pEncryptedPart,
                                  CK_ULONG_PTR pulEncryptedPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_EncryptFinal (CK_SESSION_HANDLE hSession,
                                 CK_BYTE_PTR pLastEncryptedPart,
                                 CK_ULONG_PTR pulLastEncryptedPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_no_mechanisms_C_DecryptInit (CK_SESSION_HANDLE hSession,
                                  CK_MECHANISM_PTR pMechanism,
                                  CK_OBJECT_HANDLE hKey)
{
  Session *session;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);

  return CKR_MECHANISM_INVALID;
}

CK_RV
mock_not_initialized_C_Decrypt (CK_SESSION_HANDLE hSession,
                                CK_BYTE_PTR pEncryptedData,
                                CK_ULONG ulEncryptedDataLen,
                                CK_BYTE_PTR pData,
                                CK_ULONG_PTR pulDataLen)
{
  return CKR_OPERATION_NOT_INITIALIZED;
}

CK_RV
mock_unsupported_C_DecryptUpdate (CK_SESSION_HANDLE hSession,
                                  CK_BYTE_PTR pEncryptedPart,
                                  CK_ULONG ulEncryptedPartLen,
                                  CK_BYTE_PTR pPart,
                                  CK_ULONG_PTR pulPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_DecryptFinal (CK_SESSION_HANDLE hSession,
                                 CK_BYTE_PTR pLastPart,
                                 CK_ULONG_PTR pulLastPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_DigestInit (CK_SESSION_HANDLE hSession,
                               CK_MECHANISM_PTR pMechanism)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_Digest (CK_SESSION_HANDLE hSession,
                           CK_BYTE_PTR pData,
                           CK_ULONG ulDataLen,
                           CK_BYTE_PTR pDigest,
                           CK_ULONG_PTR pulDigestLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_DigestUpdate (CK_SESSION_HANDLE hSession,
                                 CK_BYTE_PTR pPart,
                                 CK_ULONG ulPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_DigestKey (CK_SESSION_HANDLE hSession,
                              CK_OBJECT_HANDLE hKey)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_DigestFinal (CK_SESSION_HANDLE hSession,
                                CK_BYTE_PTR pDigest,
                                CK_ULONG_PTR pulDigestLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_no_mechanisms_C_SignInit (CK_SESSION_HANDLE hSession,
                               CK_MECHANISM_PTR pMechanism,
                               CK_OBJECT_HANDLE hKey)
{
  Session *session;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);

  return CKR_MECHANISM_INVALID;
}

CK_RV
mock_not_initialized_C_Sign (CK_SESSION_HANDLE hSession,
                             CK_BYTE_PTR pData,
                             CK_ULONG ulDataLen,
                             CK_BYTE_PTR pSignature,
                             CK_ULONG_PTR pulSignatureLen)
{
  return CKR_OPERATION_NOT_INITIALIZED;
}

CK_RV
mock_unsupported_C_SignUpdate (CK_SESSION_HANDLE hSession,
                               CK_BYTE_PTR pPart,
                               CK_ULONG ulPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_SignFinal (CK_SESSION_HANDLE hSession,
                              CK_BYTE_PTR pSignature,
                              CK_ULONG_PTR pulSignatureLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_SignRecoverInit (CK_SESSION_HANDLE hSession,
                                    CK_MECHANISM_PTR pMechanism,
                                    CK_OBJECT_HANDLE hKey)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_SignRecover (CK_SESSION_HANDLE hSession,
                                CK_BYTE_PTR pData,
                                CK_ULONG ulDataLen,
                                CK_BYTE_PTR pSignature,
                                CK_ULONG_PTR pulSignatureLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_no_mechanisms_C_VerifyInit (CK_SESSION_HANDLE hSession,
                                 CK_MECHANISM_PTR pMechanism,
                                 CK_OBJECT_HANDLE hKey)
{
  Session *session;

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);

  return CKR_MECHANISM_INVALID;
}

CK_RV
mock_not_initialized_C_Verify (CK_SESSION_HANDLE hSession,
                               CK_BYTE_PTR pData,
                               CK_ULONG ulDataLen,
                               CK_BYTE_PTR pSignature,
                               CK_ULONG ulSignatureLen)
{
  return CKR_OPERATION_NOT_INITIALIZED;
}

CK_RV
mock_unsupported_C_VerifyUpdate (CK_SESSION_HANDLE hSession,
                                 CK_BYTE_PTR pPart,
                                 CK_ULONG ulPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_VerifyFinal (CK_SESSION_HANDLE hSession,
                                CK_BYTE_PTR pSignature,
                                CK_ULONG pulSignatureLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_VerifyRecoverInit (CK_SESSION_HANDLE hSession,
                                      CK_MECHANISM_PTR pMechanism,
                                      CK_OBJECT_HANDLE hKey)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_VerifyRecover (CK_SESSION_HANDLE hSession,
                                  CK_BYTE_PTR pSignature,
                                  CK_ULONG pulSignatureLen,
                                  CK_BYTE_PTR pData,
                                  CK_ULONG_PTR pulDataLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_DigestEncryptUpdate (CK_SESSION_HANDLE hSession,
                                        CK_BYTE_PTR pPart,
                                        CK_ULONG ulPartLen,
                                        CK_BYTE_PTR pEncryptedPart,
                                        CK_ULONG_PTR ulEncryptedPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_DecryptDigestUpdate (CK_SESSION_HANDLE hSession,
                                        CK_BYTE_PTR pEncryptedPart,
                                        CK_ULONG ulEncryptedPartLen,
                                        CK_BYTE_PTR pPart,
                                        CK_ULONG_PTR pulPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_SignEncryptUpdate (CK_SESSION_HANDLE hSession,
                                      CK_BYTE_PTR pPart,
                                      CK_ULONG ulPartLen,
                                      CK_BYTE_PTR pEncryptedPart,
                                      CK_ULONG_PTR ulEncryptedPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_DecryptVerifyUpdate (CK_SESSION_HANDLE hSession,
                                        CK_BYTE_PTR pEncryptedPart,
                                        CK_ULONG ulEncryptedPartLen,
                                        CK_BYTE_PTR pPart,
                                        CK_ULONG_PTR pulPartLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_GenerateKey (CK_SESSION_HANDLE hSession,
                                CK_MECHANISM_PTR pMechanism,
                                CK_ATTRIBUTE_PTR pTemplate,
                                CK_ULONG ulCount,
                                CK_OBJECT_HANDLE_PTR phKey)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_no_mechanisms_C_GenerateKeyPair (CK_SESSION_HANDLE hSession,
                                      CK_MECHANISM_PTR pMechanism,
                                      CK_ATTRIBUTE_PTR pPublicKeyTemplate,
                                      CK_ULONG ulPublicKeyAttributeCount,
                                      CK_ATTRIBUTE_PTR pPrivateKeyTemplate,
                                      CK_ULONG ulPrivateKeyAttributeCount,
                                      CK_OBJECT_HANDLE_PTR phPublicKey,
                                      CK_OBJECT_HANDLE_PTR phPrivateKey)
{
  Session *session;

  g_return_val_if_fail (pMechanism, CKR_MECHANISM_INVALID);
  g_return_val_if_fail (pPublicKeyTemplate, CKR_TEMPLATE_INCOMPLETE);
  g_return_val_if_fail (ulPublicKeyAttributeCount, CKR_TEMPLATE_INCOMPLETE);
  g_return_val_if_fail (pPrivateKeyTemplate, CKR_TEMPLATE_INCOMPLETE);
  g_return_val_if_fail (ulPrivateKeyAttributeCount, CKR_TEMPLATE_INCOMPLETE);
  g_return_val_if_fail (phPublicKey, CKR_ARGUMENTS_BAD);
  g_return_val_if_fail (phPrivateKey, CKR_ARGUMENTS_BAD);

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);

  return CKR_MECHANISM_INVALID;
}

CK_RV
mock_no_mechanisms_C_WrapKey (CK_SESSION_HANDLE hSession,
                              CK_MECHANISM_PTR pMechanism,
                              CK_OBJECT_HANDLE hWrappingKey,
                              CK_OBJECT_HANDLE hKey,
                              CK_BYTE_PTR pWrappedKey,
                              CK_ULONG_PTR pulWrappedKeyLen)
{
  Session *session;

  g_return_val_if_fail (pMechanism, CKR_MECHANISM_INVALID);
  g_return_val_if_fail (hWrappingKey, CKR_OBJECT_HANDLE_INVALID);
  g_return_val_if_fail (hKey, CKR_OBJECT_HANDLE_INVALID);
  g_return_val_if_fail (pulWrappedKeyLen, CKR_WRAPPED_KEY_LEN_RANGE);

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);

  return CKR_MECHANISM_INVALID;
}

CK_RV
mock_no_mechanisms_C_UnwrapKey (CK_SESSION_HANDLE hSession,
                                CK_MECHANISM_PTR pMechanism,
                                CK_OBJECT_HANDLE hUnwrappingKey,
                                CK_BYTE_PTR pWrappedKey,
                                CK_ULONG ulWrappedKeyLen,
                                CK_ATTRIBUTE_PTR pTemplate,
                                CK_ULONG ulCount,
                                CK_OBJECT_HANDLE_PTR phKey)
{
  Session *session;

  g_return_val_if_fail (pMechanism, CKR_MECHANISM_INVALID);
  g_return_val_if_fail (hUnwrappingKey, CKR_WRAPPING_KEY_HANDLE_INVALID);
  g_return_val_if_fail (pWrappedKey, CKR_WRAPPED_KEY_INVALID);
  g_return_val_if_fail (ulWrappedKeyLen, CKR_WRAPPED_KEY_LEN_RANGE);
  g_return_val_if_fail (phKey, CKR_ARGUMENTS_BAD);
  g_return_val_if_fail (pTemplate, CKR_TEMPLATE_INCOMPLETE);
  g_return_val_if_fail (ulCount, CKR_TEMPLATE_INCONSISTENT);

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);

  return CKR_MECHANISM_INVALID;
}

CK_RV
mock_no_mechanisms_C_DeriveKey (CK_SESSION_HANDLE hSession,
                                CK_MECHANISM_PTR pMechanism,
                                CK_OBJECT_HANDLE hBaseKey,
                                CK_ATTRIBUTE_PTR pTemplate,
                                CK_ULONG ulCount,
                                CK_OBJECT_HANDLE_PTR phKey)
{
  Session *session;

  g_return_val_if_fail (pMechanism, CKR_MECHANISM_INVALID);
  g_return_val_if_fail (ulCount, CKR_TEMPLATE_INCOMPLETE);
  g_return_val_if_fail (pTemplate, CKR_TEMPLATE_INCOMPLETE);
  g_return_val_if_fail (phKey, CKR_ARGUMENTS_BAD);

  session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
  g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);

  return CKR_MECHANISM_INVALID;
}

CK_RV
mock_unsupported_C_SeedRandom (CK_SESSION_HANDLE hSession,
                               CK_BYTE_PTR pSeed,
                               CK_ULONG ulSeedLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_RV
mock_unsupported_C_GenerateRandom (CK_SESSION_HANDLE hSession,
                                   CK_BYTE_PTR pRandomData,
                                   CK_ULONG ulRandomLen)
{
  return CKR_FUNCTION_NOT_SUPPORTED;
}

CK_FUNCTION_LIST mock_default_functions = {
  { 2, 11 },        /* version */
  mock_validate_and_C_Initialize,
  mock_C_Finalize,
  mock_C_GetInfo,
  mock_C_GetFunctionList,
  mock_C_GetSlotList,
  mock_C_GetSlotInfo,
  mock_C_GetTokenInfo,
  mock_C_GetMechanismList,
  mock_C_GetMechanismInfo,
  mock_specific_args_C_InitToken,
  mock_C_InitPIN,
  mock_C_SetPIN,
  mock_C_OpenSession,
  mock_C_CloseSession,
  mock_C_CloseAllSessions,
  mock_C_GetSessionInfo,
  mock_unsupported_C_GetOperationState,
  mock_unsupported_C_SetOperationState,
  mock_C_Login,
  mock_C_Logout,
  mock_C_CreateObject,
  mock_unsupported_C_CopyObject,
  mock_C_DestroyObject,
  mock_unsupported_C_GetObjectSize,
  mock_C_GetAttributeValue,
  mock_C_SetAttributeValue,
  mock_C_FindObjectsInit,
  mock_C_FindObjects,
  mock_C_FindObjectsFinal,
  mock_no_mechanisms_C_EncryptInit,
  mock_not_initialized_C_Encrypt,
  mock_unsupported_C_EncryptUpdate,
  mock_unsupported_C_EncryptFinal,
  mock_no_mechanisms_C_DecryptInit,
  mock_not_initialized_C_Decrypt,
  mock_unsupported_C_DecryptUpdate,
  mock_unsupported_C_DecryptFinal,
  mock_unsupported_C_DigestInit,
  mock_unsupported_C_Digest,
  mock_unsupported_C_DigestUpdate,
  mock_unsupported_C_DigestKey,
  mock_unsupported_C_DigestFinal,
  mock_no_mechanisms_C_SignInit,
  mock_not_initialized_C_Sign,
  mock_unsupported_C_SignUpdate,
  mock_unsupported_C_SignFinal,
  mock_unsupported_C_SignRecoverInit,
  mock_unsupported_C_SignRecover,
  mock_no_mechanisms_C_VerifyInit,
  mock_not_initialized_C_Verify,
  mock_unsupported_C_VerifyUpdate,
  mock_unsupported_C_VerifyFinal,
  mock_unsupported_C_VerifyRecoverInit,
  mock_unsupported_C_VerifyRecover,
  mock_unsupported_C_DigestEncryptUpdate,
  mock_unsupported_C_DecryptDigestUpdate,
  mock_unsupported_C_SignEncryptUpdate,
  mock_unsupported_C_DecryptVerifyUpdate,
  mock_unsupported_C_GenerateKey,
  mock_no_mechanisms_C_GenerateKeyPair,
  mock_no_mechanisms_C_WrapKey,
  mock_no_mechanisms_C_UnwrapKey,
  mock_no_mechanisms_C_DeriveKey,
  mock_unsupported_C_SeedRandom,
  mock_unsupported_C_GenerateRandom,
  mock_C_GetFunctionStatus,
  mock_C_CancelFunction,
  mock_unsupported_C_WaitForSlotEvent
};
