/*

  sshfsm.h

  Author: Antti Huima <huima@ssh.fi>

  Copyright (c) 1999 SSH Communications Security, Finland
  All rights reserved.

  Created Thu Aug 26 12:21:07 1999.

  */

#ifndef SSHFSM_H_INCLUDED
#define SSHFSM_H_INCLUDED

/* Type definition of a finite state machine object. */
typedef struct ssh_fsm *SshFSM;

/* Type definition of a thread object. */
typedef struct ssh_fsm_thread *SshFSMThread;

/* These are the allowed return values from a step function. */
typedef enum {
  SSH_FSM_CONTINUE,             /* Continue from the next state */
  SSH_FSM_FINISH,               /* End of thread. */
  SSH_FSM_SUSPENDED,            /* Waiting for an async call. */
  
  /* This doesn't need to be returned explicitly. */
  SSH_FSM_WAIT_CONDITION       /* Waiting for a condition variable.
                                   This is automatically returned by
                                   SSH_FSM_CONDITION_WAIT(...) and
                                   does not need to be explicitly
                                   returned by user code. */
} SshFSMStepStatus;

/* The type of step functions. */
typedef SshFSMStepStatus (* SshFSMStepCB)(SshFSMThread thread);

/* A destructor type for internal global data structures. The blob
   `data' itself will be automatically ssh_xfree'd after the
   destructor returns. */
typedef void (* SshFSMDestructor)(void *gdata);

/* A destructor type for thread-specific internal data structures. The
   blob `tdata' itself will be automatically ssh_xfree'd after the
   destructor returns. */
typedef void (* SshFSMThreadDestructor)(void *tdata);

/* Message handler type. */
typedef void (* SshFSMMessageHandler)(SshFSMThread thread,
                                      SshUInt32 message);

/* State array item type. */
typedef struct ssh_fsm_state_map_item {
  char *state_id;               /* Unique ID for the state. */
  char *descr;                  /* Description of the state,
                                   for debugging purposes. */
  SshFSMStepCB func;            /* A function used to run a step
                                   starting from the state. */
} SshFSMStateMapItem;

/* Create a new finite state machine. `states' must be constant and
   remain valid forever (a static array perhaps). */
SshFSM ssh_fsm_allocate(size_t internal_data_size,
                        SshFSMStateMapItem *states,
                        int num_states,
                        SshFSMDestructor destructor);

/* Destroy the FSM when next reaching the event loop. 
   This checks that there are no active threads. */
void ssh_fsm_destroy(SshFSM fsm);

/* Set the next state. */
void ssh_fsm_set_next(SshFSMThread thread, char *next_state);

/* Get the internal global data structure. */
void *ssh_fsm_get_gdata(SshFSMThread thread);

/* Get the internal global data structure. */
void *ssh_fsm_get_gdata_fsm(SshFSM fsm);

/* Get the internal thread-specific data structure. */
void *ssh_fsm_get_tdata(SshFSMThread thread);

/* Set the debugging name. */
void ssh_fsm_set_thread_name(SshFSMThread thread, const char *name);

/* Get the debugging name. */
const char *ssh_fsm_get_thread_name(SshFSMThread thread);

/* Get the state id of the current state of the thread. */
const char *ssh_fsm_get_thread_current_state(SshFSMThread thread);

/* Fork, i.e. create a new thread. `fsm' is the state the thread will
   run on, `internal_data_size' is the size of the internal data blob
   to be allocated, `first_state' is the state where the thread starts
   from. `message_handle' is the message handling function and
   `destructor' is the optional destructor function. */
SshFSMThread ssh_fsm_spawn(SshFSM fsm,
                           size_t internal_data_size,
                           char *first_state,
                           SshFSMMessageHandler message_handler,
                           SshFSMThreadDestructor destructor);

/* Revive a thread from an external callback. */
void ssh_fsm_continue(SshFSMThread thread);

/* Get the underlying FSM for a thread. */
SshFSM ssh_fsm_get_fsm(SshFSMThread thread);

/* Push a thread-specific data object for a thread. */
void ssh_fsm_push_tdata(SshFSMThread thread, void *tdata);

/* Pop a thread-specific data pointer (and return it, for that matter). */
void *ssh_fsm_pop_tdata(SshFSMThread thread);

/* Push a message handler. */
void ssh_fsm_push_ehandler(SshFSMThread thread,
                           SshFSMMessageHandler message_handler);

/* Pop a message handler. */
SshFSMMessageHandler ssh_fsm_pop_ehandler(SshFSMThread thread);

/* Messages. */

/* Throw a message to another thread that must belong to the
   same FSM.

   If `recipient' does not have a message handler then the call
   does nothing. */
void ssh_fsm_throw(SshFSMThread thread,
                   SshFSMThread recipient,
                   SshUInt32 message);

