diff --git a/include/pico/fsm.h b/include/pico/fsm.h new file mode 100644 index 0000000..be84ca1 --- /dev/null +++ b/include/pico/fsm.h @@ -0,0 +1,51 @@ +/** \ingroup Protocol + * @file + * @author Claudio Dettoni + * @version $(VERSION) + * + * @section LICENSE + * + * (C) Copyright Cambridge Authentication Ltd, 2017 + * + * This file is part of libpico. + * + * Libpico is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Libpico is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with libpico. If not, see + * . + * + * + * @section DESCRIPTION + * + * This file contains definitions that are common to fsmpico.h and fsmservice.h + */ + +/** \addtogroup Protocol + * @{ + */ + +#ifndef __FSM_H +#define __FSM_H (1) + +typedef void (*FsmWrite)(char const * data, size_t length, void * user_data); +typedef void (*FsmSetTimeout)(int timeout, void * user_data); +typedef void (*FsmError)(void * user_data); +typedef void (*FsmReconnect)(void * user_data); +typedef void (*FsmListen)(void * user_data); +typedef void (*FsmDisconnect)(void * user_data); +typedef void (*FsmAuthenticated)(int status, void * user_data); +typedef void (*FsmSessionEnded)(void * user_data); +typedef void (*FsmStatusUpdate)(int state, void * user_data); + +#endif + +/** @} addtogroup Protocol */ diff --git a/include/pico/fsmpico.h b/include/pico/fsmpico.h index 1de5226..96d7f0e 100644 --- a/include/pico/fsmpico.h +++ b/include/pico/fsmpico.h @@ -57,6 +57,8 @@ #ifndef __FSMPICO_H #define __FSMPICO_H (1) +#include "pico/fsm.h" + // Defines // Structure definitions @@ -94,15 +96,6 @@ typedef enum _FSMPICOSTATE { */ typedef struct _FsmPico FsmPico; -typedef void (*FsmWrite)(char const * data, size_t length, void * user_data); -typedef void (*FsmSetTimeout)(int timeout, void * user_data); -typedef void (*FsmError)(void * user_data); -typedef void (*FsmReconnect)(void * user_data); -typedef void (*FsmDisconnect)(void * user_data); -typedef void (*FsmAuthenticated)(int status, void * user_data); -typedef void (*FsmSessionEnded)(void * user_data); -typedef void (*FsmStatusUpdate)(FSMPICOSTATE state, void * user_data); - // Function prototypes // Set things up using these functions diff --git a/include/pico/fsmservice.h b/include/pico/fsmservice.h index f9c828c..7b3e96f 100644 --- a/include/pico/fsmservice.h +++ b/include/pico/fsmservice.h @@ -62,6 +62,7 @@ #include "pico/shared.h" #include "pico/users.h" #include "pico/dllpublic.h" +#include "pico/fsm.h" // Defines @@ -102,15 +103,6 @@ typedef enum _FSMSERVICESTATE { */ typedef struct _FsmService FsmService; -typedef void (*FsmWrite)(char const * data, size_t length, void * user_data); -typedef void (*FsmSetTimeout)(int timeout, void * user_data); -typedef void (*FsmError)(void * user_data); -typedef void (*FsmDisconnect)(void * user_data); -typedef void (*FsmListen)(void * user_data); -typedef void (*FsmAuthenticated)(int status, void * user_data); -typedef void (*FsmSessionEnded)(void * user_data); -typedef void (*FsmStatusUpdate)(int state, void * user_data); - // Function prototypes // Set things up using these functions diff --git a/tests/Makefile.am b/tests/Makefile.am index 7834276..a50c036 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,6 @@ AUTOMAKE_OPTIONS = subdir-objects -TESTS = test_base64 test_buffer test_cryptosupport test_sigmaverifier test_channel test_json test_users test_auth test_displayqr test_continuous test_beacons +TESTS = test_base64 test_buffer test_cryptosupport test_sigmaverifier test_channel test_json test_users test_auth test_displayqr test_continuous test_beacons test_fsm noinst_LTLIBRARIES = lib_mockbt.la lib_mockbt_la_SOURCES = \ diff --git a/tests/Makefile.in b/tests/Makefile.in index 87805cd..be791c9 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -92,7 +92,8 @@ TESTS = test_base64$(EXEEXT) test_buffer$(EXEEXT) \ test_cryptosupport$(EXEEXT) test_sigmaverifier$(EXEEXT) \ test_channel$(EXEEXT) test_json$(EXEEXT) test_users$(EXEEXT) \ test_auth$(EXEEXT) test_displayqr$(EXEEXT) \ - test_continuous$(EXEEXT) test_beacons$(EXEEXT) + test_continuous$(EXEEXT) test_beacons$(EXEEXT) \ + test_fsm$(EXEEXT) check_PROGRAMS = $(am__EXEEXT_1) subdir = tests ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -123,7 +124,8 @@ am__EXEEXT_1 = test_base64$(EXEEXT) test_buffer$(EXEEXT) \ test_cryptosupport$(EXEEXT) test_sigmaverifier$(EXEEXT) \ test_channel$(EXEEXT) test_json$(EXEEXT) test_users$(EXEEXT) \ test_auth$(EXEEXT) test_displayqr$(EXEEXT) \ - test_continuous$(EXEEXT) test_beacons$(EXEEXT) + test_continuous$(EXEEXT) test_beacons$(EXEEXT) \ + test_fsm$(EXEEXT) test_auth_SOURCES = test_auth.c test_auth_OBJECTS = test_auth.$(OBJEXT) test_auth_LDADD = $(LDADD) @@ -156,6 +158,10 @@ test_displayqr_SOURCES = test_displayqr.c test_displayqr_OBJECTS = test_displayqr.$(OBJEXT) test_displayqr_LDADD = $(LDADD) test_displayqr_DEPENDENCIES = ../libpico.la .libs/lib_mockbt.la +test_fsm_SOURCES = test_fsm.c +test_fsm_OBJECTS = test_fsm.$(OBJEXT) +test_fsm_LDADD = $(LDADD) +test_fsm_DEPENDENCIES = ../libpico.la .libs/lib_mockbt.la test_json_SOURCES = test_json.c test_json_OBJECTS = test_json.$(OBJEXT) test_json_LDADD = $(LDADD) @@ -204,11 +210,11 @@ am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(lib_mockbt_la_SOURCES) test_auth.c test_base64.c \ test_beacons.c test_buffer.c test_channel.c test_continuous.c \ - test_cryptosupport.c test_displayqr.c test_json.c \ + test_cryptosupport.c test_displayqr.c test_fsm.c test_json.c \ test_sigmaverifier.c test_users.c DIST_SOURCES = $(lib_mockbt_la_SOURCES) test_auth.c test_base64.c \ test_beacons.c test_buffer.c test_channel.c test_continuous.c \ - test_cryptosupport.c test_displayqr.c test_json.c \ + test_cryptosupport.c test_displayqr.c test_fsm.c test_json.c \ test_sigmaverifier.c test_users.c am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ @@ -574,7 +580,7 @@ lib_mockbt_la_SOURCES = \ lib_mockbt_la_CFLAGS = $(AM_CFLAGS) @PICOBT_CFLAGS@ AM_CFLAGS = -Wall -Werror -I"../include" -pthread @CHECK_CFLAGS@ -LDADD = ../libpico.la .libs/lib_mockbt.la @CHECK_LIBS@ @PICOLIB_LIBS@ +LDADD = ../libpico.la .libs/lib_mockbt.la @CHECK_LIBS@ @PICOLIB_LIBS@ all: all-am .SUFFIXES: @@ -672,6 +678,10 @@ test_displayqr$(EXEEXT): $(test_displayqr_OBJECTS) $(test_displayqr_DEPENDENCIES @rm -f test_displayqr$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_displayqr_OBJECTS) $(test_displayqr_LDADD) $(LIBS) +test_fsm$(EXEEXT): $(test_fsm_OBJECTS) $(test_fsm_DEPENDENCIES) $(EXTRA_test_fsm_DEPENDENCIES) + @rm -f test_fsm$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_fsm_OBJECTS) $(test_fsm_LDADD) $(LIBS) + test_json$(EXEEXT): $(test_json_OBJECTS) $(test_json_DEPENDENCIES) $(EXTRA_test_json_DEPENDENCIES) @rm -f test_json$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_json_OBJECTS) $(test_json_LDADD) $(LIBS) @@ -700,6 +710,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_continuous.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_cryptosupport.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_displayqr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_fsm.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_json.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_sigmaverifier.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_users.Po@am__quote@ @@ -1013,6 +1024,13 @@ test_beacons.log: test_beacons$(EXEEXT) --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +test_fsm.log: test_fsm$(EXEEXT) + @p='test_fsm$(EXEEXT)'; \ + b='test_fsm'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) .test.log: @p='$<'; \ $(am__set_b); \ diff --git a/tests/test_fsm.c b/tests/test_fsm.c new file mode 100644 index 0000000..e45ac2e --- /dev/null +++ b/tests/test_fsm.c @@ -0,0 +1,749 @@ +/** + * @file + * @author cd611@cam.ac.uk + * @version $(VERSION) + * + * @section LICENSE + * + * (C) Copyright Cambridge Authentication Ltd, 2018 + * + * This file is part of libpico. + * + * Libpico is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Libpico is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with libpico. If not, see + * . + * + * + * @brief Unit tests for the state machines + * @section DESCRIPTION + * + * Performs unit tests to the state machines FsmPico and FsmService. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Defines +typedef enum { + READ, + CONNECTED, + DISCONNECTED, + TIMEOUT, + STOP, + + // Speciall type of event stop event_loop_thread + STOP_LOOP +} EVENT_TYPE; + +// Structure definitions +typedef struct { + EVENT_TYPE type; + FsmService* service; + FsmPico* pico; + int time; + + char data[1024]; + int data_len; +} Event; + +typedef struct node_st { + struct node_st* next; + Event event; +} Node; + +typedef struct queue_st { + Node* head; +} Queue; + +// Function prototypes + +// Global variables +static int currentTime = 0; +pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; + + +// Function definitions +void push_event(Queue* queue, Event event) { + bool isService = event.service != NULL; + bool isPico = event.pico != NULL; + + ck_assert(isService != isPico); + + pthread_mutex_lock(&queue_mutex); + Node* queue_head = queue->head; + if (queue_head == NULL) { + Node* newNode = malloc(sizeof(Node)); + newNode->next = NULL; + newNode->event = event; + queue->head = newNode; + } else { + if (event.time < queue_head->event.time) { + Node* newNode = malloc(sizeof(Node)); + newNode->next = queue_head; + newNode->event = event; + queue->head = newNode; + } else { + Node* previousNode = queue_head; + Node* currentNode = queue_head->next; + while (currentNode != NULL && event.time >= currentNode->event.time) { + previousNode = currentNode; + currentNode = currentNode->next; + } + Node* newNode = malloc(sizeof(Node)); + newNode->next = currentNode; + newNode->event = event; + previousNode->next = newNode; + queue->head = queue_head; + } + } + + pthread_mutex_unlock(&queue_mutex); +} + +void push_read(Queue* queue, FsmPico* pico, FsmService* service, int currentTime, const char* data, int length) { + Event e; + e.type = READ; + e.service = service; + e.pico = pico; + e.time = currentTime; + memcpy(e.data, data, length); + e.data_len = length; + push_event(queue, e); +} + +void push_connected(Queue* queue, FsmPico* pico, FsmService* service, int currentTime) { + Event e; + e.type = CONNECTED; + e.service = service; + e.pico = pico; + e.time = currentTime; + push_event(queue, e); +} + +void push_disconnected(Queue* queue, FsmPico* pico, FsmService* service, int currentTime){ + Event e; + e.type = DISCONNECTED; + e.service = service; + e.pico = pico; + e.time = currentTime; + push_event(queue, e); +} + +void push_timeout(Queue* queue, FsmPico* pico, FsmService* service, int currentTime, int timeout) { + // Look for some previous timeout comming from the same service/pico + pthread_mutex_lock(&queue_mutex); + Node* ptr = queue->head; + Node* prev = NULL; + while (ptr != NULL && (ptr->event.type != TIMEOUT || ptr->event.service != service || ptr->event.pico != pico)) { + prev = ptr; + ptr = ptr->next; + } + if (ptr != NULL) { + // Found some timeout. So remove it + if (prev == NULL) { + ck_assert(ptr == queue->head); + queue->head = ptr->next; + } else { + prev->next = ptr->next; + } + free(ptr); + } + pthread_mutex_unlock(&queue_mutex); + + // Add the new timeout + Event e; + e.type = TIMEOUT; + e.service = service; + e.pico = pico; + e.time = currentTime + timeout; + push_event(queue, e); +} + +void push_stop(Queue* queue, FsmPico* pico, FsmService* service, int currentTime){ + Event e; + e.type = STOP; + e.service = service; + e.pico = pico; + e.time = currentTime; + return push_event(queue, e); +} + +void process_event(Event event) { + bool isService = event.service != NULL; + bool isPico = event.pico != NULL; + + ck_assert(isService != isPico); + + switch (event.type) { + case READ: + if (isService) { + fsmservice_read(event.service, event.data, event.data_len); + } else { + fsmpico_read(event.pico, event.data, event.data_len); + } + break; + + case CONNECTED: + if (isService) { + fsmservice_connected(event.service); + } else { + fsmpico_connected(event.pico); + } + break; + + case DISCONNECTED: + if (isService) { + fsmservice_disconnected(event.service); + } else { + fsmpico_disconnected(event.pico); + } + break; + + case TIMEOUT: + if (isService) { + fsmservice_timeout(event.service); + } else { + fsmpico_timeout(event.pico); + } + break; + case STOP: + if (isService) { + fsmservice_stop(event.service); + } else { + fsmpico_stop(event.pico); + } + break; + case STOP_LOOP: + ck_assert(false); + break; + } +} + +START_TEST (fsm_fsm_test) { + FsmService* serv = fsmservice_new(); + FsmPico* pico = fsmpico_new(); + Queue queue = {NULL}; + currentTime = 0; + int cycles = 0; + + Shared * picoShared = shared_new(); + shared_load_or_generate_pico_keys(picoShared, "testpicokey.pub", "testpicokey.priv"); + Buffer * picoExtraData = buffer_new(0); + buffer_append_string(picoExtraData, "p@ssword"); + + Shared * servShared = shared_new(); + shared_load_or_generate_keys(servShared, "testkey.pub", "testkey.priv"); + Buffer * servExtraData = buffer_new(0); + + EC_KEY* servIdPKey = shared_get_service_identity_public_key(servShared); + EC_KEY* picoIdPKey = shared_get_pico_identity_public_key(picoShared); + EVP_PKEY* picoIdSKey = keypair_getprivatekey(shared_get_pico_identity_key(picoShared)); + Buffer * picoIdDer = buffer_new(0); + + Buffer * symmetricKey = buffer_new(0); + cryptosupport_generate_symmetric_key(symmetricKey, CRYPTOSUPPORT_AESKEY_SIZE); + Users * users = users_new(); + users_add_user(users, "Donald", picoIdPKey, symmetricKey); + + bool calledAuthenticated = false; + + void serviceWrite(char const * data, size_t length, void * user_data){ + push_read(&queue, pico, NULL, currentTime, data, length); + } + + void serviceSetTimeout(int timeout, void * user_data) { + push_timeout(&queue, NULL, serv, currentTime, timeout); + } + + void serviceDisconnect(void * user_data){ + push_disconnected(&queue, pico, NULL, currentTime); + } + + void picoWrite(char const * data, size_t length, void * user_data) { + push_read(&queue, NULL, serv, currentTime, data, length); + } + + void picoSetTimeout(int timeout, void * user_data) { + push_timeout(&queue, pico, NULL, currentTime, timeout); + } + + void picoReconnect(void * user_data) { + push_connected(&queue, pico, NULL, currentTime); + push_connected(&queue, NULL, serv, currentTime); + } + + void picoDisconnect(void * user_data) { + push_disconnected(&queue, NULL, serv, currentTime); + } + + void picoStatusUpdate(FSMPICOSTATE state, void * user_data) { + if (state == FSMPICOSTATE_PICOREAUTH) { + cycles++; + if (cycles > 3) { + push_stop(&queue, pico, NULL, currentTime); + } + } + } + + void serviceAuthenticated(int status, void * user_data) { + calledAuthenticated = true; + ck_assert(status == 1); + Buffer const * user = fsmservice_get_user(serv); + Buffer const * extraData = fsmservice_get_received_extra_data(serv); + Buffer const * receivedSymKey = fsmservice_get_symmetric_key(serv); + + ck_assert(buffer_equals(receivedSymKey, symmetricKey)); + ck_assert(!strcmp(buffer_get_buffer(user), "Donald")); + ck_assert(!strcmp(buffer_get_buffer(extraData), "p@ssword")); + } + + fsmservice_set_functions(serv, serviceWrite, serviceSetTimeout, NULL, NULL, serviceDisconnect, serviceAuthenticated, NULL, NULL); + fsmservice_set_continuous(serv, true); + fsmpico_set_functions(pico, picoWrite, picoSetTimeout, NULL, picoReconnect, picoDisconnect, NULL, NULL, picoStatusUpdate); + + // We have to duplicate the objects because fsmpico tries to delete them later + cryptosupport_getprivateder(picoIdSKey, picoIdDer); + fsmpico_start(pico, picoExtraData, EC_KEY_dup(servIdPKey), EC_KEY_dup(picoIdPKey), cryptosupport_read_buffer_private_key(picoIdDer)); + fsmservice_start(serv, servShared, users, servExtraData); + + // To kick start we have to "connect" both sides + push_connected(&queue, NULL, serv, currentTime); + push_connected(&queue, pico, NULL, currentTime); + + while (queue.head != NULL) { + Node* head = queue.head; + queue.head = head->next; + currentTime = head->event.time; + process_event(head->event); + free(head); + } + + ck_assert(cycles == 4); + ck_assert(calledAuthenticated); + + fsmservice_delete(serv); + fsmpico_delete(pico); + shared_delete(servShared); + shared_delete(picoShared); + buffer_delete(picoExtraData); + buffer_delete(servExtraData); + buffer_delete(picoIdDer); + users_delete(users); + buffer_delete(symmetricKey); +} +END_TEST + +void * event_loop_thread(void * arg) { + Queue* queuePtr = (Queue*) arg; + + while(true) { + pthread_mutex_lock(&queue_mutex); + Node* head = queuePtr->head; + if (head != NULL) { + while (head->event.type == TIMEOUT && head->event.time > currentTime) { + // Wait a little bit so the other thread can synchronize + currentTime += 100; + pthread_mutex_unlock(&queue_mutex); + sleep(0.01); + pthread_mutex_lock(&queue_mutex); + head = queuePtr->head; + } + queuePtr->head = head->next; + currentTime = head->event.time; + pthread_mutex_unlock(&queue_mutex); + if (head->event.type == STOP_LOOP) { + free(head); + break; + } + process_event(head->event); + free(head); + } else { + pthread_mutex_unlock(&queue_mutex); + sleep(0.1); + } + } + + return NULL; +} + +START_TEST (fsm_pico_test) { + FsmPico* pico = fsmpico_new(); + Queue queue = {NULL}; + currentTime = 0; + sem_t read_semaphore; + sem_t connect_semaphore; + + char global_data[1024]; + int global_data_len = 0; + pthread_mutex_t global_data_mutex = PTHREAD_MUTEX_INITIALIZER; + + sem_init(&read_semaphore, 0, 0); + sem_init(&connect_semaphore, 0, 0); + + Shared * picoShared = shared_new(); + shared_load_or_generate_pico_keys(picoShared, "testpicokey.pub", "testpicokey.priv"); + Buffer * picoExtraData = buffer_new(0); + buffer_append_string(picoExtraData, "p@ssword"); + + Shared * servShared = shared_new(); + shared_load_or_generate_keys(servShared, "testkey.pub", "testkey.priv"); + + EC_KEY* servIdPKey = shared_get_service_identity_public_key(servShared); + EC_KEY* picoIdPKey = shared_get_pico_identity_public_key(picoShared); + EVP_PKEY* picoIdSKey = keypair_getprivatekey(shared_get_pico_identity_key(picoShared)); + Buffer * picoIdDer = buffer_new(0); + + Buffer * symmetricKey = buffer_new(0); + cryptosupport_generate_symmetric_key(symmetricKey, CRYPTOSUPPORT_AESKEY_SIZE); + Users * users = users_new(); + users_add_user(users, "Synchronous", picoIdPKey, symmetricKey); + + int cycles = 0; + + bool channelOpen(RVPChannel * channel) { + sem_wait(&connect_semaphore); + push_connected(&queue, pico, NULL, currentTime); + return true; + } + bool channelClose(RVPChannel * channel) { + push_disconnected(&queue, pico, NULL, currentTime); + return true; + } + bool channelWrite(RVPChannel * channel, char * data, int length) { + uint32_t receivedLength = 0; + receivedLength |= ((unsigned char*) data)[0] << 24; + receivedLength |= ((unsigned char*) data)[1] << 16; + receivedLength |= ((unsigned char*) data)[2] << 8; + receivedLength |= ((unsigned char*) data)[3] << 0; + ck_assert_int_eq(length - 4 , receivedLength); + push_read(&queue, pico, NULL, currentTime, data + 4, length - 4); + return true; + } + bool channelRead(RVPChannel * channel, Buffer * buffer) { + sem_wait(&read_semaphore); + bool ret = true; + pthread_mutex_lock(&global_data_mutex); + if (global_data_len == -1) { + ret = false; + } else { + buffer_clear(buffer); + buffer_append(buffer, global_data, global_data_len); + } + pthread_mutex_unlock(&global_data_mutex); + return ret; + } + + void picoWrite(char const * data, size_t length, void * user_data) { + pthread_mutex_lock(&global_data_mutex); + memcpy(global_data, data, length); + global_data_len = length; + pthread_mutex_unlock(&global_data_mutex); + sem_post(&read_semaphore); + } + + void picoSetTimeout(int timeout, void * user_data) { + ck_assert_int_eq(timeout, 10000); + push_timeout(&queue, pico, NULL, currentTime, timeout); + } + + void picoReconnect(void * user_data) { + sem_post(&connect_semaphore); + } + + void picoDisconnect(void * user_data) { + } + + void picoStatusUpdate(FSMPICOSTATE state, void * user_data) { + if (state == FSMPICOSTATE_PICOREAUTH) { + cycles++; + if (cycles > 3) { + push_stop(&queue, pico, NULL, currentTime); + + pthread_mutex_lock(&global_data_mutex); + global_data_len = -1; + pthread_mutex_unlock(&global_data_mutex); + sem_post(&read_semaphore); + + } + } + } + + fsmpico_set_functions(pico, picoWrite, picoSetTimeout, NULL, picoReconnect, picoDisconnect, NULL, NULL, picoStatusUpdate); + + // We have to duplicate the objects because fsmpico tries to delete them later + cryptosupport_getprivateder(picoIdSKey, picoIdDer); + fsmpico_start(pico, picoExtraData, EC_KEY_dup(servIdPKey), EC_KEY_dup(picoIdPKey), cryptosupport_read_buffer_private_key(picoIdDer)); + sem_post(&connect_semaphore); + + pthread_t prover_td; + pthread_create(&prover_td, NULL, event_loop_thread, &queue); + + RVPChannel * channel = channel_new(); + channel_set_functions(channel, NULL, channelOpen, channelClose, channelWrite, channelRead, NULL, NULL, NULL); + Buffer * returnedExtraData = buffer_new(0); + Buffer * returnedSymmetricKey = buffer_new(0); + Buffer * returnedUsername = buffer_new(0); + bool result = sigmaverifier_session(servShared, channel, users, NULL, returnedExtraData, returnedSymmetricKey, true, 0); + ck_assert(result); + + EC_KEY *pico_key = shared_get_pico_identity_public_key(servShared); + buffer_append_buffer(returnedUsername, users_search_by_key(users, pico_key)); + buffer_append(returnedUsername, "", 1); + ck_assert(buffer_equals(returnedSymmetricKey, symmetricKey)); + ck_assert(!strcmp(buffer_get_buffer(returnedUsername), "Synchronous")); + ck_assert(!strcmp(buffer_get_buffer(returnedExtraData), "p@ssword")); + + Continuous * continuous = continuous_new(); + continuous_set_channel(continuous, channel); + continuous_set_shared_key(continuous, shared_get_shared_key(servShared)); + + result = continuous_cycle_start(continuous); + ck_assert(result); + + result = continuous_continue(continuous, NULL); + ck_assert(result); + + result = continuous_continue(continuous, NULL); + ck_assert(result); + + result = continuous_continue(continuous, NULL); + ck_assert(result); + + result = continuous_continue(continuous, NULL); + ck_assert(!result); + + ck_assert(cycles == 4); + + Event e; + e.type = STOP_LOOP; + e.service = NULL; + e.pico = pico; + e.time = currentTime; + push_event(&queue, e); + pthread_join(prover_td, NULL); + + buffer_delete(returnedExtraData); + buffer_delete(returnedSymmetricKey); + buffer_delete(returnedUsername); + channel_delete(channel); + + fsmpico_delete(pico); + shared_delete(servShared); + shared_delete(picoShared); + buffer_delete(picoExtraData); + buffer_delete(picoIdDer); + users_delete(users); + buffer_delete(symmetricKey); +} +END_TEST + +START_TEST (fsm_service_test) { + FsmService* serv = fsmservice_new(); + Queue queue = {NULL}; + currentTime = 0; + sem_t read_semaphore; + sem_t authenticated_semaphore; + sem_t stop_semaphore; + + char global_data[1024]; + int global_data_len = 0; + pthread_mutex_t global_data_mutex = PTHREAD_MUTEX_INITIALIZER; + + sem_init(&read_semaphore, 0, 0); + sem_init(&authenticated_semaphore, 0, 0); + sem_init(&stop_semaphore, 0, 0); + + Shared * picoShared = shared_new(); + shared_load_or_generate_pico_keys(picoShared, "testpicokey.pub", "testpicokey.priv"); + Buffer * picoExtraData = buffer_new(0); + buffer_append_string(picoExtraData, "p@ssword"); + + Shared * servShared = shared_new(); + shared_load_or_generate_keys(servShared, "testkey.pub", "testkey.priv"); + Buffer * servExtraData = buffer_new(0); + buffer_append_string(servExtraData, "SERVICE EXTRA"); + + EC_KEY* picoIdPKey = shared_get_pico_identity_public_key(picoShared); + + Buffer * symmetricKey = buffer_new(0); + cryptosupport_generate_symmetric_key(symmetricKey, CRYPTOSUPPORT_AESKEY_SIZE); + Users * users = users_new(); + users_add_user(users, "Synchronous", picoIdPKey, symmetricKey); + + bool calledAuthenticated = false; + int cycles = 0; + + bool channelOpen(RVPChannel * channel) { + push_connected(&queue, NULL, serv, currentTime); + return true; + } + bool channelClose(RVPChannel * channel) { + push_disconnected(&queue, NULL, serv, currentTime); + return true; + } + bool channelWrite(RVPChannel * channel, char * data, int length) { + uint32_t receivedLength = 0; + receivedLength |= ((unsigned char*) data)[0] << 24; + receivedLength |= ((unsigned char*) data)[1] << 16; + receivedLength |= ((unsigned char*) data)[2] << 8; + receivedLength |= ((unsigned char*) data)[3] << 0; + ck_assert_int_eq(length - 4 , receivedLength); + push_read(&queue, NULL, serv, currentTime, data + 4, length - 4); + return true; + } + bool channelRead(RVPChannel * channel, Buffer * buffer) { + sem_wait(&read_semaphore); + bool ret = true; + pthread_mutex_lock(&global_data_mutex); + if (global_data_len == -1) { + ret = false; + } else { + buffer_clear(buffer); + buffer_append(buffer, global_data, global_data_len); + } + pthread_mutex_unlock(&global_data_mutex); + return ret; + } + + void serviceWrite(char const * data, size_t length, void * user_data) { + pthread_mutex_lock(&global_data_mutex); + memcpy(global_data, data, length); + global_data_len = length; + pthread_mutex_unlock(&global_data_mutex); + sem_post(&read_semaphore); + } + + void serviceSetTimeout(int timeout, void * user_data) { + push_timeout(&queue, NULL, serv, currentTime, timeout); + } + + void serviceDisconnect(void * user_data){ + push_disconnected(&queue, NULL, serv, currentTime); + } + + void serviceAuthenticated(int status, void * user_data) { + calledAuthenticated = true; + ck_assert(status == 1); + Buffer const * user = fsmservice_get_user(serv); + Buffer const * extraData = fsmservice_get_received_extra_data(serv); + Buffer const * receivedSymKey = fsmservice_get_symmetric_key(serv); + + ck_assert(buffer_equals(receivedSymKey, symmetricKey)); + ck_assert(!strcmp(buffer_get_buffer(user), "Synchronous")); + ck_assert(!strcmp(buffer_get_buffer(extraData), "p@ssword")); + sem_post(&authenticated_semaphore); + } + + void serviceStatusUpdate(int state, void * user_data) { + if (state == FSMSERVICESTATE_PICOREAUTH) { + cycles++; + if (cycles > 3) { + push_stop(&queue, NULL, serv, currentTime); + sem_post(&stop_semaphore); + } + } + } + + fsmservice_set_functions(serv, serviceWrite, serviceSetTimeout, NULL, NULL, serviceDisconnect, serviceAuthenticated, NULL, serviceStatusUpdate); + fsmservice_set_continuous(serv, true); + + fsmservice_start(serv, servShared, users, servExtraData); + + pthread_t prover_td; + pthread_create(&prover_td, NULL, event_loop_thread, &queue); + + RVPChannel * channel = channel_new(); + channel_set_functions(channel, NULL, channelOpen, channelClose, channelWrite, channelRead, NULL, NULL, NULL); + Buffer * returnedExtraData = buffer_new(0); + channel_open(channel); + bool result = sigmaprover(picoShared, channel, picoExtraData, returnedExtraData); + ck_assert(result); + + sem_wait(&authenticated_semaphore); + ck_assert(calledAuthenticated); + ck_assert(!strcmp(buffer_get_buffer(returnedExtraData), "SERVICE EXTRA")); + + Continuous * continuous = continuous_new(); + continuous_set_channel(continuous, channel); + continuous_set_shared_key(continuous, shared_get_shared_key(servShared)); + + result = continuous_cycle_start_pico(continuous, NULL); + ck_assert(result); + + int timeout = 0; + result = continuous_reauth_pico(continuous, NULL, &timeout); + ck_assert(result); + + result = continuous_reauth_pico(continuous, NULL, &timeout); + ck_assert(result); + + result = continuous_reauth_pico(continuous, NULL, &timeout); + ck_assert(result); + + // Wait for the other thread to complete the cycles before stopping the loop + sem_wait(&stop_semaphore); + + Event e; + e.type = STOP_LOOP; + e.service = serv; + e.pico = NULL; + e.time = currentTime; + push_event(&queue, e); + pthread_join(prover_td, NULL); + + ck_assert_int_eq(cycles, 4); + + buffer_delete(returnedExtraData); + channel_delete(channel); + + fsmservice_delete(serv); + shared_delete(servShared); + shared_delete(picoShared); + buffer_delete(picoExtraData); + users_delete(users); + buffer_delete(symmetricKey); +} +END_TEST + + +int main (void) { + int number_failed; + Suite * s; + SRunner *sr; + TCase * tc; + + s = suite_create("Libpico"); + + tc = tcase_create("FSM"); + tcase_add_test(tc, fsm_fsm_test); + tcase_add_test(tc, fsm_pico_test); + tcase_add_test(tc, fsm_service_test); + suite_add_tcase(s, tc); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0) ? 0 : -1; +} +