#include "test_common.h"

// Объем памяти на токене ограничен, поэтому задавать 
// здесь большие значения не рекомендуется.
#define THREADCNT 3	// количество потоков (максимум зависит от системы)
#define NUMSESS 2	// количество сессий в потоке
#define NUMOBJ 5	// максимальное количество объектов в сессии

CK_RV
open_session_and_login( CK_SLOT_ID SlotID )
{
	CK_FLAGS          flags;
	CK_SESSION_HANDLE h_session;
	CK_RV             rc;
	CK_BYTE           ltrue  = TRUE;

   flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
   rc = funcs->C_OpenSession( 
	   SlotID, flags, NULL, NULL, &h_session );

   rc = funcs->C_Login( h_session, CKU_USER, 
	   user_pin, strlen(user_pin) );

   return rc;

}

int create_token_objects( int id, int is )
{
	CK_FLAGS          flags;
	CK_SESSION_HANDLE h_session;
	CK_RV             rc;
	CK_BYTE           ltrue  = TRUE;
	CK_BYTE           lfalse = FALSE;
	CK_OBJECT_CLASS     data_class         = CKO_DATA;
	CK_UTF8CHAR label[32];
	CK_CHAR app[] = "ls_hw11_library";
	CK_BYTE             data_value[]       
		= "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
	CK_ATTRIBUTE        data_attribs[] =
	{
		{CKA_CLASS,			&data_class,	sizeof(data_class)},
		{CKA_APPLICATION,	app,			strlen(app)+1},
		{CKA_TOKEN,			&ltrue,         sizeof(ltrue)},
		{CKA_VALUE,			&data_value,	sizeof(data_value)},
		{CKA_PRIVATE,		&lfalse,        sizeof(lfalse)}
	};
	CK_ATTRIBUTE label_attribs[] =
	{
		{CKA_LABEL,	label, 0},
	};
	CK_OBJECT_HANDLE   obj_list[NUMOBJ];
	CK_ULONG           i = 0;
	CK_ULONG count = NUMOBJ;

	flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;
	rc = funcs->C_OpenSession( 
		SlotId, flags, NULL, NULL, &h_session );
	if (rc != CKR_OK) {
		show_error("   C_OpenSession", rc );
		goto done;
	}
	fprintf(stderr, "Thread %d: Session %d opened\n", id, is);

	// В каждой сессии мы выполняем операции создания, 
	// модификации и уничтожения случайного количества
	// объектов токена, чтобы создать конкурентную обстановку
	// между потоками.

	rc = funcs->C_GenerateRandom(h_session, 
		(CK_BYTE *)&count, sizeof(count));
	if (rc != CKR_OK) {
		show_error("   C_GenerateRandom", rc );
		goto done;
	}
	count = count % NUMOBJ + 1;

	for (i=0; i<count; i++) {
		rc = funcs->C_CreateObject( h_session, 
			data_attribs, sizeof(data_attribs)/sizeof(CK_ATTRIBUTE), 
			&obj_list[i] );
		if (rc != CKR_OK) {
			show_error("   C_CreateObject", rc );
			goto done;
		}
		sprintf(label, "data_%d_%d_%d", id, is, i+1);
		label_attribs[0].ulValueLen = strlen(label)+1;

		rc = funcs->C_SetAttributeValue( h_session,	obj_list[i], 			
			label_attribs, sizeof(label_attribs)/sizeof(CK_ATTRIBUTE));
		if (rc != CKR_OK) {
			show_error("   C_SetAttributeValue", rc );
			goto done;
		}
		fprintf(stderr, "Object %s created\n", label);
	}		
	fprintf(stderr, 
		"Thread %d: %d objects of session %d created\n", 
		id, count, is);

	for (i=0; i<count; i++) {
		rc = funcs->C_DestroyObject( h_session, obj_list[i] );
		if (rc != CKR_OK) {
			show_error("   C_DestroyObject", rc );
			goto done;
		}
		sprintf(label, "data_%d_%d_%d", id, is, i+1);
		fprintf(stderr, "Object %s destroyed\n", label);
	}
	fprintf(stderr, 
		"Thread %d: %d objects of %d destroyed\n", 
		id, count, is);

done:

	funcs->C_CloseSession( h_session );

	fprintf(stderr, "Session %d closed\n", id);

	return rc;
}