/* Kill a thread that was suspended. Calling this function is legal
   only if it can be guaranteed that the thread won't get any 
   ssh_fsm_continue calls after this; that is, that the thread was not
   waiting for an external callback that couldn't be cancelled.
   
   If the thread `thread' was waiting for a condition variable, then
   the thread is automatically removed from the variable's waiting
   list. */
void ssh_fsm_kill_thread(SshFSMThread thread);

/* Return the topmost element of the conf stack but do not remove it. */
/* Condition variables. */
typedef struct ssh_fsm_cond_var *SshFSMCondition;

/* Create a new condition variable. */
SshFSMCondition ssh_fsm_condition_create(SshFSM fsm);

/* Destroy a condition variable. When a condition variable is destroyed,
   not threads may be waiting for it. Use SSH_FSM_CONDITION_BROADCAST if
   there are some threads left and you want to release them prior
   to destroying. */
void ssh_fsm_condition_destroy(SshFSMCondition condition);

/* Do not call these functions directly, prefer the macros below instead. */
void ssh_fsm_condition_wait(SshFSMThread thread, SshFSMCondition cv);
void ssh_fsm_condition_signal(SshFSMThread thread, SshFSMCondition cv);
void ssh_fsm_condition_broadcast(SshFSMThread thread, SshFSMCondition cv);

/* The async function call streamlining functions. Do not call these
   directly! Use macros below instead. */
void ssh_fsm_set_callback_flag(SshFSMThread thread);
void ssh_fsm_drop_callback_flag(SshFSMThread thread);
Boolean ssh_fsm_get_callback_flag(SshFSMThread thread);

/* Wait for a condition. */
#define SSH_FSM_CONDITION_WAIT(cv)      \
do                                      \
{                                       \
  ssh_fsm_condition_wait(thread, cv);   \
  return SSH_FSM_WAIT_CONDITION;        \
}                                       \
while(0)

/* Signal a condition. */
#define SSH_FSM_CONDITION_SIGNAL(cv)    \
  ssh_fsm_condition_signal(thread, cv)

/* Broadcast a condition. */
#define SSH_FSM_CONDITION_BROADCAST(cv) \
  ssh_fsm_condition_broadcast(thread, cv)

/* Calculate the size of a state array. */
#define SSH_FSM_NUM_STATES(array) (sizeof(array)/sizeof(array[0]))

/* Function header for a step function. */
#define SSH_FSM_STEP(name) SshFSMStepStatus name(SshFSMThread thread)

/* Get global data item. */
#define SSH_FSM_GDATA(gtype)    \
  gtype gdata = ssh_fsm_get_gdata(thread)

/* Get thread data. */
#define SSH_FSM_TDATA(ttype)    \
  ttype tdata = ssh_fsm_get_tdata(thread)

/* Get both datas. */
#define SSH_FSM_DATA(gtype, ttype)      \
  SSH_FSM_GDATA(gtype); SSH_FSM_TDATA(ttype)

/* Fork. */
#define SSH_FSM_FORK(s,f,e,d) \
  ssh_fsm_spawn(ssh_fsm_get_fsm(thread), s, f, e, d)

/* Throw. */
#define SSH_FSM_THROW(r, e) \
  ssh_fsm_throw(thread, r, e)

/* Next state. */
#define SSH_FSM_SET_NEXT(n) ssh_fsm_set_next(thread, n)

/* State arrays made easy. See e.g. sshfsmstreams_states.h. */
#define SSH_FSM_STATE(x,y,z) { x, y, z },

/* Call a function (in general, run a block) that will return a
   callback, either immediately or later. Can be used only inside step
   functions. Terminates the step function after the block. */
#define SSH_FSM_ASYNC_CALL(x)                           \
do                                                      \
{                                                       \
  SSH_ASSERT(!(ssh_fsm_get_callback_flag(thread)));     \
  ssh_fsm_set_callback_flag(thread);                    \
  do                                                    \
    {                                                   \
      x;                                                \
    }                                                   \
  while(0);                                             \
  if (ssh_fsm_get_callback_flag(thread))                \
    return SSH_FSM_SUSPENDED;                           \
  return SSH_FSM_CONTINUE;                              \
}                                                       \
while(0)

/* This macro can be used inside a callback to revive the thread. Use
   in conjunction with calls made with SSH_FSM_ASYNC_CALL. This macro
   does *NOT* return implicitly because the callback might want a
   value to be returned. */
#define SSH_FSM_CONTINUE_AFTER_CALLBACK(thread)         \
do                                                      \
{                                                       \
  SSH_ASSERT(ssh_fsm_get_callback_flag(thread));        \
  ssh_fsm_drop_callback_flag(thread);                   \
  ssh_fsm_continue(thread);                             \
}                                                       \
while(0)

#endif /* SSHFSM_H_INCLUDED */
