/*
 * COPYRIGHT (c) International Business Machines Corp. 2005-2017
 *
 * This program is provided under the terms of the Common Public License,
 * version 1.0 (CPL-1.0). Any use, reproduction or distribution for this
 * software constitutes recipient's acceptance of CPL-1.0 terms which can be
 * found in the file LICENSE file or at
 * https://opensource.org/licenses/cpl1.0.php
 */

/* File: sess_mgmt.c */

#include <windows.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>

#include "pkcs11types.h"
#include "regress.h"
#include "mech_to_str.h"
#include "common.c"

void dump_session_info(CK_SESSION_INFO * info)
{
    printf("   CK_SESSION_INFO:\n");
    printf("      slotID:         %ld\n", info->slotID);
    printf("      state:          ");
    switch (info->state) {
    case CKS_RO_PUBLIC_SESSION:
        printf("CKS_RO_PUBLIC_SESSION\n");
        break;
    case CKS_RW_PUBLIC_SESSION:
        printf("CKS_RW_PUBLIC_SESSION\n");
        break;
    case CKS_RO_USER_FUNCTIONS:
        printf("CKS_RO_USER_FUNCTIONS\n");
        break;
    case CKS_RW_USER_FUNCTIONS:
        printf("CKS_RW_USER_FUNCTIONS\n");
        break;
    case CKS_RW_SO_FUNCTIONS:
        printf("CKS_RW_SO_FUNCTIONS\n");
        break;
    }
    printf("      flags:          %p\n", (void *) info->flags);
    printf("      ulDeviceError:  %ld\n", info->ulDeviceError);
}

CK_RV do_OpenSession(void)
{
    CK_SLOT_ID slot_id;
    CK_FLAGS flags;
    CK_SESSION_HANDLE handle;
    CK_RV rc;

    printf("do_OpenSession...\n");

    slot_id = SLOT_ID;
    flags = CKF_SERIAL_SESSION; // read-only session

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &handle);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #1", rc);
        return rc;
    }

    rc = funcs->C_CloseSession(handle);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #1", rc);
        return rc;
    }

    printf("Looks okay...\n");

    return rc;
}

CK_RV do_OpenSession2(void)
{
    CK_SLOT_ID slot_id;
    CK_FLAGS flags;
    CK_SESSION_HANDLE h1, h2;
    CK_RV rc;

    printf("do_OpenSession2...\n");

    slot_id = SLOT_ID;
    flags = CKF_SERIAL_SESSION; // read-only session

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h1);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #1", rc);
        return rc;
    }

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h2);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #2", rc);
        return rc;
    }

    rc = funcs->C_CloseSession(h1);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #1", rc);
        return rc;
    }

    rc = funcs->C_CloseSession(h2);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #2", rc);
        return rc;
    }

    printf("Looks okay...\n");

    return rc;
}

CK_RV do_CloseAllSessions(void)
{
    CK_SLOT_ID slot_id;
    CK_FLAGS flags;
    CK_SESSION_HANDLE h1, h2, h3;
    CK_RV rc;

    printf("do_CloseAllSessions...\n");

    slot_id = SLOT_ID;
    flags = CKF_SERIAL_SESSION; // read-only session

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h1);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #1", rc);
        return rc;
    }

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h2);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #2", rc);
        return rc;
    }

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h3);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #3", rc);
        return rc;
    }

    rc = funcs->C_CloseAllSessions(slot_id);
    if (rc != CKR_OK) {
        show_error("   C_CloseAllSessions", rc);
        return rc;
    }

    printf("Looks okay...\n");

    return rc;
}

CK_RV do_GetSessionInfo(void)
{
    CK_SLOT_ID slot_id;
    CK_FLAGS flags;
    CK_SESSION_HANDLE h1, h2, h3;
    CK_SESSION_INFO info;
    CK_RV rc;

    printf("do_GetSessionInfo...\n");

    slot_id = SLOT_ID;
    flags = CKF_SERIAL_SESSION; // read-only session

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h1);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #1", rc);
        return rc;
    }

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h2);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #2", rc);
        return rc;
    }

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h3);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #3", rc);
        return rc;
    }

    rc = funcs->C_GetSessionInfo(h1, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #1", rc);
        return rc;
    }

    dump_session_info(&info);

    rc = funcs->C_GetSessionInfo(h2, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #2", rc);
        return rc;
    }

    dump_session_info(&info);

    rc = funcs->C_GetSessionInfo(h2, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #3", rc);
        return rc;
    }

    dump_session_info(&info);

    rc = funcs->C_CloseAllSessions(slot_id);
    if (rc != CKR_OK) {
        show_error("   C_CloseAllSessions", rc);
        return rc;
    }

    printf("Looks okay...\n");

    return rc;
}

