/* 
 * $Id: idrp_sm.c,v 1.4 1995/09/21 16:12:06 sjr Exp $
 * Merit IDRP release 1.1 (gated 3.5.4).  Copyright (c) 1994 by Merit Network, Inc. 
 */

#include "include.h"
#include "iso.h"
#include "idrp.h"
#include <stdio.h>


/* idrp_sm.c
 *  
 * IDRP State machine
 *	idrp_sm(peer,event) 
 */ 
  
 
static void
state_change __PF3(peer, idrpPeer*, newState, int, p_oldstate, int*)
{
	if (*p_oldstate != peer->state)
		{	
			trace_tf(peer->trace_options, TR_STATE, 0, ("IDRP %s state %s -> %s", peer->name, trace_state((*p_oldstate), idrpStates), 
			trace_state(peer->state, idrpStates)));
			
		}
	*p_oldstate = peer->state;
	peer->state = newState;
}
	

int 
idrp_sm(peer,event)
idrpPeer *peer;
int	event;
{
int return_code = 0;
int old_state;

/* idrp state machine  */

    trace_tf(peer->trace_options, TR_STATE, 0, ("IDRP entered idrp_sm in state %s with event %s", 
						trace_state(peer->state, idrpStates), 
						trace_state(event, idrpEvents)));
	

    old_state = peer->state;
         
    switch (event) 
      {
      /* start event */

      case IDRP_START_EVENT:
	switch (peer->state) {
		case IDRP_CLOSED:
			state_change(peer, IDRP_OPEN_SENT, &old_state);
			send_open(peer,FALSE);
			break;

		case IDRP_OPEN_RCVD:
		case IDRP_OPEN_SENT:
		case IDRP_ESTABLISHED:
		case IDRP_CLOSE_WAIT:
		default:
			trace_log_tf(peer->trace_options, TR_STATE, LOG_ERR, ("IDRP error %s start event in state %s", peer->name, trace_state(peer->state,
																	       idrpStates)));
			break;
		}
	break;
	
     /* stop event */

     case IDRP_STOP_EVENT:
	switch (peer->state) {
		case IDRP_OPEN_RCVD:
		case IDRP_OPEN_SENT:
		case IDRP_ESTABLISHED:
			/* begin close sets the close_wait timer 
			 */
 
			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			send_cease(peer);
			break;

		case IDRP_CLOSED:
		case IDRP_CLOSE_WAIT:
		default:
			trace_log_tf(peer->trace_options, TR_STATE, LOG_ERR, 
				     ("IDRP %s stop event in state %s", peer->name, 
				      trace_state(peer->state,
						  idrpStates)));
				
			break;
		}
	break;
  
    /* idrp Close wait timer expired event */
  
    case IDRP_CLOSE_TIMEOUT:
	switch (peer->state) {
        	case IDRP_CLOSE_WAIT:
			/* close down this peer and then restart */ 
			state_change(peer, IDRP_CLOSED, &old_state);
               		close_now (peer); 
			if (peer->idrp_flags &  (IDRPF_TRY_CONNECT | IDRPF_CONNECT))
				{ 
				peer->idrp_flags == IDRPF_TRY_CONNECT;
				}
       		        break;

        	default:
		                trace_log_tf(peer->trace_options, TR_STATE, LOG_ERR, ("IDRP %s close_wait timer expired in state %s", peer->name, trace_state(peer->state,
       		                 idrpStates)));
				
       			break;
		}
	break;

    /* case Expiration of hold timer */

    case IDRP_HOLDTIME_TIMEOUT:
	switch(peer->state)
		{
		case IDRP_ESTABLISHED:
			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			peer->last_error_pdu.error_code = IDRP_ERROR_HOLDTIME_EXPIRED;
			peer->last_error_pdu.error_subcode = (u_char)NULL;
			peer->last_error_pdu.data_len = 0;
			peer->last_error_pdu.data2_len = 0;
			idrp_send_error(peer,IDRP_ERROR_HOLDTIME_EXPIRED); 
			break;	
	
		case IDRP_OPEN_RCVD:
				trace_tf(peer->trace_options, TR_STATE,0,("Hold timer expired in state OPEN_RCVD peer %s",peer->name));
				
			break;

        	case IDRP_OPEN_SENT:
				trace_tf(peer->trace_options, TR_STATE,0,("Hold timer expired in state OPEN_SENT peer %s",peer->name));

			break; 

		case IDRP_CLOSED:
		case IDRP_CLOSE_WAIT:
			break;	
		}
	break;
	

    /* case BISPDU with errors */

     case IDRP_HEADER_ERROR:
			trace_log_tf(peer->trace_options, TR_STATE, LOG_ERR, ("IDRP peer %s got pdu with header error in state %d", peer->name,peer->state));
			
	     	break;

    case IDRP_KEEPALIVE:
	{
	switch (peer->state) {
         
		case IDRP_CLOSED:
				trace_tf(peer->trace_options, TR_STATE, 0, ("peer %s received keepalive in CLOSED state",peer->name)); 
				
			break;


		case IDRP_OPEN_RCVD:
			if (acked_something) {
			 state_change(peer, IDRP_ESTABLISHED, &old_state);

				/* do a path up with KEEPALIVE and UPDATE  PDUS */
				/* here's where I think I should flush queue, but
				 * the keepalives stopped  - hmmm
				 */  
				flush_xmit_queue(peer);
	                        path_up(peer);
       				}
			else
				{
				begin_close(peer);
				state_change(peer, IDRP_CLOSE_WAIT, &old_state);
				peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
				peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_OPEN_RCVED;
				peer->last_error_pdu.data_len = 0;
				peer->last_error_pdu.data2_len = 0;
				idrp_send_error(peer,IDRP_ERROR_FSM);
				}
	                break;

        	case IDRP_OPEN_SENT:
			/* If we are in state OPEN_SENT, this is a FSM error. 
			 * close wait timer set by begin_close
			 */
			begin_close(peer); 
			state_change(peer, IDRP_CLOSE_WAIT, &old_state); 
			peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
			peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_OPEN_SENT;
			peer->last_error_pdu.data_len = 0;
			peer->last_error_pdu.data2_len = 0;
                	idrp_send_error(peer, IDRP_ERROR_FSM);
			break;

		case IDRP_CLOSE_WAIT:
			send_cease(peer);
			task_timer_set(peer->idrp_closewait_timer, IDRP_CLOSE_WAIT_TIME,(time_t) 0);
			trace_tf(peer->trace_options, TR_STATE, 0, ("peer %s received keepalive in Close_wait state",peer->name));
				
			break;

		case IDRP_ESTABLISHED:
			 trace_tf(peer->trace_options, TR_STATE, 0, ("peer %s received keepalive in Established state",peer->name));
				

			peer->keepAlives_Since_last_Update++;

			/* restart of hold timer done in pdu parsing so
		 	 * that best processing time to hold time expiration takes place 
			 */ 
			break;
		}  /* end of switch statement */
	}	/* end of KEEPALIVE case */	
	break;

   /* CEASE With no errors */	
   case	IDRP_CEASE:
	{
	switch (peer->state)
		{
		case IDRP_OPEN_RCVD:
		case IDRP_OPEN_SENT:
		case IDRP_ESTABLISHED:
			close_now (peer);
			send_cease(peer);
			state_change(peer, IDRP_CLOSED, &old_state);
			if (peer->idrp_flags & (IDRPF_TRY_CONNECT | IDRPF_CONNECT))
				{
				peer->idrp_flags = IDRPF_TRY_CONNECT;
				}
			break;

		case IDRP_CLOSED:
		case IDRP_CLOSE_WAIT:
			/* make sure all timers and queues are flushed 
			 */
			close_now (peer);
			state_change(peer, IDRP_CLOSED, &old_state);
			if (peer->idrp_flags & (IDRPF_TRY_CONNECT | IDRPF_CONNECT))
				{
				peer->idrp_flags = IDRPF_TRY_CONNECT;
				}
			break;

		}

	} 
	break;


   case IDRP_OPEN_ERROR:

	switch (peer->state) {
		case IDRP_CLOSED:
	                trace_tf(peer->trace_options, TR_STATE, 0, ("peer %s received OPEN w/error in CLOSED state",peer->name));
				
			break;

		case IDRP_OPEN_RCVD:
		case IDRP_OPEN_SENT:
			begin_close(peer);
			idrp_send_error(peer,IDRP_ERROR_OPEN_PDU);
			break;

		case IDRP_CLOSE_WAIT:
				trace_tf(peer->trace_options, TR_STATE, 0, ("peer %s received OPEN w/error in CLOSE_WAIT state",peer->name));
				
			break;

		case IDRP_ESTABLISHED:
			begin_close(peer);
			idrp_send_error(peer,IDRP_ERROR_OPEN_PDU);
			break;
		}
	state_change(peer, IDRP_CLOSE_WAIT, &old_state);
	break;

	/* valid IDRP open */

   case IDRP_OPEN:

        /*
         * If state is OPEN_SENT or OPEN_RCVD and the received open acked ours, bring up the
         * connection.  If it didn't ack, go to OPEN_RCVD (and wait for the retransmission timer
         * to go off and resend our OPEN).  If wrong state, ignore it.
         */
        switch (peer->state) {

        case IDRP_OPEN_SENT:
        case IDRP_OPEN_RCVD:
                if (acked_something && (peer->send_unack == peer->send_next))
			{
			state_change(peer, IDRP_ESTABLISHED, &old_state);
                        path_up(peer);
			}
                else 	{
			u_int freeze_seq = TRUE;
			state_change(peer, IDRP_OPEN_RCVD, &old_state);
			pull_old_open_rexmit(peer);
			send_open(peer,freeze_seq);
			}
                break;

	case IDRP_ESTABLISHED:
		/* Begin close set close wait timer, and
		 * clear xmit and rexmit queues
		 */

		begin_close(peer);
		state_change(peer, IDRP_CLOSE_WAIT, &old_state);
		peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
		peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_ESTABLISHED;
		peer->last_error_pdu.data_len = 0;
		peer->last_error_pdu.data2_len = 0;
		idrp_send_error(peer,IDRP_ERROR_FSM);
		break;

	case IDRP_CLOSE_WAIT:
		send_cease(peer);
                task_timer_set(peer->idrp_closewait_timer, IDRP_CLOSE_WAIT_TIME, (time_t) 0);
                break;


	case IDRP_CLOSED:
		task_timer_set(peer->idrp_closestay_timer,IDRP_CLOSE_STAY_TIME,(time_t) 0);
		if (peer->listen_for_open)
			{
			state_change(peer, IDRP_OPEN_RCVD, &old_state);
			send_open(peer,FALSE);
			}
		break;
			
        default:
			
			trace_tf(peer->trace_options, TR_STATE, 0, ("OPEN received from peer %s in state %d", peer->name, peer->state));
			
                break;
	}
	break;

    case IDRP_UPDATE_ERROR:
	{		
	switch (peer->state) {
		case IDRP_OPEN_RCVD:
		case IDRP_ESTABLISHED:
			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			idrp_send_error(peer,IDRP_ERROR_UPDATE_PDU);
			break;

		case IDRP_OPEN_SENT:
			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
			peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_OPEN_SENT;
			peer->last_error_pdu.data_len = 0;
			peer->last_error_pdu.data2_len = 0;
			idrp_send_error(peer,IDRP_ERROR_FSM);
			break;

		case IDRP_CLOSED:
		case IDRP_CLOSE_WAIT:
		default:
				trace_tf(peer->trace_options, TR_STATE, 0, ("IDRP UPDATE header error and Close wait peer %s",peer->name));
				
			break;
		}  /* end of switch statement */
	}  /* end of Update w/error case */
	break;

      case IDRP_UPDATE:
	return_code = 0;
	switch (peer->state)
		{
		case IDRP_CLOSED:
				trace_tf(peer->trace_options, TR_STATE, 0, ("IDRP UPDATE received in Close state peer %s",peer->name));
				
			break;

		case IDRP_OPEN_RCVD:
			/* if valid ack -> established */
			if (acked_something)
				{
				state_change(peer, IDRP_ESTABLISHED, &old_state);
				return_code = 1;
				}
			else	{
				begin_close(peer);
				state_change(peer, IDRP_CLOSE_WAIT, &old_state);
				peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
				peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_OPEN_RCVED;  
				peer->last_error_pdu.data_len = 0;
				peer->last_error_pdu.data2_len = 0;
				idrp_send_error(peer,IDRP_ERROR_FSM);
				}
			break;

		case IDRP_OPEN_SENT:
			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
			peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_OPEN_SENT;  
			peer->last_error_pdu.data_len = 0;
			idrp_send_error(peer,IDRP_ERROR_FSM);
			break;

		case IDRP_CLOSE_WAIT:
			send_cease(peer);
			task_timer_set(peer->idrp_closewait_timer, IDRP_CLOSE_WAIT_TIME, (time_t) 0);
			break;

		case IDRP_ESTABLISHED:
			/* return code to process pdu */
			return_code = 1;
			break;
	
		default:
				trace_log_tf(peer->trace_options, 0, LOG_ERR, ("IDRP UPDATE and state machine in illegal state %d peer %s",peer->name));
				
			break;
      		}  /* end of switch */
	   break;

    /* logic for IDRP_ERROR and IDRP_ERROR_ERROR  is the same */ 
    
   case IDRP_ERROR_ERROR:
	switch (peer->state)
		{
		case IDRP_OPEN_RCVD:
		case IDRP_CLOSE_WAIT:
		case IDRP_OPEN_SENT:	
		case IDRP_ESTABLISHED:
			close_now(peer);
			state_change(peer, IDRP_CLOSED, &old_state);
			send_cease(peer);
			break;

		case IDRP_CLOSED:
		default:
				trace_tf(peer->trace_options, TR_STATE, 0, (" IDRP ERROR pdu received from peer%s",peer->name));

			break;	 
		}
	break;

   /* error with out error */ 

   case IDRP_ERROR:
	switch (peer->state)
		{
		case IDRP_OPEN_RCVD:
		case IDRP_CLOSE_WAIT:
		case IDRP_OPEN_SENT:	
		case IDRP_ESTABLISHED:
			close_now(peer);
			send_cease(peer);
			state_change(peer, IDRP_CLOSED, &old_state);

			if (peer->idrp_flags & (IDRPF_TRY_CONNECT | IDRPF_CONNECT))
				{
				peer->idrp_flags = IDRPF_TRY_CONNECT;
				} 
			break;
		case IDRP_CLOSED:
		default:
				trace_tf(peer->trace_options, TR_STATE, 0, (" IDRP ERROR pdu received from peer%s",peer->name));
				
			break;	 

		}
	break;

   /* refresh with error */ 

    case IDRP_REFRESH_ERROR:
	switch (peer->state)
		{
		case IDRP_OPEN_SENT:	
			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
			peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_OPEN_SENT;
			peer->last_error_pdu.data_len = 0;
			peer->last_error_pdu.data2_len = 0;
			idrp_send_error(peer,IDRP_REFRESH_ERROR);
			break;
		
		case IDRP_OPEN_RCVD:
			/* parsing of pdu has already set error codes */
			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			idrp_send_error(peer,IDRP_REFRESH_ERROR);
			break;

		case IDRP_ESTABLISHED:
			/* parsing of pdu has already set error codes */
			/* clear down peer */

			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			idrp_send_error(peer,IDRP_REFRESH_ERROR);
			break;

		case IDRP_CLOSED:
		case IDRP_CLOSE_WAIT:
		default:
				trace_tf(peer->trace_options, TR_STATE, 0, (" IDRP RIB REFRESH pdu received from peer %s",peer->name));
				
			break;	 
		}
	break;

   /* IDRP RIB REFRESH packet */

   case IDRP_REFRESH:
	return_code = 0; 
	switch(peer->state)
		{
		case IDRP_CLOSED:
			trace_tf(peer->trace_options, TR_STATE, 0, (" IDRP RIB REFRESH pdu received from peer %s",peer->name));
				
			break;

		case IDRP_OPEN_RCVD:
			if (acked_something)
				{
				state_change(peer, IDRP_ESTABLISHED, &old_state);
				path_up(peer);
				send_keepalive(peer); /* %% Send RIB contents */
				return_code = 1;
				}
			else	{
				begin_close(peer);
				state_change(peer, IDRP_CLOSE_WAIT, &old_state);
				peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
				peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_OPEN_RCVED;
				peer->last_error_pdu.data_len = 0;	
				peer->last_error_pdu.data2_len = 0;
				idrp_send_error(peer,IDRP_ERROR_FSM);
				}
			break;
				
	
		case IDRP_OPEN_SENT:
			begin_close(peer);
			state_change(peer, IDRP_CLOSE_WAIT, &old_state);
			peer->last_error_pdu.error_code = IDRP_ERROR_FSM;
			peer->last_error_pdu.error_subcode = IDRP_FSM_ERROR_OPEN_SENT;
			peer->last_error_pdu.data_len = 0;
			peer->last_error_pdu.data2_len = 0;
			idrp_send_error(peer,IDRP_ERROR_FSM);
			break;
	
		case IDRP_ESTABLISHED:
			/* hold timer restart the first time the pdu 
			 * is identified as valid to insure the
			 * best results for connections
			 */

			return_code = 1;
			break;

		case IDRP_CLOSE_WAIT:
			send_cease (peer);
			task_timer_set(peer->idrp_closewait_timer,IDRP_CLOSE_WAIT_TIME,(time_t) 0);
			trace_tf(peer->trace_options, TR_STATE, 0, ("peer %s received RIB Refresh PDU in Close_wait state",peer->name));
				
			break;
		}  /* end of switch within Rib Refresh w/no errors */
	break;

    } /* end of switch with events */

    if (old_state != peer->state)
	{
	  trace_tf(peer->trace_options, TR_STATE, 0, ("IDRP %s state %s -> %s", peer->name, trace_state(old_state, idrpStates), trace_state(peer->state, idrpStates)));
		
	}
    return(return_code);
}
