/*
 *  PPPFSM.C	-- PPP Finite State Machine
 *
 *	This implementation of PPP is declared to be in the public domain.
 *
 *	Jan 91	Bill_Simpson@um.cc.umich.edu
 *		Computer Systems Consulting Services
 *
 *	Acknowledgements and correction history may be found in PPP.C
 */

#include "global.h"
#include "mbuf.h"
#include "iface.h"
#include "ppp.h"
#include "pppfsm.h"
#include "ppplcp.h"
#include "trace.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: pppfsm.c,v 1.10 1997/01/19 21:13:05 root Exp $";
#endif

#ifdef PPP

static const char *fsmStates[] =
{
	"Closed",
	"Listen",
	"Req Sent",
	"Ack Rcvd",
	"Ack Sent",
	"Opened",
	"TermSent"
};

const char *fsmCodes[] =
{
	NULLCHAR,
	"Config Req",
	"Config Ack",
	"Config Nak",
	"Config Reject",
	"Termin Req",
	"Termin Ack",
	"Code Reject",
	"Protocol Reject",
	"Echo Request",
	"Echo Reply",
	"Discard Request",
};

static int fsm_sendtermreq (struct fsm_s * fsm_p);
static int fsm_sendtermack (struct fsm_s * fsm_p, byte_t id);
static void fsm_timeout (void *vp);
static void fsm_reset (struct fsm_s * fsm_p);
static void fsm_opening (struct fsm_s * fsm_p);
static struct mbuf *htoncnf (struct config_hdr *cnf, struct mbuf *data);



/************************************************************************/
/* Convert header in host form to network form */

static struct mbuf *
htoncnf (struct config_hdr *cnf, struct mbuf *data)
{
struct mbuf *bp;
register unsigned char *cp;

	/* Prepend bytes for LCP/IPCP header */
	if ((bp = pushdown (data, CONFIG_HDR_LEN)) == NULLBUF) {
		free_p (data);
		return NULLBUF;
	}
	/* Load header with proper values */
	cp = bp->data;
	*cp++ = cnf->code;
	*cp++ = cnf->id;
	(void) put16 (cp, cnf->len);

	return bp;
}



/* Extract header from incoming packet */
int
ntohcnf (struct config_hdr *cnf, struct mbuf **bpp)
{
unsigned char cnfb[CONFIG_HDR_LEN];

	if (cnf == NULL)
		return -1;

	if (pullup (bpp, cnfb, CONFIG_HDR_LEN) < CONFIG_HDR_LEN)
		return -1;

	cnf->code = cnfb[0];
	cnf->id = cnfb[1];
	cnf->len = get16 ((char *) &cnfb[2]);
	return 0;
}



/***************************************/
/* Extract configuration option header */

int
ntohopt (struct option_hdr *opt, struct mbuf **bpp)
{
unsigned char optb[OPTION_HDR_LEN];

	if (opt == NULL)
		return -1;

	if (pullup (bpp, optb, OPTION_HDR_LEN) < OPTION_HDR_LEN)
		return -1;

	opt->type = optb[0];
	opt->len = optb[1];
	return 0;
}



/************************************************************************/

void
fsm_no_action (struct fsm_s *fsm_p OPTIONAL)
{
	PPP_DEBUG_ROUTINES ("fsm_no_action()");
}



int
fsm_no_check (struct fsm_s *fsm_p OPTIONAL, struct config_hdr *hdr OPTIONAL, struct mbuf *bp OPTIONAL)
{
	PPP_DEBUG_ROUTINES ("fsm_no_check()");
	return 0;
}



/************************************************************************/
/* General log routine */

void
fsm_log (struct fsm_s *fsm_p, const char *comment)
{
	if (PPPtrace > 1)
		trace_log (PPPiface, "%s PPP/%s %-8s; %s", fsm_p->ppp_p->iface->name,
			fsm_p->pdc->name, fsmStates[fsm_p->state], comment);
}



/************************************************************************/
/* Set a timer in case an expected event does not occur */

