#include "test_common.h"

CK_RV do_obj_test(CK_BBOOL is_priv, CK_OBJECT_CLASS oclass_data);

// TEST_OBJ_MAX_NEW_SIZE<=TEST_OBJ_SIZE
#define TEST_OBJ_SIZE            256
#define TEST_OBJ_MAX_NEW_SIZE    200

#define PRINT_ERROR_POINT                              \
    printf("Test error (File:%s, Func:%s, Line:%d)\n", \
           __FILE__, __FUNCTION__, __LINE__);

#define TEST_ERROR(msg)    { \
        PRINT_ERROR_POINT;   \
        printf msg;          \
        goto on_error;       \
}

int main(int argc, char *argv[])
{
    CK_RV    rc;  // Return Code
    int      i;
    CK_BBOOL priv  = CK_FALSE;
    int      count = 1;

    printf("create_obj test\n");
    for (i = 1; i < argc; i++)
    {
        if (strcmp("-api", argv[i]) == 0)
        {
            ++i;
            api_path = argv[i];
        }
        else if (strcmp("-user_pin", argv[i]) == 0)
        {
            ++i;
            user_pin = (CK_UTF8CHAR *)argv[i];
        }
        else if (strcmp("-count", argv[i]) == 0)
        {
            i++;
            count = atoi(argv[i]);
        }
    }

    for (i = 0; i < count; i++)
    {
        printf("======= Iteration %d of %d =========\n", i + 1, count);
        printf("<<< Testing public objects >>>\n");
        priv = CK_FALSE;
        rc   = do_obj_test(priv, CKO_DATA);
        if (rc != CKR_OK)
        {
            PROCESS_ERROR("do_obj_test", rc, on_error);
        }
        printf("<<< Testing private objects >>>\n");
        priv = CK_TRUE;
        rc   = do_obj_test(priv, CKO_DATA);
        if (rc != CKR_OK)
        {
            PROCESS_ERROR("do_obj_test", rc, on_error);
        }
    }
    printf("create_obj test SUCCESS\n");
    return CKR_OK;
 on_error:
    printf("create_obj test failed\n");
    return rc;
}

