/*
 * Copyright(c) 2006 to 2018 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 <stdio.h>
#include <stdbool.h>
#include "CUnit/CUnit.h"
#include "CUnit/Test.h"

#include "dds/ddsrt/sync.h"
#include "dds/ddsrt/misc.h"
#include "dds/dds.h"
#include "dds__types.h"
#include "dds__entity.h"
#include "dds/security/core/dds_security_fsm.h"

#define CHECK_BIT(var, pos) ((var) & (1<<(pos)))
#define FSM_AUTH_ARG 10

static dds_entity_t g_participant = 0;
static ddsrt_mutex_t g_lock;
static struct dds_security_fsm_control *g_fsm_control = NULL;

#define DO_SIMPLE(name, var, bit) static void name(struct dds_security_fsm *fsm, void *arg) { \
  DDSRT_UNUSED_ARG(fsm); \
  DDSRT_UNUSED_ARG(arg); \
  printf("Transition %s\n", __FUNCTION__); \
  ddsrt_mutex_lock(&g_lock); \
  visited_##var |= 1u << (bit); \
  ddsrt_mutex_unlock(&g_lock); \
}

typedef enum {
  VALIDATION_PENDING_RETRY,
  VALIDATION_FAILED,
  VALIDATION_OK,
  VALIDATION_OK_FINAL_MESSAGE,
  VALIDATION_PENDING_HANDSHAKE_MESSAGE,
  VALIDATION_PENDING_HANDSHAKE_REQUEST,
  plugin_ret_MAX
} plugin_ret;

static struct dds_security_fsm *fsm_auth;
static uint32_t visited_auth = 0;
static uint32_t correct_fsm = 0;
static uint32_t correct_arg = 0;
static int validate_remote_identity_first = 1;
static int begin_handshake_reply_first = 1;


static plugin_ret validate_remote_identity(void)
{
  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 plugin_ret begin_handshake_reply(void)
{
  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 plugin_ret get_shared_secret(void)
{
  return VALIDATION_OK;
}

/* State actions */
static void fsm_validate_remote_identity(struct dds_security_fsm *fsm, void *arg)
{
  DDSRT_UNUSED_ARG(arg);
  plugin_ret ret = validate_remote_identity();
  printf("[%p] State %s (ret %d)\n", fsm, __FUNCTION__, (int) ret);
  dds_security_fsm_dispatch(fsm, (int32_t) ret, false);
}

static void fsm_begin_handshake_reply(struct dds_security_fsm *fsm, void *arg)
{
  DDSRT_UNUSED_ARG(arg);
  plugin_ret ret = begin_handshake_reply();
  if (ret == VALIDATION_OK_FINAL_MESSAGE)
    ret = get_shared_secret();
  printf("[%p] State %s (ret %d)\n", fsm, __FUNCTION__, (int) ret);
  dds_security_fsm_dispatch(fsm, (int32_t) ret, false);
}

/* 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 state_beginHandshakeReply = {fsm_begin_handshake_reply, 0};
static dds_security_fsm_state state_beginHsReplyWait = {NULL, 100000000};

static void a(struct dds_security_fsm *fsm, void *arg)
{
  printf("[%p] Transition %s\n", fsm, __FUNCTION__);
  ddsrt_mutex_lock (&g_lock);
  if (arg != NULL)
    correct_arg = *((int *)arg) == FSM_AUTH_ARG ? 1 : 0;
  correct_fsm = (fsm == fsm_auth) ? 1 : 0;
  visited_auth |= 1u << 0;
  ddsrt_mutex_unlock (&g_lock);
}

DO_SIMPLE(b, auth, 1)
DO_SIMPLE(c, auth, 2)
DO_SIMPLE(d, auth, 3)
DO_SIMPLE(e, auth, 4)
DO_SIMPLE(f, auth, 5)
DO_SIMPLE(g, auth, 6)
DO_SIMPLE(h, auth, 7)

#define SHM_MSG_RECEIVED (plugin_ret_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    |        state_beginHandshakeReply        |
 *          f()                         |----------------------------------------|
 *            .-------------------------| fsm_begin_handshake_reply()            |
 *            |                         |    - dispatch VALIDATION_PENDING_RETRY |
 *            v                         |    - dispatch VALIDATION_OK            |
 * .-----------------------.    100ms   '----------------------------------------'
 * | state_beginHsReplyWait |    h()                ^        VALIDATION_OK |
 * |-----------------------|-----------------------'        g()           |
 * '-----------------------'                                              v
 *                                                                       .-.
 *                                                                       '-'
 */