void
fsm_timer (struct fsm_s *fsm_p)
{
	PPP_DEBUG_ROUTINES ("fsm_timer()");

	start_timer (&(fsm_p->timer));
}



/************************************************************************/
/* Send a packet to the remote host */

int
fsm_send (struct fsm_s *fsm_p, byte_t code, byte_t id, struct mbuf *data)
{
struct ppp_s *ppp_p = fsm_p->ppp_p;
struct iface *iface = ppp_p->iface;
struct config_hdr hdr;
struct mbuf *bp;

	switch (hdr.code = code) {
		case CONFIG_REQ:
		case TERM_REQ:
		case ECHO_REQ:
			/* Save ID field for match against replies from remote host */
			fsm_p->lastid = ppp_p->id;
			/* fallthru */
		case PROT_REJ:
		case DISCARD_REQ:
			/* Use a unique ID field value */
			hdr.id = ppp_p->id++;
			break;

		case CONFIG_ACK:
		case CONFIG_NAK:
		case CONFIG_REJ:
		case TERM_ACK:
		case CODE_REJ:
		case ECHO_REPLY:
			/* Use ID sent by remote host */
			hdr.id = id;
			break;

		default:
			/* we're in trouble */
			trace_log (PPPiface, "%s PPP/%s %-8s; Send with bogus code: %d",
				iface->name, fsm_p->pdc->name, fsmStates[fsm_p->state], code);
			return -1;
	}

	switch (code) {
		case ECHO_REQ:
		case ECHO_REPLY:
		case DISCARD_REQ:
			{
				struct lcp_s *lcp_p = fsm_p->pdv;

				if ((bp = pushdown (data, 4)) == NULLBUF) {
					free_p (data);
					return -1;
				}
				(void) put32 (bp->data, lcp_p->local.work.magic_number);
				data = bp;
			}
			break;
		default:
			break;
	}

	hdr.len = len_p (data) + CONFIG_HDR_LEN;

	/* Prepend header to packet data */
	if ((data = htoncnf (&hdr, data)) == NULLBUF)
		return -1;

	if (PPPtrace > 1)
		trace_log (PPPiface, "%s PPP/%s %-8s; Sending %s, id: %d, len: %d",
			iface->name, fsm_p->pdc->name, fsmStates[fsm_p->state],
			fsmCodes[code], hdr.id, hdr.len);

	ppp_p->OutNCP[fsm_p->pdc->fsmi]++;

	return ((*iface->output) (iface, NULLCHAR, NULLCHAR, fsm_p->pdc->protocol, data));
}



/************************************************************************/
/* Send a configuration request */

int
fsm_sendreq (struct fsm_s *fsm_p)
{
struct mbuf *bp;

	PPP_DEBUG_ROUTINES ("fsm_sendreq()");

#if 0
	if (fsm_p->retry <= 0)
		return -1;
#endif

	fsm_p->retry--;
	fsm_timer (fsm_p);

	bp = (*fsm_p->pdc->makereq) (fsm_p);
	return (fsm_send (fsm_p, CONFIG_REQ, 0, bp));
}



/************************************************************************/
/* Send a termination request */

static int
fsm_sendtermreq (struct fsm_s *fsm_p)
{
	PPP_DEBUG_ROUTINES ("fsm_sendtermreq()");

#if 0
	if (fsm_p->retry <= 0)
		return -1;
#endif

	fsm_p->retry--;
	fsm_timer (fsm_p);
	return (fsm_send (fsm_p, TERM_REQ, 0, NULLBUF));
}



/************************************************************************/
/* Send Terminate Ack */

static int
fsm_sendtermack (struct fsm_s *fsm_p, byte_t id)
{
	PPP_DEBUG_ROUTINES ("fsm_sendtermack()");

	return (fsm_send (fsm_p, TERM_ACK, id, NULLBUF));
}



/************************************************************************/
/* Reset state machine */