// This is a messy function but it does alot of tests:
//
//  1) Create 1 RO session and 2 RW sessions
//  2) Log the USER into session #1.  Verify that all 3 become USER sessions.
//  3) Try to login again, this time to session #2.  Verify that it fails
//  4) Logout session #1
//  5) Try to logout from session #2.  Verify that this fails.
//  6) Try to log the SO into session #1.  Verify that it fails (RO session exists)
//  7) Try to log the SO into session #2.  Verify that it fails (RO session exists)
//  8) Close all sessions
//  9) Creaate 2 RW sessions
//  A) Log the SO into one.  Verify that both are now SO sessions.
//  B) Create a 3rd RW session.  Verify that it immediately becomes an SO session
//  C) Try to create a RO session.  Verify that it fails (SO session exists)
//  D) Close all sessions and return
//
CK_RV do_LoginLogout(void)
{
    CK_SLOT_ID slot_id;
    CK_FLAGS flags;
    CK_SESSION_HANDLE h1, h2, h3, h4;
    CK_SESSION_INFO info;
    CK_RV rc;
    CK_BYTE user_pin[PKCS11_MAX_PIN_LEN];
    CK_ULONG user_pin_len;
    CK_BYTE so_pin[PKCS11_MAX_PIN_LEN];
    CK_ULONG so_pin_len;

    printf("do_LoginLogout...\n");

    if (get_user_pin(user_pin))
        return CKR_FUNCTION_FAILED;

    user_pin_len = (CK_ULONG) strlen((char *) user_pin);

    if (get_so_pin(so_pin))
        return CKR_FUNCTION_FAILED;

    so_pin_len = (CK_ULONG) strlen((char *) so_pin);

    slot_id = SLOT_ID;
    flags = CKF_SERIAL_SESSION; // read-only session

    // create 3 sessions.  1 RO, two RW
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h1);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #1", rc);
        return rc;
    }

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h2);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #2", rc);
        return rc;
    }

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h3);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #3", rc);
        return rc;
    }

    // log the first session in.  all sessions should become USER sessions
    rc = funcs->C_Login(h1, CKU_USER, user_pin, user_pin_len);
    if (rc != CKR_OK) {
        show_error("   C_Login #1", rc);
        return rc;
    }

    rc = funcs->C_GetSessionInfo(h1, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #1", rc);
        return rc;
    }

    dump_session_info(&info);

    rc = funcs->C_GetSessionInfo(h2, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #2", rc);
        return rc;
    }

    dump_session_info(&info);

    rc = funcs->C_GetSessionInfo(h2, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #3", rc);
        return rc;
    }

    dump_session_info(&info);

    // now, try to log in session #2.  this should fail (already logged in)
    rc = funcs->C_Login(h2, CKU_USER, user_pin, user_pin_len);
    if (rc != CKR_USER_ALREADY_LOGGED_IN) {
        show_error("   C_Login #2", rc);
        PRINT_ERR("   Expected CKR_USER_ALREADY_LOGGED_IN\n");
        return -1;
    }

    // now, try to logout twice
    rc = funcs->C_Logout(h1);
    if (rc != CKR_OK) {
        show_error("   C_Logout #1", rc);
        return rc;
    }

    rc = funcs->C_Logout(h2);
    if (rc != CKR_USER_NOT_LOGGED_IN) {
        show_error("   C_Logout #2", rc);
        PRINT_ERR("   Expected CKR_USER_NOT_LOGGED_IN\n");
        return rc;
    }

    // now, try to log the SO in.  this should fail since H1 is a RO session
    rc = funcs->C_Login(h1, CKU_SO, so_pin, so_pin_len);
    if (rc != CKR_SESSION_READ_ONLY_EXISTS) {
        show_error("   C_Login #4", rc);
        PRINT_ERR("   Expected CKR_SESSION_READ_ONLY_EXISTS\n");
        return -1;
    }

    rc = funcs->C_Login(h2, CKU_SO, so_pin, so_pin_len);
    if (rc != CKR_SESSION_READ_ONLY_EXISTS) {
        show_error("   C_Login #5", rc);
        PRINT_ERR("   Expected CKR_SESSION_READ_ONLY_EXISTS\n");
        return -1;
    }

    // log completely out
    rc = funcs->C_CloseAllSessions(slot_id);
    if (rc != CKR_OK) {
        show_error("   C_CloseAllSessions #1", rc);
        return rc;
    }

    // now, start two RW sessions
    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h1);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #4", rc);
        return rc;
    }

    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h2);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #5", rc);
        return rc;
    }

    // now, try to log the SO in.  this should work
    rc = funcs->C_Login(h1, CKU_SO, so_pin, so_pin_len);
    if (rc != CKR_OK) {
        show_error("   C_Login #6", rc);
        return rc;
    }

    rc = funcs->C_GetSessionInfo(h1, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #4", rc);
        return rc;
    }

    dump_session_info(&info);

    rc = funcs->C_GetSessionInfo(h2, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #5", rc);
        return rc;
    }

    dump_session_info(&info);

    // now, create a 3rd RW session.
    // verify that it is automatically an SO session
    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h3);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #6", rc);
        return rc;
    }

    rc = funcs->C_GetSessionInfo(h3, &info);
    if (rc != CKR_OK) {
        show_error("   C_GetSessionInfo #6", rc);
        return rc;
    }

    dump_session_info(&info);

    // now, try to create a 4th session.  RO this time.  Should fail
    flags = CKF_SERIAL_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &h4);
    if (rc != CKR_SESSION_READ_WRITE_SO_EXISTS) {
        show_error("   C_OpenSession #6", rc);
        PRINT_ERR("   Expected CKR_SESSION_READ_WRITE_SO_EXISTS\n");
        return -1;
    }

    // we're done...close all sessions
    rc = funcs->C_CloseAllSessions(slot_id);
    if (rc != CKR_OK) {
        show_error("   C_CloseAllSessions #2:  %d", rc);
        return rc;
    }

    printf("Looks okay...\n");

    return rc;
}