static 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, &state_beginHandshakeReply},
    {&state_beginHandshakeReply,      VALIDATION_PENDING_RETRY,             f, &state_beginHsReplyWait},
    {&state_beginHandshakeReply,      VALIDATION_OK,                        g, NULL}, // Reaching NULL means end of state-diagram
    {&state_beginHsReplyWait,         DDS_SECURITY_FSM_EVENT_TIMEOUT,       h, &state_beginHandshakeReply}
};
static const uint32_t HandshakeTransistionsSize = sizeof(HandshakeTransistions)/sizeof(HandshakeTransistions[0]);


/* Example State Machine properties and methods */
typedef enum {
    eventX, eventY, eventZ,
} test_events;

static struct dds_security_fsm *fsm_test;
static uint32_t visited_test = 0;
static int do_stuff_counter = 0;
static int do_other_stuff_counter = 0;

DO_SIMPLE(do_start, test, 0)
DO_SIMPLE(do_restart, test, 1)
DO_SIMPLE(do_event_stuff, test, 4)

static void do_stuff(struct dds_security_fsm *fsm, void *arg)
{
  DDSRT_UNUSED_ARG(fsm);
  DDSRT_UNUSED_ARG(arg);
  printf("Transition %s - %d\n", __FUNCTION__, do_stuff_counter);
  ddsrt_mutex_lock (&g_lock);
  visited_test |= 1u << 2;
  ddsrt_mutex_unlock (&g_lock);
  if (do_stuff_counter < 2)
    dds_security_fsm_dispatch(fsm, eventZ, false);
  else if (do_stuff_counter == 2)
    dds_security_fsm_dispatch(fsm, eventY, false);
  ++do_stuff_counter;
}

static void do_other_stuff(struct dds_security_fsm *fsm, void *arg)
{
  DDSRT_UNUSED_ARG(fsm);
  DDSRT_UNUSED_ARG(arg);
  printf("Transition %s - %d\n", __FUNCTION__, do_other_stuff_counter);
  ddsrt_mutex_lock (&g_lock);
  visited_test |= 1u << 3;
  ddsrt_mutex_unlock (&g_lock);
  if (do_other_stuff_counter == 0)
    dds_security_fsm_dispatch(fsm, DDS_SECURITY_FSM_EVENT_AUTO, false);
  else if (do_other_stuff_counter == 1)
    dds_security_fsm_dispatch(fsm, eventY, false);
  else if (do_other_stuff_counter == 2)
    dds_security_fsm_dispatch(fsm, eventX, false);
  ++do_other_stuff_counter;
}

static dds_security_fsm_state state_a = {do_stuff, 0};
static dds_security_fsm_state state_b = {do_stuff, 100000000};
static dds_security_fsm_state state_c = {NULL, 0};
static dds_security_fsm_state state_d = {do_other_stuff, 0};

static dds_security_fsm_transition Transitions[] = {
    {NULL,     DDS_SECURITY_FSM_EVENT_AUTO, do_start,       &state_a}, // NULL state is the start state
    {&state_a, eventZ,                      NULL,           &state_b},
    {&state_a, eventY,                      do_other_stuff, &state_c},
    {&state_b, eventX,                      NULL,           NULL}, // Reaching NULL means end of state-diagram
    {&state_b, eventZ,                      do_restart,     &state_a},
    {&state_c, DDS_SECURITY_FSM_EVENT_AUTO, do_event_stuff, &state_d},
    {&state_d, eventY,                      do_event_stuff, &state_d},
    {&state_d, eventX,                      do_stuff,       NULL}, // Reaching NULL means end of sttimeoutate-diagram
};
static const uint32_t TransitionsSize = sizeof(Transitions)/sizeof(Transitions[0]);