static void
fsm_reset (struct fsm_s *fsm_p)
{
	PPP_DEBUG_ROUTINES ("fsm_reset()");

	fsm_p->state = (fsm_p->flags & (FSM_ACTIVE | FSM_PASSIVE))
		? fsmLISTEN : fsmCLOSED;
	fsm_p->retry = fsm_p->try_req;
	fsm_p->retry_nak = fsm_p->try_nak;

	(*fsm_p->pdc->reset) (fsm_p);
}



/************************************************************************/
/* Configuration negotiation complete */

static void
fsm_opening (struct fsm_s *fsm_p)
{
	fsm_log (fsm_p, "Opened");

	stop_timer (&(fsm_p->timer));

	(*fsm_p->pdc->opening) (fsm_p);
	fsm_p->state = fsmOPENED;
}



/************************************************************************/
/*			E V E N T   P R O C E S S I N G			*/
/************************************************************************/

/* Process incoming packet */

void
fsm_proc (struct fsm_s *fsm_p, struct mbuf *bp)
{
struct config_hdr hdr;

	PPPtrace = fsm_p->ppp_p->trace;
	PPPiface = fsm_p->ppp_p->iface;

	if (ntohcnf (&hdr, &bp) == -1)
		fsm_log (fsm_p, "short configuration packet");

	if (PPPtrace > 1)
		trace_log (PPPiface, "%s PPP/%s %-8s; Processing %s, id: %d, len: %d",
			fsm_p->ppp_p->iface->name, fsm_p->pdc->name, fsmStates[fsm_p->state],
			fsmCodes[hdr.code], hdr.id, hdr.len);

	hdr.len -= CONFIG_HDR_LEN;	/* Length includes envelope */
	trim_mbuf (&bp, hdr.len);	/* Trim off padding */

	switch (hdr.code) {
		case CONFIG_REQ:
			switch (fsm_p->state) {
				case fsmOPENED:	/* Unexpected event? */
					(*fsm_p->pdc->closing) (fsm_p);
					fsm_reset (fsm_p);
					/* fallthru */
				case fsmLISTEN:
					(*fsm_p->pdc->starting) (fsm_p);
					(void) fsm_sendreq (fsm_p);
					/* fallthru */
				case fsmREQ_Sent:
				case fsmACK_Sent:	/* Unexpected event? */
					fsm_p->state =
						((*fsm_p->pdc->request) (fsm_p, &hdr, bp) == 0)
						? fsmACK_Sent : fsmREQ_Sent;
					break;

				case fsmACK_Rcvd:
					if ((*fsm_p->pdc->request) (fsm_p, &hdr, bp) == 0)
						fsm_opening (fsm_p);
					else 	/* give peer time to respond */
						fsm_timer (fsm_p);
					break;

				case fsmCLOSED:
					/* Don't accept any connections */
					(void) fsm_sendtermack (fsm_p, hdr.id);
					/* fallthru */
				case fsmTERM_Sent:
					/* We are attempting to close connection; */
					/* wait for timeout to resend a Terminate Request */
					free_p (bp);
					break;
				default:
					break;
			}
			break;

		case CONFIG_ACK:
			switch (fsm_p->state) {
				case fsmREQ_Sent:
					if ((*fsm_p->pdc->ack) (fsm_p, &hdr, bp) == 0)
						fsm_p->state = fsmACK_Rcvd;
					break;

				case fsmACK_Sent:
					if ((*fsm_p->pdc->ack) (fsm_p, &hdr, bp) == 0)
						fsm_opening (fsm_p);
					break;

				case fsmOPENED:	/* Unexpected event? */
					(*fsm_p->pdc->closing) (fsm_p);
					(*fsm_p->pdc->starting) (fsm_p);
					fsm_reset (fsm_p);
					/* fallthru */
				case fsmACK_Rcvd:	/* Unexpected event? */
					free_p (bp);
					(void) fsm_sendreq (fsm_p);
					fsm_p->state = fsmREQ_Sent;
					break;

				case fsmCLOSED:
				case fsmLISTEN:
					/* Out of Sync; kill the remote */
					(void) fsm_sendtermack (fsm_p, hdr.id);
					/* fallthru */
				case fsmTERM_Sent:
					/* We are attempting to close connection; */
					/* wait for timeout to resend a Terminate Request */
					free_p (bp);
					break;
				default:
					break;
			}
			break;

		case CONFIG_NAK:
			switch (fsm_p->state) {
				case fsmREQ_Sent:
				case fsmACK_Sent:
					/* Update our config request to reflect NAKed options */
					if ((*fsm_p->pdc->nak) (fsm_p, &hdr, bp) == 0)
						/* Send updated config request */
						(void) fsm_sendreq (fsm_p);
					break;

				case fsmOPENED:	/* Unexpected event? */
					(*fsm_p->pdc->closing) (fsm_p);
					(*fsm_p->pdc->starting) (fsm_p);
					fsm_reset (fsm_p);
					/* fallthru */
				case fsmACK_Rcvd:	/* Unexpected event? */
					free_p (bp);
					(void) fsm_sendreq (fsm_p);
					fsm_p->state = fsmREQ_Sent;
					break;

				case fsmCLOSED:
				case fsmLISTEN:
					/* Out of Sync; kill the remote */
					(void) fsm_sendtermack (fsm_p, hdr.id);
					/* fallthru */
				case fsmTERM_Sent:
					/* We are attempting to close connection; */
					/* wait for timeout to resend a Terminate Request */
					free_p (bp);
					break;
				default:
					break;
			}
			break;

		case CONFIG_REJ:
			switch (fsm_p->state) {
				case fsmREQ_Sent:
				case fsmACK_Sent:
					if ((*fsm_p->pdc->reject) (fsm_p, &hdr, bp) == 0)
						(void) fsm_sendreq (fsm_p);
					break;

				case fsmOPENED:	/* Unexpected event? */
					(*fsm_p->pdc->closing) (fsm_p);
					(*fsm_p->pdc->starting) (fsm_p);
					fsm_reset (fsm_p);
					/* fallthru */
				case fsmACK_Rcvd:	/* Unexpected event? */
					free_p (bp);
					(void) fsm_sendreq (fsm_p);
					fsm_p->state = fsmREQ_Sent;
					break;

				case fsmCLOSED:
				case fsmLISTEN:
					/* Out of Sync; kill the remote */
					(void) fsm_sendtermack (fsm_p, hdr.id);
					/* fallthru */
				case fsmTERM_Sent:
					/* We are attempting to close connection; */
					/* wait for timeout to resend a Terminate Request */
					free_p (bp);
					break;
				default:
					break;
			}
			break;

		case TERM_REQ:
			fsm_log (fsm_p, "Peer requested Termination");

			switch (fsm_p->state) {
				case fsmOPENED:
					(void) fsm_sendtermack (fsm_p, hdr.id);
					(*fsm_p->pdc->closing) (fsm_p);
					(*fsm_p->pdc->stopping) (fsm_p);
					fsm_reset (fsm_p);
					break;

				case fsmACK_Rcvd:
				case fsmACK_Sent:
					fsm_p->state = fsmREQ_Sent;
					/* fallthru */
				case fsmREQ_Sent:
				case fsmTERM_Sent:
					/* waiting for timeout */
					/* fallthru */
				case fsmCLOSED:
				case fsmLISTEN:
					/* Unexpected, but make them happy */
					(void) fsm_sendtermack (fsm_p, hdr.id);
					break;
				default:
					break;
			}
			break;

		case TERM_ACK:
			switch (fsm_p->state) {
				case fsmTERM_Sent:
					stop_timer (&(fsm_p->timer));

					fsm_log (fsm_p, "Terminated");
					(*fsm_p->pdc->stopping) (fsm_p);
					fsm_reset (fsm_p);
					break;

				case fsmOPENED:
					/* Remote host has abruptly closed connection */
					fsm_log (fsm_p, "Terminated unexpectly");
					(*fsm_p->pdc->closing) (fsm_p);
					fsm_reset (fsm_p);
					if (fsm_sendreq (fsm_p) == 0)
						fsm_p->state = fsmREQ_Sent;
					break;

				case fsmACK_Sent:
				case fsmACK_Rcvd:
					fsm_p->state = fsmREQ_Sent;
					/* fallthru */
				case fsmREQ_Sent:
					/* waiting for timeout */
					/* fallthru */
				case fsmCLOSED:
				case fsmLISTEN:
				default:
					/* Unexpected, but no action needed */
					break;
			}
			break;

		case CODE_REJ:
			trace_log (PPPiface, "%s PPP/%s Code Reject; indicates faulty implementation",
				fsm_p->ppp_p->iface->name, fsm_p->pdc->name);
			(*fsm_p->pdc->stopping) (fsm_p);
			fsm_reset (fsm_p);
			free_p (bp);
			break;

		case PROT_REJ:
			trace_log (PPPiface, "%s PPP/%s Protocol Reject; please do not use this protocol",
				fsm_p->ppp_p->iface->name, fsm_p->pdc->name);
			free_p (bp);
			break;

		case ECHO_REQ:
			switch (fsm_p->state) {
				case fsmOPENED:
					(void) fsm_send (fsm_p, ECHO_REPLY, hdr.id, bp);
					break;

				case fsmCLOSED:
				case fsmLISTEN:
					/* Out of Sync; kill the remote */
					(void) fsm_sendtermack (fsm_p, hdr.id);
					/* fallthru */
				case fsmREQ_Sent:
				case fsmACK_Rcvd:
				case fsmACK_Sent:
				case fsmTERM_Sent:
				default:
					/* ignore */
					free_p (bp);
					break;
			}
			break;

		case ECHO_REPLY:
		case DISCARD_REQ:
		case QUALITY_REPORT:
			free_p (bp);
			break;

		default:
			trace_log (PPPiface, "%s PPP/%s Unknown packet type: %d; Sending Code Reject",
				fsm_p->ppp_p->iface->name, fsm_p->pdc->name, hdr.code);

			hdr.len += CONFIG_HDR_LEN;	/* restore length */
			bp = htoncnf (&hdr, bp);	/* put header back on */
			(void) fsm_send (fsm_p, CODE_REJ, hdr.id, bp);

			switch (fsm_p->state) {
				case fsmREQ_Sent:
				case fsmACK_Rcvd:
				case fsmACK_Sent:
				case fsmOPENED:
					fsm_p->state = fsmLISTEN;
					break;

				case fsmCLOSED:
				case fsmLISTEN:
				case fsmTERM_Sent:
				default:
					/* no change */
					break;
			}
			break;
	}
}



