/*************************************************************************
* Rutoken                                                                *
* Copyright (c) 2003-2017, CJSC Aktiv-Soft. All rights reserved.         *
* Подробная информация:  http://www.rutoken.ru                           *
*------------------------------------------------------------------------*
* Пример работы с Рутокен при помощи библиотеки PKCS#11 на языке C       *
*------------------------------------------------------------------------*
* Использование команд получения информации о событиях в слотах:         *
*  - инициализация библиотеки;                                           *
*  - проверка события в каком-либо слоте (без блокировки                 *
*    выполнения потока приложения);                                      *
*  - ожидание события потоком в каком-либо слоте (с блокировкой          *
*    выполнения потока приложения).                                      *
*------------------------------------------------------------------------*
* Данный пример является самодостаточным.                                *
*************************************************************************/

#include <Common.h>

/*************************************************************************
* Количество потоков, одновременно ожидающих события в каком-либо слоте  *
*************************************************************************/
#define MONITORING_THREADS_NUMBER    1

/*************************************************************************
* Структура данных, содержащая параметры работы                          *
* для функции ожидания событий в слотах                                  *
*************************************************************************/
typedef struct _MONITORING_THREADS_PARAMS {
	CK_FUNCTION_LIST_PTR m_functionList;
	CK_FLAGS m_flags;
	int m_threadNumber;
} MONITORING_THREADS_PARAMS, * PMONITORING_THREADS_PARAMS;

/*************************************************************************
* Запустить поток, ожидающий событие в слоте.                            *
* До наступления события выполнение потока заблокировано                 *
*************************************************************************/
void MonitoringSlots(void* param)      // Указатель на структуру данных типа MONITORING_THREADS_PARAMS с параметрами для запуска потоков
{
	CK_FUNCTION_LIST_PTR functionList; // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST

	CK_SLOT_ID slot;                   // Идентификатор слота, в котором произошло событие
	CK_SLOT_INFO slotInfo;             // Структура данных типа CK_SLOT_INFO с информацией о слоте

	CK_FLAGS flags;                    // Вспомогательная переменная для хранения флагов, передаваемых в функцию C_WaitForSlotEvent
	int threadNumber;                  // Вспомогательная переменная для хранения порядкового номера запущенного потока

	CK_RV rv = CKR_OK;                 // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11

	/*************************************************************************
	* Получить из структуры данных типа MONITORING_THREADS_PARAMS            *
	* параметры для дальнейшей работы                                        *
	*************************************************************************/
	PMONITORING_THREADS_PARAMS threadParam = (PMONITORING_THREADS_PARAMS)param;

	functionList = threadParam->m_functionList;
	flags = threadParam->m_flags;
	threadNumber = threadParam->m_threadNumber;

	for (;; ) {
		/*************************************************************************
		* Ожидать событие в некотором слоте, режим работы функции                *
		* C_WaitForSlotEvent зависит от значения флагов flags                    *
		*************************************************************************/
		slot = 0xFFFFFFFF;
		rv = functionList->C_WaitForSlotEvent(flags, &slot, NULL_PTR);
		CHECK_AND_LOG(" C_WaitForSlotEvent (blocking)", (rv == CKR_OK) || (rv == CKR_CRYPTOKI_NOT_INITIALIZED),
		              rvToStr(rv), thread_exit);

		if (rv == CKR_CRYPTOKI_NOT_INITIALIZED) {
			printf(" PKCS#11 finalized.\n");
			break;
		}

		/*************************************************************************
		* Получить информацию о слоте                                            *
		*************************************************************************/
		rv = functionList->C_GetSlotInfo(slot, &slotInfo);
		CHECK_AND_LOG(" C_GetSlotInfo", rv == CKR_OK, rvToStr(rv), thread_exit);

		/*************************************************************************
		* Распечатать информацию о номере потока и событии в слоте               *
		*************************************************************************/
		printf("\n Monitoring thread: %d\n", threadNumber);
		printf("  Slot ID:          0x%8.8x \n", (int)slot);
		if (slotInfo.flags & CKF_TOKEN_PRESENT) {
			printf("  Token has been attached!\n");
		} else {
			printf("  Token has been detached!\n");
		}
	}

thread_exit:
	printf("Exiting from thread: %d\n\n", threadNumber);
}

