diff --git a/src/ddsrt/include/dds/ddsrt/threads.h b/src/ddsrt/include/dds/ddsrt/threads.h index e258218..c4d3d81 100644 --- a/src/ddsrt/include/dds/ddsrt/threads.h +++ b/src/ddsrt/include/dds/ddsrt/threads.h @@ -140,11 +140,20 @@ ddsrt_nonnull((1,2,3,4)); /** * @brief Retrieve integer representation of the given thread id. * - * @returns The integer representation of the given thread. + * @returns The integer representation of the current thread. */ DDS_EXPORT ddsrt_tid_t ddsrt_gettid(void); +/** + * @brief Retrieve integer representation of the given thread id. + * + * @returns The integer representation of the given thread. + */ +DDS_EXPORT ddsrt_tid_t +ddsrt_gettid_for_thread( ddsrt_thread_t thread); + + /** * @brief Return thread ID of the calling thread. * diff --git a/src/ddsrt/src/threads/freertos/threads.c b/src/ddsrt/src/threads/freertos/threads.c index be4e177..be636c5 100644 --- a/src/ddsrt/src/threads/freertos/threads.c +++ b/src/ddsrt/src/threads/freertos/threads.c @@ -83,6 +83,13 @@ ddsrt_gettid(void) return status.xTaskNumber; } +DDS_EXPORT ddsrt_tid_t +ddsrt_gettid_for_thread( ddsrt_thread_t thread) +{ + return (ddsrt_tid_t)thread.task; +} + + ddsrt_thread_t ddsrt_thread_self(void) { diff --git a/src/ddsrt/src/threads/posix/threads.c b/src/ddsrt/src/threads/posix/threads.c index 0d2c387..2affcdb 100644 --- a/src/ddsrt/src/threads/posix/threads.c +++ b/src/ddsrt/src/threads/posix/threads.c @@ -346,6 +346,14 @@ ddsrt_gettid(void) return tid; } +ddsrt_tid_t +ddsrt_gettid_for_thread( ddsrt_thread_t thread) +{ + return (ddsrt_tid_t) thread.v; + +} + + ddsrt_thread_t ddsrt_thread_self(void) { diff --git a/src/ddsrt/src/threads/windows/threads.c b/src/ddsrt/src/threads/windows/threads.c index 2bc8052..a028e6c 100644 --- a/src/ddsrt/src/threads/windows/threads.c +++ b/src/ddsrt/src/threads/windows/threads.c @@ -173,6 +173,14 @@ ddsrt_gettid(void) return GetCurrentThreadId(); } + +ddsrt_tid_t +ddsrt_gettid_for_thread( ddsrt_thread_t thread) +{ + return (ddsrt_tid_t) thread.tid; + +} + ddsrt_thread_t ddsrt_thread_self( void) diff --git a/src/security/core/CMakeLists.txt b/src/security/core/CMakeLists.txt index 6802c73..b0701e9 100644 --- a/src/security/core/CMakeLists.txt +++ b/src/security/core/CMakeLists.txt @@ -14,6 +14,7 @@ PREPEND(srcs_security_core "${CMAKE_CURRENT_LIST_DIR}/src" dds_security_utils.c dds_security_plugins.c shared_secret.c + dds_security_fsm.c ) PREPEND(hdrs_public_security_core "${CMAKE_CURRENT_LIST_DIR}/include/security/core" @@ -21,6 +22,7 @@ PREPEND(hdrs_public_security_core "${CMAKE_CURRENT_LIST_DIR}/include/security/co dds_security_types.h dds_security_utils.h dds_security_plugins.h + dds_security_fsm.h shared_secret.h ) @@ -52,4 +54,4 @@ install( DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/dds/security/core/" COMPONENT dev) -add_subdirectory(tests/plugin_loading) +add_subdirectory(tests) diff --git a/src/security/core/include/dds/security/core/dds_security_fsm.h b/src/security/core/include/dds/security/core/dds_security_fsm.h new file mode 100644 index 0000000..db6374a --- /dev/null +++ b/src/security/core/include/dds/security/core/dds_security_fsm.h @@ -0,0 +1,224 @@ +/* + * Copyright(c) 2006 to 2019 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +#ifndef DDS_SECURITY_FSM_H +#define DDS_SECURITY_FSM_H + +#include "dds/ddsrt/time.h" +#include "dds/ddsrt/threads.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#define DDS_SECURITY_FSM_EVENT_AUTO (-1) +#define DDS_SECURITY_FSM_EVENT_TIMEOUT (-2) + +typedef enum { + DDS_SECURITY_FSM_DEBUG_ACT_DISPATCH, + DDS_SECURITY_FSM_DEBUG_ACT_DISPATCH_DIRECT, + DDS_SECURITY_FSM_DEBUG_ACT_HANDLING +} DDS_SECURITY_FSM_DEBUG_ACT; + +struct dds_security_fsm; +struct dds_security_fsm_context; + +typedef ddsrt_thread_t (*dds_security_fsm_thread_create_func)(const char *name, ddsrt_thread_routine_t f, void *arg); + +typedef void (*dds_security_fsm_thread_destroy_func)( ddsrt_thread_t tid); + +/** + * Template for user-defined state methods. + * It is allowed to call dds_security_fsm_dispatch() from within a dispatch function. + */ +typedef void (*dds_security_fsm_action)(struct dds_security_fsm *fsm, void *arg); + +/** + * State struct + * + * - func : optional user defined function, invoked by when reaching this state + * - timeout : optional timeout which is controlled by the fsm + */ +typedef struct dds_security_fsm_state { + const dds_security_fsm_action func; + dds_duration_t timeout; +} dds_security_fsm_state; + +/** + * Template for user-defined debug methods. + * It'll be called for every dispatched event, regardless of which state it + * is in (which is also provided). + * This can be used to get extra information about the behaviour of the + * state machine. + * It is not allowed to call any fsm API functions from within this + * debug callback. + */ +typedef void (*dds_security_fsm_debug)(struct dds_security_fsm *fsm, DDS_SECURITY_FSM_DEBUG_ACT act, + const dds_security_fsm_state *current, int event_id, void *arg); + +/** + * Transition definitions + * + * begin : start state (to transition from) + * event_id : indicate the event responsible for the transition + * func : user defined function, invoked during transition + * end : end state (to transition to) + */ +typedef struct dds_security_fsm_transition { + const dds_security_fsm_state *begin; + const int event_id; + const dds_security_fsm_action func; + const dds_security_fsm_state *end; +} dds_security_fsm_transition; + +/** + * Create a new fsm context + * Creates an fsm context. The fsm context manages the global state of the fsm's created within + * this context. The fsm context uses a number of threads to control the state machined allocated + * to this context. A thread create callback has to be provided to created the threads in the + * context of the caller. + * + * @param thr_create_func a callback function used to created the threads used to manage + * the allocated state machines + * + * @return Returns the new fsm context on success. Null on failure. + */ +DDS_EXPORT struct dds_security_fsm_context * +dds_security_fsm_context_create( dds_security_fsm_thread_create_func thr_create_func); + +/** + * Destroys a fsm context + * The function clears the fsm context and stops the associated threads. The thread destroy + * function is called to allow the caller to free resources associated with the threads and + * to wait for the threads to exit. + * + * @param context the context to be destroyed + * @param thr_destroy_func a callback function used to wait a thread to terminate + * the allocated state machine + */ +DDS_EXPORT void +dds_security_fsm_context_destroy(struct dds_security_fsm_context *context, + dds_security_fsm_thread_destroy_func thr_destroy_func); + +/** + * Create a new fsm + * Initializes a new fsm. Fsm does not start. + * + * @param transitions array of transitions which the defines the functioning of the state machine + * @param size number of transitions + * @param arg Extra data to pass to the fsm. Will be passed to all user defined callback + * methods. + * + * @return Returns the new created state machine on success. Null on failure. + */ +DDS_EXPORT struct dds_security_fsm * +dds_security_fsm_create(struct dds_security_fsm_context *context, + const dds_security_fsm_transition *transitions, int size, void *arg); + +/** + * Start a fsm + * Starts the fsm, start with the firs transition + * + * @param fsm fsm to start. + */ +DDS_EXPORT void +dds_security_fsm_start(struct dds_security_fsm *fsm); + +/** + * Set an overall timeout for the given state machine + * Will be monitoring the overall timeout of the given state machine, + * invoking a user defined callback when the given timeout expires. + * Timeout will be aborted upon a cleanup of the state machine. + * + * @param fsm fsm to set the overall timeout for + * @param func user defined function which is called when the + * overall timeout expires. + * @param timeout indicates the overall timeout + */ +DDS_EXPORT void +dds_security_fsm_set_timeout(struct dds_security_fsm *fsm, dds_security_fsm_action func, dds_time_t timeout); + +/** + * Set an debug callback for the given state machine. + * + * @param fsm fsm to set the overall timeout for + * @param func user defined function which is called for every + * event, whether being dispatched or actually + * handled. + */ +DDS_EXPORT void +dds_security_fsm_set_debug(struct dds_security_fsm *fsm, dds_security_fsm_debug func); + +/** + * Dispatches the next event + * Assignment for the state machine to transisiton to the next state. + * + * @param fsm The state machine + * @param event_id Indicate where to transisition to (outcome of current state) + */ +DDS_EXPORT void +dds_security_fsm_dispatch(struct dds_security_fsm *fsm, int32_t event_id); + +/** + * Dispatches the next event with priority + * Assignment for the state machine to transisiton to the next state. + * This event will be placed at the top of the event list. + * + * @param fsm The state machine + * @param event_id Indicate where to transisition to (outcome of current state) + */ +DDS_EXPORT void +dds_security_fsm_dispatch_direct(struct dds_security_fsm *fsm, int32_t event_id); + +/** + * Retrieve the current state of a given state machine + * + * @param fsm The state machine + * + * @return The current state of the given state machine + */ +DDS_EXPORT const dds_security_fsm_state* +dds_security_fsm_current_state(struct dds_security_fsm *fsm); + +/** + * Clean the given state machine + * Cleaning up the given state machine. This will abort all timeouts for + * this state machine and remove all events from the internals. + * + * @param fsm The state machine to clean. + */ +DDS_EXPORT void +dds_security_fsm_cleanup(struct dds_security_fsm *fsm); + +/** + * Freeing the state machine. + * Stops all running timeouts and events and cleaning all memory + * related to this machine. + * + * When calling this from another thread, then it may block until + * a possible concurrent event has finished. After this call, the + * fsm may not be used anymore. + * + * When in the fsm action callback function context, this will + * not block. It will garbage collect when the event has been + * handled. + * + * @param fsm The state machine to free + */ +DDS_EXPORT void /* Implicit cleanup. */ +dds_security_fsm_free(struct dds_security_fsm *fsm); + +#if defined (__cplusplus) +} +#endif + +#endif /* DDS_SECURITY_FSM_H */ diff --git a/src/security/core/src/dds_security_fsm.c b/src/security/core/src/dds_security_fsm.c new file mode 100644 index 0000000..220b017 --- /dev/null +++ b/src/security/core/src/dds_security_fsm.c @@ -0,0 +1,749 @@ +/* + * Copyright(c) 2006 to 2019 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +#include +#include +#include "dds/security/core/dds_security_fsm.h" +#include "dds/ddsrt/threads.h" +#include "dds/ddsrt/sync.h" +#include "dds/ddsrt/heap.h" +#include "dds/ddsrt/atomics.h" +#include "dds/ddsrt/retcode.h" +#include "dds/ddsrt/time.h" +#include "dds/ddsrt/timeconv.h" +#include + +typedef struct dds_security_fsm { + const dds_security_fsm_transition *transitions; + int size; + void *arg; + const dds_security_fsm_state *current; + dds_time_t current_state_endtime; + ddsrt_atomic_uint32_t ref_cnt; + dds_security_fsm_debug debug_func; + struct dds_security_fsm_context *context; + + struct dds_security_fsm *next; + struct dds_security_fsm *prev; +} dds_security_fsm; + +typedef struct fsm_event { + struct dds_security_fsm *fsm; + int event_id; + struct fsm_event *next; + struct fsm_event *prev; +} fsm_event; + +typedef struct fsm_state_timeout { + struct dds_security_fsm *fsm; + dds_time_t endtime; +} fsm_state_timeout; + +typedef struct fsm_overall_timeout { + struct dds_security_fsm *fsm; + dds_time_t endtime; + dds_security_fsm_action func; + struct fsm_overall_timeout *next; + struct fsm_overall_timeout *prev; +} fsm_overall_timeout; + +typedef struct dds_security_fsm_context { + ddsrt_thread_t fsm_tid; + ddsrt_thread_t fsm_timeout_tid; + bool fsm_teardown; + fsm_event *fsm_queue; + fsm_overall_timeout *fsm_overall_timeouts; + ddsrt_mutex_t fsm_fsms_mutex; + dds_security_fsm *fsm_fsms; + fsm_state_timeout *fsm_next_state_timeout; + ddsrt_mutex_t fsm_state_timeout_mutex; + ddsrt_cond_t fsm_event_cond; + ddsrt_mutex_t fsm_event_cond_mutex; + + // Overall timeout guard + ddsrt_cond_t fsm_overall_timeout_cond; + ddsrt_mutex_t fsm_overall_timeout_cond_mutex; +} dds_security_fsm_context; + +static dds_security_fsm_context *fsm_context = NULL; + +// Thread safe initialization of the Generic State Machine Utility +bool dds_security_fsm_initialized = false; +static ddsrt_atomic_uint32_t _fsmInitCount = DDSRT_ATOMIC_UINT32_INIT(0); + +static void fsm_dispatch(struct dds_security_fsm *fsm, int event_id, int lifo) { + fsm_event *event; + dds_security_fsm_context *context; + + assert(fsm); + + if (fsm->size < 0) { + /* This fsm is cleaned up (but probably not freed yet). + * So, ignore the new event. */ + return; + } + + context = fsm->context; + assert(context); + + if (fsm->debug_func) { + fsm->debug_func(fsm, + lifo ? DDS_SECURITY_FSM_DEBUG_ACT_DISPATCH_DIRECT : DDS_SECURITY_FSM_DEBUG_ACT_DISPATCH, + fsm->current, event_id, fsm->arg); + } + + event = ddsrt_malloc(sizeof(fsm_event)); + event->fsm = fsm; + event->event_id = event_id; + event->next = NULL; + event->prev = NULL; + + if (lifo) { + /* Insert event at the top of the event list */ + if (context->fsm_queue) { + context->fsm_queue->prev = event; + } + event->next = context->fsm_queue; + context->fsm_queue = event; + } else { + /* Insert FIFO event */ + if (context->fsm_queue) { + fsm_event *last = context->fsm_queue; + while (last->next != NULL ) { + last = last->next; + } + last->next = event; + event->prev = last; + } else { + context->fsm_queue = event; + } + } +} + +static void fsm_set_next_state_timeout(dds_security_fsm_context *context, + dds_security_fsm *ignore) { + dds_security_fsm *fsm; + + ddsrt_mutex_lock(&context->fsm_event_cond_mutex); + + // reset the current time + context->fsm_next_state_timeout->endtime = DDS_NEVER; + context->fsm_next_state_timeout->fsm = NULL; + + fsm = context->fsm_fsms; + while (fsm) { + if ((fsm->current) && (fsm->current->timeout) && (fsm != ignore)) { + // first set the endtime of this state (if not set) + if (fsm->current_state_endtime == 0) { + fsm->current_state_endtime = ddsrt_time_add_duration(dds_time(), + fsm->current->timeout); + } + // Initialize the current endtime + if (context->fsm_next_state_timeout->fsm == NULL) { + context->fsm_next_state_timeout->endtime = fsm->current_state_endtime; + context->fsm_next_state_timeout->fsm = fsm; + } else if (fsm->current_state_endtime + < context->fsm_next_state_timeout->endtime) { + context->fsm_next_state_timeout->endtime = fsm->current_state_endtime; + context->fsm_next_state_timeout->fsm = fsm; + } + } + fsm = fsm->next; + } + + ddsrt_mutex_unlock(&context->fsm_event_cond_mutex); +} + +static void fsm_state_change(fsm_event *event) { + dds_security_fsm *fsm = event->fsm; + dds_security_fsm_context *context = fsm->context; + int event_id = event->event_id; + int i, j; + + if (fsm->debug_func) { + fsm->debug_func(fsm, DDS_SECURITY_FSM_DEBUG_ACT_HANDLING, fsm->current, event_id, + fsm->arg); + } + + for (i = 0; !context->fsm_teardown && i < fsm->size; i++) { + if ((fsm->transitions[i].begin == fsm->current) + && (fsm->transitions[i].event_id == event_id)) { + /* Transition. */ + if (fsm->transitions[i].func) { + fsm->transitions[i].func(fsm, fsm->arg); + } + /* New state. */ + fsm->current = fsm->transitions[i].end; + if (fsm->current) { + if (fsm->current->func) { + fsm->current->func(fsm, fsm->arg); + } + /* Reset timeout. */ + fsm->current_state_endtime = ddsrt_time_add_duration(dds_time(), + fsm->current->timeout); + /* Check if an auto transition is to be dispatched */ + for (j = 0; j < fsm->size; j++) { + if ((fsm->transitions[j].begin == fsm->current) + && (fsm->transitions[j].event_id == DDS_SECURITY_FSM_EVENT_AUTO)) { + dds_security_fsm_dispatch_direct(fsm, DDS_SECURITY_FSM_EVENT_AUTO); + } + } + } + } + } +} + +static uint32_t +fsm_thread(void *a) { + dds_security_fsm_context *context = a; + dds_duration_t dur_to_wait; + dds_time_t now = DDS_TIME_INVALID; + fsm_event *event; + + while (!context->fsm_teardown) { + event = NULL; + + ddsrt_mutex_lock(&context->fsm_event_cond_mutex); + if (!context->fsm_queue) { + if (context->fsm_next_state_timeout->endtime == DDS_NEVER) { + dur_to_wait = DDS_NEVER; + } else { + now = dds_time(); + dur_to_wait = context->fsm_next_state_timeout->endtime - now; + } + if (dur_to_wait > 0) { + if (ddsrt_cond_waitfor(&context->fsm_event_cond, + &context->fsm_event_cond_mutex, dur_to_wait) == false) { + if (context->fsm_next_state_timeout->fsm) { + /* Next timeout could have changed. */ + if (context->fsm_next_state_timeout->endtime != DDS_NEVER + && (context->fsm_next_state_timeout->endtime - now <= 0)) { + fsm_dispatch(context->fsm_next_state_timeout->fsm, + DDS_SECURITY_FSM_EVENT_TIMEOUT, 1); + } + } + } + } else { + if (context->fsm_next_state_timeout->fsm) { + fsm_dispatch(context->fsm_next_state_timeout->fsm, + DDS_SECURITY_FSM_EVENT_TIMEOUT, 1); + } + } + } else { + event = context->fsm_queue; + context->fsm_queue = context->fsm_queue->next; + if (context->fsm_queue) { + context->fsm_queue->prev = NULL; + } + ddsrt_atomic_inc32(&(event->fsm->ref_cnt)); + } + ddsrt_mutex_unlock(&context->fsm_event_cond_mutex); + + if (event) { + fsm_state_change(event); + if (ddsrt_atomic_dec32_nv(&(event->fsm->ref_cnt)) == 0) { + ddsrt_free(event->fsm); + } + ddsrt_free(event); + } + fsm_set_next_state_timeout(context, NULL); + } + return 0; +} + +static fsm_overall_timeout * +fsm_get_first_overall_timeout(dds_security_fsm_context *context) { + fsm_overall_timeout *timeout; + fsm_overall_timeout *first_timeout; + dds_time_t first_time = DDS_NEVER; + + timeout = context->fsm_overall_timeouts; + first_timeout = context->fsm_overall_timeouts; + while (timeout) { + if (timeout->endtime < first_time) { + first_time = timeout->endtime; + first_timeout = timeout; + } + timeout = timeout->next; + } + + return first_timeout; +} + +static void fsm_remove_overall_timeout_from_list(dds_security_fsm_context *context, + fsm_overall_timeout *timeout) { + fsm_overall_timeout *tmp_next_timeout; + fsm_overall_timeout *tmp_prev_timeout; + + if (timeout) { + + tmp_next_timeout = timeout->next; + tmp_prev_timeout = timeout->prev; + if (tmp_prev_timeout) { + tmp_prev_timeout->next = tmp_next_timeout; + } + if (tmp_next_timeout) { + tmp_next_timeout->prev = tmp_prev_timeout; + } + + if (timeout == context->fsm_overall_timeouts) { + context->fsm_overall_timeouts = tmp_next_timeout; + } + + ddsrt_free(timeout); + timeout = NULL; + } +} + +static uint32_t +fsm_run_timeout(void *arg) { + dds_security_fsm_context *context = arg; + dds_return_t result; + fsm_overall_timeout *to; + dds_time_t time_to_wait; + dds_time_t now; + + while (!context->fsm_teardown) { + ddsrt_mutex_lock(&context->fsm_overall_timeout_cond_mutex); + to = fsm_get_first_overall_timeout(context); + if (to) { + struct dds_security_fsm *fsm = to->fsm; + ddsrt_atomic_inc32(&(fsm->ref_cnt)); + + result = DDS_RETCODE_TIMEOUT; + now = dds_time(); + if (to->endtime > now) { + time_to_wait = to->endtime - now; + result = ddsrt_cond_waitfor(&context->fsm_overall_timeout_cond, + &context->fsm_overall_timeout_cond_mutex, time_to_wait); + } + + if (result == DDS_RETCODE_TIMEOUT) { + /* Prevent calling timeout when the fsm has been cleaned. */ + dds_security_fsm_action func = to->func; + fsm_remove_overall_timeout_from_list(context, to); + if (fsm->size > 0) { + ddsrt_mutex_unlock(&context->fsm_overall_timeout_cond_mutex); + func(fsm, fsm->arg); + ddsrt_mutex_lock(&context->fsm_overall_timeout_cond_mutex); + } + } + + if (ddsrt_atomic_dec32_nv(&(fsm->ref_cnt)) == 0) { + ddsrt_free(fsm); + } + } else { + ddsrt_cond_wait(&context->fsm_overall_timeout_cond, + &context->fsm_overall_timeout_cond_mutex); + } + ddsrt_mutex_unlock(&context->fsm_overall_timeout_cond_mutex); + } + return 0; +} + +static void fsm_remove_fsm_list(dds_security_fsm *fsm) { + dds_security_fsm_context *context; + dds_security_fsm *tmp_next_fsm; + dds_security_fsm *tmp_prev_fsm; + + if (fsm) { + context = fsm->context; + + ddsrt_mutex_lock(&context->fsm_fsms_mutex); + tmp_next_fsm = fsm->next; + tmp_prev_fsm = fsm->prev; + if (tmp_prev_fsm) { + tmp_prev_fsm->next = tmp_next_fsm; + } + if (tmp_next_fsm) { + tmp_next_fsm->prev = tmp_prev_fsm; + } + if (fsm == context->fsm_fsms) { + context->fsm_fsms = tmp_next_fsm; + } + ddsrt_mutex_unlock(&context->fsm_fsms_mutex); + + ddsrt_mutex_lock(&context->fsm_overall_timeout_cond_mutex); + ddsrt_cond_signal(&context->fsm_overall_timeout_cond); + ddsrt_mutex_unlock(&context->fsm_overall_timeout_cond_mutex); + } +} + +static ddsrt_thread_t fsm_thread_create( const char *name, + ddsrt_thread_routine_t f, void *arg) { + ddsrt_thread_t tid; + ddsrt_threadattr_t threadAttr; + + ddsrt_threadattr_init(&threadAttr); + if (ddsrt_thread_create(&tid, name, &threadAttr, f, arg) != DDS_RETCODE_OK) { + memset(&tid, 0, sizeof(ddsrt_thread_t)); + } + return tid; +} +#ifdef AT_PROC_EXIT_IMPLEMENTED +static void fsm_thread_destroy( ddsrt_thread_t tid) { + uint32_t thread_result; + + (void) ddsrt_thread_join( tid, &thread_result); +} +#endif + +struct dds_security_fsm_context * +dds_security_fsm_context_create( dds_security_fsm_thread_create_func thr_create_func) { + struct dds_security_fsm_context *context; + + context = ddsrt_malloc(sizeof(*context)); + + context->fsm_next_state_timeout = ddsrt_malloc(sizeof(fsm_state_timeout)); + context->fsm_next_state_timeout->endtime = DDS_NEVER; + context->fsm_next_state_timeout->fsm = NULL; + + context->fsm_teardown = false; + context->fsm_queue = NULL; + context->fsm_overall_timeouts = NULL; + context->fsm_fsms = NULL; + + (void) ddsrt_mutex_init( &context->fsm_fsms_mutex ); + + // Overall timeout guard + (void) ddsrt_mutex_init( &context->fsm_overall_timeout_cond_mutex ); + (void) ddsrt_cond_init( &context->fsm_overall_timeout_cond ); + + // State timeouts + (void) ddsrt_mutex_init(&context->fsm_state_timeout_mutex ); + + // Events + (void) ddsrt_mutex_init(&context->fsm_event_cond_mutex ); + (void) ddsrt_cond_init(&context->fsm_event_cond ); + + context->fsm_tid = thr_create_func( "dds_security_fsm", fsm_thread, context); + context->fsm_timeout_tid = thr_create_func( "dds_security_fsm_timeout", + fsm_run_timeout, context); + + return context; +} + +void dds_security_fsm_context_destroy(dds_security_fsm_context *context, + dds_security_fsm_thread_destroy_func thr_destroy_func) { + if (context) { + context->fsm_teardown = true; + + ddsrt_mutex_lock( &context->fsm_overall_timeout_cond_mutex); + ddsrt_cond_signal( &context->fsm_overall_timeout_cond); + ddsrt_mutex_unlock( &context->fsm_overall_timeout_cond_mutex); + + ddsrt_mutex_lock(&context->fsm_event_cond_mutex); + ddsrt_cond_signal(&context->fsm_event_cond); + ddsrt_mutex_unlock(&context->fsm_event_cond_mutex); + + thr_destroy_func( context->fsm_tid); + ddsrt_mutex_destroy(&context->fsm_event_cond_mutex); + ddsrt_cond_destroy(&context->fsm_event_cond); + + thr_destroy_func( context->fsm_timeout_tid); + ddsrt_mutex_destroy(&context->fsm_fsms_mutex); + ddsrt_mutex_destroy(&context->fsm_overall_timeout_cond_mutex); + ddsrt_cond_destroy(&context->fsm_overall_timeout_cond); + + ddsrt_free(context->fsm_next_state_timeout); + } +} +#ifdef AT_PROC_EXIT_IMPLEMENTED +static void fsm_fini(void) { + dds_security_fsm_context_destroy(fsm_context, NULL, fsm_thread_destroy); + + /* os_osExit(); ???? */ +} + +#endif +static bool fsm_init_once(void) { + bool ret = true; + uint32_t initCount; + + initCount = ddsrt_atomic_inc32_nv(&_fsmInitCount); + + if (initCount == 1) { + assert( dds_security_fsm_initialized == false ); + + /* ddsrt_osInit(); ??? */ + + fsm_context = dds_security_fsm_context_create( fsm_thread_create); + + if (fsm_context) { + /* os_procAtExit( fsm_fini ); ??? */ + dds_security_fsm_initialized = true; + } else { + ret = false; + } + } else { + if (dds_security_fsm_initialized == false) { + /* Another thread is currently initializing the fsm. Since + * both results (osr_fsm and osr_timeout) should be ddsrt_resultSuccess + * a sleep is performed, to ensure that (if succeeded) successive + * init calls will also actually pass. + */ + dds_sleepfor( DDS_MSECS( 100 )); + } + if (dds_security_fsm_initialized == false) { + /* Initialization did not succeed, undo increment and return error */ + initCount = ddsrt_atomic_dec32_nv(&_fsmInitCount); + ret = false; + } + } + return ret; +} + +static int /* 1 = ok, other = error */ +fsm_validate(const dds_security_fsm_transition *transitions, int size) { + int i; + + for (i = 0; i < size; i++) { + /* It needs to have a start. */ + if ((transitions[i].begin == NULL ) + && (transitions[i].event_id == DDS_SECURITY_FSM_EVENT_AUTO)) { + return 1; + } + } + + return 0; +} + +struct dds_security_fsm * +dds_security_fsm_create(struct dds_security_fsm_context *context, + const dds_security_fsm_transition *transitions, int size, void *arg) { + struct dds_security_fsm* fsm = NULL; + struct dds_security_fsm_context *ctx = NULL; + + assert(transitions); + assert(size > 0); + + if (context == NULL) { + if (fsm_init_once()) { + ctx = fsm_context; + } + } else { + ctx = context; + } + + if (ctx) { + if (fsm_validate(transitions, size) == 1) { + fsm = ddsrt_malloc(sizeof(struct dds_security_fsm)); + fsm->transitions = transitions; + fsm->size = size; + fsm->arg = arg; + fsm->current = NULL; + fsm->debug_func = NULL; + fsm->next = NULL; + fsm->prev = NULL; + fsm->context = ctx; + ddsrt_atomic_st32( &fsm->ref_cnt, 1 ); + fsm->current_state_endtime = 0; + + ddsrt_mutex_lock(&fsm->context->fsm_fsms_mutex); + if (fsm->context->fsm_fsms) { + dds_security_fsm *last = fsm->context->fsm_fsms; + while (last->next != NULL ) { + last = last->next; + } + last->next = fsm; + fsm->prev = last; + } else { + fsm->context->fsm_fsms = fsm; + } + ddsrt_mutex_unlock(&fsm->context->fsm_fsms_mutex); + } + } + return fsm; +} + +void dds_security_fsm_start(struct dds_security_fsm *fsm) { + assert(fsm); + dds_security_fsm_dispatch(fsm, DDS_SECURITY_FSM_EVENT_AUTO); +} + +void dds_security_fsm_set_timeout(struct dds_security_fsm *fsm, dds_security_fsm_action func, + dds_time_t timeout) { + fsm_overall_timeout *to; + dds_security_fsm_context *context; + + assert(fsm); + + context = fsm->context; + assert(context); + + to = ddsrt_malloc(sizeof(fsm_overall_timeout)); + to->fsm = fsm; + to->func = func; + to->endtime = ddsrt_time_add_duration( dds_time(), timeout); + to->next = NULL; + to->prev = NULL; + + ddsrt_mutex_lock(&context->fsm_overall_timeout_cond_mutex); + if (context->fsm_overall_timeouts) { + fsm_overall_timeout *last = context->fsm_overall_timeouts; + while (last->next != NULL ) { + last = last->next; + } + last->next = to; + to->prev = last; + } else { + context->fsm_overall_timeouts = to; + } + ddsrt_cond_signal(&context->fsm_overall_timeout_cond); + ddsrt_mutex_unlock(&context->fsm_overall_timeout_cond_mutex); +} + +void dds_security_fsm_set_debug(struct dds_security_fsm *fsm, dds_security_fsm_debug func) { + dds_security_fsm_context *context; + + assert(fsm); + + context = fsm->context; + assert(context); + + ddsrt_mutex_lock(&context->fsm_overall_timeout_cond_mutex); + fsm->debug_func = func; + ddsrt_mutex_unlock(&context->fsm_overall_timeout_cond_mutex); +} + +void dds_security_fsm_dispatch(struct dds_security_fsm *fsm, int32_t event_id) { + dds_security_fsm_context *context; + + assert(fsm); + + context = fsm->context; + assert(context); + + ddsrt_mutex_lock(&context->fsm_event_cond_mutex); + fsm_dispatch(fsm, event_id, 0); + ddsrt_cond_signal(&context->fsm_event_cond); + ddsrt_mutex_unlock(&context->fsm_event_cond_mutex); +} + +void dds_security_fsm_dispatch_direct(struct dds_security_fsm *fsm, int32_t event_id) { + dds_security_fsm_context *context; + + assert(fsm); + + context = fsm->context; + assert(context); + + ddsrt_mutex_lock(&context->fsm_event_cond_mutex); + fsm_dispatch(fsm, event_id, 1); + ddsrt_cond_signal(&context->fsm_event_cond); + ddsrt_mutex_unlock(&context->fsm_event_cond_mutex); +} + +const dds_security_fsm_state* +dds_security_fsm_current_state(struct dds_security_fsm *fsm) { + assert(fsm); + return fsm->current; +} + +void dds_security_fsm_cleanup(struct dds_security_fsm *fsm) { + dds_security_fsm_context *context; + fsm_event *event; + fsm_event *tmp_prev_event; + fsm_event *tmp_next_event; + fsm_overall_timeout *timeout; + + assert(fsm); + + context = fsm->context; + assert(context); + + // Signal the timeout thread. + // First hold to lock to the overall timeout list + // so that the next timeout can't be determined until + // we've done removing the overall timeout of this fsm + + // Signal the thread so that it's not using timeout structs + ddsrt_mutex_lock(&context->fsm_overall_timeout_cond_mutex); + ddsrt_cond_signal(&context->fsm_overall_timeout_cond); + + timeout = context->fsm_overall_timeouts; + + // Search the overall timeout of this fsm + while (timeout) { + if (timeout->fsm == fsm) { + break; + } + timeout = timeout->next; + } + fsm_remove_overall_timeout_from_list(context, timeout); + ddsrt_mutex_unlock(&context->fsm_overall_timeout_cond_mutex); + + /* The current fsm could be the one that would trigger a possible timeout. + * Reset the state timeout and make sure it's not the current fsm. */ + fsm_set_next_state_timeout(context, fsm); + + /* Now, remove all possible events from the queue related to the fsm. */ + ddsrt_mutex_lock(&context->fsm_event_cond_mutex); + event = context->fsm_queue; + while (event) { + if (event->fsm == fsm) { + tmp_next_event = event->next; + tmp_prev_event = event->prev; + if (tmp_prev_event) { + tmp_prev_event->next = tmp_next_event; + } + if (tmp_next_event) { + tmp_next_event->prev = tmp_prev_event; + } + if (event == context->fsm_queue) { + context->fsm_queue = tmp_next_event; + } + ddsrt_free(event); + event = tmp_next_event; + } else { + event = event->next; + } + } + ddsrt_cond_signal(&context->fsm_event_cond); + ddsrt_mutex_unlock(&context->fsm_event_cond_mutex); +} + +void dds_security_fsm_free(struct dds_security_fsm *fsm) { + ddsrt_tid_t self = ddsrt_gettid_for_thread( ddsrt_thread_self() ); + dds_security_fsm_context *context; + + assert(fsm); + + context = fsm->context; + assert(context); + + /* Indicate termination. */ + fsm->size = -1; + + /* Cleanup stuff. */ + dds_security_fsm_cleanup(fsm); + fsm_remove_fsm_list(fsm); + + /* Is this being freed from the FSM context? */ + if ((self == ddsrt_gettid_for_thread( context->fsm_tid ) ) + || (self == ddsrt_gettid_for_thread( context->fsm_timeout_tid ) ) ) { + /* Yes. + * Just reduce the reference count and let the garbage collection be + * done by the FSM context after event handling. */ + ddsrt_atomic_dec32(&(fsm->ref_cnt)); + } else { + /* No. + * Block the outside thread until a possible concurrent event + * has being handled. */ + while (ddsrt_atomic_ld32( &(fsm->ref_cnt)) > 1) { + /* Currently, an event is still being handled for this FSM. */ + dds_sleepfor( 10 * DDS_NSECS_IN_MSEC ); + } + /* We have the only reference, so it's safe to free the FSM. */ + ddsrt_free(fsm); + } +} diff --git a/src/security/core/tests/CMakeLists.txt b/src/security/core/tests/CMakeLists.txt new file mode 100644 index 0000000..e002ba4 --- /dev/null +++ b/src/security/core/tests/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# Copyright(c) 2006 to 2019 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +include (GenerateExportHeader) +include (CUnit) + +set(security_core_test_sources + "tc_fsm.c" + ) + +add_definitions(-DDDSI_INCLUDE_SECURITY) +add_cunit_executable(cunit_security_core ${security_core_test_sources}) + +target_include_directories( + cunit_security_core PRIVATE + "$" + "$>" + "$>" + "$>" + "$" + "$" + "$" + ) + + +target_link_libraries(cunit_security_core PRIVATE ddsc security_api) +target_include_directories(cunit_security_core PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + + + +# configure_file("config_env.h.in" "config_env.h") \ No newline at end of file diff --git a/src/security/core/tests/tc_fsm.c b/src/security/core/tests/tc_fsm.c new file mode 100644 index 0000000..442b921 --- /dev/null +++ b/src/security/core/tests/tc_fsm.c @@ -0,0 +1,766 @@ +#include "dds/security/core/dds_security_fsm.h" +#include "dds/ddsrt/sync.h" +#include "dds/ddsrt/misc.h" +#include +#include +#include "CUnit/CUnit.h" +#include "CUnit/Test.h" + +#define CHECK_BIT(var, pos) ((var) & (1<<(pos))) + +#define FSM_AUTH_ARG 10 + +#define DB_TC_PRINT_DEBUG (false) + +static struct dds_security_fsm *fsm_auth; +static struct dds_security_fsm *fsm_test; +static struct dds_security_fsm *fsm_timeout; +static struct dds_security_fsm *fsm_timeout2; +static struct dds_security_fsm *fsm_timeout3; + +static uint32_t visited_auth = 0; +static uint32_t visited_test = 0; +static uint32_t visited_timeout = 0; + +uint32_t correct_fsm = 0; +uint32_t correct_arg = 0; + +uint32_t correct_fsm_timeout = 0; +uint32_t correct_arg_timeout = 0; + +static ddsrt_cond_t stop_timeout_cond; +static ddsrt_mutex_t stop_timeout_cond_mutex; +static uint32_t stop_timeout_cond_cnt = 0; +static int validate_remote_identity_first = 1; +static int begin_handshake_reply_first = 1; +static int do_stuff_counter = 0; +static int do_other_stuff_counter = 0; +/* + * Authentication State Machine properties and methods + */ +typedef enum { + VALIDATION_PENDING_RETRY, + VALIDATION_FAILED, + VALIDATION_OK, + VALIDATION_OK_FINAL_MESSAGE, + VALIDATION_PENDING_HANDSHAKE_MESSAGE, + VALIDATION_PENDING_HANDSHAKE_REQUEST, + PluginReturn_MAX +} PluginReturn; + +static PluginReturn validate_remote_identity(void) { + + if (DB_TC_PRINT_DEBUG) { + printf("validate_remote_identity - %d\n", validate_remote_identity_first); + } + if (validate_remote_identity_first) { + validate_remote_identity_first = 0; + return VALIDATION_PENDING_RETRY; + } + return VALIDATION_PENDING_HANDSHAKE_MESSAGE; +} + +static PluginReturn begin_handshake_reply(void) { + + if (DB_TC_PRINT_DEBUG) { + printf("begin_handshake_reply - %d\n", begin_handshake_reply_first); + } + if (begin_handshake_reply_first) { + begin_handshake_reply_first = 0; + return VALIDATION_PENDING_RETRY; + } + return VALIDATION_OK_FINAL_MESSAGE; +} + +static PluginReturn get_shared_secret(void) { + return VALIDATION_OK; +} + +/* State actions. */ +static void fsm_validate_remote_identity(struct dds_security_fsm *fsm, void *arg) { + PluginReturn ret; + + DDSRT_UNUSED_ARG(arg); + + ret = validate_remote_identity(); + + if (DB_TC_PRINT_DEBUG) { + printf("[%p] State %s (ret %d)\n", fsm, __FUNCTION__, (int) ret); + } + + dds_security_fsm_dispatch(fsm, (int32_t) ret); +} + +static void fsm_begin_handshake_reply(struct dds_security_fsm *fsm, void *arg) { + PluginReturn ret; + + DDSRT_UNUSED_ARG(arg); + + ret = begin_handshake_reply(); + if (ret == VALIDATION_OK_FINAL_MESSAGE) { + ret = get_shared_secret(); + } + + if (DB_TC_PRINT_DEBUG) { + printf("[%p] State %s (ret %d)\n", fsm, __FUNCTION__, (int) ret); + } + + dds_security_fsm_dispatch(fsm, (int32_t) ret); +} + +/* A few states from the handshake state-machine. */ +static dds_security_fsm_state StateValidateRemoteIdentity = { + fsm_validate_remote_identity, 0}; +static dds_security_fsm_state StateValRemIdentityRetryWait = {NULL, 100000000}; +static dds_security_fsm_state StateHandshakeInitMessageWait = {NULL, 0}; +static dds_security_fsm_state StateBeginHandshakeReply = {fsm_begin_handshake_reply, 0}; +static dds_security_fsm_state StateBeginHsReplyWait = {NULL, 100000000}; + +static void a(struct dds_security_fsm *fsm, void *arg) { + int *fsm_arg; + + if (DB_TC_PRINT_DEBUG) { + printf("[%p] Transition %s\n", fsm, __FUNCTION__); + } + + if (arg != NULL) { + fsm_arg = (int *) arg; + + if (*fsm_arg == FSM_AUTH_ARG) { + correct_arg = 1; + } else { + correct_arg = 0; + } + } + + if (fsm == fsm_auth) { + correct_fsm = 1; + } else { + correct_fsm = 0; + } + visited_auth |= 1UL << 0; +} + +static void b(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("[%p] Transition %s\n", fsm, __FUNCTION__); + visited_auth |= 1UL << 1; +} + +static void c(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("[%p] Transition %s\n", fsm, __FUNCTION__); + visited_auth |= 1UL << 2; +} + +static void d(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("[%p] Transition %s\n", fsm, __FUNCTION__); + visited_auth |= 1UL << 3; +} + +static void e(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("[%p] Transition %s\n", fsm, __FUNCTION__); + visited_auth |= 1UL << 4; +} + +static void f(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("[%p] Transition %s\n", fsm, __FUNCTION__); + visited_auth |= 1UL << 5; +} + +static void g(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("[%p] Transition %s\n", fsm, __FUNCTION__); + visited_auth |= 1UL << 6; +} + +static void h(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("[%p] Transition %s\n", fsm, __FUNCTION__); + visited_auth |= 1UL << 7; +} + +#define SHM_MSG_RECEIVED (PluginReturn_MAX + 1) + +/* + * .--. + * |##|--------------------------------------. + * '--' a() v + * .----------------------------------------------------. + * | StateValidateRemoteIdentity | + * |----------------------------------------------------| + * .------>| fsm_validate_remote_identity() | + * | | - dispatch VALIDATION_PENDING_RETRY | + * 100ms| | - dispatch VALIDATION_PENDING_HANDSHAKE_MESSAGE | + * d() | '----------------------------------------------------' + * | VALIDATION_PENDING_RETRY| | VALIDATION_PENDING_HANDSHAKE_MESSAGE + * | b() | | c() + * | | | + * .------------------------------. | | .-------------------------------. + * | StateValRemIdentityRetryWait | | | | StateHandshakeInitMessageWait | + * |------------------------------|<----------' '------>|-------------------------------| + * '------------------------------' '-------------------------------' + * SHM_MSG_RECEIVED | + * e() | + * v + * .----------------------------------------. + * VALIDATION_PENDING_RETRY | StateBeginHandshakeReply | + * f() |----------------------------------------| + * .-------------------------| fsm_begin_handshake_reply() | + * | | - dispatch VALIDATION_PENDING_RETRY | + * v | - dispatch VALIDATION_OK | + * .-----------------------. 100ms '----------------------------------------' + * | StateBeginHsReplyWait | h() ^ VALIDATION_OK | + * |-----------------------|-----------------------' g() | + * '-----------------------' v + * .-. + * '-' + */ +dds_security_fsm_transition HandshakeTransistions[] = + {{NULL, DDS_SECURITY_FSM_EVENT_AUTO, a, &StateValidateRemoteIdentity}, // NULL state is the start state + {&StateValidateRemoteIdentity, VALIDATION_PENDING_RETRY, b, + &StateValRemIdentityRetryWait}, + {&StateValidateRemoteIdentity, + VALIDATION_PENDING_HANDSHAKE_MESSAGE, c, + &StateHandshakeInitMessageWait}, + {&StateValRemIdentityRetryWait, + DDS_SECURITY_FSM_EVENT_TIMEOUT, d, &StateValidateRemoteIdentity}, + { + &StateHandshakeInitMessageWait, SHM_MSG_RECEIVED, e, + &StateBeginHandshakeReply}, + {&StateBeginHandshakeReply, + VALIDATION_PENDING_RETRY, f, &StateBeginHsReplyWait}, + { + &StateBeginHandshakeReply, VALIDATION_OK, g, NULL}, // Reaching NULL means end of state-diagram + {&StateBeginHsReplyWait, DDS_SECURITY_FSM_EVENT_TIMEOUT, h, + &StateBeginHandshakeReply},}; + +/* + * Example State Machine properties and methods + */ +typedef enum { + eventX, eventY, eventZ, +} test_events; + +/* The functions called from the state-machine. */ +static void doStart(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("Transition %s\n", __FUNCTION__); + visited_test |= 1UL << 0; +} + +static void doRestart(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("Transition %s\n", __FUNCTION__); + visited_test |= 1UL << 1; +} + +static void doEventStuff(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) + printf("Transition %s\n", __FUNCTION__); + visited_test |= 1UL << 4; +} + +static void doStuff(struct dds_security_fsm *fsm, void *arg) { + + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + + if (DB_TC_PRINT_DEBUG) { + printf("Transition %s - %d\n", __FUNCTION__, do_stuff_counter); + } + visited_test |= 1UL << 2; + + if (do_stuff_counter == 0) { + dds_security_fsm_dispatch(fsm, eventZ); + } else if (do_stuff_counter == 2) { + dds_security_fsm_dispatch(fsm, eventY); + } + ++do_stuff_counter; +} + +static void doOtherStuff(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + + if (DB_TC_PRINT_DEBUG) { + printf("Transition %s - %d\n", __FUNCTION__, do_other_stuff_counter); + } + visited_test |= 1UL << 3; + if (do_other_stuff_counter == 0) { + dds_security_fsm_dispatch(fsm, DDS_SECURITY_FSM_EVENT_AUTO); + } + if (do_other_stuff_counter == 1) { + dds_security_fsm_dispatch(fsm, eventY); + } else if (do_other_stuff_counter == 2) { + dds_security_fsm_dispatch(fsm, eventX); + } + ++do_other_stuff_counter; +} + +dds_security_fsm_state StateA = {doStuff, 0}; +dds_security_fsm_state StateB = {doStuff, 100000000}; +dds_security_fsm_state StateC = {NULL, 0}; +dds_security_fsm_state StateD = {doOtherStuff, 0}; + +dds_security_fsm_transition transitions[] = { + {NULL, DDS_SECURITY_FSM_EVENT_AUTO, doStart, &StateA}, // NULL state is the start state + {&StateA, eventZ, NULL, &StateB}, + {&StateA, eventY, doOtherStuff, &StateC}, + {&StateB, eventX, NULL, NULL}, // Reaching NULL means end of state-diagram + {&StateB, eventZ, doRestart, &StateA}, + {&StateC, DDS_SECURITY_FSM_EVENT_AUTO, + doEventStuff, &StateD}, + {&StateD, eventY, doEventStuff, &StateD}, + { + &StateD, eventX, doStuff, NULL}, // Reaching NULL means end of state-diagram +}; + +/* + * Timeout State Machine properties and methods + */ +typedef enum { + eventToTimeout, eventToEnd, +} timeout_events; + +/* The functions callld from the state-machine. */ +static void doInterupt(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) { + printf("Transition %s\n", __FUNCTION__); + } + visited_timeout |= 1UL << 0; +} + +static void doTimeout(struct dds_security_fsm *fsm, void *arg) { + dds_duration_t delay4 = 4 * DDS_NSECS_IN_SEC; + + DDSRT_UNUSED_ARG(arg); + + if (DB_TC_PRINT_DEBUG) { + printf("Transition >>>> %s %d\n", __FUNCTION__, stop_timeout_cond_cnt); + } + visited_timeout |= 1UL << 1; + + stop_timeout_cond_cnt++; + ddsrt_mutex_lock(&stop_timeout_cond_mutex); + (void) ddsrt_cond_waitfor(&stop_timeout_cond, &stop_timeout_cond_mutex, + delay4); + ddsrt_mutex_unlock(&stop_timeout_cond_mutex); + stop_timeout_cond_cnt--; + + if (DB_TC_PRINT_DEBUG) { + printf("Transition <<<< %s %d\n", __FUNCTION__, stop_timeout_cond_cnt); + } + + dds_security_fsm_dispatch(fsm, eventToTimeout); +} + +static void TimeoutCallback(struct dds_security_fsm *fsm, void *arg) { + int *fsm_arg; + + if (DB_TC_PRINT_DEBUG) { + printf("TimeoutCallback\n"); + } + + visited_timeout |= 1UL << 2; + + if (arg != NULL) { + fsm_arg = (int *) arg; + + if (*fsm_arg == FSM_AUTH_ARG) { + correct_arg_timeout = 1; + } else { + correct_arg_timeout = 0; + } + } + if (fsm == fsm_timeout) { + correct_fsm_timeout = 1; + } else { + correct_fsm_timeout = 0; + } +} + +static void TimeoutCallback2(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + if (DB_TC_PRINT_DEBUG) { + printf("TimeoutCallback2\n"); + } + visited_timeout |= 1UL << 3; +} + +dds_security_fsm_state StateTimeout = {doTimeout, 0}; +dds_security_fsm_state StateInterupt = {doInterupt, 0}; + +dds_security_fsm_transition timeout_transitions[] = {{NULL, DDS_SECURITY_FSM_EVENT_AUTO, NULL, + &StateTimeout}, // NULL state is the start state + {&StateTimeout, eventToTimeout, NULL, &StateInterupt}, + {&StateInterupt, + eventToEnd, NULL, NULL}, // Reaching NULL means end of state-diagram +}; + +static dds_time_t time0 = 0; +static dds_time_t time1 = 0; +static dds_time_t time2 = 0; +static dds_time_t time3 = 0; + +static void StateParTime1(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + time1 = dds_time(); +} + +static void StateParTime2(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + time2 = dds_time(); +} + +static void StateParTime3(struct dds_security_fsm *fsm, void *arg) { + DDSRT_UNUSED_ARG(fsm); + DDSRT_UNUSED_ARG(arg); + time3 = dds_time(); +} + +dds_security_fsm_state StateParTimeout1 = {NULL, DDS_NSECS_IN_SEC}; +dds_security_fsm_state StateParTimeout2 = {NULL, 2 * DDS_NSECS_IN_SEC}; +dds_security_fsm_state StateParTimeout3 = {NULL, DDS_NSECS_IN_SEC}; + +dds_security_fsm_transition par_timeout_transitions_1[] = {{NULL, DDS_SECURITY_FSM_EVENT_AUTO, + NULL, &StateParTimeout1}, // NULL state is the start state + {&StateParTimeout1, DDS_SECURITY_FSM_EVENT_TIMEOUT, &StateParTime1, NULL}, // Reaching NULL means end of state-diagram +}; + +dds_security_fsm_transition par_timeout_transitions_2[] = {{NULL, DDS_SECURITY_FSM_EVENT_AUTO, + NULL, &StateParTimeout2}, // NULL state is the start state + {&StateParTimeout2, DDS_SECURITY_FSM_EVENT_TIMEOUT, &StateParTime2, NULL}, // Reaching NULL means end of state-diagram +}; + +dds_security_fsm_transition par_timeout_transitions_3[] = {{NULL, DDS_SECURITY_FSM_EVENT_AUTO, + NULL, &StateParTimeout3}, // NULL state is the start state + {&StateParTimeout3, DDS_SECURITY_FSM_EVENT_TIMEOUT, &StateParTime3, NULL}, // Reaching NULL means end of state-diagram +}; + +int fsm_arg = FSM_AUTH_ARG; +dds_time_t delay1 = DDS_NSECS_IN_SEC; +dds_time_t delay2 = 2 * DDS_NSECS_IN_SEC; +dds_time_t delay30 = 30 * DDS_NSECS_IN_SEC; +int timeout; +static const dds_duration_t msec100 = 100 * DDS_NSECS_IN_MSEC; + +static void init_testcase(void) { + (void) ddsrt_mutex_init(&stop_timeout_cond_mutex); + (void) ddsrt_cond_init(&stop_timeout_cond); + +} + +static void fini_testcase(void) { + ddsrt_cond_destroy(&stop_timeout_cond); + ddsrt_mutex_destroy(&stop_timeout_cond_mutex); +} + +CU_Test(ddssec_fsm, create, .init = init_testcase, .fini = fini_testcase) { + + /* + * Test single running state machine + * Check creation of a single State Machine + */ + + fsm_auth = dds_security_fsm_create(NULL, HandshakeTransistions, + sizeof(HandshakeTransistions) / sizeof(HandshakeTransistions[0]), + &fsm_arg); + CU_ASSERT_FATAL(fsm_auth != NULL) + + // set a delay that doesn't expire. Should be terminate when fsm is freed. + dds_security_fsm_set_timeout(fsm_auth, TimeoutCallback, delay30); + dds_security_fsm_start(fsm_auth); + + /** + * Check the result of one running State Machine + */ + + // Wait for the last state to occur + timeout = 100; /* 10 sec */ + while ((dds_security_fsm_current_state(fsm_auth) != &StateHandshakeInitMessageWait) + && (timeout > 0)) { + dds_sleepfor(msec100); + timeout--; + } + CU_ASSERT(timeout > 0); + dds_security_fsm_dispatch(fsm_auth, SHM_MSG_RECEIVED); + + timeout = 100; /* 10 sec */ + while ((dds_security_fsm_current_state(fsm_auth) != NULL) && (timeout > 0)) { + dds_sleepfor(msec100); + timeout--; + } + + CU_ASSERT(timeout > 0); + CU_ASSERT( + CHECK_BIT(visited_auth, 0) && CHECK_BIT(visited_auth, 1) && CHECK_BIT(visited_auth, 2) && + CHECK_BIT(visited_auth, 3) && CHECK_BIT(visited_auth, 4) && CHECK_BIT(visited_auth, 5) && + CHECK_BIT(visited_auth, 6) && CHECK_BIT(visited_auth, 7)); + + /* + * "Check correct callback parameter passing (from fsm to user defined methods) "); + */ + CU_ASSERT(correct_arg && correct_fsm); + dds_security_fsm_free(fsm_auth); + + /* Check whether timeout callback has NOT been invoked */ + CU_ASSERT(visited_timeout == 0); +} + +/* + * Test multiple (2) running state machines + */ +CU_Test(ddssec_fsm, multiple, .init = init_testcase, .fini = fini_testcase) { + + /*Check creation of multiple (2) State Machines*/ + validate_remote_identity_first = 0; + begin_handshake_reply_first = 0; + visited_auth = 0; + + fsm_auth = dds_security_fsm_create(NULL, HandshakeTransistions, + sizeof(HandshakeTransistions) / sizeof(HandshakeTransistions[0]), NULL); + fsm_test = dds_security_fsm_create(NULL, transitions, + sizeof(transitions) / sizeof(transitions[0]), NULL); + CU_ASSERT_FALSE(fsm_auth == NULL || fsm_test == NULL); + dds_security_fsm_start(fsm_auth); + dds_security_fsm_start(fsm_test); + + /*Check the results of multiple running State Machines */ + + // Wait for the last state to occur + timeout = 100; /* 10 sec */ + while ((dds_security_fsm_current_state(fsm_auth) != &StateHandshakeInitMessageWait) + && (timeout > 0)) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + CU_ASSERT_FATAL(timeout > 0); + timeout = 100; /* 10 sec */ + dds_security_fsm_dispatch(fsm_auth, SHM_MSG_RECEIVED); + while ((dds_security_fsm_current_state(fsm_auth) != NULL) && (timeout > 0)) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + CU_ASSERT_FATAL(timeout > 0); + // not all bits are set since we're running the state machine a second time + CU_ASSERT_FATAL( + CHECK_BIT(visited_auth, 0) && !CHECK_BIT(visited_auth, 1) && CHECK_BIT(visited_auth, 2) && + !CHECK_BIT(visited_auth, 3) && CHECK_BIT(visited_auth, 4) && !CHECK_BIT(visited_auth, 5) && + CHECK_BIT(visited_auth, 6) && !CHECK_BIT(visited_auth, 7)); + + timeout = 100; /* 10 sec */ + // Wait for the last state to occur + while ((dds_security_fsm_current_state(fsm_test) != NULL) && timeout > 0) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + CU_ASSERT_FATAL(timeout > 0); + CU_ASSERT_FATAL( + CHECK_BIT(visited_test, 0) && CHECK_BIT(visited_test, 1) && CHECK_BIT(visited_test, 2) && + CHECK_BIT(visited_test, 3)); + dds_security_fsm_free(fsm_auth); + dds_security_fsm_free(fsm_test); + +} + +/** + * Check creation of State Machine for timeout purposes + */ +CU_Test(ddssec_fsm, timeout, .init = init_testcase, .fini = fini_testcase) { + + /* + * Test timeout monitoring of state machines + */ + fsm_timeout = dds_security_fsm_create(NULL, timeout_transitions, + sizeof(timeout_transitions) / sizeof(timeout_transitions[0]), &fsm_arg); + CU_ASSERT(fsm_timeout != NULL); + + dds_security_fsm_set_timeout(fsm_timeout, TimeoutCallback, delay1); + dds_security_fsm_start(fsm_timeout); + + /*Check the result of the running State Machine for timeout purposes*/ + + // Wait for the last state to occur + timeout = 100; /* 10 sec */ + while ((dds_security_fsm_current_state(fsm_timeout) != &StateInterupt) && (timeout > 0)) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + CU_ASSERT(timeout > 0); + CU_ASSERT( + CHECK_BIT(visited_timeout, 0) && CHECK_BIT(visited_timeout, 1) && CHECK_BIT(visited_timeout, 2)); + CU_ASSERT(correct_arg_timeout && correct_fsm_timeout); + + dds_security_fsm_free(fsm_timeout); + +} + +/** + * Check the double global timeout + */ +CU_Test(ddssec_fsm, double_timeout, .init = init_testcase, .fini = fini_testcase) { + + visited_timeout = 0; + fsm_timeout = dds_security_fsm_create(NULL, timeout_transitions, + sizeof(timeout_transitions) / sizeof(timeout_transitions[0]), &fsm_arg); + CU_ASSERT(fsm_timeout != NULL); + fsm_timeout2 = dds_security_fsm_create(NULL, timeout_transitions, + sizeof(timeout_transitions) / sizeof(timeout_transitions[0]), &fsm_arg); + CU_ASSERT(fsm_timeout2 != NULL); + dds_security_fsm_set_timeout(fsm_timeout, TimeoutCallback, delay1); + dds_security_fsm_set_timeout(fsm_timeout2, TimeoutCallback2, delay2); + dds_security_fsm_start(fsm_timeout); + dds_security_fsm_start(fsm_timeout2); + timeout = 100; /* 10 sec */ + while ((CHECK_BIT(visited_timeout, 2) == 0) && (timeout > 0)) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + CU_ASSERT(CHECK_BIT(visited_timeout, 2)); + dds_security_fsm_cleanup(fsm_timeout); + timeout = 100; /* 10 sec */ + while ((CHECK_BIT(visited_timeout, 3) == 0) && (timeout > 0)) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + CU_ASSERT(CHECK_BIT(visited_timeout, 3)); + dds_security_fsm_free(fsm_timeout); + dds_security_fsm_free(fsm_timeout2); + ddsrt_mutex_lock(&stop_timeout_cond_mutex); + ddsrt_cond_signal(&stop_timeout_cond); + ddsrt_mutex_unlock(&stop_timeout_cond_mutex); + +} + +/** + * Check parallel state timeouts + */ +CU_Test(ddssec_fsm, parallel_timeout, .init = init_testcase, .fini = fini_testcase) { + visited_timeout = 0; + fsm_timeout = dds_security_fsm_create(NULL, par_timeout_transitions_1, + sizeof(par_timeout_transitions_1) / sizeof(par_timeout_transitions_1[0]), + &fsm_arg); + CU_ASSERT(fsm_timeout != NULL); + fsm_timeout2 = dds_security_fsm_create(NULL, par_timeout_transitions_2, + sizeof(par_timeout_transitions_2) / sizeof(par_timeout_transitions_2[0]), + &fsm_arg); + CU_ASSERT(fsm_timeout2 != NULL); + fsm_timeout3 = dds_security_fsm_create(NULL, par_timeout_transitions_3, + sizeof(par_timeout_transitions_3) / sizeof(par_timeout_transitions_3[0]), + &fsm_arg); + CU_ASSERT(fsm_timeout3 != NULL); + dds_duration_t delta1; + dds_duration_t delta2; + dds_duration_t delta3; + + time0 = dds_time(); + dds_security_fsm_start(fsm_timeout); + dds_security_fsm_start(fsm_timeout2); + dds_security_fsm_start(fsm_timeout3); + + /* Wait for both to end. */ + timeout = 100; /* 10 sec */ + /* First, they have to be started. */ + while (((dds_security_fsm_current_state(fsm_timeout) == NULL) + || (dds_security_fsm_current_state(fsm_timeout2) == NULL) + || (dds_security_fsm_current_state(fsm_timeout3) == NULL)) && (timeout > 0)) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + /* Then, they have to have ended. */ + while (((dds_security_fsm_current_state(fsm_timeout) != NULL) + || (dds_security_fsm_current_state(fsm_timeout2) != NULL) + || (dds_security_fsm_current_state(fsm_timeout3) != NULL)) && (timeout > 0)) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + + /* + * There should be about 1 second difference between all times: + * time1 = time0 + 1 + * time2 = time0 + 2 + * time3 = time0 + 1 + */ + delta1 = time1 - time0; + delta2 = time2 - time0; + delta3 = time3 - time0; + printf("Time0 %" PRId64 "\n", time0); + printf("Time1 %" PRId64 "\n", time1); + printf("Time2 %" PRId64 "\n", time2); + printf("Time3 %" PRId64 "\n", time3); + printf("Delta1 %" PRId64 "\n", delta1); + printf("Delta2 %" PRId64 "\n", delta2); + printf("Delta3 %" PRId64 "\n", delta3); + CU_ASSERT(delta1 > 750 * DDS_NSECS_IN_MSEC); + CU_ASSERT(delta1 < 1250 * DDS_NSECS_IN_MSEC); + CU_ASSERT(delta2 > 1750 * DDS_NSECS_IN_MSEC); + CU_ASSERT(delta2 < 2250 * DDS_NSECS_IN_MSEC); + CU_ASSERT(delta3 > 750 * DDS_NSECS_IN_MSEC); + CU_ASSERT(delta3 < 1250 * DDS_NSECS_IN_MSEC); + + dds_security_fsm_free(fsm_timeout); + dds_security_fsm_free(fsm_timeout2); + dds_security_fsm_free(fsm_timeout3); + +} + +/** + * Delete with event timeout + */ +CU_Test(ddssec_fsm, delete_with_timeout, .init = init_testcase, .fini = fini_testcase) { + + fsm_timeout = dds_security_fsm_create(NULL, timeout_transitions, + sizeof(timeout_transitions) / sizeof(timeout_transitions[0]), &fsm_arg); + CU_ASSERT (fsm_timeout != NULL); + visited_timeout = 0; + dds_security_fsm_start(fsm_timeout); + + /* Wait until we're in the timeout function. */ + timeout = 100; /* 10 sec */ + while ((visited_timeout == 0) && (timeout > 0)) { + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + timeout--; + } + + dds_security_fsm_free(fsm_timeout); + dds_sleepfor(100 * DDS_NSECS_IN_MSEC); + + /* Just for safety to be certain that the condition isn't used anymore before destroying it. */ + while (stop_timeout_cond_cnt > 0) { + dds_time_t d = DDS_NSECS_IN_SEC; + ddsrt_cond_signal(&stop_timeout_cond); + dds_sleepfor(d); + } + + +} +