/************************************************************************/
/* Timeout while waiting for reply from remote host */

static void
fsm_timeout (void *vp)
{
struct fsm_s *fsm_p = (struct fsm_s *) vp;

	PPPtrace = fsm_p->ppp_p->trace;
	PPPiface = fsm_p->ppp_p->iface;

	fsm_log (fsm_p, "Timeout");

	switch (fsm_p->state) {
		case fsmREQ_Sent:
		case fsmACK_Rcvd:
		case fsmACK_Sent:
			if (fsm_p->retry > 0) {
				(void) fsm_sendreq (fsm_p);
				if (fsm_p->state == fsmACK_Rcvd)
					fsm_p->state = fsmREQ_Sent;
			} else {
				fsm_log (fsm_p, "Request retry exceeded");
				fsm_reset (fsm_p);
			}
			break;

		case fsmTERM_Sent:
			if (fsm_p->retry > 0)
				(void) fsm_sendtermreq (fsm_p);
			else {
				fsm_log (fsm_p, "Terminate retry exceeded");
				(*fsm_p->pdc->stopping) (fsm_p);
				fsm_reset (fsm_p);
			}
			break;

		case fsmCLOSED:
		case fsmLISTEN:
		case fsmOPENED:
		default:
			/* nothing to do */
			break;
	}
}



