#include "test_common.h"

#define THREADCNT 3	// max 100
#define NUMSESS 3	// sessions number in single thread
#define NUMOBJ 3	// objects number in single session (max 20)

int  do_GetInfo(void);
extern CK_CHAR *api_path;
extern CK_CHAR *user_pin;
extern CK_CHAR *hw_user_pin;
CK_SLOT_ID_PTR SlotList = NULL;
CK_ULONG SlotCount = 0;
CK_SLOT_ID  SlotId;
CK_CHAR *sopin = NULL;
CK_CHAR *userpin = NULL;

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 do_create_token_objects( int id )
{
	CK_FLAGS          flags;
	CK_SESSION_HANDLE h_session;
	CK_RV             rc;
	CK_BYTE           ltrue  = TRUE;
	CK_BYTE           lfalse = FALSE;
	CK_OBJECT_HANDLE    h_cert1 = CK_INVALID_HANDLE;
	CK_OBJECT_CLASS     cert1_class         = CKO_CERTIFICATE;
	CK_CERTIFICATE_TYPE cert1_type          = CKC_X_509;
	CK_BYTE             cert1_subject[]     = "Certificate subject #1";
	CK_BYTE             cert1_id[]          = "Certificate ID #1";
	CK_BYTE             cert1_value[]       
		= "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
	CK_ATTRIBUTE        cert1_attribs[] =
	{
		{CKA_CLASS,            &cert1_class,   sizeof(cert1_class)   },
		{CKA_TOKEN,            &ltrue,         sizeof(ltrue)         },
		{CKA_CERTIFICATE_TYPE, &cert1_type,    sizeof(cert1_type)    },
		{CKA_SUBJECT,          &cert1_subject, sizeof(cert1_subject) },
		{CKA_VALUE,            &cert1_value,   sizeof(cert1_value)   },
		{CKA_PRIVATE,          &ltrue,         sizeof(lfalse)        }
	};
	CK_ATTRIBUTE  cert_id_attr[] =
	{
		{CKA_ID,               &cert1_id,      sizeof(cert1_id)      }
	};
	CK_OBJECT_HANDLE   obj_list[20];
	CK_ULONG           i = 0;

	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 #1", rc );
		goto done;
	}
	fprintf(stderr, "Session %d opened\n", id);

	for (i=0; i<NUMOBJ; i++) {
		rc = funcs->C_CreateObject( h_session, 
			cert1_attribs, 6, &obj_list[i] );
		if (rc != CKR_OK) {
			show_error("   C_CreateObject #1", rc );
			goto done;
		}
	}		
	fprintf(stderr, "Objects %d created\n", id);

	for (i=0; i<NUMOBJ; i++) {
		rc = funcs->C_DestroyObject( h_session, obj_list[i] );
		if (rc != CKR_OK) {
			show_error("   C_DestroyObject #1", rc );
			goto done;
		}
	}
	fprintf(stderr, "Objects %d destroyed\n", id);

done:

	funcs->C_CloseSession( h_session );

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

	return rc;
}

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

	id = *(int *)thid;
	fprintf(stderr, "thread_wait_func started\n");
  
	while (1) {
		fprintf(stderr, 
			"Call C_WaitForSlotEvent in thread_wait_func:\n"
			"waiting for slot events with blocking...\n");
		rv = funcs->C_WaitForSlotEvent(0, &SlotId, NULL);
		fprintf(stderr, "C_WaitForSlotEvent returned 0x%X\n", rv);
		switch (rv) {
		case CKR_CRYPTOKI_NOT_INITIALIZED:
			fprintf(stderr, 
				"C_Finalize already called "
				"or C_Initialize not called yet\n");
			fprintf(stderr, "thread_wait_func finished\n");
			return rv;
		case CKR_OK:
			fprintf(stderr, 
				"The event occured on slot %d. "
				"Token removed or inserted\n", 
				SlotId);
			continue;
		default:
			fprintf(stderr, "Ignore that error...\n");
			continue;
		}
	}
}

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

	id = *(int *)thid;
	fprintf(stderr, "do_create_token_objects %d started\n", id);
  
	do {
		rv = do_create_token_objects(id * 1000 + i + 1);
		if (rv != CKR_OK) {
			fprintf(stderr, 
				"do_create_token_objects %d failed\n", id );
			return rv;
		}
	} while (++i < NUMSESS );

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

int
main( int argc, char **argv )
{
	int i, rc;
#ifdef WIN32
	HANDLE			wid;
	HANDLE			id[THREADCNT];
	DWORD	rv;
#else   
	pthread_t		wid;
	pthread_t		id[THREADCNT];
#endif
	int wthid;
	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");

	fprintf(stderr, "==>> creating thread for C_WaitForSlotEvent\n");
#ifdef WIN32
	wid = CreateThread(NULL, 0, 
		(LPTHREAD_START_ROUTINE)thread_wait_func, 
		(void *)&wthid, 0, NULL);
#else	   
	pthread_create(&wid, NULL, 
		(void*(*)(void *))thread_wait_func, (void *)&wthid);
#endif	   

	userpin = hw_user_pin;

	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
	// Здесь проверяется принудительное завершение блокировки  
	// в функции C_WaitForSlotEvent при финализации библиотеки.
	// Функция C_Finalize приказывает C_WaitForSlotEvent закончить 
	// работу и сама не не завершится, пока это не произойдет.
	fprintf(stderr, 
		"Finalizing library and killing C_WaitForSlotEvent...\n");
	funcs->C_Finalize(NULL);
#ifdef WIN32
	fprintf(stderr, "Waiting for thread_wait_func thread\n");
	rv = WaitForSingleObject(wid, INFINITE); 
	if (rv == WAIT_TIMEOUT) {
		fprintf(stderr, "Stop waiting by timeout\n");
	} else {
		fprintf(stderr, "Waiting thread completed\n");
	}
#else
	fprintf(stderr, "Joining thread_wait_func thread\n");
	pthread_join(wid, NULL);
	wthid = 0;
#endif
	fprintf(stderr, "SUCCESS\n");
	return 0;
}

