File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / mpd / src / fsm.c
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 00:39:23 2021 UTC (3 years, 9 months ago) by misho
Branches: mpd, MAIN
CVS tags: v5_9p16, v5_9, HEAD
mpd 5.9


/*
 * 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"
#include "util.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, enum fsm_state 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, void *arg)
{
  memset(fp, 0, sizeof(*fp));
  fp->type = type;
  fp->arg = arg;
  fp->log = LG_FSM | type->log;
  fp->log2 = LG_FSM | type->log2;
  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;
}

/*
 * FsmInst()
 *
 * Instantiate FSM structure from template
 */

void
FsmInst(Fsm fp, Fsm fpt, void *arg)
{
    memcpy(fp, fpt, sizeof(*fp));
    fp->arg = arg;
}

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

void
FsmNewState(Fsm fp, enum fsm_state new)
{
  enum fsm_state	old;

  /* Log it */
  Log(fp->log2, ("[%s] %s: state change %s --> %s",
    Pref(fp), Fsm(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) */
  if (old == ST_OPENED)
    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);
    TimerStartRecurring(&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)
{
    Bund		b;
    Mbuf		bp;
    struct fsmheader	hdr;

    if (fp->type->link_layer)
	b = ((Link)fp->arg)->bund;
    else
	b = (Bund)fp->arg;

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

    /* Prepend to payload */
    bp = mbcopyback(payload, -(int)sizeof(hdr), &hdr, sizeof(hdr));

    /* Send it out */
    if (fp->type->link_layer) {
	NgFuncWritePppFrameLink((Link)fp->arg, fp->type->proto, bp);
    } else {
	NgFuncWritePppFrame(b, NG_PPP_BUNDLE_LINKNUM, fp->type->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) ? mbcopyback(NULL, 0, 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->log2, ("[%s] %s: Open event", Pref(fp), Fsm(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->log2, ("[%s] %s: Up event", Pref(fp), Fsm(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->log2, ("[%s] %s: Oops, UP at %s",
	Pref(fp), Fsm(fp), FsmStateName(fp->state)));
      break;
  }
}

void
FsmDown(Fsm fp)
{
  Log(fp->log2, ("[%s] %s: Down event", Pref(fp), Fsm(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:
      FsmNewState(fp, ST_STARTING);
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      break;
    case ST_OPENED:
      FsmNewState(fp, ST_STARTING);
      FsmLayerDown(fp);
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      break;
    default:
      break;
  }
}

void
FsmClose(Fsm fp)
{
  Log(fp->log2, ("[%s] %s: Close event", Pref(fp), Fsm(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:
      FsmNewState(fp, ST_CLOSING);
      FsmInitRestartCounter(fp, fp->conf.maxterminate);
      FsmSendTerminateReq(fp);
      FsmLayerDown(fp);
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      break;
    case ST_REQSENT:
    case ST_ACKRCVD:
    case ST_ACKSENT:
      FsmNewState(fp, ST_CLOSING);
      FsmInitRestartCounter(fp, fp->conf.maxterminate);
      FsmSendTerminateReq(fp);
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      break;
    default:
      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] %s: SendConfigReq #%d", Pref(fp), Fsm(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] %s: SendTerminateReq #%d", Pref(fp), Fsm(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] %s: SendTerminateAck #%d", Pref(fp), Fsm(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] %s: SendConfigAck #%d", Pref(fp), Fsm(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] %s: SendConfigRej #%d", Pref(fp), Fsm(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] %s: SendConfigNak #%d", Pref(fp), Fsm(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;
      default:
	break;
    }
  } 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:
	FsmFailure(fp, FAIL_NEGOT_FAILURE);
	break;
      default:
        break;
    }
  }
}

void
FsmInitRestartCounter(Fsm fp, int value)
{
  const int	retry = fp->type->link_layer ?
			((Link)(fp->arg))->conf.retry_timeout : 
			((Bund)(fp->arg))->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->log2, ("[%s] %s: Oops, RCR in %s", Pref(fp), Fsm(fp), FsmStateName(fp->state)));
      mbfree(bp);
      return;
    case ST_CLOSED:
      FsmSendTerminateAck(fp);
      mbfree(bp);
      return;
    case ST_STOPPED:
      if (fp->type->Configure)
	(*fp->type->Configure)(fp);
      break;
    case ST_CLOSING:
    case ST_STOPPING:
      mbfree(bp);
      return;
    default:
      break;
  }

  /* Decode packet */
  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;
    default:
      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] %s: not converging", Pref(fp), Fsm(fp)));
      FsmFailure(fp, FAIL_NEGOT_FAILURE);
      mbfree(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;
    default:
      break;
  }
  mbfree(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, ("[%s]   Wrong id#, expecting %d", Pref(fp), (u_char) (fp->reqid - 1)));
    mbfree(bp);
    return;
  }

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

  /* Decode packet */
  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);
      TimerStart(&fp->timer);		/* Start restart timer */
      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;
    default:
      break;
  }
  mbfree(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, ("[%s]   Wrong id#, expecting %d", Pref(fp), (u_char) (fp->reqid - 1)));
    mbfree(bp);
    return;
  }

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

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

  /* Not converging? */
  if (fp->config <= 0) {
    Log(fp->log, ("[%s] %s: not converging", Pref(fp), Fsm(fp)));
    FsmFailure(fp, FAIL_NEGOT_FAILURE);
    mbfree(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;
    default:
      break;
  }
  mbfree(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, ("[%s]   Wrong id#, expecting %d", Pref(fp), (u_char) (fp->reqid - 1)));
    mbfree(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] %s: Oops, RCJ in %s", Pref(fp), Fsm(fp), FsmStateName(fp->state)));
      mbfree(bp);
      return;
    case ST_CLOSED:
    case ST_STOPPED:
      FsmSendTerminateAck(fp);
      mbfree(bp);
      return;
    case ST_CLOSING:
    case ST_STOPPING:
      mbfree(bp);
      return;
    default:
      break;
  }

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

  /* Not converging? */
  if (fp->config <= 0) {
    Log(fp->log, ("[%s] %s: not converging", Pref(fp), Fsm(fp)));
    FsmFailure(fp, FAIL_NEGOT_FAILURE);
    mbfree(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;
    default:
      break;
  }
  mbfree(bp);
}