/************************************************************************/
/*			I N I T I A L I Z A T I O N			*/
/************************************************************************/

/* Start FSM (after open event, and physical line up) */

void
fsm_start (struct fsm_s *fsm_p)
{
	if (fsm_p->pdv == NULL)
		return;

	PPPtrace = fsm_p->ppp_p->trace;
	PPPiface = fsm_p->ppp_p->iface;

	fsm_log (fsm_p, "Start");

	if (!(fsm_p->flags & (FSM_ACTIVE | FSM_PASSIVE)))
		return;

	switch (fsm_p->state) {
		case fsmCLOSED:
		case fsmLISTEN:
		case fsmTERM_Sent:
			(*fsm_p->pdc->starting) (fsm_p);
			fsm_reset (fsm_p);

			if (fsm_p->flags & FSM_ACTIVE) {
				(void) fsm_sendreq (fsm_p);
				fsm_p->state = fsmREQ_Sent;
			}
			break;
		default:
			/* already started */
			break;
	}
}



/************************************************************************/
/* Physical Line Down Event */

void
fsm_down (struct fsm_s *fsm_p)
{
	if (fsm_p->pdv == NULL)
		return;

	PPPtrace = fsm_p->ppp_p->trace;
	PPPiface = fsm_p->ppp_p->iface;

	fsm_log (fsm_p, "Down");

	switch (fsm_p->state) {
		case fsmREQ_Sent:
		case fsmACK_Rcvd:
		case fsmACK_Sent:
			stop_timer (&(fsm_p->timer));
			fsm_reset (fsm_p);
			break;

		case fsmOPENED:
			(*fsm_p->pdc->closing) (fsm_p);
			/* fallthru */
		case fsmTERM_Sent:
			fsm_reset (fsm_p);
			break;

		case fsmCLOSED:
		case fsmLISTEN:
		default:
			/* nothing to do */
			break;
	}
}



