#include <time.h>
#include "test_common.h"

CK_RV test_crypto();
CK_RV test_gost28147_cfb_random(CK_SESSION_HANDLE sess, CK_SESSION_HANDLE sess2);

int main(int argc, char *argv[])
{
    CK_RV rc = CKR_OK;

    rc = test_main(argc, argv);
    if (rc != CKR_OK)
    {
        fprintf(stderr, "test_main failed: 0x%lx\n", rc);
        return rc;
    }
    rc = test_crypto();
    if (rc != CKR_OK)
    {
        fprintf(stderr, "test_crypto failed: 0x%lx\n", rc);
        return rc;
    }
    return CKR_OK;
}

CK_RV test_crypto()
{
    CK_RV             rc, ret;
    CK_SESSION_HANDLE hSession, hSession2;
    CK_MECHANISM_INFO minfo;

    rc = funcs->C_OpenSession(SlotId,
                              CKF_RW_SESSION | CKF_SERIAL_SESSION,
                              NULL_PTR, NULL_PTR, &hSession);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
                "ERROR call to C_OpenSession failed, rc = 0x%lx\n", rc);
        goto out;
    }
    fprintf(stderr, "C_OpenSession success\n");
    rc = funcs->C_OpenSession(SlotId,
                              CKF_RW_SESSION | CKF_SERIAL_SESSION,
                              NULL_PTR, NULL_PTR, &hSession2);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
                "ERROR call to C_OpenSession failed, rc = 0x%lx\n", rc);
        goto out;
    }
    fprintf(stderr, "C_OpenSession 2 success\n");

    rc = funcs->C_GetMechanismInfo(SlotId, CKM_GOST28147, &minfo);
    if (rc != CKR_OK)
    {
        fprintf(stderr,
                "\n======== Mechanism CKM_GOST28147 not supported ===========\n");
    }
    else
    {
		rc = funcs->C_GetMechanismInfo(SlotId, CKM_GOST28147_KEY_GEN, &minfo);
		if (rc != CKR_OK)
		{
			fprintf(stderr,
                "\n======== Mechanism CKM_GOST28147_KEY_GEN not supported ===========\n");
		} else {
			fprintf(stderr,
					"\n==================== CKM_GOST28147 CFB random test ==================\n");
			rc = test_gost28147_cfb_random(hSession, hSession2);
			if (rc != CKR_OK)
			{
				fprintf(stderr,
						"ERROR CKM_GOST28147 CFB random test failed, rc = 0x%lx\n", rc);
				return rc;
			}
			else
			{
				fprintf(stderr, "CKM_GOST28147 CFB random test SUCCESS\n");
			}
		}
    }

    if ((rc = funcs->C_CloseSession(hSession2)) != CKR_OK)
    {
        fprintf(stderr,
                "Error: C_CloseSession 2 failed with 0x%lx\n", rc);
    }
    else
    {
        fprintf(stderr, "C_CloseSession 2 success\n");
    }

    if ((ret = funcs->C_CloseSession(hSession)) != CKR_OK)
    {
        fprintf(stderr,
                "Error: C_CloseSession failed with 0x%lx\n", ret);
        rc = ret;
    }
    else
    {
        fprintf(stderr, "C_CloseSession success\n");
    }

 out:
    return rc;
}