/* Timeout State Machine properties and methods */
typedef enum {
    event_to_timeout, event_to_interrupt, event_to_end,
} timeout_events;

struct fsm_timeout_arg {
  int id;
};

static struct dds_security_fsm *fsm_timeout;
static uint32_t visited_timeout = 0;
static uint32_t correct_fsm_timeout = 0;
static uint32_t correct_arg_timeout = 0;
static struct fsm_timeout_arg fsm_arg = { .id = FSM_AUTH_ARG };

DO_SIMPLE(do_interrupt, timeout, 0)
DO_SIMPLE(timeout_cb2, timeout, 3)

static void do_timeout(struct dds_security_fsm *fsm, void *arg)
{
  DDSRT_UNUSED_ARG(arg);
  printf("Transition >>>> %s\n", __FUNCTION__);
  ddsrt_mutex_lock (&g_lock);
  visited_timeout |= 1u << 1;
  ddsrt_mutex_unlock (&g_lock);
  printf("Transition <<<< %s\n", __FUNCTION__);
  dds_security_fsm_dispatch(fsm, event_to_timeout, false);
}

static void timeout_cb(struct dds_security_fsm *fsm, void *arg)
{
  struct fsm_timeout_arg *farg = arg;
  printf("timeout_cb\n");
  ddsrt_mutex_lock (&g_lock);
  visited_timeout |= 1u << 2;
  if (farg != NULL)
    correct_arg_timeout = farg->id == FSM_AUTH_ARG ? 1 : 0;
  correct_fsm_timeout =  fsm == fsm_timeout ? 1 : 0;
  ddsrt_mutex_unlock (&g_lock);
}

static dds_security_fsm_state state_initial      = {do_timeout, 0};
static dds_security_fsm_state state_wait_timeout = {NULL, DDS_SECS(4)};
static dds_security_fsm_state state_interrupt    = {do_interrupt, 0};

static const dds_security_fsm_transition timeout_transitions[] = {
    {NULL,                DDS_SECURITY_FSM_EVENT_AUTO,    NULL, &state_initial},  // NULL state is the start state
    {&state_initial,      event_to_timeout,               NULL, &state_wait_timeout},
    {&state_wait_timeout, DDS_SECURITY_FSM_EVENT_TIMEOUT, NULL, &state_interrupt},
    {&state_wait_timeout, event_to_interrupt,             NULL, &state_interrupt},
    {&state_interrupt,    event_to_end,                   NULL, NULL},           // Reaching NULL means end of state-diagram
};
static const uint32_t timeout_transitionsSize = sizeof(timeout_transitions)/sizeof(timeout_transitions[0]);

/* Parallel Timeout State Machines properties and methods */
static struct dds_security_fsm *fsm_timeout1;
static struct dds_security_fsm *fsm_timeout2;
static struct dds_security_fsm *fsm_timeout3;

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 state_par_time1(struct dds_security_fsm *fsm, void *arg)
{
  DDSRT_UNUSED_ARG(fsm);
  DDSRT_UNUSED_ARG(arg);
  time1 = dds_time();
}

static void state_par_time2(struct dds_security_fsm *fsm, void *arg)
{
  DDSRT_UNUSED_ARG(fsm);
  DDSRT_UNUSED_ARG(arg);
  time2 = dds_time();
}

static void state_par_time3(struct dds_security_fsm *fsm, void *arg)
{
  DDSRT_UNUSED_ARG(fsm);
  DDSRT_UNUSED_ARG(arg);
  time3 = dds_time();
}

static dds_security_fsm_state state_par_timeout1 = {NULL, DDS_SECS(1)};
static dds_security_fsm_state state_par_timeout2 = {NULL, DDS_SECS(2)};
static dds_security_fsm_state state_par_timeout3 = {NULL, DDS_SECS(1)};