/************************************************************************/
/* Close the connection */

void
fsm_close (struct fsm_s *fsm_p)
{
	if (fsm_p->pdv == NULL)
		return;

	PPPtrace = fsm_p->ppp_p->trace;
	PPPiface = fsm_p->ppp_p->iface;

	fsm_log (fsm_p, "Close");

	switch (fsm_p->state) {
		case fsmOPENED:
			(*fsm_p->pdc->closing) (fsm_p);
			/* fallthru */
		case fsmACK_Sent:
			fsm_p->retry = fsm_p->try_terminate;
			(void) fsm_sendtermreq (fsm_p);
			fsm_p->state = fsmTERM_Sent;
			break;

		case fsmREQ_Sent:
		case fsmACK_Rcvd:
			/* simply wait for REQ timeout to expire */
			fsm_p->retry = 0;
			fsm_p->state = fsmTERM_Sent;
			break;

		case fsmLISTEN:
			fsm_p->state = fsmCLOSED;
			break;

		case fsmTERM_Sent:
		case fsmCLOSED:
		default:
			/* nothing to do */
			break;
	}
}



/************************************************************************/
/* Initialize the fsm for this protocol
 * Called from protocol _init
 */

void
fsm_init (struct fsm_s *fsm_p)
{
struct timer *t = &(fsm_p->timer);

	PPP_DEBUG_ROUTINES ("fsm_init()");

	fsm_p->try_req = fsm_p->pdc->try_req;
	fsm_p->try_nak = fsm_p->pdc->try_nak;
	fsm_p->try_terminate = fsm_p->pdc->try_terminate;
	fsm_reset (fsm_p);

	/* Initialize timer */
	t->func = (void (*)(void *)) fsm_timeout;
	t->arg = (void *) fsm_p;
	set_timer (t, fsm_p->pdc->timeout);
	fsm_timer (fsm_p);
	stop_timer (t);
}



void
fsm_free (struct fsm_s *fsm_p)
{
	if (fsm_p->pdv != NULL) {
		(*fsm_p->pdc->free) (fsm_p);

		free (fsm_p->pdv);
		fsm_p->pdv = NULL;
	}
}



#endif /* PPP */