CK_RV do_OperationState1(void)
{
    CK_SLOT_ID slot_id;
    CK_SESSION_HANDLE session1, session2;
    CK_FLAGS flags;
    CK_BYTE user_pin[PKCS11_MAX_PIN_LEN];
    CK_ULONG user_pin_len;
    CK_RV rc;

    CK_BYTE original[1024];
    CK_BYTE crypt1[1024];
    CK_BYTE crypt2[1024];
    CK_BYTE trash1[8];
    CK_BYTE trash2[8];

    CK_BYTE *op_state = NULL;
    CK_ULONG op_state_len;

    CK_ULONG orig_len, crypt1_len, crypt2_len, trash1_len, trash2_len;
    CK_ULONG i;

    CK_ULONG key_len = 16;
    CK_ATTRIBUTE key_gen_tmpl[] = {
        {CKA_VALUE_LEN, &key_len, sizeof(CK_ULONG)}
    };

    CK_MECHANISM mech;
    CK_OBJECT_HANDLE h_key;


    printf("do_OperationState1...\n");
    slot_id = SLOT_ID;

    //
    // here's the goal:
    //
    //  All the hash values should be the same
    //    1) session #1 starts a multi-part encryption
    //    2) save session #1 operation state
    //    3) session #1 passes garbage to encrypt update
    //    4) session #2's operation state is set to what we saved
    //    5) sessoin #2 finishes the encryption operation
    //
    //  Session #2's results should be the same as the single-part version
    //

    // create two USER RW sessions
    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &session1);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #1", rc);
        return rc;
    }

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &session2);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #2", rc);
        return rc;
    }

    if (get_user_pin(user_pin))
        return CKR_FUNCTION_FAILED;

    user_pin_len = (CK_ULONG) strlen((char *) user_pin);

    rc = funcs->C_Login(session1, CKU_USER, user_pin, user_pin_len);
    if (rc != CKR_OK) {
        show_error("   C_Login #1", rc);
        return rc;
    }

    orig_len = sizeof(original);
    for (i = 0; i < orig_len; i++)
        original[i] = i % 255;

    trash1_len = sizeof(trash1);
    memcpy(trash1, "asdflkjasdlkjadslkj", trash1_len);

    // first generate a AES key
    mech.mechanism = CKM_AES_KEY_GEN;
    mech.ulParameterLen = 0;
    mech.pParameter = NULL;

    if (!mech_supported(slot_id, mech.mechanism)) {
        printf("Mechanism %s not supported. (skipped)\n",
               mech_to_str(mech.mechanism));
        funcs->C_CloseSession(session1);
        funcs->C_CloseSession(session2);
        return 0;
    }

    rc = funcs->C_GenerateKey(session1, &mech, key_gen_tmpl, 1, &h_key);
    if (rc != CKR_OK) {
        show_error("   C_GenerateKey #1", rc);
        return rc;
    }
    // now encrypt the original data all at once using CBC
    mech.mechanism = CKM_AES_CBC;
    mech.ulParameterLen = 16;
    mech.pParameter = "1234qwerasdfyxcv";

    rc = funcs->C_EncryptInit(session1, &mech, h_key);
    if (rc != CKR_OK) {
        show_error("   C_EncryptInit #1", rc);
        return rc;
    }

    crypt1_len = sizeof(crypt1);
    rc = funcs->C_Encrypt(session1, original, orig_len, crypt1, &crypt1_len);
    if (rc != CKR_OK) {
        show_error("   C_Encrypt #1", rc);
        return rc;
    }

    // now, begin encrypting multipart
    rc = funcs->C_EncryptInit(session1, &mech, h_key);
    if (rc != CKR_OK) {
        show_error("   C_EncryptInit #2", rc);
        return rc;
    }

    crypt2_len = sizeof(crypt2);
    rc = funcs->C_EncryptUpdate(session1, original, orig_len / 2,
                                crypt2, &crypt2_len);
    if (rc != CKR_OK) {
        show_error("   C_EncryptUpdate #1", rc);
        return rc;
    }

    // save session #1's operation state
    rc = funcs->C_GetOperationState(session1, NULL, &op_state_len);
    if (rc != CKR_OK) {
        show_error("   C_GetOperationState #1", rc);
        return rc;
    }

    op_state = (CK_BYTE *) malloc(op_state_len);
    if (!op_state) {
        show_error("   HOST MEMORY ERROR", (CK_ULONG) CKR_HOST_MEMORY);
        return -1;
    }

    rc = funcs->C_GetOperationState(session1, op_state, &op_state_len);
    if (rc != CKR_OK) {
        show_error("   C_GetOperationState #1", rc);
        return rc;
    }

    // now, encrypt some garbage.  this will affect the CBC even if
    // we throw the encrypted garbage away
    trash2_len = sizeof(trash2);
    rc = funcs->C_EncryptUpdate(session1, trash1, trash1_len,
                                trash2, &trash2_len);
    if (rc != CKR_OK) {
        show_error("   C_EncryptUpdate #2", rc);
        return rc;
    }

    // restore session #1's operation state that we just saved back
    // into session #2 and continue with the encryption
    rc = funcs->C_SetOperationState(session2, op_state, op_state_len, h_key, 0);
    if (rc != CKR_OK) {
        show_error("   C_SetOperationState #1", rc);
        return rc;
    }

    free(op_state);

    // now, encrypt the rest of the original data
    i = crypt2_len;
    crypt2_len = sizeof(crypt2) - crypt2_len;
    rc = funcs->C_EncryptUpdate(session2,
                                original + orig_len / 2, orig_len / 2,
                                crypt2 + i, &crypt2_len);
    if (rc != CKR_OK) {
        show_error("   C_EncryptUpdate #3", rc);
        return rc;
    }

    crypt2_len += i;

    trash2_len = sizeof(trash2);
    rc = funcs->C_EncryptFinal(session2, trash2, &trash2_len);
    if (rc != CKR_OK) {
        show_error("   C_EncryptFinal #1", rc);
        return rc;
    }

    if (crypt2_len != crypt1_len) {
        PRINT_ERR("   ERROR:  Lengths don't match\n");
        return -1;
    }

    if (memcmp(crypt1, crypt2, crypt1_len) != 0) {
        PRINT_ERR("   ERROR:  crypt1 != crypt2\n");
        return -1;
    }

    rc = funcs->C_CloseSession(session1);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #1", rc);
        return rc;
    }

    rc = funcs->C_CloseSession(session2);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #2", rc);
        return rc;
    }

    printf("Looks okay...\n");

    return rc;
}