static dds_security_fsm_transition parallel_timeout_transitions_1[] = {
    {NULL,                DDS_SECURITY_FSM_EVENT_AUTO,    NULL,             &state_par_timeout1}, // NULL state is the startfsm_control_thread state
    {&state_par_timeout1, DDS_SECURITY_FSM_EVENT_TIMEOUT, &state_par_time1, NULL},              // Reaching NULL means end of state-diagram
};
static const uint32_t parallel_timeout_transitionsSize_1 = sizeof(parallel_timeout_transitions_1) / sizeof(parallel_timeout_transitions_1[0]);

static dds_security_fsm_transition parallel_timeout_transitions_2[] = {
    {NULL,                DDS_SECURITY_FSM_EVENT_AUTO,    NULL,             &state_par_timeout2}, // NULL state is the start state
    {&state_par_timeout2, DDS_SECURITY_FSM_EVENT_TIMEOUT, &state_par_time2, NULL},              // Reaching NULL means end of state-diagram
};
static const uint32_t parallel_timeout_transitionsSize_2 = sizeof(parallel_timeout_transitions_2) / sizeof(parallel_timeout_transitions_2[0]);

static dds_security_fsm_transition parallel_timeout_transitions_3[] = {
    {NULL,                DDS_SECURITY_FSM_EVENT_AUTO,    NULL,             &state_par_timeout3}, // NULL state is the start state
    {&state_par_timeout3, DDS_SECURITY_FSM_EVENT_TIMEOUT, &state_par_time3, NULL},              // Reaching NULL means end of state-diagram
};
static const uint32_t parallel_timeout_transitionsSize_3 = sizeof(parallel_timeout_transitions_3) / sizeof(parallel_timeout_transitions_3[0]);

static void fsm_control_init(void)
{
  struct dds_entity *e;
  g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL);
  CU_ASSERT_FATAL(g_participant > 0);
  ddsrt_mutex_init(&g_lock);
  dds_return_t rc = dds_entity_pin(g_participant, &e);
  CU_ASSERT_FATAL(rc == 0);
  g_fsm_control = dds_security_fsm_control_create(&e->m_domain->gv);
  CU_ASSERT_FATAL(g_fsm_control != NULL);
  dds_entity_unpin(e);
  rc = dds_security_fsm_control_start(g_fsm_control, NULL);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
}

static void fsm_control_fini(void)
{
  dds_security_fsm_control_stop(g_fsm_control);
  dds_security_fsm_control_free(g_fsm_control);
  ddsrt_mutex_destroy (&g_lock);
  dds_delete(g_participant);
}

CU_Test(ddssec_fsm, create, .init = fsm_control_init, .fini = fsm_control_fini)
{
  /* Test single running state machine. Check creation of a single State Machine */
  fsm_auth = dds_security_fsm_create(g_fsm_control, HandshakeTransistions, HandshakeTransistionsSize, &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, timeout_cb, DDS_SECS(30));
  dds_security_fsm_start(fsm_auth);

  int n = 100;
  while (dds_security_fsm_current_state(fsm_auth) != &StateHandshakeInitMessageWait && n-- > 0)
    dds_sleepfor(DDS_MSECS(100));
  CU_ASSERT(n > 0);
  dds_security_fsm_dispatch(fsm_auth, SHM_MSG_RECEIVED, false);

  n = 100;
  while (dds_security_fsm_current_state(fsm_auth) != NULL && n-- > 0)
    dds_sleepfor(DDS_MSECS(100));
  CU_ASSERT(n > 0);

  ddsrt_mutex_lock (&g_lock);
  CU_ASSERT(visited_auth == 0xff);
  ddsrt_mutex_unlock (&g_lock);

  /* 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 */
  ddsrt_mutex_lock (&g_lock);
  CU_ASSERT(visited_timeout == 0);
  ddsrt_mutex_unlock (&g_lock);
}

