
/*
 * fsm.c
 *
 * Written by Toshiharu OHNO <tony-o@iij.ad.jp>
 * Copyright (c) 1993, Internet Initiative Japan, Inc. All rights reserved.
 * See ``COPYRIGHT.iij''
 * 
 * Rewritten by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 */

#include "ppp.h"
#include "fsm.h"
#include "ngfunc.h"

/*
 * DEFINITIONS
 */

  #define FSM_MAXCONFIG		10
  #define FSM_MAXNAK		10
  #define FSM_MAX_OPTS		100
  #define FSM_MAXTERMINATE	2
  #define FSM_MAXFAILURE	5

/* FSM restart options */

  /* #define RESTART_OPENED */
  /* #define RESTART_CLOSING */
  /* #define RESTART_STOPPED */

  struct fsmcodedesc {
    void	(*action)(Fsm fp, FsmHeader hdr, Mbuf bp);
    const char	*name;
  };

/* Size of REQ, ACK, NAK, and REJ buffers */

  #define REQ_BUFFER_SIZE	256
  #define REJ_BUFFER_SIZE	256
  #define ACK_BUFFER_SIZE	256
  #define NAK_BUFFER_SIZE	256

/*
 * INTERNAL FUNCTIONS
 */

  static void	FsmNewState(Fsm f, int state);

  static void	FsmSendConfigReq(Fsm fp);
  static void	FsmSendTerminateReq(Fsm fp);
  static void	FsmSendTerminateAck(Fsm fp);
  static void	FsmInitRestartCounter(Fsm fp, int value);
  static void	FsmInitMaxFailure(Fsm fp, int value);
  static void	FsmInitMaxConfig(Fsm fp, int value);
  static void	FsmTimeout(void *arg);

  static void	FsmRecvConfigReq(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvConfigAck(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvConfigNak(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvTermReq(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvTermAck(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvConfigRej(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvCodeRej(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvProtoRej(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvEchoReq(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvEchoRep(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvDiscReq(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvIdent(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvTimeRemain(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvResetReq(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvResetAck(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvVendor(Fsm fp, FsmHeader lhp, Mbuf bp);
  static void	FsmRecvRxjPlus(Fsm fp);

  static void	FsmLayerUp(Fsm fp);
  static void	FsmLayerDown(Fsm fp);
  static void	FsmLayerStart(Fsm fp);
  static void	FsmLayerFinish(Fsm fp);

  static Mbuf	FsmCheckMagic(Fsm fp, Mbuf bp);
  static void	FsmEchoTimeout(void *arg);
  static void	FsmDecodeBuffer(Fsm fp, u_char *buf, int size, int mode);
  static int	FsmExtractOptions(Fsm fp, u_char *data,
		  int dlen, FsmOption opts, int max);

/*
 * GLOBAL VARIABLES
 */

  u_int		gAckSize, gNakSize, gRejSize;

/*
 * INTERNAL VARIABLES
 */

/*
 * It's OK for these to be statically allocated, because we always
 * do our work with them all in one whole step without stopping.
 */

  static u_char	gRejBuf[REJ_BUFFER_SIZE];
  static u_char	gAckBuf[ACK_BUFFER_SIZE];
  static u_char	gNakBuf[NAK_BUFFER_SIZE];

  static const struct fsmcodedesc FsmCodes[] = {
   { FsmRecvVendor,	"Vendor Packet" },
   { FsmRecvConfigReq,  "Configure Request" },
   { FsmRecvConfigAck,  "Configure Ack" },
   { FsmRecvConfigNak,  "Configure Nak" },
   { FsmRecvConfigRej,  "Configure Reject" },
   { FsmRecvTermReq,    "Terminate Request" },
   { FsmRecvTermAck,    "Terminate Ack" },
   { FsmRecvCodeRej,    "Code Reject" },
   { FsmRecvProtoRej,   "Protocol Reject" },
   { FsmRecvEchoReq,    "Echo Request" },
   { FsmRecvEchoRep,    "Echo Reply" },
   { FsmRecvDiscReq,    "Discard Request" },
   { FsmRecvIdent,      "Ident" },
   { FsmRecvTimeRemain, "Time Remain" },
   { FsmRecvResetReq,   "Reset Request" },
   { FsmRecvResetAck,   "Reset Ack" },
  };

  #define NUM_FSM_CODES	(sizeof(FsmCodes) / sizeof(*FsmCodes))

/*
 * FsmInit()
 *
 * Initialize FSM structure and install some default values
 */

void
FsmInit(Fsm fp, FsmType type)
{
  memset(fp, 0, sizeof(*fp));
  fp->type = type;
  fp->log = LG_FSM | type->log;
  fp->conf.maxconfig = FSM_MAXCONFIG;
  fp->conf.maxterminate = FSM_MAXTERMINATE;
  fp->conf.maxfailure = FSM_MAXFAILURE;
  fp->conf.check_magic = FALSE;
  fp->conf.passive = FALSE;
  fp->state = ST_INITIAL;
  fp->reqid = 1;
  fp->rejid = 1;
  fp->echoid = 1;
}

/*
 * FsmNewState()
 *
 * Change state of a FSM. Also, call the configuration routine
 * if this is an appropriate time.
 */

void
FsmNewState(Fsm fp, int new)
{
  int	old;

  /* Log it */
  Log(fp->log, ("%s: state change %s --> %s",
    Pref(fp), FsmStateName(fp->state), FsmStateName(new)));

  /* Change state and call protocol's own handler, if any */
  old = fp->state;
  if (fp->type->NewState)
    (*fp->type->NewState)(fp, old, new);
  fp->state = new;
  if ((new >= ST_INITIAL && new <= ST_STOPPED) || (new == ST_OPENED))
    TimerStop(&fp->timer);

  /* Turn on/off keep-alive echo packets (if so configured) */
  TimerStop(&fp->echoTimer);
  if (new == ST_OPENED && fp->conf.echo_int != 0) {
    fp->quietCount = 0;
    memset(&fp->idleStats, 0, sizeof(fp->idleStats));
    TimerInit(&fp->echoTimer, "FsmKeepAlive",
      fp->conf.echo_int * SECONDS, FsmEchoTimeout, fp);
    TimerStart(&fp->echoTimer);
  }
}

/*
 * FsmOutputMbuf()
 *
 * Send out a control packet with specified contents. Consumes the mbuf.
 */

void
FsmOutputMbuf(Fsm fp, u_int code, u_int id, Mbuf payload)
{
  FsmOutputMbuf2(fp->type->proto, fp->type->link_layer, code, id, payload);
}

/*
 * FsmOutputMbuf2()
 */

void
FsmOutputMbuf2(u_short proto, int linklayer, u_int code, u_int id, Mbuf payload)
{
  Mbuf			bp;
  struct fsmheader	hdr;

  /* Build header */
  hdr.id = id;
  hdr.code = code;
  hdr.length = htons(sizeof(hdr) + plength(payload));

  /* Prepend to payload */
  bp = mbwrite(mballoc(MB_FSM, sizeof(hdr)), (u_char *) &hdr, sizeof(hdr));
  bp->next = payload;

  /* Send it out */
  NgFuncWritePppFrame(linklayer ?
    lnk->bundleIndex : NG_PPP_BUNDLE_LINKNUM, proto, bp);
}

/*
 * FsmOutput()
 *
 * Send out a control packet with specified contents
 */

void
FsmOutput(Fsm fp, u_int code, u_int id, u_char *ptr, int len)
{
  Mbuf	bp;

  bp = (len > 0) ? mbwrite(mballoc(MB_FSM, len), ptr, len) : NULL;
  FsmOutputMbuf(fp, code, id, bp);
}

/*
 * FsmOpen()
 *
 * XXX The use of the restart option should probably be
 * XXX configured per FSM via the initialization structure.
 * XXX For now, we just make it mandatory via #ifdef's.
 */

void
FsmOpen(Fsm fp)
{
  Log(fp->log, ("%s: Open event", Pref(fp)));
  switch (fp->state) {
    case ST_INITIAL:
      FsmNewState(fp, ST_STARTING);
      FsmLayerStart(fp);
      break;
    case ST_STARTING:
      break;
    case ST_CLOSED:
      if (fp->type->Configure)
	(*fp->type->Configure)(fp);
      FsmNewState(fp, ST_REQSENT);
      FsmLayerStart(fp);		/* Missing in RFC 1661 */
      FsmInitRestartCounter(fp, fp->conf.maxconfig);
      FsmInitMaxFailure(fp, fp->conf.maxfailure);
      FsmInitMaxConfig(fp, fp->conf.maxconfig);
      FsmSendConfigReq(fp);
      break;
    case ST_REQSENT:
    case ST_ACKRCVD:
    case ST_ACKSENT:
      break;

    case ST_OPENED:
#ifdef RESTART_OPENED
      FsmDown(fp);
      FsmUp(fp);
#endif
      break;

    case ST_CLOSING:
      FsmNewState(fp, ST_STOPPING);
    case ST_STOPPING:
#ifdef RESTART_CLOSING
      FsmDown(fp);
      FsmUp(fp);
#endif
      break;

    case ST_STOPPED:
#ifdef RESTART_STOPPED
      FsmDown(fp);
      FsmUp(fp);
#endif
      break;
  }
}

void
FsmUp(Fsm fp)
{
  Log(fp->log, ("%s: Up event", Pref(fp)));
  switch (fp->state) {
    case ST_INITIAL:
      FsmNewState(fp, ST_CLOSED);
      break;
    case ST_STARTING:
      if (fp->type->Configure)
	(*fp->type->Configure)(fp);
      FsmNewState(fp, ST_REQSENT);
      FsmInitRestartCounter(fp, fp->conf.maxconfig);
      FsmInitMaxFailure(fp, fp->conf.maxfailure);
      FsmInitMaxConfig(fp, fp->conf.maxconfig);
      FsmSendConfigReq(fp);
      break;
    default:
      Log(fp->log, ("%s: Oops, UP at %s",
	Pref(fp), FsmStateName(fp->state)));
      break;
  }
}

void
FsmDown(Fsm fp)
{
  Log(fp->log, ("%s: Down event", Pref(fp)));
  switch (fp->state) {
    case ST_CLOSING:
      FsmLayerFinish(fp);		/* Missing in RFC 1661 */
      /* fall through */
    case ST_CLOSED:
      FsmNewState(fp, ST_INITIAL);
      break;
    case ST_STOPPED:
      FsmNewState(fp, ST_STARTING);
      FsmLayerStart(fp);
      break;
    case ST_STOPPING:
    case ST_REQSENT:
    case ST_ACKRCVD:
    case ST_ACKSENT:
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      FsmNewState(fp, ST_STARTING);
      break;
    case ST_OPENED:
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      FsmNewState(fp, ST_STARTING);
      FsmLayerDown(fp);
      break;
  }
}

void
FsmClose(Fsm fp)
{
  Log(fp->log, ("%s: Close event", Pref(fp)));
  switch (fp->state) {
    case ST_STARTING:
      FsmNewState(fp, ST_INITIAL);
      FsmLayerFinish(fp);
      break;
    case ST_STOPPED:
      FsmNewState(fp, ST_CLOSED);
      break;
    case ST_STOPPING:
      FsmNewState(fp, ST_CLOSING);
      break;
    case ST_OPENED:
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      FsmNewState(fp, ST_CLOSING);
      FsmInitRestartCounter(fp, fp->conf.maxterminate);
      FsmSendTerminateReq(fp);
      FsmLayerDown(fp);
      break;
    case ST_REQSENT:
    case ST_ACKRCVD:
    case ST_ACKSENT:
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      FsmNewState(fp, ST_CLOSING);
      FsmInitRestartCounter(fp, fp->conf.maxterminate);
      FsmSendTerminateReq(fp);
      break;
  }
}

/*
 * Send functions
 */

static void
FsmSendConfigReq(Fsm fp)
{
  u_char	reqBuf[REQ_BUFFER_SIZE];
  u_char	*cp;

  /* Build and display config request */
  Log(fp->log, ("%s: SendConfigReq #%d", Pref(fp), fp->reqid));
  cp = (*fp->type->BuildConfigReq)(fp, reqBuf);
  FsmDecodeBuffer(fp, reqBuf, cp - reqBuf, MODE_NOP);

  /* Send it */
  FsmOutput(fp, CODE_CONFIGREQ, fp->reqid++, reqBuf, cp - reqBuf);

  /* Restart restart timer and decrement restart counter */
  TimerStart(&fp->timer);
  fp->restart--;
  fp->config--;
}

static void
FsmSendTerminateReq(Fsm fp)
{
  Log(fp->log, ("%s: SendTerminateReq #%d", Pref(fp), fp->reqid));
  FsmOutput(fp, CODE_TERMREQ, fp->reqid++, NULL, 0);
  if (fp->type->SendTerminateReq)
    (*fp->type->SendTerminateReq)(fp);
  TimerStart(&fp->timer);	/* Restart restart timer */
  fp->restart--;		/* Decrement restart counter */
}

static void
FsmSendTerminateAck(Fsm fp)
{
  Log(fp->log, ("%s: SendTerminateAck #%d", Pref(fp), fp->reqid));
  FsmOutput(fp, CODE_TERMACK, fp->reqid++, NULL, 0);
  if (fp->type->SendTerminateAck)
    (*fp->type->SendTerminateAck)(fp);
}

static void
FsmSendConfigAck(Fsm fp, FsmHeader lhp, u_char *option, int count)
{
  Log(fp->log, ("%s: SendConfigAck #%d", Pref(fp), lhp->id));
  FsmDecodeBuffer(fp, option, count, MODE_NOP);
  FsmOutput(fp, CODE_CONFIGACK, lhp->id, option, count);
}

static void
FsmSendConfigRej(Fsm fp, FsmHeader lhp, u_char *option, int count)
{
  Log(fp->log, ("%s: SendConfigRej #%d", Pref(fp), lhp->id));
  FsmDecodeBuffer(fp, option, count, MODE_NOP);
  FsmOutput(fp, CODE_CONFIGREJ, lhp->id, option, count);
  fp->failure--;
}

static void
FsmSendConfigNak(Fsm fp, FsmHeader lhp, u_char *option, int count)
{
  Log(fp->log, ("%s: SendConfigNak #%d", Pref(fp), lhp->id));
  FsmDecodeBuffer(fp, option, count, MODE_NOP);
  FsmOutput(fp, CODE_CONFIGNAK, lhp->id, option, count);
  fp->failure--;
}

/*
 * Timeout actions
 */

static void
FsmTimeout(void *arg)
{
  Fsm	fp = (Fsm) arg;

  if (fp->restart > 0) {	/* TO+ */
    switch (fp->state) {
      case ST_CLOSING:
      case ST_STOPPING:
	FsmSendTerminateReq(fp);
	break;
      case ST_REQSENT:
      case ST_ACKSENT:
	FsmSendConfigReq(fp);
	break;
      case ST_ACKRCVD:
	FsmNewState(fp, ST_REQSENT);
	FsmSendConfigReq(fp);
	break;
    }
    TimerStart(&fp->timer);
  } else {				/* TO- */
    switch (fp->state) {
      case ST_CLOSING:
	FsmNewState(fp, ST_CLOSED);
	FsmLayerFinish(fp);
	break;
      case ST_STOPPING:
	FsmNewState(fp, ST_STOPPED);
	FsmLayerFinish(fp);
	break;
      case ST_REQSENT:
      case ST_ACKSENT:
      case ST_ACKRCVD:
	if (fp->type->UnConfigure)
	  (*fp->type->UnConfigure)(fp);
	FsmNewState(fp, ST_STOPPED);
	if (!fp->conf.passive)
	  FsmLayerFinish(fp);
	FsmFailure(fp, FAIL_NEGOT_FAILURE);
	break;
    }
  }
}

void
FsmInitRestartCounter(Fsm fp, int value)
{
  const int	retry = fp->type->link_layer ?
			lnk->conf.retry_timeout : bund->conf.retry_timeout;

  TimerStop(&fp->timer);
  TimerInit(&fp->timer,
    fp->type->name, retry * SECONDS, FsmTimeout, (void *) fp);
  fp->restart = value;
}

void
FsmInitMaxFailure(Fsm fp, int value)
{
  fp->failure = value;
}

void
FsmInitMaxConfig(Fsm fp, int value)
{
  fp->config = value;
}

/*
 * Actions that happen when we receive packets
 */

/* RCR */

static void
FsmRecvConfigReq(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  int	fullyAcked;

  /* Check and process easy cases */
  switch (fp->state) {
    case ST_INITIAL:
    case ST_STARTING:
      Log(fp->log, ("%s: Oops, RCR in %s", Pref(fp), FsmStateName(fp->state)));
      PFREE(bp);
      return;
    case ST_CLOSED:
      FsmSendTerminateAck(fp);
      PFREE(bp);
      return;
    case ST_STOPPED:
      if (fp->type->Configure)
	(*fp->type->Configure)(fp);
      break;
    case ST_CLOSING:
    case ST_STOPPING:
      PFREE(bp);
      return;
  }

  /* Decode packet */
  bp = mbunify(bp);
  FsmDecodeBuffer(fp, MBDATA(bp), MBLEN(bp), MODE_REQ);

  /* State specific actions */
  switch (fp->state) {
    case ST_OPENED:
      FsmLayerDown(fp);
      FsmSendConfigReq(fp);
      break;
    case ST_STOPPED:
      FsmLayerStart(fp);		/* Missing in RFC 1661 */
      FsmInitRestartCounter(fp, fp->conf.maxconfig);
      FsmInitMaxFailure(fp, fp->conf.maxfailure);
      FsmInitMaxConfig(fp, fp->conf.maxconfig);
      FsmSendConfigReq(fp);
      break;
  }

  /* What did we think of packet? */
  fullyAcked = (gNakSize == 0 && gRejSize == 0);
  if (fullyAcked)
    FsmSendConfigAck(fp, lhp, gAckBuf, gAckSize);
  else {
    if (fp->failure <= 0) {
      Log(fp->log, ("%s: not converging", Pref(fp)));
      FsmFailure(fp, FAIL_NEGOT_FAILURE);
      PFREE(bp);
      return;
    } else {
      if (gRejSize)
	FsmSendConfigRej(fp, lhp, gRejBuf, gRejSize);
      else if (gNakSize)
	FsmSendConfigNak(fp, lhp, gNakBuf, gNakSize);
    }
  }

  /* Continue with state transition */
  switch (fp->state) {
    case ST_STOPPED:
    case ST_OPENED:
      if (fullyAcked)
	FsmNewState(fp, ST_ACKSENT);
      else
	FsmNewState(fp, ST_REQSENT);
      break;
    case ST_REQSENT:
      if (fullyAcked)
	FsmNewState(fp, ST_ACKSENT);
      break;
    case ST_ACKRCVD:
      if (fullyAcked)
      {
	FsmNewState(fp, ST_OPENED);
	FsmLayerUp(fp);
      }
      break;
    case ST_ACKSENT:
      if (!fullyAcked)
	FsmNewState(fp, ST_REQSENT);
      break;
  }
  PFREE(bp);
}

/* RCA */

static void
FsmRecvConfigAck(Fsm fp, FsmHeader lhp, Mbuf bp)
{

  /* Check sequence number */
  if (lhp->id != (u_char) (fp->reqid - 1)) {
    Log(fp->log, (" Wrong id#, expecting %d", (u_char) (fp->reqid - 1)));
    PFREE(bp);
    return;
  }

  /* XXX We should verify the contents are equal to our last sent config-req */

  /* Decode packet */
  bp = mbunify(bp);
  FsmDecodeBuffer(fp, MBDATA(bp), MBLEN(bp), MODE_NOP);

  /* Do whatever */
  switch (fp->state) {
    case ST_CLOSED:
    case ST_STOPPED:
      FsmSendTerminateAck(fp);
      break;
    case ST_CLOSING:
    case ST_STOPPING:
      break;
    case ST_REQSENT:
      FsmNewState(fp, ST_ACKRCVD);
      FsmInitRestartCounter(fp, fp->conf.maxconfig);
      FsmInitMaxFailure(fp, fp->conf.maxfailure);
      FsmInitMaxConfig(fp, fp->conf.maxconfig);
      break;
    case ST_ACKRCVD:
      FsmNewState(fp, ST_REQSENT);
      FsmSendConfigReq(fp);
      break;
    case ST_ACKSENT:
      FsmNewState(fp, ST_OPENED);
      FsmInitRestartCounter(fp, fp->conf.maxconfig);
      FsmInitMaxFailure(fp, fp->conf.maxfailure);
      FsmInitMaxConfig(fp, fp->conf.maxconfig);
      FsmLayerUp(fp);
      break;
    case ST_OPENED:
      FsmNewState(fp, ST_REQSENT);
      FsmLayerDown(fp);
      FsmSendConfigReq(fp);
      break;
  }
  PFREE(bp);
}

/* RCN */

static void
FsmRecvConfigNak(Fsm fp, FsmHeader lhp, Mbuf bp)
{

  /* Check sequence number */
  if (lhp->id != (u_char) (fp->reqid - 1)) {
    Log(fp->log, (" Wrong id#, expecting %d", (u_char) (fp->reqid - 1)));
    PFREE(bp);
    return;
  }

  /* Check and process easy cases */
  switch (fp->state) {
    case ST_INITIAL:
    case ST_STARTING:
      Log(fp->log, ("%s: Oops, RCN in %s", Pref(fp), FsmStateName(fp->state)));
      PFREE(bp);
      return;
    case ST_CLOSED:
    case ST_STOPPED:
      FsmSendTerminateAck(fp);
      PFREE(bp);
      return;
    case ST_CLOSING:
    case ST_STOPPING:
      PFREE(bp);
      return;
  }

  /* Decode packet */
  bp = mbunify(bp);
  FsmDecodeBuffer(fp, MBDATA(bp), MBLEN(bp), MODE_NAK);

  /* Not converging? */
  if (fp->config <= 0) {
    Log(fp->log, ("%s: not converging", Pref(fp)));
    FsmFailure(fp, FAIL_NEGOT_FAILURE);
    PFREE(bp);
    return;
  }

  /* Do whatever */
  switch (fp->state) {
    case ST_REQSENT:
    case ST_ACKSENT:
      FsmInitRestartCounter(fp, fp->conf.maxconfig);
      FsmSendConfigReq(fp);
      break;
    case ST_OPENED:
      FsmLayerDown(fp);
      /* fall through */
    case ST_ACKRCVD:
      FsmNewState(fp, ST_REQSENT);
      FsmSendConfigReq(fp);
      break;
  }
  PFREE(bp);
}

/* RCJ */

static void
FsmRecvConfigRej(Fsm fp, FsmHeader lhp, Mbuf bp)
{

  /* Check sequence number */
  if (lhp->id != (u_char) (fp->reqid - 1)) {
    Log(fp->log, (" Wrong id#, expecting %d", (u_char) (fp->reqid - 1)));
    PFREE(bp);
    return;
  }

  /* XXX should verify contents are a subset of previously sent config-req */

  /* Check and process easy cases */
  switch (fp->state) {
    case ST_INITIAL:
    case ST_STARTING:
      Log(fp->log, ("%s: Oops, RCJ in %s", Pref(fp), FsmStateName(fp->state)));
      PFREE(bp);
      return;
    case ST_CLOSED:
    case ST_STOPPED:
      FsmSendTerminateAck(fp);
      PFREE(bp);
      return;
    case ST_CLOSING:
    case ST_STOPPING:
      PFREE(bp);
      return;
  }

  /* Decode packet */
  bp = mbunify(bp);
  FsmDecodeBuffer(fp, MBDATA(bp), MBLEN(bp), MODE_REJ);

  /* Not converging? */
  if (fp->config <= 0) {
    Log(fp->log, ("%s: not converging", Pref(fp)));
    FsmFailure(fp, FAIL_NEGOT_FAILURE);
    PFREE(bp);
    return;
  }

  /* Do whatever */
  switch (fp->state) {
    case ST_REQSENT:
    case ST_ACKSENT:
      FsmInitRestartCounter(fp, fp->conf.maxconfig);
      FsmSendConfigReq(fp);
      break;
    case ST_OPENED:
      FsmNewState(fp, ST_REQSENT);
      FsmLayerDown(fp);
      FsmSendConfigReq(fp);
      break;
    case ST_ACKRCVD:
      FsmNewState(fp, ST_REQSENT);
      FsmSendConfigReq(fp);
      break;
  }
  PFREE(bp);
}

/* RTR */

static void
FsmRecvTermReq(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  RecordLinkUpDownReason(fp->type->link_layer ? lnk : NULL,
    0, STR_PEER_DISC, lcats(STR_PEER_DISCONNECT));
  switch (fp->state) {
    case ST_INITIAL:
    case ST_STARTING:
      Log(fp->log, ("%s: Oops, RTR in %s", Pref(fp), FsmStateName(fp->state)));
      break;
    case ST_CLOSED:
    case ST_STOPPED:
    case ST_CLOSING:
    case ST_STOPPING:
    case ST_REQSENT:
      FsmSendTerminateAck(fp);
      break;
    case ST_ACKRCVD:
    case ST_ACKSENT:
      FsmNewState(fp, ST_REQSENT);
      FsmSendTerminateAck(fp);
      break;
    case ST_OPENED:
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      FsmNewState(fp, ST_STOPPING);
      FsmSendTerminateAck(fp);
      FsmLayerDown(fp);
      FsmInitRestartCounter(fp, 0);	/* Zero restart counter */
      TimerStart(&fp->timer);		/* Start restart timer */
      break;
  }
  PFREE(bp);
}

/* RTA */

static void
FsmRecvTermAck(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  switch (fp->state) {
    case ST_CLOSING:
      FsmNewState(fp, ST_CLOSED);
      FsmLayerFinish(fp);
      break;
    case ST_STOPPING:
      FsmNewState(fp, ST_STOPPED);
      FsmLayerFinish(fp);
      break;
    case ST_ACKRCVD:
      FsmNewState(fp, ST_REQSENT);
      break;
    case ST_OPENED:
      FsmNewState(fp, ST_REQSENT);
      FsmLayerDown(fp);
      FsmSendConfigReq(fp);
      break;
  }
  PFREE(bp);
}

/*
 * FsmRecvCodeRej()
 *
 * By default, this is fatal for most codes
 */

static void
FsmRecvCodeRej(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  u_char	code = 0;
  int		fatal;

  /* Get code and log it */
  bp = mbread(bp, &code, sizeof(code), NULL);
  Log(fp->log, ("%s: code %s was rejected", Pref(fp), FsmCodeName(code)));

  /* Determine fatalness */
  if (fp->type->RecvCodeRej)
    fatal = (*fp->type->RecvCodeRej)(fp, code, bp);
  else {
    switch (code) {
      case CODE_CONFIGREQ:
      case CODE_CONFIGACK:
      case CODE_CONFIGNAK:
      case CODE_CONFIGREJ:
      case CODE_TERMREQ:
      case CODE_TERMACK:
      case CODE_CODEREJ:
      case CODE_PROTOREJ:
      case CODE_ECHOREQ:
      case CODE_ECHOREP:
      case CODE_RESETREQ:
      case CODE_RESETACK:
	fatal = TRUE;
	break;
      case CODE_VENDOR:
      case CODE_DISCREQ:
      case CODE_IDENT:
      case CODE_TIMEREM:
      default:		/* if we don't know it, that makes two of us */
	fatal = FALSE;
	break;
    }
  }

  /* Possibly shut down as a result */
  if (fatal)
    FsmFailure(fp, FAIL_RECD_CODEREJ);		/* RXJ- */
  else
    FsmRecvRxjPlus(fp);
  PFREE(bp);
}

/*
 * FsmRecvProtoRej()
 *
 * By default, this is non-fatal
 */

static void
FsmRecvProtoRej(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  u_short	proto = 0;
  int		fatal = FALSE;

  bp = mbread(bp, (u_char *) &proto, sizeof(proto), NULL);
  proto = ntohs(proto);
  Log(fp->log, ("%s: protocol %s was rejected", Pref(fp), ProtoName(proto)));
  if (fp->state == ST_OPENED && fp->type->RecvProtoRej)
    fatal = (*fp->type->RecvProtoRej)(fp, proto, bp);
  if (fatal)
    FsmFailure(fp, FAIL_RECD_PROTREJ);		/* RXJ- */
  else
    FsmRecvRxjPlus(fp);
  PFREE(bp);
}

/*
 * FsmRecvRxjPlus()
 */

static void
FsmRecvRxjPlus(Fsm fp)				/* RXJ+ */
{
  switch (fp->state) {
    case ST_ACKRCVD:
      FsmNewState(fp, ST_REQSENT);
      break;
  }
}

/*
 * FsmFailure()
 *
 * Call this at any point if something fatal happens that is inherent
 * to the FSM itself, like a RXJ- event or negotiation failed to converge.
 * The action taken is like the RXJ- event.
 */

void
FsmFailure(Fsm fp, enum fsmfail reason)
{
  Log(fp->log, ("%s: %s", Pref(fp), FsmFailureStr(reason)));

  /* Let layer do any special handling of error code */
  if (fp->type->Failure)
    (*fp->type->Failure)(fp, reason);

  /* Shut this state machine down */
  switch (fp->state) {
    case ST_CLOSING:
      FsmNewState(fp, ST_CLOSED);
      /* fall through */
    case ST_CLOSED:
      FsmLayerFinish(fp);
      break;
    case ST_STOPPING:
      FsmNewState(fp, ST_STOPPED);
      FsmLayerFinish(fp);
      break;
    case ST_ACKRCVD:
    case ST_ACKSENT:
    case ST_REQSENT:
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      FsmNewState(fp, ST_STOPPED);
      if (!fp->conf.passive)
	FsmLayerFinish(fp);
      break;

    /*
     * In the opened state, the peer FSM has somehow died or did something
     * horribly wrong to us. So the common sense action in this case is to 
     * bring the whole link down, rather than restarting just the FSM.
     * We do this by telling the lower layer to restart using tlf and tls
     * while also pretending that we got a DOWN event.
     */
    case ST_OPENED:
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      FsmLayerFinish(fp);
      FsmLayerStart(fp);
      FsmNewState(fp, ST_STARTING);	/* Pretend we got a DOWN event */
      FsmLayerDown(fp);
      break;

    /*
     * In the STOPPED state, then most likely the negotiation timed out
     * or didn't converge. Just wait for the DOWN event from the previously
     * issued tlf action.
     */
    case ST_STOPPED:
      if (!fp->conf.passive)
	FsmLayerFinish(fp);
      break;
  }
}

/*
 * FsmFailureStr()
 */

const char *
FsmFailureStr(enum fsmfail reason)
{
  const char	*string = NULL;

  switch (reason) {
    case FAIL_NEGOT_FAILURE:
      string = lcats(STR_FAIL_NEGOT_FAILURE);
      break;
    case FAIL_RECD_BADMAGIC:
      string = lcats(STR_FAIL_RECD_BADMAGIC);
      break;
    case FAIL_RECD_CODEREJ:
      string = lcats(STR_FAIL_RECD_CODEREJ);
      break;
    case FAIL_RECD_PROTREJ:
      string = lcats(STR_FAIL_RECD_PROTREJ);
      break;
    case FAIL_WAS_PROTREJ:
      string = lcats(STR_FAIL_WAS_PROTREJ);
      break;
    case FAIL_ECHO_TIMEOUT:
      string = lcats(STR_FAIL_ECHO_TIMEOUT);
      break;
    case FAIL_CANT_ENCRYPT:
      string = lcats(STR_FAIL_CANT_ENCRYPT);
      break;
    default:
      assert(0);
  }
  return(string);
}

/*
 * FsmSendEchoReq()
 *
 * Send an echo request containing the supplied payload data.
 * Consumes the mbuf.
 */

void
FsmSendEchoReq(Fsm fp, Mbuf payload)
{
  u_int32_t	const self_magic = htonl(lnk->lcp.want_magic);
  Mbuf		bp;

  if (fp->state != ST_OPENED)
    return;

  /* Prepend my magic number */
  bp = mbwrite(mballoc(MB_ECHO, sizeof(self_magic)),
    (u_char *) &self_magic, sizeof(self_magic));
  bp->next = payload;

  /* Send it */
  Log(LG_ECHO, ("%s: SendEchoReq #%d", Pref(fp), fp->echoid));
  FsmOutputMbuf(fp, CODE_ECHOREQ, fp->echoid++, bp);
}

/*
 * FsmSendIdent()
 *
 * Send an LCP ident packet. Consumes the mbuf.
 */

void
FsmSendIdent(Fsm fp, const char *ident)
{
  u_int32_t	self_magic = htonl(lnk->lcp.want_magic);
  const int	len = strlen(ident) + 1;	/* include NUL to be nice */
  Mbuf		bp;

  /* Leave magic zero unless fully opened, as IDENT can be sent anytime */
  if (fp->state != ST_OPENED)
    self_magic = 0;

  /* Prepend my magic number */
  bp = mbwrite(mballoc(MB_FSM, sizeof(self_magic)),
    (u_char *) &self_magic, sizeof(self_magic));
  bp->next = mbwrite(mballoc(MB_FSM, len), (u_char *) ident, len);

  /* Send it */
  Log(LG_FSM, ("%s: SendIdent #%d", Pref(fp), fp->echoid));
  ShowMesg(LG_FSM, ident, len);
  FsmOutputMbuf(fp, CODE_IDENT, fp->echoid++, bp);
}

/*
 * FsmRecvEchoReq()
 */

static void
FsmRecvEchoReq(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  u_int32_t	self_magic;
  Mbuf		mbp;

  /* Validate magic number */
  bp = FsmCheckMagic(fp, bp);

  /* If not opened, do nothing */
  if (fp->state != ST_OPENED) {
    PFREE(bp);
    return;
  }

  /* Stick my magic number in there instead */
  self_magic = htonl(lnk->lcp.want_magic);
  mbp = mbwrite(mballoc(MB_FSM, sizeof(self_magic)),
    (u_char *) &self_magic, sizeof(self_magic));
  mbp->next = bp;
  bp = mbp;

  /* Send it back, preserving everything else */
  Log(LG_ECHO, ("%s: SendEchoRep #%d", Pref(fp), lhp->id));
  FsmOutputMbuf(fp, CODE_ECHOREP, lhp->id, bp);
}

/*
 * FsmRecvEchoRep()
 */

static void
FsmRecvEchoRep(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  bp = FsmCheckMagic(fp, bp);
  PFREE(bp);
}

/*
 * FsmRecvDiscReq()
 */

static void
FsmRecvDiscReq(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  bp = FsmCheckMagic(fp, bp);
  if (fp->type->RecvDiscReq)
    (*fp->type->RecvDiscReq)(fp, bp);
  PFREE(bp);
}

/*
 * FsmRecvIdent()
 */

static void
FsmRecvIdent(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  bp = FsmCheckMagic(fp, bp);
  if (fp->type->RecvIdent)
    (*fp->type->RecvIdent)(fp, bp);
  else
    if (bp)
      ShowMesg(fp->log, MBDATA(bp), MBLEN(bp));
  PFREE(bp);
}

/*
 * FsmRecvVendor()
 */

static void
FsmRecvVendor(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  bp = FsmCheckMagic(fp, bp);
  if (fp->type->RecvVendor)
    (*fp->type->RecvVendor)(fp, bp);
  PFREE(bp);
}

/*
 * FsmRecvTimeRemain()
 */

static void
FsmRecvTimeRemain(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  bp = FsmCheckMagic(fp, bp);
  if (fp->type->RecvTimeRemain)
    (*fp->type->RecvTimeRemain)(fp, bp);
  else {
    u_int32_t	remain = 0;

    bp = mbread(bp, (u_char *) &remain, sizeof(remain), NULL);
    remain = ntohl(remain);
    Log(fp->log, (" %u seconds remain", remain));
    if (bp)
      ShowMesg(fp->log, MBDATA(bp), MBLEN(bp));
  }
  PFREE(bp);
}

/*
 * FsmRecvResetReq()
 */

static void
FsmRecvResetReq(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  if (fp->type->RecvResetReq)
    (*fp->type->RecvResetReq)(fp, lhp->id, bp);
  PFREE(bp);
}

/*
 * FsmRecvResetAck()
 */

static void
FsmRecvResetAck(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  if (fp->type->RecvResetAck)
    (*fp->type->RecvResetAck)(fp, lhp->id, bp);
  PFREE(bp);
}

/*
 * FsmLayerUp()
 */

static void
FsmLayerUp(Fsm fp)
{
  Log(fp->log, ("%s: LayerUp", Pref(fp)));
  if (fp->type->LayerUp)
    (*fp->type->LayerUp)(fp);
}

/*
 * FsmLayerDown()
 */

static void
FsmLayerDown(Fsm fp)
{
  Log(fp->log, ("%s: LayerDown", Pref(fp)));
  if (fp->type->LayerDown)
    (*fp->type->LayerDown)(fp);
}

/*
 * FsmLayerStart()
 */

static void
FsmLayerStart(Fsm fp)
{
  Log(fp->log, ("%s: LayerStart", Pref(fp)));
  if (fp->type->LayerStart)
    (*fp->type->LayerStart)(fp);
}

/*
 * FsmLayerFinish()
 */

static void
FsmLayerFinish(Fsm fp)
{
  Log(fp->log, ("%s: LayerFinish", Pref(fp)));
  if (fp->type->LayerFinish)
    (*fp->type->LayerFinish)(fp);
}

/*
 * FsmCheckMagic()
 */

static Mbuf
FsmCheckMagic(Fsm fp, Mbuf bp)
{
  u_int32_t	peer_magic;
  u_int32_t	peer_magic_ought;

  /* Read magic number */
  bp = mbread(bp, (u_char *) &peer_magic, sizeof(peer_magic), NULL);
  peer_magic = ntohl(peer_magic);

  /* What should magic number be? */
  if (PROT_LINK_LAYER(fp->type->proto))
    peer_magic_ought = lnk->lcp.peer_magic;
  else
    peer_magic_ought = 0;

  /* Verify */
  if (fp->conf.check_magic && peer_magic != 0
     && peer_magic != peer_magic_ought) {
    Log(fp->log, ("%s: magic number is wrong: 0x%08x != 0x%08x",
      Pref(fp), peer_magic, peer_magic_ought));
    FsmFailure(fp, FAIL_RECD_BADMAGIC);
  }
  return(bp);
}

/*
 * FsmEchoTimeout()
 */

static void
FsmEchoTimeout(void *arg)
{
  Fsm				const fp = (Fsm) arg;
  struct ng_ppp_link_stat	oldStats;

  /* See if there was any traffic since last time */
  oldStats = fp->idleStats;
  NgFuncGetStats(fp->type->link_layer ?
    lnk->bundleIndex : NG_PPP_BUNDLE_LINKNUM, FALSE, &fp->idleStats);
  if (fp->idleStats.recvFrames > oldStats.recvFrames)
    fp->quietCount = 0;
  else
    fp->quietCount++;

  /* See if peer hasn't responded for too many requests */
  TimerStop(&fp->echoTimer);
  switch (fp->quietCount) {

      /* Peer failed to reply to previous echo request */
    default:
      Log(LG_ECHO|fp->log,
	("%s: no reply to %d echo request(s)",
	Pref(fp), fp->quietCount - 1));

      /* Has peer failed to reply for maximum allowable interval? */
      if (fp->quietCount * fp->conf.echo_int >= fp->conf.echo_max) {
	FsmFailure(fp, FAIL_ECHO_TIMEOUT);
	break;
      }
      /* fall through */
    case 1:	/* one interval of silence elapsed; send first echo request */
      FsmSendEchoReq(fp, NULL);
      /* fall through */
    case 0:
      TimerStart(&fp->echoTimer);
      break;
  }
}

/*
 * FsmInput()
 *
 * Deal with an incoming packet for FSM pointed to by "fp"
 */

void
FsmInput(Fsm fp, Mbuf bp, int linkNum)
{
  int			log, recd_len, length;
  struct fsmheader	hdr;

  /* Check for runt frames; discard them */
  if ((recd_len = plength(bp)) < sizeof(hdr)) {
    Log(fp->log, ("%s: runt packet: %d bytes", Pref(fp), recd_len));
    PFREE(bp);
    return;
  }

  /* Read in the header */
  bp = mbread(bp, (u_char *) &hdr, sizeof(hdr), NULL);
  length = ntohs(hdr.length);

  /* Make sure length is sensible; discard otherwise */
  if (length < sizeof(hdr) || length > recd_len) {
    Log(fp->log, ("%s: bad length: says %d, rec'd %d",
      Pref(fp), length, recd_len));
    PFREE(bp);
    return;
  }

  /* Truncate off any padding bytes */
  if (length < recd_len)
    bp = mbtrunc(bp, length - sizeof(hdr));

  /* Check for a valid code byte -- if not, send code-reject */
  if (!((1 << hdr.code) & fp->type->known_codes)) {	/* RUC */
    Log(fp->log, ("%s: unknown code %d", Pref(fp), hdr.code));
    bp = mbunify(bp);
    FsmOutput(fp, CODE_CODEREJ, fp->rejid++, MBDATA(bp), MBLEN(bp));
    PFREE(bp);
    return;
  }

  /* Log it */
  if (hdr.code == CODE_ECHOREQ || hdr.code == CODE_ECHOREP)
    log = LG_ECHO;
  else if (hdr.code == CODE_RESETREQ || hdr.code == CODE_RESETACK)
    log = fp->type->log2;
  else
    log = fp->log;
  Log(log, ("%s: rec'd %s #%d link %d (%s)",
    Pref(fp), FsmCodeName(hdr.code), (int) hdr.id,
    (int16_t)linkNum, FsmStateName(fp->state)));

  /* Do whatever */
  (*FsmCodes[hdr.code].action)(fp, &hdr, bp);
}

/*
 * FsmConfValue()
 *
 * Write in a configuration item with "len" bytes of information.
 * Special cases: length -2 or -4 means convert to network byte order.
 */

u_char *
FsmConfValue(u_char *cp, int type, int len, const void *data)
{
  u_char	*bytes = (u_char *) data;
  u_short	sv;
  u_long	lv;

  /* Special cases */
  switch (len) {
    case -2:
      len = 2;
      sv = htons(*((u_short *) data));
      bytes = (u_char *) &sv;
      break;
    case -4:
      len = 4;
      lv = htonl(*((u_long *) data));
      bytes = (u_char *) &lv;
      break;
    default:
      break;
  }

  /* Add header and data */
  *cp++ = type;
  *cp++ = len + 2;
  while (len-- > 0)
    *cp++ = *bytes++;

  /* Done */
  return(cp);
}

/*
 * FsmExtractOptions()
 *
 * Extract options from a config packet
 */

static int
FsmExtractOptions(Fsm fp, u_char *data, int dlen, FsmOption opts, int max)
{
  int	num;

  for (num = 0; dlen >= 2 && num < max; num++) {
    FsmOption	const opt = &opts[num];

    opt->type = data[0];
    opt->len = data[1];
    opt->data = &data[2];
    if (opt->len < 2 || opt->len > dlen)
      break;
    data += opt->len;
    dlen -= opt->len;
  }
  if (dlen != 0)
    LogDumpBuf(LG_ERR, data, dlen,
      "%s: %d extra garbage bytes in config packet", Pref(fp), dlen);
  return(num);
}

/*
 * FsmFindOption()
 */

FsmOptInfo
FsmFindOptInfo(FsmOptInfo list, u_char type)
{
  for (; list->name; list++)
    if (list->type == type)
      return(list);
  return(NULL);
}

/*
 * FsmDecodeBuffer()
 */

static void
FsmDecodeBuffer(Fsm fp, u_char *buf, int size, int mode)
{
  struct fsmoption	opts[FSM_MAX_OPTS];
  int			num;

  if (mode == MODE_REQ)
    gAckSize = gNakSize = gRejSize = 0;
  num = FsmExtractOptions(fp, buf, size, opts, FSM_MAX_OPTS);
  (*fp->type->DecodeConfig)(fp, opts, num, mode);
}

/*
 * FsmAck()
 */

void
FsmAck(Fsm fp, const struct fsmoption *opt)
{
  if (gAckSize + opt->len > sizeof(gAckBuf)) {
    Log(LG_ERR, ("%s: %s buffer full", Pref(fp), "ack"));
    return;
  }
  memcpy(&gAckBuf[gAckSize], opt, 2);
  memcpy(&gAckBuf[gAckSize + 2], opt->data, opt->len - 2);
  gAckSize += opt->len;
}

/*
 * FsmNak()
 */

void
FsmNak(Fsm fp, const struct fsmoption *opt)
{
  if (gNakSize + opt->len > sizeof(gNakBuf)) {
    Log(LG_ERR, ("%s: %s buffer full", Pref(fp), "nak"));
    return;
  }
  memcpy(&gNakBuf[gNakSize], opt, 2);
  memcpy(&gNakBuf[gNakSize + 2], opt->data, opt->len - 2);
  gNakSize += opt->len;
}

/*
 * FsmRej()
 */

void
FsmRej(Fsm fp, const struct fsmoption *opt)
{
  if (gRejSize + opt->len > sizeof(gRejBuf)) {
    Log(LG_ERR, ("%s: %s buffer full", Pref(fp), "rej"));
    return;
  }
  memcpy(&gRejBuf[gRejSize], opt, 2);
  memcpy(&gRejBuf[gRejSize + 2], opt->data, opt->len - 2);
  gRejSize += opt->len;
}

/*
 * Pref()
 *
 * Cook up a little prefix for printing stuff to the log.
 * This is statically allocated so must be printed or
 * whatever immediately.
 */

char *
Pref(Fsm fp)
{
  static char	buf[100];

  snprintf(buf, sizeof(buf), "[%s] %s",
    fp->type->link_layer ? lnk->name : bund->name, fp->type->name);
  return(buf);
}

/*
 * FsmCodeName()
 *
 * This is statically allocated so must be printed or
 * whatever immediately.
 */

const char *
FsmCodeName(int code)
{
  static char	buf[20];

  if (code >= 0 && code < NUM_FSM_CODES)
    return(FsmCodes[code].name);
  snprintf(buf, sizeof(buf), "%d", code);
  return(buf);
}

/*
 * FsmStateName()
 */

const char *
FsmStateName(int state)
{
  switch (state) {
    case ST_INITIAL:	return "Initial";
    case ST_STARTING:	return "Starting";
    case ST_CLOSED:	return "Closed";
    case ST_STOPPED:	return "Stopped";
    case ST_CLOSING:	return "Closing";
    case ST_STOPPING:	return "Stopping";
    case ST_REQSENT:	return "Req-Sent";
    case ST_ACKRCVD:	return "Ack-Rcvd";
    case ST_ACKSENT:	return "Ack-Sent";
    case ST_OPENED:	return "Opened";
  }
  return "???";
}