CK_RV do_OperationState2(void)
{
    CK_SLOT_ID slot_id;
    CK_SESSION_HANDLE session1, session2, session3;
    CK_FLAGS flags;
    CK_BYTE user_pin[PKCS11_MAX_PIN_LEN];
    CK_ULONG user_pin_len;
    CK_RV rc;

    CK_BYTE original[1024];
    CK_BYTE digest1[16];
    CK_BYTE digest2[16];
    CK_BYTE digest3[16];

    CK_ULONG orig_len;
    CK_ULONG digest1_len, digest2_len, digest3_len;

    CK_BYTE *op_state1 = NULL;
    CK_BYTE *op_state2 = NULL;
    CK_ULONG op_state1_len;
    CK_ULONG op_state2_len;

    CK_ULONG i;

    CK_MECHANISM mech;

    printf("do_OperationState2...\n");
    slot_id = SLOT_ID;

    //
    // here's the goal:
    //  1) session #1 digests the first 499 bytes
    //  2) session #2 digests the first 27 bytes
    //  3) session #3 digests the whole thing
    //  3) we save both operation states
    //  4) we set the operation states to the 'other' session thereby
    //     switching sessions.  Session #2 picks up where session #1 was
    //     saved, session #1 picks up where session #2 was saved.
    //  5) session #1 digests the final (1024 - 27) bytes
    //  6) session #2 digests the final (1024 - 499) bytes
    //
    //  All the hash values should be the same
    //

    // create three USER RW sessions
    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &session1);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #1", rc);
        return rc;
    }

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &session2);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #2", rc);
        return rc;
    }

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &session3);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #3", rc);
        return rc;
    }

    if (get_user_pin(user_pin))
        return CKR_FUNCTION_FAILED;

    user_pin_len = (CK_ULONG) strlen((char *) user_pin);

    rc = funcs->C_Login(session1, CKU_USER, user_pin, user_pin_len);
    if (rc != CKR_OK) {
        show_error("   C_Login #1", rc);
        return rc;
    }

    orig_len = sizeof(original);
    for (i = 0; i < orig_len; i++)
        original[i] = i % 255;

    mech.mechanism = CKM_MD5;
    mech.pParameter = NULL;
    mech.ulParameterLen = 0;

    if (!mech_supported(slot_id, mech.mechanism)) {
        printf("Mechanism %s not supported. (skipped)\n",
               mech_to_str(mech.mechanism));
        funcs->C_CloseSession(session1);
        funcs->C_CloseSession(session2);
        funcs->C_CloseSession(session3);
        return 0;
    }

    rc = funcs->C_DigestInit(session1, &mech);
    if (rc != CKR_OK) {
        show_error("   C_DigestInit #1", rc);
        return rc;
    }

    rc = funcs->C_DigestInit(session2, &mech);
    if (rc != CKR_OK) {
        show_error("   C_DigestInit #2", rc);
        return rc;
    }

    rc = funcs->C_DigestInit(session3, &mech);
    if (rc != CKR_OK) {
        show_error("   C_DigestInit #3", rc);
        return rc;
    }

    rc = funcs->C_DigestUpdate(session1, original, 499);
    if (rc != CKR_OK) {
        show_error("   C_DigestUpdate #1", rc);
        return rc;
    }

    rc = funcs->C_DigestUpdate(session2, original, 27);
    if (rc != CKR_OK) {
        show_error("   C_DigestUpdate #2", rc);
        return rc;
    }

    orig_len = sizeof(original);
    digest3_len = sizeof(digest3);
    rc = funcs->C_Digest(session3, original, orig_len, digest3, &digest3_len);
    if (rc != CKR_OK) {
        show_error("   C_Digest #1", rc);
        return rc;
    }

    // save the operation states of sessions 1 and 2
    rc = funcs->C_GetOperationState(session1, NULL, &op_state1_len);
    if (rc != CKR_OK) {
        show_error("   C_GetOperationState #1", rc);
        return rc;
    }

    op_state1 = (CK_BYTE *) malloc(op_state1_len);
    if (!op_state1) {
        show_error("   HOST MEMORY ERROR", (CK_ULONG) CKR_HOST_MEMORY);
        return -1;
    }

    rc = funcs->C_GetOperationState(session1, op_state1, &op_state1_len);
    if (rc != CKR_OK) {
        show_error("   C_GetOperationState #2", rc);
        return rc;
    }

    rc = funcs->C_GetOperationState(session2, NULL, &op_state2_len);
    if (rc != CKR_OK) {
        show_error("   C_GetOperationState #3", rc);
        return rc;
    }

    op_state2 = (CK_BYTE *) malloc(op_state2_len);
    if (!op_state2) {
        show_error("   HOST MEMORY ERROR", (CK_ULONG) CKR_HOST_MEMORY);
        return -1;
    }

    rc = funcs->C_GetOperationState(session2, op_state2, &op_state2_len);
    if (rc != CKR_OK) {
        show_error("   C_GetOperationState #4", rc);
        return rc;
    }

    // switch the states
    rc = funcs->C_SetOperationState(session1, op_state2, op_state2_len, 0, 0);
    if (rc != CKR_OK) {
        show_error("   C_SetOperationState #2", rc);
        return rc;
    }

    rc = funcs->C_SetOperationState(session2, op_state1, op_state1_len, 0, 0);
    if (rc != CKR_OK) {
        show_error("   C_SetOperationState #3", rc);
        return rc;
    }

    // now, finish the digest operations
    rc = funcs->C_DigestUpdate(session2, original + 499, (orig_len - 499));
    if (rc != CKR_OK) {
        show_error("   C_DigestUpdate #3", rc);
        return rc;
    }

    rc = funcs->C_DigestUpdate(session1, original + 27, orig_len - 27);
    if (rc != CKR_OK) {
        show_error("   C_DigestUpdate #4", rc);
        return rc;
    }

    digest1_len = sizeof(digest1);
    rc = funcs->C_DigestFinal(session1, digest1, &digest1_len);
    if (rc != CKR_OK) {
        show_error("   C_DigestFinal #1", rc);
        return rc;
    }

    digest2_len = sizeof(digest2);
    rc = funcs->C_DigestFinal(session2, digest2, &digest2_len);
    if (rc != CKR_OK) {
        show_error("   C_DigestFinal #2", rc);
        return rc;
    }

    if (digest1_len != digest2_len || digest1_len != digest3_len) {
        PRINT_ERR("   ERROR:  digested lengths don't match\n");
        return -1;
    }

    if (memcmp(digest1, digest2, digest1_len) != 0) {
        PRINT_ERR("   ERROR:  digest1 != digest2\n");
        return -1;
    }

    if (memcmp(digest1, digest3, digest1_len) != 0) {
        PRINT_ERR("   ERROR:  digest1 != digest3\n");
        return -1;
    }

    rc = funcs->C_CloseSession(session1);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #3", rc);
        return rc;
    }

    rc = funcs->C_CloseSession(session2);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #4", rc);
        return rc;
    }

    rc = funcs->C_CloseSession(session3);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #5", rc);
        return rc;
    }

    printf("Looks okay...\n");

    free(op_state1);
    free(op_state2);
    return rc;
}