/* Test multiple (2) running state machines */
CU_Test(ddssec_fsm, multiple, .init = fsm_control_init, .fini = fsm_control_fini)
{
  validate_remote_identity_first = 0;
  begin_handshake_reply_first = 0;
  visited_auth = 0;
  visited_test = 0;

  fsm_auth = dds_security_fsm_create(g_fsm_control, HandshakeTransistions, HandshakeTransistionsSize, NULL);
  CU_ASSERT_FATAL(fsm_auth != NULL);

  fsm_test = dds_security_fsm_create(g_fsm_control, Transitions, TransitionsSize, NULL);
  CU_ASSERT_FATAL(fsm_test != NULL);

  dds_security_fsm_start(fsm_auth);
  dds_security_fsm_start(fsm_test);

  /* Check the results of multiple running State Machines */
  int n = 100; /* 10 sec */
  while (dds_security_fsm_current_state(fsm_auth) != &StateHandshakeInitMessageWait && n-- > 0)
    dds_sleepfor(DDS_MSECS(100));
  CU_ASSERT_FATAL(n > 0);

  n = 100; /* 10 sec */
  dds_security_fsm_dispatch(fsm_auth, SHM_MSG_RECEIVED, false);
  while (dds_security_fsm_current_state(fsm_auth) != NULL && n-- > 0)
    dds_sleepfor(DDS_MSECS(100));
  CU_ASSERT_FATAL(n > 0);

  // not all bits are set since we're running the state machine a second time
  ddsrt_mutex_lock (&g_lock);
  CU_ASSERT_FATAL(visited_auth == 0x55);
  ddsrt_mutex_unlock (&g_lock);

  /* Wait for the last state to occur */
  n = 100; /* 10 sec */
  while (dds_security_fsm_current_state(fsm_test) != NULL && n-- > 0)
    dds_sleepfor(DDS_MSECS(100));
  CU_ASSERT_FATAL(n > 0);

  ddsrt_mutex_lock (&g_lock);
  CU_ASSERT(visited_test == 0x1f);
  ddsrt_mutex_unlock (&g_lock);

  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 = fsm_control_init, .fini = fsm_control_fini)
{
  visited_timeout = 0;
  /* Test timeout monitoring of state machines */
  fsm_timeout = dds_security_fsm_create(g_fsm_control, timeout_transitions, timeout_transitionsSize, &fsm_arg);
  CU_ASSERT(fsm_timeout != NULL);
  dds_security_fsm_set_timeout(fsm_timeout, timeout_cb, DDS_SECS(1));
  dds_security_fsm_start(fsm_timeout);

  int n = 100; /* 10 sec */
  ddsrt_mutex_lock (&g_lock);
  while (visited_timeout != 0x7 && n-- > 0)
  {
    ddsrt_mutex_unlock (&g_lock);
    dds_sleepfor(DDS_MSECS(100));
    ddsrt_mutex_lock (&g_lock);
  }
  CU_ASSERT(n > 0);
  CU_ASSERT(visited_timeout == 0x7);
  CU_ASSERT(correct_arg_timeout && correct_fsm_timeout);
  ddsrt_mutex_unlock (&g_lock);
  dds_security_fsm_free(fsm_timeout);
}

/* Check the double global timeout */
CU_Test(ddssec_fsm, double_timeout, .init = fsm_control_init, .fini = fsm_control_fini)
{
  visited_timeout = 0;
  fsm_timeout = dds_security_fsm_create(g_fsm_control, timeout_transitions, timeout_transitionsSize, &fsm_arg);
  CU_ASSERT(fsm_timeout != NULL);

  fsm_timeout2 = dds_security_fsm_create(g_fsm_control, timeout_transitions, timeout_transitionsSize, &fsm_arg);
  CU_ASSERT(fsm_timeout2 != NULL);

  dds_security_fsm_set_timeout(fsm_timeout, timeout_cb, DDS_SECS(1));
  dds_security_fsm_set_timeout(fsm_timeout2, timeout_cb2, DDS_SECS(2));
  dds_security_fsm_start(fsm_timeout);
  dds_security_fsm_start(fsm_timeout2);
  int n = 100;
  ddsrt_mutex_lock (&g_lock);
  while (visited_timeout != 0xf && n-- > 0)
  {
    ddsrt_mutex_unlock (&g_lock);
    dds_sleepfor(DDS_MSECS(100));
    ddsrt_mutex_lock (&g_lock);
  }
  CU_ASSERT(visited_timeout == 0xf);
  ddsrt_mutex_unlock (&g_lock);
  dds_security_fsm_free(fsm_timeout);
  dds_security_fsm_free(fsm_timeout2);
}