/* RTR */

static void
FsmRecvTermReq(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  (void)lhp;
  if (fp->type->link_layer) {
    RecordLinkUpDownReason(NULL, (Link)(fp->arg), 0, STR_PEER_DISC, NULL);
  }
  switch (fp->state) {
    case ST_INITIAL:
    case ST_STARTING:
      Log(fp->log, ("[%s] %s: Oops, RTR in %s", Pref(fp), Fsm(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:
      FsmNewState(fp, ST_STOPPING);
      FsmSendTerminateAck(fp);
      FsmLayerDown(fp);
      FsmInitRestartCounter(fp, 0);	/* Zero restart counter */
      TimerStart(&fp->timer);		/* Start restart timer */
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(fp);
      break;
  }
  mbfree(bp);
}

/* RTA */

static void
FsmRecvTermAck(Fsm fp, FsmHeader lhp, Mbuf bp)
{
  (void)lhp;
  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;
    default:
      break;
  }
  mbfree(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;

  (void)lhp;
  /* Get code and log it */
  bp = mbread(bp, &code, sizeof(code));
  Log(fp->log, ("[%s] %s: code %s was rejected", Pref(fp), Fsm(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);
  mbfree(bp);
}

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

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

  (void)lhp;
  bp = mbread(bp, &proto, sizeof(proto));
  proto = ntohs(proto);
  Log(fp->log, ("[%s] %s: protocol %s was rejected", Pref(fp), Fsm(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);
  mbfree(bp);
}

/*
 * FsmRecvRxjPlus()
 */

static void
FsmRecvRxjPlus(Fsm fp)				/* RXJ+ */
{
  switch (fp->state) {
    case ST_ACKRCVD:
      FsmNewState(fp, ST_REQSENT);
      break;
    default:
      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: %s", Pref(fp), Fsm(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:
      FsmNewState(fp, ST_STOPPED);
      if (!fp->conf.passive)
	FsmLayerFinish(fp);
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(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:
      FsmNewState(fp, ST_STOPPING);
      FsmInitRestartCounter(fp, fp->conf.maxterminate);
      FsmSendTerminateReq(fp);
      FsmLayerDown(fp);
      if (fp->type->UnConfigure)
	(*fp->type->UnConfigure)(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;
    default:
      break;
  }
}

/*
 * FsmFailureStr()
 */

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

  switch (reason) {
    case FAIL_NEGOT_FAILURE:
      string = STR_FAIL_NEGOT_FAILURE;
      break;
    case FAIL_RECD_BADMAGIC:
      string = STR_FAIL_RECD_BADMAGIC;
      break;
    case FAIL_RECD_CODEREJ:
      string = STR_FAIL_RECD_CODEREJ;
      break;
    case FAIL_RECD_PROTREJ:
      string = STR_FAIL_RECD_PROTREJ;
      break;
    case FAIL_WAS_PROTREJ:
      string = STR_FAIL_WAS_PROTREJ;
      break;
    case FAIL_ECHO_TIMEOUT:
      string = STR_FAIL_ECHO_TIMEOUT;
      break;
    case FAIL_CANT_ENCRYPT:
      string = 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(((Link)(fp->arg))->lcp.want_magic);
  Mbuf		bp;

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

  /* Prepend my magic number */
  bp = mbcopyback(payload, -(int)sizeof(self_magic), &self_magic, sizeof(self_magic));

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

/*
 * FsmSendIdent()
 *
 * Send an LCP ident packet.
 */

void
FsmSendIdent(Fsm fp, const char *ident)
{
  u_int32_t	self_magic = htonl(((Link)(fp->arg))->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 = mbcopyback(NULL, 0, (u_char *) &self_magic, sizeof(self_magic));
  bp = mbcopyback(bp, sizeof(self_magic), ident, len);

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

/*
 * FsmSendTimeRemaining()
 *
 * Send an LCP Time-Remaining packet.
 */

void
FsmSendTimeRemaining(Fsm fp, u_int seconds)
{
  u_int32_t	self_magic = htonl(((Link)(fp->arg))->lcp.want_magic);
  u_int32_t	data = htonl(seconds);
  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 = mbcopyback(NULL, 0, (u_char *) &self_magic, sizeof(self_magic));
  bp = mbcopyback(bp, sizeof(self_magic), &data, 4);

  /* Send it */
  Log(fp->log2, ("[%s] %s: SendTimeRemaining #%d", Pref(fp), Fsm(fp), fp->echoid));
  Log(fp->log2, ("[%s]   %u seconds remain", Pref(fp), seconds));
  FsmOutputMbuf(fp, CODE_TIMEREM, fp->echoid++, bp);
}

/*
 * FsmRecvEchoReq()
 */

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

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

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

  /* Stick my magic number in there instead */
  self_magic = htonl(((Link)(fp->arg))->lcp.want_magic);
  bp = mbcopyback(bp, -(int)sizeof(self_magic), &self_magic, sizeof(self_magic));

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

/*
 * FsmRecvEchoRep()
 */

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

/*
 * FsmRecvDiscReq()
 */

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

/*
 * FsmRecvIdent()
 */

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

/*
 * FsmRecvVendor()
 */

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

/*
 * FsmRecvTimeRemain()
 */

static void
FsmRecvTimeRemain(Fsm fp, FsmHeader lhp, Mbuf bp)
{
    (void)lhp;
    bp = FsmCheckMagic(fp, bp);
    if (bp) {
	u_int32_t	remain = 0;
	mbcopy(bp, 0, &remain, sizeof(remain));
	remain = ntohl(remain);
	Log(fp->log, ("[%s]   %u seconds remain", Pref(fp), remain));
    }
    if (fp->type->RecvTimeRemain)
	(*fp->type->RecvTimeRemain)(fp, bp);
    mbfree(bp);
}

/*
 * FsmRecvResetReq()
 */

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

/*
 * FsmRecvResetAck()
 */

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

/*
 * FsmLayerUp()
 */

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

/*
 * FsmLayerDown()
 */

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

/*
 * FsmLayerStart()
 */

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

/*
 * FsmLayerFinish()
 */

static void
FsmLayerFinish(Fsm fp)
{
  Log(fp->log2, ("[%s] %s: LayerFinish", Pref(fp), Fsm(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, &peer_magic, sizeof(peer_magic));
  peer_magic = ntohl(peer_magic);

  /* What should magic number be? */
  if (PROT_LINK_LAYER(fp->type->proto))
    peer_magic_ought = ((Link)(fp->arg))->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] %s: magic number is wrong: 0x%08x != 0x%08x",
      Pref(fp), Fsm(fp), peer_magic, peer_magic_ought));
    FsmFailure(fp, FAIL_RECD_BADMAGIC);
  }
  return(bp);
}

/*
 * FsmEchoTimeout()
 */

static void
FsmEchoTimeout(void *arg)
{
    Fsm			const fp = (Fsm) arg;
    Bund		b;
    Link		l;
#ifndef NG_PPP_STATS64
    struct ng_ppp_link_stat	oldStats;
#else
    struct ng_ppp_link_stat64	oldStats;
#endif

    if (fp->type->link_layer) {
	l = (Link)fp->arg;
	b = l->bund;
    } else {
	b = (Bund)fp->arg;
	l = NULL;
    }
    
    if (!b) {
	/* We can't get link stat without bundle present */
	return;
    }

    /* See if there was any traffic since last time */
    oldStats = fp->idleStats;
#ifndef NG_PPP_STATS64
    NgFuncGetStats(b, l ?
	l->bundleIndex : NG_PPP_BUNDLE_LINKNUM, &fp->idleStats);
#else
    NgFuncGetStats64(b, l ?
	l->bundleIndex : NG_PPP_BUNDLE_LINKNUM, &fp->idleStats);
#endif
    if (fp->idleStats.recvFrames > oldStats.recvFrames)
	fp->quietCount = 0;
    else
	fp->quietCount++;

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

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

        /* Has peer failed to reply for maximum allowable interval? */
        if (fp->quietCount * fp->conf.echo_int >= fp->conf.echo_max) {
	    TimerStop(&fp->echoTimer);
	    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:
        break;
    }
}

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

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

    /* Check for runt frames; discard them */
    if ((recd_len = MBLEN(bp)) < sizeof(hdr)) {
	Log(fp->log, ("[%s] %s: runt packet: %u bytes", Pref(fp), Fsm(fp), recd_len));
	mbfree(bp);
	return;
    }

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

    /* Make sure length is sensible; discard otherwise */
    if (length < sizeof(hdr) || length > recd_len) {
	Log(fp->log, ("[%s] %s: bad length: says %u, rec'd %u",
    	    Pref(fp), Fsm(fp), length, recd_len));
	mbfree(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 ((hdr.code >= NUM_FSM_CODES) ||
	    (((1 << hdr.code) & fp->type->known_codes) == 0)) {	/* RUC */
	Log(fp->log, ("[%s] %s: unknown code %d", Pref(fp), Fsm(fp), hdr.code));
	FsmOutputMbuf(fp, CODE_CODEREJ, fp->rejid++, 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->log2;
    else
	log = fp->log;
    Log(log, ("[%s] %s: rec'd %s #%d (%s)",
	Pref(fp), Fsm(fp), FsmCodeName(hdr.code), (int) hdr.id,
	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)
{
  const u_char	*bytes = (const u_char *) data;
  u_int16_t	sv;
  u_int32_t	lv;

  /* Special cases */
  switch (len) {
    case -2:
      len = 2;
      sv = htons(*((const u_int16_t *) data));
      bytes = (u_char *) &sv;
      break;
    case -4:
      len = 4;
      lv = htonl(*((const u_int32_t *) 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] %s: %d extra garbage bytes in config packet", Pref(fp), Fsm(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: %s buffer full", Pref(fp), Fsm(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: %s buffer full", Pref(fp), Fsm(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: %s buffer full", Pref(fp), Fsm(fp), "rej"));
    return;
  }
  memcpy(&gRejBuf[gRejSize], opt, 2);
  memcpy(&gRejBuf[gRejSize + 2], opt->data, opt->len - 2);
  gRejSize += opt->len;
}

/*
 * FsmCodeName()
 */

const char *
FsmCodeName(int code)
{
  if (code >= 0 && code < (int)NUM_FSM_CODES)
    return (FsmCodes[code].name);
  return ("UNKNOWN");
}

/*
 * FsmStateName()
 */

const char *
FsmStateName(enum fsm_state 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 "???";
}


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>