CK_RV test_gost28147_cfb_random(CK_SESSION_HANDLE sess, CK_SESSION_HANDLE sess2)
{
    CK_RV                  rc = CKR_OK;
    CK_ULONG               len, state_len;
    CK_OBJECT_HANDLE       keyh = CK_INVALID_HANDLE;
    CK_BYTE                *state = NULL;
    CK_MECHANISM           mechanism_desc = { CKM_GOST28147, NULL, 0 };
    CK_MECHANISM_PTR       mechanism      = &mechanism_desc;

    static CK_BBOOL        ltrue    = CK_TRUE;
//    static CK_BBOOL        lfalse   = CK_FALSE;
    static CK_OBJECT_CLASS oclass   = CKO_SECRET_KEY;
    static CK_KEY_TYPE     key_type = CKK_GOST28147;
    //par_cipher_Z (TC 26 Z)
    static CK_BYTE      par_cipher_Z[] = {
        0x06, 0x09, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x02, 0x05, 0x01, 0x01
    };
    CK_MECHANISM           mechanism_keygen_desc = { CKM_GOST28147_KEY_GEN, NULL, 0 };
    CK_MECHANISM_PTR       mechanism_keygen      = &mechanism_keygen_desc;
    CK_ATTRIBUTE key_template[] = {
        { CKA_CLASS,           &oclass,			sizeof(oclass)   },
        { CKA_KEY_TYPE,        &key_type,		sizeof(key_type) },
        { CKA_GOST28147PARAMS, par_cipher_Z,	sizeof(par_cipher_Z)},
        { CKA_ENCRYPT,         &ltrue,			sizeof(CK_BBOOL) },
        { CKA_DECRYPT,         &ltrue,			sizeof(CK_BBOOL) }
    };
	CK_ULONG	data_len_max = 8192;
	CK_ULONG	data_len_min = 3;
	CK_ULONG	data_len = data_len_max;
	CK_ULONG	part_len = data_len/3;
    CK_BYTE     *data = NULL;
    CK_BYTE		iv[8];
	CK_ULONG	iv_len = 8;
	CK_BYTE		keyval[32];
	CK_ATTRIBUTE attr;
    CK_BYTE     *value = NULL;
    CK_BYTE     *value2 = NULL;
	int rounds = 5;
	int i;

	printf("Encryption/Decryptions In Two Sessions\n"
		"With Random Key\n"
		"And With Random IV\n"
		"And With Two Random Data Parts\n"
		"And Using Get/SetOperationState\n");

	for (i = 0; i < rounds; i++) {
		printf("==== Iteration %d/%d ==================\n", i+1, rounds);
		data_len = get_random_size(sess, data_len_min, data_len_max);
		if (data_len == 0) {
			fprintf(stderr, "get_random_size failed\n");
			rc = CKR_FUNCTION_FAILED;
			goto end;
		}
		part_len = get_random_size(sess, data_len/3, data_len*2/3);
		if (part_len == 0) {
			fprintf(stderr, "get_random_size failed\n");
			rc = CKR_FUNCTION_FAILED;
			goto end;
		}
		printf("data_len = %ld, part_len = %ld\n", 
			data_len, part_len);
		data = (CK_BYTE *)malloc(data_len);
		if (!data) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		value = (CK_BYTE *)malloc(data_len);
		if (!value) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		value2 = (CK_BYTE *)malloc(data_len);
		if (!value2) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		rc = funcs->C_GenerateRandom(sess, data, data_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GenerateRandom failed: 0x%lx\n", rc);
			goto end;
		}
		printf("data: 0x%02X, 0x%02X, 0x%02X, 0x%02X, ... 0x%02X\n", 
			data[0], data[1], data[2], data[3], data[data_len-1]);

		rc = funcs->C_GenerateKey(sess, mechanism_keygen,
			key_template, sizeof(key_template) / sizeof(CK_ATTRIBUTE), &keyh);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GenerateKey failed: 0x%lx\n", rc);
			return rc;
		}
		attr.type       = CKA_VALUE;
		attr.pValue     = keyval;
		attr.ulValueLen = sizeof(keyval);
		rc              = funcs->C_GetAttributeValue(sess, keyh, &attr, 1);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetAttributeValue failed: 0x%lx\n", rc);
			goto end;
		}
		printf("key:\n");
		print_bytes(keyval, sizeof(keyval));

		rc = funcs->C_GenerateRandom(sess, iv, iv_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GenerateRandom failed: 0x%lx\n", rc);
			goto end;
		}
		printf("IV:\n");
		print_bytes(iv, iv_len);

		mechanism->pParameter     = iv;
		mechanism->ulParameterLen = iv_len;

		rc = funcs->C_EncryptInit(sess, mechanism, keyh);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_EncryptInit failed: 0x%lx\n", rc);
			goto end;
		}

		len = data_len;
		rc  = funcs->C_EncryptUpdate(sess, data, part_len, value, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_EncryptUpdate failed: 0x%lx\n", rc);
			goto end;
		}

		// Save session operation state
		state_len = 0;
		rc        = funcs->C_GetOperationState(sess, NULL, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		state     = malloc(state_len);
		if (!state) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		rc = funcs->C_GetOperationState(sess, state, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}

		// Restore saved operation state to other session
		rc = funcs->C_SetOperationState(sess2, state, state_len,
										keyh, CK_INVALID_HANDLE);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_SetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		memcpy(value2, value, data_len);

		free(state);
		state = NULL;
		state_len = 0;
		rc        = funcs->C_GetOperationState(sess2, NULL, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		state = malloc(state_len);
		if (!state) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		rc    = funcs->C_GetOperationState(sess2, state, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}

		free(state);
		state = NULL;
		state_len = 0;
		rc        = funcs->C_GetOperationState(sess, NULL, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		state = malloc(state_len);
		if (!state) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		rc    = funcs->C_GetOperationState(sess, state, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		rc = funcs->C_SetOperationState(sess, state, state_len,
										keyh, CK_INVALID_HANDLE);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_SetOperationState failed: 0x%lx\n", rc);
			goto end;
		}

		// Continue operations in first session
		len = data_len - part_len;
		rc  = funcs->C_EncryptUpdate(sess,
			data + part_len, data_len - part_len, value + part_len, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_EncryptUpdate failed: 0x%lx\n", rc);
			goto end;
		}

		// Continue restored operations in second session
		len = data_len - part_len;
		rc  = funcs->C_EncryptUpdate(sess2,
			data + part_len, data_len - part_len, value2 + part_len, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_EncryptUpdate failed: 0x%lx\n", rc);
			goto end;
		}

		// Finalize operations in first session
		len = data_len - data_len;
		rc  = funcs->C_EncryptFinal(sess, value + data_len, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_EncryptFinal failed: 0x%lx\n", rc);
			goto end;
		}
		memcpy(value2, value, data_len);

		free(state);
		state = NULL;
		state_len = 0;
		rc        = funcs->C_GetOperationState(sess2, NULL, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		state = malloc(state_len);
		if (!state) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		rc    = funcs->C_GetOperationState(sess2, state, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}

		// Finalize operations in second session
		len = data_len - data_len;
		rc  = funcs->C_EncryptFinal(sess2, value2 + data_len, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_EncryptFinal failed: 0x%lx\n", rc);
			goto end;
		}
		// len == 0
		printf("Encrypted data: 0x%02X, 0x%02X, 0x%02X, 0x%02X, ... 0x%02X\n", 
			value2[0], value2[1], value2[2], value2[3], data[data_len-1]);


		rc = funcs->C_DecryptInit(sess2, mechanism, keyh);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptInit failed: 0x%lx\n", rc);
			goto end;
		}

		len = data_len;
		rc  = funcs->C_Decrypt(sess2, value2, len, value, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_Decrypt failed: 0x%lx\n", rc);
			goto end;
		}

		if (len != data_len) {
			fprintf(stderr, "Invalid decrypted data length\n");
			goto end;
		}
		if (memcmp(value, data, len) != 0) {
			fprintf(stderr, "Invalid decrypted data\n");
			goto end;
		}

		rc = funcs->C_DecryptInit(sess2, mechanism, keyh);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptInit failed: 0x%lx\n", rc);
			goto end;
		}

		len = part_len;
		rc  = funcs->C_DecryptUpdate(sess2, value2, part_len, value, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptUpdate failed: 0x%lx\n", rc);
			goto end;
		}
		len = data_len - part_len;
		rc  = funcs->C_DecryptUpdate(sess2,
			value2 + part_len, data_len - part_len, value + part_len, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptUpdate failed: 0x%lx\n", rc);
			goto end;
		}
		len = data_len;
		rc  = funcs->C_DecryptFinal(sess2, value + data_len, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptFinal failed: 0x%lx\n", rc);
			goto end;
		}

		// len == 0
		if (memcmp(value, data, len) != 0) {
			fprintf(stderr, "Invalid decrypted data\n");
			goto end;
		}

		rc = funcs->C_DecryptInit(sess, mechanism, keyh);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptInit failed: 0x%lx\n", rc);
			goto end;
		}
		free(state);
		state = NULL;
		state_len = 0;
		rc        = funcs->C_GetOperationState(sess, NULL, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		state = malloc(state_len);
		if (!state) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		rc    = funcs->C_GetOperationState(sess, state, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		len = part_len;
		rc = funcs->C_DecryptUpdate(sess, value2, len, value, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptUpdate failed: 0x%lx\n", rc);
			goto end;
		}
		free(state);
		state = NULL;
		state_len = 0;
		rc        = funcs->C_GetOperationState(sess, NULL, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		state = malloc(state_len);
		if (!state) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		rc    = funcs->C_GetOperationState(sess, state, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		len = data_len - part_len;
		rc = funcs->C_DecryptUpdate(sess,
			 value2 + part_len, len, value + part_len, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptUpdate failed: 0x%lx\n", rc);
			goto end;
		}
		free(state);
		state = NULL;
		state_len = 0;
		rc        = funcs->C_GetOperationState(sess, NULL, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		state = malloc(state_len);
		if (!state) {
			fprintf(stderr, "malloc(%ld) failed\n", data_len);
			rc = CKR_HOST_MEMORY;
			goto end;
		}
		rc    = funcs->C_GetOperationState(sess, state, &state_len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_GetOperationState failed: 0x%lx\n", rc);
			goto end;
		}
		rc = funcs->C_DecryptFinal(sess, value + data_len, &len);
		if (rc != CKR_OK)
		{
			fprintf(stderr, "C_DecryptFinal failed: 0x%lx\n", rc);
			goto end;
		}
		free(state);
		state = NULL;

		// len == 0
		len = data_len;
		if (memcmp(value, data, len) != 0) {
			fprintf(stderr, "Invalid decrypted data\n");
			goto end;
		}

		printf("Decrypted data OK\n");
		if (keyh != CK_INVALID_HANDLE)
		{
			funcs->C_DestroyObject(sess, keyh);
			keyh = CK_INVALID_HANDLE;
		}
		if (data) {
			free(data);
			data = NULL;
		}
		if (value) {
			free(value);
			value = NULL;
		}
		if (value2) {
			free(value2);
			value2 = NULL;
		}
		printf("===============================\n");
	}

    printf("SUCCESS\n");
    rc = CKR_OK;
 end:
    if (keyh != CK_INVALID_HANDLE)
    {
        funcs->C_DestroyObject(sess, keyh);
    }
	if (data) free(data);
	if (value) free(value);
	if (value2) free(value2);

    return rc;
}