CK_RV do_OperationState3(void)
{
    CK_SLOT_ID slot_id;
    CK_SESSION_HANDLE session1, session2, session3;
    CK_FLAGS flags;
    CK_BYTE user_pin[PKCS11_MAX_PIN_LEN];
    CK_ULONG user_pin_len;
    CK_RV rc;

    CK_BYTE original[1024];
    CK_BYTE digest1[16];
    CK_BYTE digest2[16];
    CK_BYTE digest3[16];
    CK_BYTE junk[1024];

    CK_ULONG orig_len, junk_len;
    CK_ULONG digest1_len, digest2_len, digest3_len;

    CK_BYTE *op_state2 = NULL;
    CK_ULONG op_state2_len;

    CK_ULONG i;

    CK_ULONG key_len = 16;
    CK_ATTRIBUTE key_gen_tmpl[] = {
        {CKA_VALUE_LEN, &key_len, sizeof(CK_ULONG)}
    };

    CK_MECHANISM mech1, mech2;
    CK_OBJECT_HANDLE key;


    printf("do_OperationState3...\n");
    slot_id = SLOT_ID;

    //
    // here's the goal:
    //  1) session #1 starts a multi-part encrypt
    //  2) session #2 starts a multi-part digest
    //  3) session #3 digests the whole thing
    //  4) assign session #2's operating state to session #1
    //  5) session #1 tries C_EncryptUpdate.  Should fail.
    //  6) session #1 finishes the multi-part digest
    //  7) session #2 finishes the multi-part digest
    //
    //  All the hash values should be the same
    //

    // create three USER RW sessions
    flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &session1);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #1", rc);
        return rc;
    }

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &session2);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #2", rc);
        return rc;
    }

    rc = funcs->C_OpenSession(slot_id, flags, NULL, NULL, &session3);
    if (rc != CKR_OK) {
        show_error("   C_OpenSession #3", rc);
        return rc;
    }

    if (get_user_pin(user_pin))
        return CKR_FUNCTION_FAILED;

    user_pin_len = (CK_ULONG) strlen((char *) user_pin);

    rc = funcs->C_Login(session1, CKU_USER, user_pin, user_pin_len);
    if (rc != CKR_OK) {
        show_error("   C_Login #1", rc);
        return rc;
    }

    orig_len = sizeof(original);
    for (i = 0; i < orig_len; i++)
        original[i] = i % 255;

    mech1.mechanism = CKM_AES_KEY_GEN;
    mech1.pParameter = NULL;
    mech1.ulParameterLen = 0;

    if (!mech_supported(slot_id, mech1.mechanism)) {
        printf("Mechanism %s not supported. (skipped)\n",
               mech_to_str(mech1.mechanism));
        funcs->C_CloseSession(session1);
        funcs->C_CloseSession(session2);
        funcs->C_CloseSession(session3);
        return 0;
    }

    rc = funcs->C_GenerateKey(session1, &mech1, key_gen_tmpl, 1, &key);
    if (rc != CKR_OK) {
        show_error("   C_GenerateKey #1", rc);
        return rc;
    }

    mech1.mechanism = CKM_AES_ECB;
    mech1.pParameter = NULL;
    mech1.ulParameterLen = 0;

    if (!mech_supported(slot_id, mech1.mechanism)) {
        printf("Mechanism %s not supported. (skipped)\n",
               mech_to_str(mech1.mechanism));
        funcs->C_CloseSession(session1);
        funcs->C_CloseSession(session2);
        funcs->C_CloseSession(session3);
        return 0;
    }

    rc = funcs->C_EncryptInit(session1, &mech1, key);
    if (rc != CKR_OK) {
        show_error("   C_EncryptInit #1", rc);
        return rc;
    }

    mech2.mechanism = CKM_MD5;
    mech2.pParameter = NULL;
    mech2.ulParameterLen = 0;

    if (!mech_supported(slot_id, mech2.mechanism)) {
        printf("Mechanism %s not supported. (skipped)\n",
               mech_to_str(mech2.mechanism));
        funcs->C_CloseSession(session1);
        funcs->C_CloseSession(session2);
        funcs->C_CloseSession(session3);
        return 0;
    }

    rc = funcs->C_DigestInit(session2, &mech2);
    if (rc != CKR_OK) {
        show_error("   C_DigestInit #1", rc);
        return rc;
    }

    rc = funcs->C_DigestInit(session3, &mech2);
    if (rc != CKR_OK) {
        show_error("   C_DigestInit #2", rc);
        return rc;
    }

    rc = funcs->C_DigestUpdate(session2, original, 499);
    if (rc != CKR_OK) {
        show_error("   C_DigestUpdate #1", rc);
        return rc;
    }

    orig_len = sizeof(original);
    digest3_len = sizeof(digest3);
    rc = funcs->C_Digest(session3, original, orig_len, digest3, &digest3_len);
    if (rc != CKR_OK) {
        show_error("   C_Digest #1", rc);
        return rc;
    }

    rc = funcs->C_GetOperationState(session2, NULL, &op_state2_len);
    if (rc != CKR_OK) {
        show_error("   C_GetOperationState #1", rc);
        return rc;
    }

    op_state2 = (CK_BYTE *) malloc(op_state2_len);
    if (!op_state2) {
        show_error("   HOST MEMORY ERROR #1", (CK_ULONG) CKR_HOST_MEMORY);
        return -1;
    }

    rc = funcs->C_GetOperationState(session2, op_state2, &op_state2_len);
    if (rc != CKR_OK) {
        show_error("   C_GetOperationState #2", rc);
        return rc;
    }

    rc = funcs->C_SetOperationState(session1, op_state2, op_state2_len, 0, 0);
    if (rc != CKR_OK) {
        show_error("   C_SetOperationState #1", rc);
        return rc;
    }

    // session #1 should not be set to do digest not encryption
    junk_len = sizeof(junk);
    rc = funcs->C_EncryptUpdate(session1, original, 499, junk, &junk_len);
    if (rc != CKR_OPERATION_NOT_INITIALIZED) {
        show_error("   C_EncryptUpdate #1", rc);
        PRINT_ERR("   Expected CKR_OPERATION_NOT_INITIALIZED\n");
        return -1;
    }

    // now, finish the digest operations
    rc = funcs->C_DigestUpdate(session1, original + 499, (orig_len - 499));
    if (rc != CKR_OK) {
        show_error("   C_DigestUpdate #2", rc);
        return rc;
    }

    rc = funcs->C_DigestUpdate(session2, original + 499, (orig_len - 499));
    if (rc != CKR_OK) {
        show_error("   C_DigestUpdate #3", rc);
        return rc;
    }

    digest1_len = sizeof(digest1);
    rc = funcs->C_DigestFinal(session1, digest1, &digest1_len);
    if (rc != CKR_OK) {
        show_error("   C_DigestFinal #1", rc);
        return rc;
    }

    digest2_len = sizeof(digest2);
    rc = funcs->C_DigestFinal(session2, digest2, &digest2_len);
    if (rc != CKR_OK) {
        show_error("   C_DigestFinal #2", rc);
        return rc;
    }

    if (digest1_len != digest2_len || digest1_len != digest3_len) {
        PRINT_ERR("   ERROR:  digested lengths don't match\n");
        return -1;
    }

    if (memcmp(digest1, digest2, digest1_len) != 0) {
        PRINT_ERR("   ERROR:  digest1 != digest2\n");
        return -1;
    }

    if (memcmp(digest1, digest3, digest1_len) != 0) {
        PRINT_ERR("   ERROR:  digest1 != digest3\n");
        return -1;
    }

    rc = funcs->C_CloseSession(session1);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #3", rc);
        return rc;
    }

    rc = funcs->C_CloseSession(session2);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #4", rc);
        return rc;
    }

    rc = funcs->C_CloseSession(session3);
    if (rc != CKR_OK) {
        show_error("   C_CloseSession #5", rc);
        return rc;
    }

    printf("Looks okay...\n");

    free(op_state2);
    return rc;
}

