#include "test_common.h"

CK_SLOT_ID SlotId;

CK_SLOT_ID_PTR SlotList = NULL;
CK_ULONG SlotCount = 0;

int
main(int argc, char *argv[]){
	CK_RV rc; // Return Code
	CK_FLAGS flags = 0; // Bit mask for what options were passed in
	CK_SESSION_HANDLE sess;
	CK_ULONG i;
	static CK_OBJECT_CLASS oclass_data = CKO_DATA;
	static CK_BBOOL ltrue = CK_TRUE;
	static CK_BBOOL lfalse = CK_FALSE;
	CK_ATTRIBUTE data_find[] = {
		{CKA_CLASS, &oclass_data, sizeof(oclass_data)},
	};
	CK_UTF8CHAR label[] = "data";
	CK_CHAR app[] = "lsms11";
	CK_BYTE data_value[] = {
		0x66, 0x6F, 0x40, 0x63, 0x72, 0x79, 0x70, 0x74, 
		0x6F, 0x70, 0x72, 0x6F, 0x2E, 0x72, 0x75, 0x31, 
		0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 
		0x13, 0x02, 0x52, 0x55, 0x31, 0x13, 0x30, 0x11, 
	};
	CK_ATTRIBUTE data_create[] = {
		{CKA_CLASS, &oclass_data, sizeof(oclass_data)},
		{CKA_APPLICATION, app, strlen(app)+1},
		{CKA_TOKEN, &ltrue, sizeof(ltrue)},
		{CKA_LABEL, label, strlen(label)+1},
		{CKA_VALUE, data_value, sizeof(data_value)},
	};
	CK_BYTE get_value[1024];
	CK_ATTRIBUTE tmpl_value[] = {
		{CKA_VALUE, get_value, sizeof(get_value)},
	};
	CK_ATTRIBUTE data_value_find[] = {
		{CKA_CLASS, &oclass_data, sizeof(oclass_data)},
		{CKA_VALUE, data_value, sizeof(data_value)},
	};
	CK_ULONG count, number;
	CK_ULONG max_number = 20;
	CK_OBJECT_HANDLE hObj = CK_INVALID_HANDLE;
//	CK_OBJECT_HANDLE hNewObj = CK_INVALID_HANDLE;

	printf("CKO_DATA test\n");
	for (i=1; i<(CK_ULONG)argc; i++) {
		if (strcmp("-api", argv[i]) == 0) {
			++i;
			api_path = argv[i];
		} else if (strcmp("-user_pin", argv[i]) == 0) {
			++i;
			user_pin = argv[i];
		}
	}
	/* Load the PKCS11 library */
	rc = init(api_path, &funcs, NULL);
	if (rc != CKR_OK) {
		printf("init failed\n");
		return rc;
	}

	rc = get_slot_list(funcs, 
                    &SlotList, 
                    &SlotCount);
	if (rc != CKR_OK) {
		printf("get_slot_list failed, rc = 0x%x\n", rc );
		return rc;
	}
	rc = find_slot_with_token(funcs, 
                           SlotList, 
                           SlotCount, 
                           &SlotId);
	if (rc != CKR_OK) {
		printf("find_slot_with_token failed, rc = 0x%x\n", rc );
		return rc;
	}

	flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
	rc = funcs->C_OpenSession( SlotId, flags, NULL, NULL, &sess );
	if (rc != CKR_OK) {
		printf("C_OpenSession failed, rc = 0x%x\n", rc );
		return rc;
	}

	rc = funcs->C_Login(sess, CKU_USER, user_pin, strlen(user_pin));
	if (rc != CKR_OK) {
		printf("C_Login failed, rc = 0x%x\n", rc );
		return rc;
	}
	printf("C_Login (CKU_USER) success\n");

	// Ищем все доступные объекты CKO_DATA
	rc = funcs->C_FindObjectsInit(sess, 
		data_find, sizeof(data_find)/sizeof(CK_ATTRIBUTE)); 
	if (rc != CKR_OK) {
		printf("C_FindObjectsFinal failed, rc = 0x%x\n", rc );
		return rc;
	}
	count = 0;
	do {
		number = 0;
		hObj = CK_INVALID_HANDLE;
		rc = funcs->C_FindObjects(sess, &hObj, 1, &number);
		if (rc != CKR_OK) {
			printf("C_FindObjects failed, rc = 0x%x\n", rc );
			return rc;
		}
		if (number > 0 && hObj != CK_INVALID_HANDLE) {
			count ++;
			printf("C_FindObjects handle 0x%x found\n", hObj );
		}
	} while (number > 0 && hObj != CK_INVALID_HANDLE);
	rc = funcs->C_FindObjectsFinal(sess);
	if (rc != CKR_OK) {
		printf("C_FindObjectsFinal failed, rc = 0x%x\n", rc );
		return rc;
	}
	printf("%ld data objects found for session\n", count);

	for (i=0; i<max_number; i++) {

		rc = funcs->C_CreateObject(sess, 
			data_create, sizeof(data_create)/sizeof(CK_ATTRIBUTE), &hObj); 
		if (rc != CKR_OK) {
			printf("C_CreateObject failed, rc = 0x%x\n", rc );
			return rc;
		}
		printf("C_CreateObject success, handle: 0x%x\n", hObj);
		// Другой параллельный процесс мог удалить этот объект,
		// поэтому нужно обрабатывать такие ситуации.
		rc = funcs->C_GetAttributeValue(sess, hObj,
			tmpl_value, sizeof(tmpl_value)/sizeof(CK_ATTRIBUTE));
		if (rc != CKR_OK) {
			if (rc == CKR_OBJECT_HANDLE_INVALID) {
				printf("Object destroyed by other process\n");
			} else {
				printf("C_GetAttributeValue failed, rc = 0x%x\n", rc );
				return rc;
			}
		} else {
			printf("C_GetAttributeValue success\n");
			if (tmpl_value[0].ulValueLen != sizeof(data_value)) {
				printf("Invalid CKA_VALUE length\n");
				return -1;
			}
			if (memcmp(get_value, data_value, sizeof(data_value)) != 0 ) {
				printf("Invalid CKA_VALUE\n");
				return -1;
			}
		}
		// Ищем все доступные объекты CKO_DATA по значению CKA_VALUE
		// и удаляем их.
		rc = funcs->C_FindObjectsInit(sess, 
			data_value_find, sizeof(data_value_find)/sizeof(CK_ATTRIBUTE)); 
		if (rc != CKR_OK) {
			printf("C_FindObjectsFinal failed, rc = 0x%x\n", rc );
			return rc;
		}
		count = 0;
		do {
			number = 0;
			hObj = CK_INVALID_HANDLE;
			rc = funcs->C_FindObjects(sess, &hObj, 1, &number);
			if (rc != CKR_OK) {
				printf("C_FindObjects failed, rc = 0x%x\n", rc );
				return rc;
			}
			if (number > 0 && hObj != CK_INVALID_HANDLE) {
				count ++;
				printf("C_FindObjects handle 0x%x found\n", hObj );
				printf("C_DestroyObject for 0x%x handle\n", hObj);
				rc = funcs->C_DestroyObject(sess, hObj);
				if (rc != CKR_OK) {
					if (rc != CKR_OBJECT_HANDLE_INVALID) {
						printf("C_DestroyObject failed, rc = 0x%x\n", rc );
						return rc;
					} else {
						printf("Object 0x%x destroyed by other process\n", hObj);
					}
				} else {					
					printf("C_DestroyObject success\n");
				}
			}
		} while (number > 0 && hObj != CK_INVALID_HANDLE);
		rc = funcs->C_FindObjectsFinal(sess);
		if (rc != CKR_OK) {
			printf("C_FindObjectsFinal failed, rc = 0x%x\n", rc );
			return rc;
		}
		printf("%ld data objects found for given CKA_VALUE\n", count);

	}

	rc = funcs->C_CloseSession(sess);
	if (rc != CKR_OK) {
		printf("C_CloseSession failed, rc = 0x%x\n", rc );
		return rc;
	}
	printf("CKO_DATA test SUCCESS\n");
	return CKR_OK;
}