CK_RV do_obj_test(CK_BBOOL is_priv, CK_OBJECT_CLASS oclass)
{
    CK_RV             rc;        // Return Code
    CK_FLAGS          flags = 0; // Bit mask for what options were passed in
    CK_SESSION_HANDLE sess;
    CK_SLOT_INFO      sinfo;
    CK_ULONG          i;
    CK_ULONG          len, new_len;
    static CK_BYTE    data_value[TEST_OBJ_SIZE],
                      data_tmp[TEST_OBJ_SIZE], 
					  data_new[TEST_OBJ_MAX_NEW_SIZE];
    static CK_BBOOL   ltrue       = CK_TRUE;
//    static CK_BBOOL   lfalse      = CK_FALSE;
    static CK_UTF8CHAR       *label       = (CK_UTF8CHAR *)"data0";
	CK_ULONG label_len = (CK_ULONG)strlen((char *)label) + 1;
    static CK_UTF8CHAR       *label_new   = (CK_UTF8CHAR *)"data_new (big label)";
	CK_ULONG label_new_len = (CK_ULONG)strlen((char *)label_new) + 1;
    static CK_UTF8CHAR       *app         = (CK_UTF8CHAR *)"ls11sw";
	CK_ULONG app_len = (CK_ULONG)strlen((char *)app) + 1;
    CK_ATTRIBUTE      data_create[] = {
        { CKA_VALUE,       data_value, sizeof(data_value) },
        { CKA_CLASS,       NULL,        0    },	// 1
        { CKA_APPLICATION, NULL,        0    }, // 2 
        { CKA_TOKEN,       &ltrue,     sizeof(ltrue)      },
        { CKA_LABEL,       NULL,        0    }, // 4
        { CKA_PRIVATE,     NULL,		0    },	// 5
    };
    CK_OBJECT_HANDLE  hObj = CK_INVALID_HANDLE;
    CK_ATTRIBUTE      t[]  = {
        { CKA_CLASS, NULL,      0     },	// 0
        { CKA_LABEL, NULL,      0     },	// 1
        { CKA_VALUE, data_value, sizeof(data_value) }
    };
	CK_ULONG t_count = sizeof(t)/sizeof(CK_ATTRIBUTE);
    CK_ATTRIBUTE      t1            = { 
		CKA_VALUE, data_tmp, sizeof(data_tmp) 
	};
    CK_ATTRIBUTE      attr_update[] = {
        { CKA_LABEL, NULL, 0     },	// 0
        { CKA_VALUE, data_new,  TEST_OBJ_MAX_NEW_SIZE }
    };

	data_create[1].pValue = &oclass;
	data_create[1].ulValueLen = sizeof(oclass);
	data_create[2].pValue = app;
	data_create[2].ulValueLen = app_len;
	data_create[4].pValue = label;
	data_create[4].ulValueLen = label_len;
	data_create[5].pValue = &is_priv;
	data_create[5].ulValueLen = sizeof(is_priv);
	t[0].pValue = &oclass;
	t[0].ulValueLen = sizeof(oclass);
	t[1].pValue = label;
	t[1].ulValueLen = label_len;
	attr_update[0].pValue = label_new;
	attr_update[0].ulValueLen = label_new_len;

    rc = init_all();
    if (rc != CKR_OK)
    {
        PROCESS_ERROR("init_all", rc, on_error);
    }
    printf("init_all OK\n");

    for (i = 0; i < SlotCount; i++)
    {
        SlotId = SlotList[i];
        rc     = funcs->C_GetSlotInfo(SlotId, &sinfo);
        if (sinfo.flags & CKF_TOKEN_PRESENT)
        {
            flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
            rc    = funcs->C_OpenSession(SlotId, flags, NULL, NULL, &sess);
            if (rc != CKR_OK)
            {
				PROCESS_ERROR("C_OpenSession", rc, on_error);
            }
            printf("C_OpenSession OK\n");
			len = get_random_size(sess, 1, TEST_OBJ_SIZE);
			if (len == 0) {
				rc = CKR_FUNCTION_FAILED;
				PROCESS_ERROR("get_random_size", rc, on_error);
			}
			if (len > TEST_OBJ_SIZE) {
				rc = CKR_DATA_LEN_RANGE;
				PROCESS_ERROR("get_random_size", rc, on_error);
			}
            printf("get_random_size OK, len = %ld\n", len);
			t[2].ulValueLen = len;
			rc = funcs->C_GenerateRandom(sess, data_value, len);
			if (rc != CKR_OK)
			{
				PROCESS_ERROR("C_GenerateRandom", rc, on_error);
			}
            printf("C_GenerateRandom OK\n");
// На аппаратных токенах логин обязателен для изменений на токене
//			if (is_priv){
            rc = funcs->C_Login(sess,
                                CKU_USER, user_pin, (CK_ULONG)strlen((char *)user_pin));
            if (rc != CKR_OK && rc != CKR_USER_ALREADY_LOGGED_IN)
            {
				PROCESS_ERROR("C_Login", rc, on_error);
            }
            printf("C_Login (CKU_USER) OK\n");
//			}

            data_create[0].ulValueLen = len;
            rc                        = funcs->C_CreateObject(sess,
                                                              data_create,
                                                              sizeof(data_create) / sizeof(CK_ATTRIBUTE),
                                                              &hObj);
            if (rc != CKR_OK)
            {
				PROCESS_ERROR("C_CreateObject", rc, on_error);
            }
            printf("C_CreateObject OK\n");

            //проверяем CKA_VALUE сразу после создания
            rc = funcs->C_GetAttributeValue(sess, hObj, &t1, 1);
            if (rc != CKR_OK)
            {
				PROCESS_ERROR("C_GetAttributeValue", rc, on_error);
            }
            if (memcmp(data_value, data_tmp, len))
            {
				rc = CKR_DATA_INVALID;
				PROCESS_ERROR("memcmp(data_value, data_tmp, len)", rc, on_error);
            }
            printf("C_GetAttributeValue OK\n");

            //при поиске объектов проверится корректность CKA_VALUE
            rc = find_obj(sess, t, t_count, &hObj);
            if (rc != CKR_OK)
            {
				PROCESS_ERROR("find_obj", rc, on_error);
            }
            printf("find_obj OK\n");

            rc = funcs->C_CloseSession(sess);
            if (rc != CKR_OK)
            {
				PROCESS_ERROR("C_CloseSession", rc, on_error);
            }
            printf("C_CloseSession OK\n");
        }
    }

    //проверяем после перезапуска библиотеки
    rc = cleanup(funcs);
    if (rc != CKR_OK)
    {
		PROCESS_ERROR("cleanup", rc, on_error);
    }
    printf("cleanup success\n");

    rc = init_all();
    if (rc != CKR_OK)
    {
		PROCESS_ERROR("init_all", rc, on_error);
    }
    printf("init_all OK\n");

    for (i = 0; i < SlotCount; i++)
    {
        SlotId = SlotList[i];
        rc     = funcs->C_GetSlotInfo(SlotId, &sinfo);
        if (!(sinfo.flags & CKF_TOKEN_PRESENT))
        {
            continue;
        }
        flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
        rc    = funcs->C_OpenSession(SlotId, flags, NULL, NULL, &sess);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_OpenSession", rc, on_error);
        }
        printf("C_OpenSession OK\n");
		new_len = get_random_size(sess, 1, TEST_OBJ_MAX_NEW_SIZE);
		if (new_len == 0) {
			rc = CKR_FUNCTION_FAILED;
			PROCESS_ERROR("get_random_size", rc, on_error);
		}
		if (new_len > TEST_OBJ_MAX_NEW_SIZE) {
			rc = CKR_DATA_LEN_RANGE;
			PROCESS_ERROR("get_random_size", rc, on_error);
		}
        printf("get_random_size OK, new_len = %ld\n", new_len);
		attr_update[1].ulValueLen = new_len;
		rc = funcs->C_GenerateRandom(sess, data_new, new_len);
		if (rc != CKR_OK)
		{
			PROCESS_ERROR("C_GenerateRandom", rc, on_error);
		}
        printf("C_GenerateRandom OK\n");
// На аппаратных токенах логин обязателен для изменений на токене
//		if (is_priv){
        rc = funcs->C_Login(sess, CKU_USER, user_pin, (CK_ULONG)strlen((char *)user_pin));
        if (rc != CKR_OK && rc != CKR_USER_ALREADY_LOGGED_IN)
        {
			PROCESS_ERROR("C_Login", rc, on_error);
        }
        printf("C_Login (CKU_USER) OK\n");
//		}

        //при поиске объектов проверится корректность CKA_VALUE
        rc = find_obj(sess, t, t_count, &hObj);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("find_obj", rc, on_error);
        }
        printf("find_obj OK\n");

        ////проверяем CKA_VALUE для найденного объекта
        t1.ulValueLen = len;
        rc            = funcs->C_GetAttributeValue(sess, hObj, &t1, 1);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_GetAttributeValue", rc, on_error);
        }
        if (memcmp(data_value, data_tmp, len))
        {
			rc = CKR_DATA_INVALID;
			PROCESS_ERROR("memcmp(data_value, data_tmp, len)", rc, on_error);
        }
        printf("C_GetAttributeValue OK\n");

        //Изменяем атрибуты объекта (label и value), в т.ч. - длину
        rc = funcs->C_SetAttributeValue(sess, hObj, attr_update, 2);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_SetAttributeValue", rc, on_error);
        }
        printf("C_SetAttributeValue OK\n");
        //проверяем CKA_VALUE сразу после создания
        t1.ulValueLen = new_len;
        rc            = funcs->C_GetAttributeValue(sess, hObj, &t1, 1);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_GetAttributeValue", rc, on_error);
        }
        if (memcmp(data_new, data_tmp, new_len))
        {
			rc = CKR_DATA_INVALID;
			PROCESS_ERROR("memcmp(data_new, data_tmp, new_len)", rc, on_error);
        }
        printf("C_GetAttributeValue OK\n");

        rc = funcs->C_CloseSession(sess);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_CloseSession", rc, on_error);
        }
        printf("C_CloseSession OK\n");
    }