/* Check parallel state timeouts */
CU_Test(ddssec_fsm, parallel_timeout, .init = fsm_control_init, .fini = fsm_control_fini)
{
  visited_timeout = 0;

  fsm_timeout1 = dds_security_fsm_create(g_fsm_control, parallel_timeout_transitions_1, parallel_timeout_transitionsSize_1, &fsm_arg);
  CU_ASSERT_FATAL(fsm_timeout1 != NULL);
  fsm_timeout2 = dds_security_fsm_create(g_fsm_control, parallel_timeout_transitions_2, parallel_timeout_transitionsSize_2, &fsm_arg);
  CU_ASSERT_FATAL(fsm_timeout2 != NULL);
  fsm_timeout3 = dds_security_fsm_create(g_fsm_control, parallel_timeout_transitions_3, parallel_timeout_transitionsSize_3, &fsm_arg);
  CU_ASSERT_FATAL(fsm_timeout3 != NULL);

  time0 = dds_time();
  dds_security_fsm_start(fsm_timeout1);
  dds_security_fsm_start(fsm_timeout2);
  dds_security_fsm_start(fsm_timeout3);

  int n = 300;
  while (((dds_security_fsm_current_state(fsm_timeout1) == NULL)
          || (dds_security_fsm_current_state(fsm_timeout2) == NULL)
          || (dds_security_fsm_current_state(fsm_timeout3) == NULL)) && n-- > 0)
  {
    dds_sleepfor(DDS_MSECS(100));
  }
  while (((dds_security_fsm_current_state(fsm_timeout1) != NULL)
          || (dds_security_fsm_current_state(fsm_timeout2) != NULL)
          || (dds_security_fsm_current_state(fsm_timeout3) != NULL)) && n-- > 0)
  {
    dds_sleepfor(DDS_MSECS(100));
  }

  dds_duration_t delta1 = time1 - time0;
  dds_duration_t delta2 = time2 - time0;
  dds_duration_t delta3 = time3 - time0;
  printf("time0 %"PRId64"\n", time0);
  printf("time1 %"PRId64", delta1 %"PRId64"\n", time1, delta1);
  printf("time2 %"PRId64", delta2 %"PRId64"\n", time2, delta2);
  printf("time3 %"PRId64", delta3 %"PRId64"\n", time3, delta3);
  CU_ASSERT(delta1 > DDS_MSECS(750));
  CU_ASSERT(delta1 < DDS_MSECS(1250));
  CU_ASSERT(delta2 > DDS_MSECS(1750));
  CU_ASSERT(delta2 < DDS_MSECS(2250));
  CU_ASSERT(delta3 > DDS_MSECS(750));
  CU_ASSERT(delta3 < DDS_MSECS(1250));

  dds_security_fsm_free(fsm_timeout1);
  dds_security_fsm_free(fsm_timeout2);
  dds_security_fsm_free(fsm_timeout3);
}

/* Delete with event timeout */
CU_Test(ddssec_fsm, delete_with_timeout, .init = fsm_control_init, .fini = fsm_control_fini)
{
  fsm_timeout = dds_security_fsm_create(g_fsm_control, timeout_transitions, timeout_transitionsSize, &fsm_arg);
  CU_ASSERT (fsm_timeout != NULL)
  visited_timeout = 0;
  dds_security_fsm_start(fsm_timeout);

  int n = 100;
  ddsrt_mutex_lock (&g_lock);
  while (visited_timeout == 0 && n-- > 0)
  {
    ddsrt_mutex_unlock (&g_lock);
    dds_sleepfor(DDS_MSECS(100));
    ddsrt_mutex_lock (&g_lock);
  }
  ddsrt_mutex_unlock (&g_lock);
  dds_security_fsm_free(fsm_timeout);
}