#ifdef WIN32
DWORD WINAPI
#else
int
#endif
thread_func(void *thid)
{
	int   i=0;
	CK_RV  rv = CKR_OK;
	int   id = *(int *)thid;

	fprintf(stderr, "thread_func %d started\n", id);
	while(i++ < NUMSESS) {
		fprintf(stderr, 
			"create_token_objects %d, session %d\n", id, i);
		rv = create_token_objects(id, i);
		if (rv != CKR_OK) {
			fprintf(stderr, 
				"create_token_objects %d, session %d failed\n", 
				id, i);
			break;
		}
		fprintf(stderr, 
			"create_token_objects %d, session %d finished\n", 
			id, i);
	}

	fprintf(stderr, "thread_func %d finished\n", id);
	return rv;
}

int
main( int argc, char **argv )
{
	int i, rc;
#ifdef WIN32
	HANDLE			id[THREADCNT];
	DWORD	rv;
#else   
	pthread_t		id[THREADCNT];
#endif
	int thid[THREADCNT];

	for (i=1; i < argc; i++) {
		if (strcmp(argv[i], "-api") == 0) {
			i++;
			api_path = argv[i];
		} else if (strcmp(argv[i], "-user_pin") == 0) {
			i++;
			user_pin = argv[i];
		} else if (strcmp(argv[i], "-h") == 0) {
			printf(
				"usage:  %s [-api <path>][-user_pin <PIN>] [-h]\n\n", 
				argv[0] );
			return 0;
		}
	}

	fprintf(stderr, "==>> init...\n");
	// Use PKCS#11 library native multithreading locks.
	//rc = init(api_path, &funcs, pinit_args_os_locking);
	// Use application-supplied multithreading callbacks for locking.
	rc = init(api_path, &funcs, pinit_args_app_locking);
	if (rc != CKR_OK) {
		fprintf(stderr, "==>> init failed\n\n");
		return rc;
	}
	fprintf(stderr, "==>> init Ok\n\n");

	fprintf(stderr, "==>> get_slot_list...\n");
	rc = get_slot_list(funcs,
                    &SlotList,
                    &SlotCount);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"==>> get_slot_list failed, rc = 0x%x\n\n", rc );
		return rc;
	}
	fprintf(stderr, "==>> get_slot_list Ok\n\n");

	fprintf(stderr, "==>> find_slot_with_token...\n");
	rc = find_slot_with_token(funcs,
                           SlotList,
                           SlotCount,
                           &SlotId);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"==>> find_slot_with_token failed, rc = 0x%x\n\n", 
			rc );
		return rc;
	}
	fprintf(stderr, "==>> find_slot_with_token Ok\n\n");
	
	rc = open_session_and_login(SlotId);
	if (rc != CKR_OK) {
		fprintf(stderr, 
			"==>> open_session_and_login failed, rc = 0x%x\n\n", 
			rc );
		return rc;
	}
	fprintf(stderr, "==>> open_session_and_login Ok\n\n");

	for (i=0;i<THREADCNT;i++){
	   thid[i] = i+1;
	   fprintf(stderr, "Creating thread %d \n", thid[i]);
#ifdef WIN32
		id[i] = CreateThread(NULL, 0, 
			(LPTHREAD_START_ROUTINE)thread_func, 
			(void *)&(thid[i]), 0, NULL);
#else	   
	   pthread_create(&id[i],NULL,
		   (void*(*)(void *))thread_func,(void *)&(thid[i]));
#endif	   
	}

	fprintf(stderr, "Waiting for all application threads\n");
#ifdef WIN32
	rv = WaitForMultipleObjects(THREADCNT, id, TRUE, INFINITE); 
	if (rv == WAIT_TIMEOUT) {
		fprintf(stderr, "Stop waiting by timeout\n");
	} else {
		fprintf(stderr, "All threads completed\n");
	}
#else
	for (i=0; i<THREADCNT; i++){
		if (thid[i]) {
			printf("Joining thread %ld\n", thid[i]);
			pthread_join(id[i],NULL);
			thid[i] = 0;
		}
	}	 
#endif

	fprintf(stderr, 
		"Finalizing library...\n");
	funcs->C_Finalize(NULL);
	fprintf(stderr, "SUCCESS\n");
	return 0;
}