//перезапускаем библиотеку еще раз и проверяем новые значения лейбла и value
    rc = cleanup(funcs);
    if (rc != CKR_OK)
    {
		PROCESS_ERROR("cleanup", rc, on_error);
    }
    printf("cleanup OK\n");
    rc = init_all();
    if (rc != CKR_OK)
    {
		PROCESS_ERROR("init_all", rc, on_error);
    }
    printf("init_all OK\n");

    t[1].pValue     = label_new;
    t[1].ulValueLen = label_new_len;
    t[2].pValue     = data_new;
    t[2].ulValueLen = new_len;
    for (i = 0; i < SlotCount; i++)
    {
        SlotId = SlotList[i];
        rc     = funcs->C_GetSlotInfo(SlotId, &sinfo);
        if (!(sinfo.flags & CKF_TOKEN_PRESENT))
        {
            continue;
        }
        flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
        rc    = funcs->C_OpenSession(SlotId, flags, NULL, NULL, &sess);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_OpenSession", rc, on_error);
        }
        printf("C_OpenSession OK\n");

// На аппаратных токенах логин обязателен для изменений на токене
//		if (is_priv){
        rc = funcs->C_Login(sess, CKU_USER, user_pin, (CK_ULONG)strlen((char *)user_pin));
        if (rc != CKR_OK && rc != CKR_USER_ALREADY_LOGGED_IN)
        {
			PROCESS_ERROR("C_Login", rc, on_error);
        }
        printf("C_Login (CKU_USER) OK\n");
//		}

        //при поиске объектов проверится корректность CKA_VALUE
        rc = find_obj(sess, t, t_count, &hObj);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("find_obj", rc, on_error);
        }
        printf("find_obj OK\n");

        //проверяем CKA_VALUE для найденного объекта
        rc = funcs->C_GetAttributeValue(sess, hObj, &t1, 1);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_GetAttributeValue", rc, on_error);
        }
        if (memcmp(data_new, data_tmp, new_len))
        {
			rc = CKR_DATA_INVALID;
			PROCESS_ERROR("memcmp(data_new, data_tmp, new_len)", rc, on_error);
        }
        printf("C_GetAttributeValue OK\n");

        //Удаляем объект за ненадобностью
        rc = funcs->C_DestroyObject(sess, hObj);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_DestroyObject", rc, on_error);
        }
        printf("C_DestroyObject OK\n");

        rc = funcs->C_CloseSession(sess);
        if (rc != CKR_OK)
        {
			PROCESS_ERROR("C_CloseSession", rc, on_error);
        }
        printf("C_CloseSession OK\n");
    }
    rc = cleanup(funcs);
    if (rc != CKR_OK)
    {
		PROCESS_ERROR("cleanup", rc, on_error);
    }
    printf("cleanup OK\n");

    printf("do_obj_test SUCCESS\n");
    return CKR_OK;

 on_error:
    return rc;
}