CK_RV sess_mgmt_functions()
{
    SYSTEMTIME t1, t2;
    CK_RV rc;

    GetSystemTime(&t1);
    rc = do_OpenSession();
    if (rc && !no_stop)
        return rc;
    GetSystemTime(&t2);
    process_time(t1, t2);


    GetSystemTime(&t1);
    rc = do_OpenSession2();
    if (rc && !no_stop)
        return rc;
    GetSystemTime(&t2);
    process_time(t1, t2);


    GetSystemTime(&t1);
    rc = do_CloseAllSessions();
    if (rc && !no_stop)
        return rc;
    GetSystemTime(&t2);
    process_time(t1, t2);


    GetSystemTime(&t1);
    rc = do_GetSessionInfo();
    if (rc && !no_stop)
        return rc;
    GetSystemTime(&t2);
    process_time(t1, t2);


    GetSystemTime(&t1);
    rc = do_LoginLogout();
    if (rc && !no_stop)
        return rc;
    GetSystemTime(&t2);
    process_time(t1, t2);


    GetSystemTime(&t1);
    rc = do_OperationState1();
    if (rc && !no_stop)
        return rc;
    GetSystemTime(&t2);
    process_time(t1, t2);


    GetSystemTime(&t1);
    rc = do_OperationState2();
    if (rc && !no_stop)
        return rc;
    GetSystemTime(&t2);
    process_time(t1, t2);


    GetSystemTime(&t1);
    rc = do_OperationState3();
    if (rc && !no_stop)
        return rc;
    GetSystemTime(&t2);
    process_time(t1, t2);

    return rc;
}