int main(void)
{
	HMODULE module;                           // Хэндл загруженной библиотеки PKCS#11

	CK_FUNCTION_LIST_PTR functionList;        // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
	CK_C_GetFunctionList getFunctionList;     // Указатель на функцию C_GetFunctionList

	CK_SLOT_ID slot = 0xFFFFFFFF;             // Идентификатор слота, в котором произошло событие
	CK_SLOT_INFO slotInfo;                    // Структура данных типа CK_SLOT_INFO с информацией о слоте

	CK_ULONG i;                               // Вспомогательная переменная. Счетчик цикла

	CK_RV rv;                                 // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11

	int errorCode = 1;                        // Флаг ошибки

	MONITORING_THREADS_PARAMS threadsWithBlocking[MONITORING_THREADS_NUMBER];
	uintptr_t threads[MONITORING_THREADS_NUMBER];

	/*************************************************************************
	* Выполнить действия для начала работы с библиотекой PKCS#11             *
	*************************************************************************/
	printf("Initialization...\n");

	/*************************************************************************
	* Загрузить библиотеку                                                   *
	*************************************************************************/
	module = LoadLibrary(PKCS11_LIBRARY_NAME); // или PKCS11ECP_LIBRARY_NAME
	CHECK(" LoadLibrary", module != NULL, exit);

	/*************************************************************************
	* Получить адрес функции запроса структуры с указателями на функции      *
	*************************************************************************/
	getFunctionList = (CK_C_GetFunctionList)GetProcAddress(module, "C_GetFunctionList");
	CHECK(" GetProcAddress", getFunctionList != NULL, unload_pkcs11);

	/*************************************************************************
	* Получить структуру с указателями на функции                            *
	*************************************************************************/
	rv = getFunctionList(&functionList);
	CHECK_AND_LOG(" Get function list", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

	/*************************************************************************
	* Инициализировать библиотеку                                            *
	*************************************************************************/
	rv = functionList->C_Initialize(NULL_PTR);
	CHECK_AND_LOG(" C_Initialize", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

	printf("\nPlease attach or detach Rutoken and press Enter...\n");
	getchar();

	for (i = 1;; ++i) {
		printf("Events counter: %d \n", (int)i);

		/*************************************************************************
		* Получить все события в слотах                                          *
		* (не блокируя поток, используем флаг CKF_DONT_BLOCK)                    *
		*************************************************************************/
		rv = functionList->C_WaitForSlotEvent(CKF_DONT_BLOCK, &slot, NULL_PTR);
		CHECK_AND_LOG(" C_WaitForSlotEvent", (rv == CKR_OK) || (rv == CKR_NO_EVENT), rvToStr(rv), finalize_pkcs11);
		if (rv == CKR_NO_EVENT) {
			break;
		}

		/*************************************************************************
		* Получить информацию о слоте                                            *
		*************************************************************************/
		rv = functionList->C_GetSlotInfo(slot, &slotInfo);
		CHECK_AND_LOG(" C_GetSlotInfo", rv == CKR_OK, rvToStr(rv), finalize_pkcs11);

		printf(" Slot ID:           0x%8.8X \n", (int)slot);
		printf(" Slot description:  %.*s \n", (int)sizeof(slotInfo.slotDescription), slotInfo.slotDescription);
		printf(" Manufacturer:      %.*s \n", (int)sizeof(slotInfo.manufacturerID), slotInfo.manufacturerID);
		printf(" Flags:             0x%8.8X \n", (unsigned)slotInfo.flags);
		printf(" Hardware version:  %d.%d \n", slotInfo.hardwareVersion.major, slotInfo.hardwareVersion.minor);
		printf(" Firmware version:  %d.%d \n\n", slotInfo.firmwareVersion.major, slotInfo.firmwareVersion.minor);
	}

	/*************************************************************************
	* Запустить поток, ожидающий событие в каком-либо слоте.                 *
	* До наступления события выполнение запущенного потока заблокировано.    *
	* Первое же событие разблокирует выполнение ожидающего потока            *
	*************************************************************************/
	for (;; ) {
		for (i = 0; i < MONITORING_THREADS_NUMBER; ++i) {
			printf("Starting monitoring thread number %d \n", (int)i);

			threadsWithBlocking[i].m_functionList = functionList;
			threadsWithBlocking[i].m_flags = 0;
			threadsWithBlocking[i].m_threadNumber = i;

			createThread(&threads[i], NULL_PTR, &MonitoringSlots, &threadsWithBlocking[i]);
		}

		printf("\n\nPlease attach or detach Rutoken or press Enter to exit.\n");

		getchar();
		break;
	}

	errorCode = 0;

	/*************************************************************************
	* Деинициализировать библиотеку                                          *
	*************************************************************************/
finalize_pkcs11:
	rv = functionList->C_Finalize(NULL_PTR);
	CHECK_RELEASE_AND_LOG(" C_Finalize", rv == CKR_OK, rvToStr(rv), errorCode);

	/*************************************************************************
	* Выгрузить библиотеку из памяти                                         *
	*************************************************************************/
unload_pkcs11:
	CHECK_RELEASE(" FreeLibrary", FreeLibrary(module), errorCode);

exit:
	if (errorCode) {
		printf("\n\nSome error occurred. Sample failed.\n");
	} else {
		printf("\n\nSample has been completed successfully.\n");
	}

	return errorCode;
}