int main(int argc, char **argv)
{
    CK_C_INITIALIZE_ARGS cinit_args;
    int rc;
    CK_RV rv;

    rc = do_ParseArgs(argc, argv);
    if (rc != 1)
        return rc;

    printf("Using slot #%lu...\n\n", SLOT_ID);
    printf("With option: no_init: %d\n", no_init);

    rc = do_GetFunctionList();
    if (!rc) {
        PRINT_ERR("ERROR do_GetFunctionList() Failed , rc = 0x%0x\n", rc);
        return rc;
    }

    memset(&cinit_args, 0x0, sizeof(cinit_args));
    cinit_args.flags = CKF_OS_LOCKING_OK;

    // SAB Add calls to ALL functions before the C_Initialize gets hit

    funcs->C_Initialize(&cinit_args);

    {
        CK_SESSION_HANDLE hsess = 0;

        rc = funcs->C_GetFunctionStatus(hsess);
        if (rc != CKR_FUNCTION_NOT_PARALLEL)
            return rc;

        rc = funcs->C_CancelFunction(hsess);
        if (rc != CKR_FUNCTION_NOT_PARALLEL)
            return rc;

    }

    rv = sess_mgmt_functions();

    /* make sure we return non-zero if rv is non-zero */
    return ((rv == 0) || (rv % 256) ? (int)rv : -1);
}
