Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Finite State Machines for Xmodem


OCT89: FINITE STATE MACHINES FOR XMODEM

FINITE STATE MACHINES FOR XMODEM

FSMs are one way to cope with the chaos of communications

This article contains the following executables: SMITH.ARC

Donald W. Smith

Don is a senior course developer and instructor for Wide Area Network Systems at Tandem Computers, Inc. He has been programming micros since CP/M days and has been programming in C since 1985. He can be contacted at 7029 Via Del Rio, San Jose, CA 95139 or on CompuServe: 76515,3406.


Even though my coworkers often refer to me as a "Commie," I don't take offense because my specialty is data communications and networking, an area of computer science that is often overlooked in favor of more stimulating topics such as compiler technology. But what we Commies do and what compiler writers do aren't that much different, particularly because we share an important tool called "finite state automata" (FSA). The main difference between what they do and what we do is where we get our events. Compiler events come from source code files (text tokens), while most communication events come from another computer, or from strange things in between.

This article presents a more generalized description of FSA, one in which data structures and code are used to implement XModem, a well-understood communications protocol, using a technique that is general enough to be implemented with just about any other protocol as well. The goal here is to explain how to use standard C language constructs to write dependable, maintainable programs using finite state machines (FSM). The applications include other communications protocols, realtime data acquisition, or just about anything with a fairly predictable flow of events.

Lexical Analyzers and Such

The Unix environment provides a number of specialized tools, YACC (yet another compiler compiler) and LEX (a lexical analyzer), for instance, that are designed to maintain the sanity of compiler writers. YACC utilizes grammatical rules that can have associated actions. YACC can be adapted to generate FSM-type machines, but requires that you learn "yet another set of syntax." (YASS?)

Other utilities have also appeared to generate FSM code. They are all valid techniques, if the tool is well understood by developers as well as maintainers. But all require an extra level of "indirection" in the coding process to learn a specialized tool. The technique I examine in this article does not use smoke screens or mirrors and requires only a standard C compiler.

Finite State Basics

Finite state machines are directed graphs where nodes are called states, and arcs are actions (or transitions). Each state includes a well-defined set of events. There must be an action and a next state defined for each event, even if the action is a null action. Events drive the machine from state to state. Once a good starting state is established and an initial event has come in, the machine is off and running.

State Diagrams

State diagrams are often used to document state machines. Figure 1 is the state diagram for XModem receive. The receive machine (Figure 2) uses a total of four states (the circles), with a maximum of four events each. As the number of events increases, the state diagrams sacrifice some attractiveness. The final state (Exit) is used only to help human readers; it does not respond to events, and is not included in the diagram. Each event directs the machine to an action, illustrated by a box with rounded corners. All actions in this example are passed parameters, which are usually #defines and are shown in parentheses. Some actions are shared by multiple events, for instance, Frame_Wait(RESEND). Unfortunately, there is no "Industry Standard" state diagram for the XModem protocol. (Ward Christensen isn't the only one who forgot this step. But then again, CP/M machines didn't do graphics well.) The state diagram in Figure 1 is exceptionally short and sweet because the protocol is simple.

Coding Technique

There are many ways to implement finite state machines. One method, using a precompiler (similar to LEX and YACC), adds an extra layer of syntax on top of the source code to identify states, events and actions. Remember, FSA are normally a compiler writer's game. What could be more natural than yet another language? The output C code is often strewn with labels and GOTOs, which are not part of my structured programming vocabulary.

It is also possible to limit the number of global variables (thus, side effects) when hand coding state machine logic. This provides stand-alone send and receive modules that share a short list of external variables. A short list of global variables in each module is declared static, to prevent them from slipping past the guards at the file door.

The C language provides the data type of pointer to function that is as flexible as using GOTOs and labels, but also provides a series of advantages, such as that parameters can be passed, values can be returned, and block structuring maintained.

Static Tables

Data structures may be initialized with anything that is a constant at compile time. Function pointers and pointers to variable locations fully qualify, but the contents of variables do not.

XMRECV (see Listing Three) shows the initialized state table using function pointers to actions (A_...). The parameter field is defined as an int. This int parameter conveniently accommodates pointers to anywhere in the small model.

Most state machines use actions that are inherently simple, and rely on entry points (labels) to prevent duplication of code. This technique uses standard functions for actions which naturally take parameters. This code requires all functions to accept a single int parameter, due to the struct used for the state table. Other parameter types are casted to int as required.

Unfortunately, this technique is not universally portable. For example, if pointers occupy more than the size of an int, a larger data type should be substituted to hold them.

Code Walkthrough

The FSM presented here consists of five modules. Listing One CTERM.H, and Listing Two, COMMN.H, define the system. Most of my discussion, however, will concentrate on the short "main loop" of the XMRECV.C file in Listing Three. The same technique is also used in the XMSEND.C module Listing Four) but refers to a different state table. The action functions speak for themselves and illustrate the XModem protocol, which has been done before. CTERM1.C, Listing Five, is a terminal emulator that demonstrates the use of state machine driven communications protocols using the C language. Use makect1 in Listing Six to compile CTERM1.C.

The story normally starts in terminal mode. To receive a file, press the PgDn key to get into the xmodem_recv() module. Note that the mode variable is set to M_XRecv on the way out of terminal mode. The initial action of A_Prep_Recv() returns the first event and, hopefully, a valid file name. A while (mode == M_XRecv) loop takes control until something good (or really bad) happens.

Within the while loop, a copy of the event is made for future reference. (Sort of like saving yesterday's newspaper, right?) Then a pointer is set up for easier access to the current state table entry (cur_entry). The current state and event of the state machine is then traced, if tracing was enabled at compile time. The predefined action to execute is determined then called, passing the appropriate parameter. Notice that the event variable is filled with the return value from the function call to new_action( ). Before leaving, the system determines a new current state from the next_state field of the state table.

In this case, the user should be presented with the opportunity to abort at any time. The routine keyfun() provides this capability, without allowing the dreaded ^C (Control-C) display or program termination. Programs that alter interrupt vectors should not abort without putting things back the way they were. One single character is defined to be the escape character (default is ESC) that takes you out of the M_XRecv via a call to the action A_Recv_End().

This code could be tighter and run faster, but a few extra variables help follow the action and keep the code more readable. Hopefully, smart compilers will use registers where possible anyway.

Designing States

One of the most challenging aspects of designing good state machines is determining how many states there should be. There are no hard and fast rules. This is one of those areas of "fuzzy logic" that intelligent humans were built to handle. The goal is to define states where only a limited number of events can occur. That number is up to the designer, but normally should not exceed ten.

It is easier to define the structure to hold the initialized state table if all states have the same number of valid events. It could be an interesting exercise for the reader to design a state table with a variable number of events for each state.

The following are a few notes to keep in mind while determining how many states to define:

  • Use a flowchart of tasks to help derive the state diagram
  • keep the main path clean (as few states/actions as possible)
  • decide which return value means "All's Well." Maybe O?

Actions and Reactions

Actions are functions that do work and return events. Most actions normally do not call other actions and perform fairly simple tasks. It is often necessary to maintain some idea of context between calls. For example, the A_Frame_Wait action maintains internal (static) counters for retries while waiting for a response to a control character sent (Ack, Nak, and so on).

Remember that states do no work. (The Governor of California would probably take exception to my last statement, but then I'd have been quoted out of context.) Actions are where the actual work is done.

The action Validate() probably does the most work in the receive logic. It updates the screen's packet counter and waits for the interrupt handler to finish receiving the frame. It then checks the packet header and the packet CRC/Checksum, and finally writes the packet data to disk if all is well. Validate() also has the authority to advance the (file) global packet counter (pkt) before returning 0, if all is well.

Some protocol purists may notice that it is possible to tolerate more than a one-second delay between characters. But some networks in use today cannot always guarantee such a luxury. Validate() returns a TIMEOUT only after two successive one-second time-outs. Remember that we already have seen the SOH, and should not need to wait much longer.

XModem uses a fixed frame size, which differs significantly from other common protocols. Protocols with an ETX (end of text) signifying the end of a variable size frame, should use another state (and action) to receive the frame before validating. It is too slow to wait for a deadline and start looking backwards. Table 1 shows a trace of a successful receive of three record files while Table 2 lists the three states involved in receiving a packet.

Table 1: A trace of successful receive

  The three states involved in receiving a packet
  _________________________________________________

  State:   Init_Recv, Event: 0, Note:  fname O.K
  State:    Incoming, Event: 2, Note:    timeout
  State:    Incoming, Event: 2, Note:    timeout
  State:    Incoming, Event: 0, Note:    got one
  State:  First_What, Event: 0, Note:    got SOH
  State:   De_Pktize, Event: 0, Note:     pkt OK
  State:    Incoming, Event: 0, Note:    got one
  State:  First_What, Event: 0, Note:    got SOH
  State:   De_Pktize, Event: 0, Note:     pkt OK
  State:    Incoming, Event: 0, Note:    got one
  State:  First_What, Event: 0, Note:    got SOH
  State:   De_Pktize, Event: 0, Note:     pkt OK
  State:    Incoming, Event: 0, Note:    got one
  State:  First_What, Event: 2, Note:    got EOT

Table 2: States in motion

  The three states involved in receiving a packet are:
  ________________________________________________________________
   1. Incoming:     Waiting for the first character.
                    Decodes the response from Frame_Wait(INIT).
                    Normally calls Which_Ctrl() action.

    2. First_What:  Decodes the response from Which_Ctrl() action based on
                     first character (SOH, CAN, EOT or ???)
                    Normally calls Validate() action.

    3. De-Pktize:   Decodes the response from Validate() action.
                    Normally calls Frame_Wait(NEXT) is all is well.

Layers and Levels

The OSI Reference model seems to have been described in every publication in the world. The goal is to provide inter-computer networking. Computers not only can communicate, but also share resources with other types of machines. They can be connected directly, or across a network of conforming machines.

The XModem protocol is not a good example of a layered protocol. It is a point-to-point file transfer protocol that requires operator intervention at both ends; crude, but functional. In OSI terms, XModem provides mostly layer 2 (link layer) services, skips layers 3 through 6, and provides a single service of layer 7 FTAM.

A good example of layering in use today is X.25. The link layer uses HDLC (high-level data link control) and is defined by a set of state diagrams between neighbors. The network layer provides routing services in complex networks, and communicates with peer network layers in distant machines.

Individual layers should be implemented as separate state machines. If the same program handles more than one layer, a mechanism to communicate between layers is required. The lower layers are given precedence when a queue of events has built up. In this case, "it" rolls uphill!

Implementing a Protocol

The first step to implementing a communications protocol is to gain a good understanding of how it works. Many are well documented, but some require the source code to unravel nuances like time-outs and recovery techniques. Some proprietary protocols must be "reverse engineered" using data line monitors and specialized tools.

The next step is to break the communications protocol down into layers, and then to define states within layers. An outliner (with hierarchies of text) can help to develop the states, events, and next states. A few walk throughs can really pay off as this step is being finalized.

Validate the machine against "script" files of events. Stub out the actions and read events from a script file. A good trace facility can record the route taken through the machine. The trace output can be compared to what was expected from the script file. New states or events can be added here without great difficulty, if necessary.

Then it is time to actually code the actions. It is amazing how quickly this can flow, given a good low-level library and proven state machines. By this time, the responsibilities of each action should be fairly well understood. Remember, actions are simply functions that return events!

The final step takes at least two computers. Always validate the protocol against the best implementation available. The closer to the source, the better the copy.

Conclusion

A friend of mine who has been on the "bleeding edge" of computer science, implementing business solutions for years, seems to thrive on the ordeal. After years in other areas of computer science, he made the conversion to communications. When asked why, he simply replied, "Because comm is where the chaos is!"

Commies are no different from other programmers. Code words line Ack, Nak, and CRC are used to scare bright, young talent away from the field. The perfect protocol has yet to be written; or if it has, it isn't in the public domain ... yet!

Bibliography

Donald Berryman, "Implementing Standard Communications Protocols in C," The C Users Journal, vol. 3, no. 1.

ISO, "Reference Model for Open Systems Interconnection," ISO 7498.

Joe Campbell, C Programmers Guide to Serial Communications, Howard W. Sams & Co., Indianapolis, Ind.: 1987.

Donald Kranz, "Christensen Protocols in C," Doctor Dobb's Journal (June 1985).

Kent Williams, "State Machines in C," Computer Language magazine (February 1986).

Availability

All source code for articles in this issue is available on a single disk. To order, send $14.95 (Calif. residents add sales tax) to Dr. Dobb's Journal, 501 Galveston Dr., Redwood City, CA 94063, or call 800-356-2002 (from inside Calif.) or 800-533-4372 (from outside Calif.). Please specify the issue number and format (MS-DOS, Macintosh, Kaypro).

_Finite State Machines for XModem_ by Donald W. Smith

[LISTING ONE]

<a name="01ff_0014">

/*  CTERM.H   defines for CTERMx series. */

#define BUFSIZE     128
#define DISKREAD    (BUFSIZE * 40)

/* some ASCII defines */
#define SOH         0x01        /* start of header */
#define EOT         0x04        /* end of transmission */
#define ACK         0x06        /* positive acknowledgement */
#define BS          0x08        /* backspace */
#define CR          0x0D        /* Carriage Return */
#define NAK         0x15        /* Negative acknowledgement */
#define CAN         0x18        /* Cancel */
#define EoF         0x1A        /* End of File (used for name) */
#define ESC         0x1B        /* ASCII escape key */
#define CRC         0x43        /* ASCII 'C' (CRC mode request) */
#define BADNAME     0x75        /* Received bad name checksum */
#define TIMEOUT     -1          /* for state machine logic */

static char *SPEED_LIST[] =
 {   "50",   "75",  "110",  "135",  "150",  "300",  "600",  "1200",
   "1800", "2000", "2400", "3600", "4800", "7200", "9600", "19200",
   "28800", "38400", "57600" };

static int SPEED_VALS[] =
 {   50,   75,  110,  135,  150,  300,  600,  1200,
   1800, 2000, 2400, 3600, 4800, 7200, 9600, 19200,
   28800, 38400, 57600,  0 };  /* zero for anchor */

static char *PARITY_LIST[] =
 { "NONE", "NONE", "ODD", "EVEN", };    /* matches L_CTRL p_enable + p_even */

static char *STOP_LIST[] =
 { "ONE", "TWO" };                      /* matches L_CTRL p_two_stops */

static char *BITS_LIST[] =
 { "FIVE", "SIX", "SEVEN", "EIGHT" };

/* Define some common values for the lctrl bit fields */
#define ate1none   0x03;
#define sev1even   0x1A;

typedef int (*action)();      /* action is a pointer to a function */

struct event_entry {
   char comment[20];      /* for commented reading and tracing capability */
   action act;            /* pointer to action function */
   int param;             /* parameter to pass to function */
   enum send_state next_state;  /* from an enumerated list of states */
  };

/* The following enumeration is used in all modules */
enum modes { M_Cmd, M_Term, M_Config, M_XSend, M_XRecv };

/* This struct maps the data packets for the protocol */
typedef struct pkt {
  unsigned char soh;
  unsigned char pkt;
  unsigned char pkt_cmp;
  unsigned char data[BUFSIZE];
  unsigned char crc1;
  unsigned char crc2;
} XPKT;

/*  Defines used for keyfun(). - Map exactly to BIOS intr 16h 0 and 1 */
#define KEYHIT     1
#define READNEXT   0
#define BIOS_KEY 0x16        /* for int86 call for keyboard interrupt */

/* The following defines are used to map scan codes for f keys and specials */
#define HOME 0x4700
#define PGUP 0x4900
#define END  0x4F00
#define PGDN 0x5100
#define INS  0x5200
#define DEL  0x5300
#define CBRK 0x0000      /* Book says 0x5400.  I see 0x0000 */







<a name="01ff_0015"><a name="01ff_0015">
<a name="01ff_0016">
[LISTING TWO]
<a name="01ff_0016">

#define PORT  2              /* currently a define 12/16/88 */
#define NAMESIZE   24        /* Used by xmsend and recv */
#define TXTRIES     5        /* Transmit retries */
#define RXTRIES    10        /* Receive retries */

/* Parameters to pass to Send Action Make_Pkt and Recv Action Frame_Wait */
#define RESEND      0
#define INIT        1
#define NEXT        2

/*  The following declaration is used to pass config info to Config_Comm().  */
typedef struct {     /* Map to UART Line Control bits */
        unsigned wlen      : 2;       /* Word length - 5 */
        unsigned two_stops : 1;       /* 1: Two stops,   0: One stop */
        unsigned parity    : 2;       /* 00, 01: NONE, 10 ODD, 11 EVEN */
        unsigned p_stuck   : 1;       /* 1: Stuck,       0: Normal */
        unsigned set_break : 1;       /* 1: Send break   0: Stop break */
        unsigned div_latch : 1;       /* 1: See divisors 0: See data bytes */
        unsigned           : 8;
      } L_CTRL;

  typedef union {
        unsigned char lctrl;
        L_CTRL lbits;
      } U_BITS;

  typedef struct
    {
      unsigned speed;       /* value from atoi() of value from speed array */
      U_BITS ubits;
    } S_INIT;






<a name="01ff_0017"><a name="01ff_0017">
<a name="01ff_0018">
[LISTING THREE]
<a name="01ff_0018">

/* XMRECV.C:  Xmodem receive state machine processing */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "cterm.h"     /* common defines and structs for cterm */
#include "commn.h"                  /* brings in S_INIT struct */

enum recv_state
  { S_Init_Recv, S_Incoming, S_First_What, S_DePktize, S_Exit };

#define TRACE 1       /* to turn on state machine tracing */
/* #define SMTRACE 1 */
#ifdef SMTRACE
static char *state_list[] =
  {"Init_Recv", "Incoming", "First_What", "De-Pktize", "Exit"};
#endif

#define RECV_EVENTS    4         /* # of events per RECV state */

/* Variables local to this file only */
static char r_fname[NAMESIZE+1]; /* name of file to open */
static FILE *r_fptr = NULL;      /* file pointer or number to use */
static int sohor = SOH;          /* location to store first char of pkt hdr */

static int pkt = 1;               /* expected packet number */
static S_INIT prev_conf;          /* save prev (parity) conf */
static int virgin = 1;            /* 0 = beyond initial NAK stage */

/* EXTERNAL variables */
extern int comport;                /* which comm port to use (from CTERM) */
extern int crc;                    /* flag for CRC (!0) or checksum  (0) */
extern unsigned crcaccum;          /* from xmutil */
extern unsigned char checksum;     /* ditto */
extern S_INIT cur_config;          /* from CTERM.  For timeout calc */
extern enum modes mode;            /* ditto  term mode or... */
extern int eschar;                 /* ditto   escape character variable */
extern int keyfun(int);            /* ditto  BIOS keyboard I/O */
extern unsigned int fgetsnn(FILE *, char *, int);

/* Messages posted by A_Recv_End */
/*  If declared as char *user_msg, can't be used in state table.
 *  No variables allowed.  But this way creates constants! */
extern char user_msg[];
extern char cancel[];
extern char badwrite[];
extern char eof_msg[];
extern char giveup[];
extern char badcomm[];

/************  Receive Actions: ********************/

/* ----- A_Prep_Recv: Prompts for file to receive, attempts open.
 *   Returns: 0 if O.K., 1 if open fails, 2 is user abort.     */
A_Prep_Recv( char *fname )
{
  int retval;

  fputs("\n Please Input file name to receive: ",stdout);
  fgetsnn (stdin, fname, NAMESIZE );
  if ( (fname[0] == eschar) || (fname[0] == '\0') )
    return(2);
  if ( (r_fptr = fopen (fname, "wb")) == NULL ) {
    printf("\n Cannot open %s.  Try again.\n", fname);
    return(1);
  }

  prev_conf = cur_config;                 /* save entry config */
  cur_config.ubits.lctrl = ate1none;      /* Force things to 8/1/N */
  Config_Comm( comport, cur_config );

  eat_noise();                  /* clear out any garbage from buffer */
  return(0);
}

/* ----- A_Frame_Wait: Send a ctrl char, wait for reply.  -------
 *   Returns: 0: OK, 1: comm error, 2: timeout, 3: no retries. */
A_Frame_Wait(int which)
{
  char inch;
  int errval;                       /* returned from reads and writes */
  int numread = 1;
  static int passes;                /* give up after 10 retries */
  static char last;
  int retval = 0;                   /* Running value to return */

  if (virgin)  {                    /* Waiting for first answer to NAK */
    switch (which) {
      case INIT:   crc = 0;         /* Go for CRC first -- fallthru will flip */
                   passes = RXTRIES;
                   pkt = 1;         /* Initialize to first expected pkt num */
      case RESEND: crc = !crc;      /* flip global flag */
                   last = (crc == 0) ? NAK : CRC;
                   break;
      default:     retval = 3;      /* Should not occur... but */
    }
  }
  else {                            /* Not virgin.  Normal Retry logic */
    switch (which) {
      case NEXT:   last = ACK;
                   passes = RXTRIES;
                   break;
      case RESEND: if (passes-- == 0) {
                     last = CAN;
                     retval = 3;
                     passes = RXTRIES;      /* Reset to default */
                   }
                   else
                     last = NAK;
                   break;
     default:      retval = 3;      /* An ounce of prevention */
    }
  }

  errval = writecomm( &last, 1);
  if (errval != 0)
    return(1);             /* Get out now! */

  eat_noise();             /* clear out any garbage */

  if (retval != 3) {
    errval = read_comm( &numread, &inch, 10000 );
    if (errval == TIMEOUT)
      return (2);
    else {                                  /* Got a live one! */
      sohor = inch;                         /* set global */
      if ( (virgin) && (inch == SOH) ) {    /* We're rolling! */
        printf("\n\nReceiving file %s using %s.\n",
                         r_fname,(crc == 0) ? "CheckSum" : "CRC" );
        fputs("\nAwaiting packet # 0001",stdout);
        virgin = 0;                         /* flip the local flag */
      }
    }
  }
  return(retval);
}

/* ----- A_Which_Ctrl:  Parses first char received. -------------
 *  Returns:  0: SOH, 1: CAN, 2: EOT, 3: unexpected (junk)     */
A_Which_Ctrl(char *lead)
{
  switch (*lead) {
    case SOH:  return(0);
    case CAN:  return(1);
    case EOT:  return(2);
    default:   return(3);
  }
}

/* ----- CRC_Good:  Calculates the CRC/Checksum. ----------------
 * Returns:  0 if OK, 2 if error                               */
CRC_Good(char *buf, int crcflag, unsigned char crchi, unsigned char crclo)
{
  register int i;

  crcaccum = 0;  /* zero out global crc and checksum value */
  checksum = 0;

  for (i = 0; i < BUFSIZE; i++, buf++)
    updcrc(*buf);
  updcrc(0);                     /* Xmodem deviant CRC calc */
  updcrc(0);

  if (crcflag == 0) {
    if (crchi != checksum)
      return(2);
  }
  else {
    if ( (crclo + ( crchi << 8)) != crcaccum )
      return(2);
  }
  return(0);
}

/* ----- Action Validate: After SOH, validates the xmodem header.
 * Returns: 0: OK, 1: bad header, 2: bad CRC, 3: char timeout. */
A_Validate(int *crcflag )
{
  int retval;
  int readnum = (*crcflag == 0) ? 131 : 132; /* pass to read_comm */
  int togo    = readnum;                     /* if partial, running count */
  int msecs;                                 /* how long to wait */
  XPKT r_pkt;                                /* packet receive buffer */
  unsigned char *diskbuf = (unsigned char *) &r_pkt.data;
  unsigned char *curptr  = (char *) &r_pkt.pkt;   /* Rem: got SOH already */
  long frame_bits = ( (BUFSIZE + 3) * 10 *1000L );

  printf("\b\b\b\b%4d",pkt);        /* Allow up to 9999 frames */

  while (readnum != 0) {
    msecs =  (int)( frame_bits / (long)cur_config.speed );
    delay(msecs);                     /* Let the interrupt handler work */
    retval  = read_comm( &readnum, curptr, msecs );
    curptr  = curptr + readnum;       /* adjust curptr to next avail loc */
    readnum = (togo -= readnum);      /* adjust BOTH to remainder of pkt */

    if (retval == TIMEOUT) {          /* Give it one more second if short */
      togo = 1;                       /* prep togo for 1 char read test */
      retval = read_comm( &togo, curptr, 1000);
      if (retval == TIMEOUT)          /* Bad news.  Dead line */
        return(3);
      curptr++;                       /* recovered!   adjust and try again */
      togo = --readnum;
    }
    frame_bits = togo * 10;           /* Adjust by bits per character */
  }

  if (~r_pkt.pkt != r_pkt.pkt_cmp) {
    return(1);
  }
  if ( r_pkt.pkt != (pkt % 256) )
    if ( r_pkt.pkt == ( (pkt - 1) & 0xFF )  ) {
      return(0);            /* duplicate packet!  Ack and ignore */
    }
    else
      return(1);        /* Nak and retry.. probably useless but... */

  retval = CRC_Good(diskbuf, *crcflag, r_pkt.crc1, r_pkt.crc2);
  if (retval != 0) {
    return(2);
  }

  fwrite(diskbuf, BUFSIZE, 1, r_fptr);
  pkt++;
  return (0);
}

/* ----- Action EatRest: Eats the rest of a packet. ---------- */
A_EatRest(int calories)
{
  int toeat = calories;
  int retval = 0;
  long frame_bits;
  char junkbuf[BUFSIZE + 4];

  if (calories > BUFSIZE)
    calories = BUFSIZE + 4;
  frame_bits = ( calories * 10 * 1000L);
  delay( (unsigned)(frame_bits/(long)cur_config.speed) + 500 );

  while (retval != TIMEOUT) {
    retval = read_comm( &toeat, junkbuf, 1000);
    toeat = 1;
  }
  retval = A_Frame_Wait(RESEND);
  return(retval);
}

/* ----- Action Recv_End: Only way out of Recv state machine.  */
A_Recv_End ( char *reason )
{
  char eotc = ACK;             /* just in case we really Receive the file */

  if (r_fptr != NULL) {        /* Did we even get started??? */
    if (reason != eof_msg) {   /* Started, but bad news during xfer */
      eotc = CAN;
      unlink(r_fname);         /* deletes the old file */
    }
    fclose(r_fptr);
    writecomm(&eotc, 1);
    Config_Comm( comport, prev_conf );   /* Put whatever parity back in */
  }

  printf("\n *** Ending session.  %s.\a\n",reason);

  virgin = 1;
  mode = M_Cmd;
  return (RECV_EVENTS - 1);   /* last event always has next state S_Exit */
}

/************  R E C E I V E    S T A T E    T A B L E  ****************/
 struct event_entry recv_machine[(int)S_Exit][RECV_EVENTS] =
 { /* S_Init_Recv */
   { {  "fname O.K"  , A_Frame_Wait   , INIT         , S_Incoming      },
     {  "fname bad"  , A_Prep_Recv    , (int)r_fname , S_Init_Recv     },
     {  "user abort" , A_Recv_End     , (int)user_msg, S_Exit          },
     {  "comm error" , A_Recv_End     , (int)badcomm , S_Exit          } },
   /* S_Incoming */
   { {  "got one"    , A_Which_Ctrl   , (int)&sohor  , S_First_What    },
     {  "comm error" , A_Recv_End     , (int)badcomm , S_Exit          },
     {  "timeout"    , A_Frame_Wait   , RESEND       , S_Incoming      },
     {  "no retries" , A_Recv_End     , (int)giveup  , S_Exit          } },
   /* S_First_What */
   { {  "got SOH"    , A_Validate     , (int)&crc    , S_DePktize      },
     {  "got CAN"    , A_Recv_End     , (int)cancel  , S_Exit          },
     {  "got EOT"    , A_Recv_End     , (int)eof_msg , S_Exit          },
     {  "got junk!"  , A_EatRest      , BUFSIZE      , S_Incoming      } },
   /* S_DePktize */
   { {  "pkt OK"     , A_Frame_Wait   , NEXT         , S_Incoming      },
     {  "bad hdr"    , A_EatRest      , BUFSIZE      , S_Incoming      },
     {  "bad CRC"    , A_Frame_Wait   , RESEND       , S_Incoming      },
     {  "timeout"    , A_Frame_Wait   , RESEND       , S_Incoming      } }
 };


/* -------------- Xmodem Receive state machine --------------- */
xmodem_recv()
{
   char inkey;                     /* place for user to abort */
   int  event;                     /* event returned from action */
   int  prevent;                   /* previous event */
   struct event_entry *cur_entry;  /* pointer to current row/col of sm */
   action new_action;              /* next action to perform */
   enum send_state cur_state  = S_Init_Recv;

   event = A_Prep_Recv(r_fname);

   while (mode == M_XRecv)
   {
     prevent = event;      /* save the previous event for next state */
     cur_entry = &recv_machine[(int)cur_state][event];

#ifdef SMTRACE
     printf("State: %16s, Event: %2d, Note: %20s\n",
          state_list[(int)cur_state], event, cur_entry->comment );
#endif

     /* Based on the current state and event, execute action(param) */
     new_action = cur_entry->act;
     event = new_action(cur_entry->param);
     cur_state  = recv_machine[(int)cur_state][prevent].next_state;

     if ( keyfun(KEYHIT) ) {
       inkey = (char) keyfun(READNEXT);    /* Truncate to key only */
       if (inkey == eschar)
         A_Recv_End(user_msg);
     }
   }
   return (0);
}






<a name="01ff_0019"><a name="01ff_0019">
<a name="01ff_001a">
[LISTING FOUR]
<a name="01ff_001a">

/* XMSEND.C   Xmodem Send state machine processing.   */

#include <conio.h>        /* for putch call */
#include <ctype.h>
#include <io.h>           /* for filelength call */
#include <stdio.h>
#include <string.h>
#include "cterm.h"
#include "commn.h"    /* brings in S_INIT struct and defines */

enum send_state
 { S_Init_Send, S_Sync_Wait, S_Make_Pkt, S_Send_Pkt, S_Data_Response, S_Exit };

#ifdef TRACE
char *state_list[] =
  {"Init_Send", "Sync_Wait", "Make_Pkt", "Send_Pkt", "Data_Response", "Exit"};
#endif

#define SEND_EVENTS    4      /* number of events handled per send state */

/* Variables local to this file only */
static char s_fname[NAMESIZE+1];     /* name of file to open */
static FILE *s_fptr = NULL;         /* file pointer or number to use */
static XPKT s_pkt;                   /* packet to send */
static S_INIT prev_conf;        /* saves previous bits, parity during xfer */

/* EXTERNAL variables and functions */
extern int comport;                /* which comm port to use (from CTERM) */
extern unsigned crcaccum;          /* from xmutil */
extern unsigned char checksum;     /* ditto */
extern int crc;                    /* ditto */
extern S_INIT cur_config;          /* from CTERMx.  For send time calc */
extern int eschar;                 /* ditto   escape character variable */
extern enum modes mode;            /* ditto   term mode or... */
extern int keyfun(int);            /* ditto   BIOS call to keyboard */

/*  If declared as char *user_msg, can't be used in state table.
 *  No variables allowed.  But this way creates constants! */
extern char user_msg[];
extern char nonak[];
extern char cancel[];
extern char badread[];
extern char eof_msg[];
extern char giveup[];

/************  Send Actions: ********************/

/* ----- A_Get_Fname:  Prompts for file to transmit, opens. -----
 * Returns: 0: OK, 1: open failed, 2: user abort. ------------ */
A_Get_Fname( char *fname )
{
  long fbytes;
  int  frecs;
  int  fsecs;

  printf("\n Please Input file name to transmit: ");
  fgetsnn (stdin, fname, NAMESIZE );
  if ( (fname[0] == eschar) || (fname[0] == '\0') )
    return(2);
  if ( (s_fptr = fopen (fname, "rb")) == NULL ) {
    printf("\n Cannot open %s.  Try again.\n", fname);
    return(1);
  }
  fbytes = filelength( fileno(s_fptr) );
  frecs = (  (fbytes / BUFSIZE) + ( (fbytes % BUFSIZE == 0) ? 0 : 1 )  );
  /* The following adds time for turn around (ACK/NAK), but no errors */
  fsecs = (int) ( (fbytes * 10) / (cur_config.speed / 2 ) );

  printf("\n File %s: %4d records, est. min:sec  %3d:%2d at %d bps.\n",
              fname, frecs, fsecs / 60, fsecs % 60, cur_config.speed  );

  prev_conf = cur_config;                 /* save entry config */
  cur_config.ubits.lctrl = ate1none;      /* Force things to 8/1/N */
  Config_Comm( comport, cur_config );

  eat_noise();        /* Clear out any garbage in the input queue */
  return(0);
}

/* ----- A_Init_Wait: Waits for initial sync character. ---------
 *  Returns: The value returned from A_Wait().                 */
A_Init_Wait(int expected)
{
  static int tries  =  2;   /* try initial CRC, then once more */
  static int passes = 10;   /* give up after 10 junk reads */
  static int last;
  int retval;

  switch(expected) {
    case CRC:  last = CRC;     /* If we really want CRC... */
               break;
    case NAK:  last = NAK;     /* or if we only want Checksum */
               break;
    case NEXT: if (--tries == 0) {     /* want to switch? */
                 last = (last == CRC) ? NAK : CRC;
                 tries = 2;
               }
  }
  printf("\rAwaiting %s...",(last == CRC) ? "CRC" : "NAK");
  retval = A_Wait(last);
  if (retval != 0) {
    if (passes-- == 0)
      return(3);           /* cancelled */
    else
     return(retval);
  }

  passes = 10;                      /* reset passes counter */
  crc = (last == CRC) ? 1 : 0;
  return(retval);
}

/* ------ A_Wait:  Waits for appropriate time for a character.
 * Returns: 0: match, 1: if other, 2: timeout, 3: cancel.      */
A_Wait( int expected)
{
  char inch;
  int errval;
  int numread = 1;
  int retval = 0;

  errval = read_comm( &numread, &inch, (expected == SOH) ?  1000 : 10000 );
  if ( numread > 0 ) {
    if (inch == (char) expected)
      retval = 0;
    else retval = (inch == CAN) ? 3 : 1 ;
  }
  else
   if (errval == TIMEOUT)
     retval = 2;

  return (retval);
}

/* ----- Action Make_Pkt: Reads from disk, formats packet. -----
 * Returns:  0: OK, 1: disk trouble, 2: EOF found.            */
A_Make_Pkt(int which )
{
  register int i;
  int errval;
  unsigned int lo_crc;
  static int pkt;
  static unsigned char *diskbuf = (unsigned char *) &s_pkt.data;
  static unsigned char *curptr;   /* where are we now? */

  crcaccum = 0;  /* zero out global crc and checksum value */
  checksum = 0;

  for (curptr = diskbuf, i = 0; i < BUFSIZE; i++, curptr++) {
    if ( (errval = getc(s_fptr)) == EOF )
      break;
    *curptr = errval;
    updcrc(errval);
  }
  if (i == 0)
    return(2);                   /* That's all folks! */

  for ( ; i < BUFSIZE; i++, curptr++) {    /* Zero fill the rest of packet */
    *curptr = 0;
    updcrc(0);
  }

  if (which == INIT) {
    printf("\n\nSending file %s using %s.\n",
                        s_fname,(crc == 0) ? "CheckSum" : "CRC");
    pkt = 1;
  }
  else pkt = (++pkt % 256);

  s_pkt.soh     = SOH;
  s_pkt.pkt     = pkt;
  s_pkt.pkt_cmp = ~pkt;
  updcrc(0);     /* finish off xmodem variation */
  updcrc(0);
  lo_crc = crcaccum;
  if (crc != 0) {
    s_pkt.crc1 = (crcaccum >> 8);    /* high byte first */
    s_pkt.crc2 = lo_crc;
  }
  else
    s_pkt.crc1 = checksum;

  return (0);
}

/* ----- Action Send_Pkt: Send a packet out the comm port. ------
 * Returns:  0: OK, 1: write err, 2: no retries, 3: cancelled  */
A_Send_Pkt( int why )
{
  static int retries = TXTRIES; /* If not general, make a global table */
  int errval;

  switch (why) {
    case NEXT:    retries = TXTRIES;
                  putch('.');         /* show we are making progress */
                  break;
    case NAK:
    case TIMEOUT:
    case RESEND:  --retries;
                  putch('R');
  }

  if (!retries) {
    retries = TXTRIES;
    return (2);
  }

  errval = writecomm( (char *) &s_pkt, (crc != 0) ? 133 : 132 );
  if (errval)
    return(1);

  eat_noise();            /* clear out any garbage */
  return (0);
}

/* ----- Action Send_End:  Only way out of the Send state. --- */
A_Send_End ( char *reason )
{
  char eotc = EOT;             /* just in case we really transmit the file */
  int notdone = 1;             /* have we received an ACK to our EOT? */
  int eotries = 10;            /* Should be enough for most */

  if (s_fptr != NULL) {        /* Did we even get started??? */
    if (reason != eof_msg) {   /* Started, but bad news */
      eotc = CAN;
      writecomm(&eotc, 1);
    }
    else                       /* eof = We did it!  Send our EOT and get out */
      while ( (notdone != 0) && (eotries--) ) {
        writecomm(&eotc, 1);
        notdone = A_Wait(ACK);
      }
    fclose(s_fptr);
    Config_Comm( comport, prev_conf );   /* Put whatever parity back in */
  }

  printf("\n *** Ending session.  %s.\a\n",reason);

  mode = M_Cmd;
  return (SEND_EVENTS - 1);    /* last event always has next state S_Exit */
}


/***************  S E N D   S T A T E    T A B L E  *******************/
 struct event_entry send_machine[(int)S_Exit][SEND_EVENTS] =
 { /* S_Init_Send */
   { {  "fname O.K"  , A_Init_Wait    , CRC          , S_Sync_Wait     },
     {  "fname bad"  , A_Get_Fname    , (int)s_fname , S_Init_Send     },
     {  "user abort" , A_Send_End     , (int)user_msg, S_Exit          },
     {  "no retries" , A_Send_End     , (int)nonak   , S_Exit          } },
   /* S_Sync_Wait */
   { {  "in sync"    , A_Make_Pkt     , INIT         , S_Make_Pkt      },
     {  "unexpected" , A_Init_Wait    , NEXT         , S_Sync_Wait     },
     {  "timeout"    , A_Init_Wait    , CRC          , S_Sync_Wait     },
     {  "cancelled"  , A_Send_End     , (int)cancel  , S_Exit          } },
   /* S_Make_Pkt */
   { {  "pkt ready"  , A_Send_Pkt     , NEXT         , S_Send_Pkt },
     {  "bad disk?"  , A_Send_End     , (int)badread , S_Exit          },
     {  "done!"      , A_Send_End     , (int)eof_msg , S_Exit          },
     {  "trouble!"   , A_Send_End     , (int)giveup  , S_Exit          } },
   /* S_Send_Pkt */
   { {  "sent O.K."  , A_Wait         , ACK          , S_Data_Response },
     {  "comm error" , A_Send_Pkt     , RESEND       , S_Send_Pkt      },
     {  "no retries" , A_Send_End     , (int)giveup  , S_Exit          },
     {  "cancelled"  , A_Send_End     , (int)cancel  , S_Exit          } },
   /* S_Data_Response */
   { {  "ack rcvd."  , A_Make_Pkt     , NEXT         , S_Make_Pkt      },
     {  "not ack"    , A_Send_Pkt     , NAK          , S_Send_Pkt      },
     {  "timeout"    , A_Send_Pkt     , TIMEOUT      , S_Send_Pkt      },
     {  "cancelled"  , A_Send_End     , (int)cancel  , S_Exit          } }
 };


/*  -------------------- Send state machine ------------------ */
xmodem_send()
{
   char inkey;                     /* In case the user wants to abort */
   int  event;                     /* event returned from action */
   int  prevent;                   /* previous event */
   struct event_entry *cur_entry;  /* pointer to current row/col of sm */
   action new_action;              /* next action to perform */
   enum send_state cur_state  = S_Init_Send;

   event = A_Get_Fname(s_fname);

   while (mode == M_XSend) {
     prevent = event;      /* save the previous event for next state */
     cur_entry = &send_machine[(int)cur_state][event];

#ifdef TRACE
     printf("State: %16s, Event: %2d, Note: %20s\n",
          state_list[(int)cur_state], event, cur_entry->comment );
#endif

     /* Based on the current state and event, execute action(param) */
     new_action = cur_entry->act;
     event = new_action(cur_entry->param);
     cur_state  = send_machine[(int)cur_state][prevent].next_state;

     if ( keyfun(KEYHIT) ) {            /* from CTERM */
       inkey = (char) keyfun(READNEXT); /* Truncate high order */
       if (inkey == eschar)
         A_Send_End(user_msg);
     }
   }
   return (0);
}







<a name="01ff_001b"><a name="01ff_001b">
<a name="01ff_001c">
[LISTING FIVE]
<a name="01ff_001c">

/* CTERM1.C  by Donald W. Smith.  CIS 76515,3406.
 *    A minimal terminal emulator to demonstrate the use of state
 *  machine driven communications protocols using the C language.
 *  Use makect1. to compile. */

#define VERSION fputs("\n\t CTERM 1.11:  4/26/89 DWS\n\n",stdout)
#define BUFLEN   200
#define LINELEN  80   /* Max user input length.  Lots of slack */

#include <conio.h>
#include <ctype.h>              /* for Turbo C is... functions */
#include <dos.h>
#include <process.h>                      /* For system() call */
#include <signal.h>          /* Ctrl-C and Ctrl-Break handling */
#include <stdio.h>
#include <stdlib.h>                       /* For system() call */
#include "cterm.h"                 /* defines for cterm series */
#include "commn.h"        /* defines shared by myint and cterm */
#include "getargs.h"                     /* for getargs access */

#define CMDDIM(x)   (sizeof(x)/sizeof(x[0]))

/* --------- GLOBALS ------------------  */
enum modes mode = M_Cmd;                 /* Term, Config, etc. */
char            inbuf[BUFLEN+1];
char            outbuf[LINELEN+1];    /* Used for short output */
S_INIT          cur_config = {1200};    /* Current port config */
int             comport = 1;       /* Current comm port number */
int             bbsmode = 0;     /* BBS (8,1,N) or T16 (7,1,E) */
int             eschar  = ESC;    /* keyboard escape character */
FILE           *cap_fptr;         /* file ptr for capture file */
static union    REGS rg;                  /* used for keyfun() */

/* ---------- External variables -------------- */
extern  unsigned _heaplen = 4096;     /* TurboC 1.5 and above  */

/* ---------- External routines ----------- */
/* -- From myint -- */
extern void     Config_Comm( int port, S_INIT conf );
extern S_INIT   Get_Conf( int port );
extern int      incomm();
extern int      Inst_IH(void interrupt (*faddr)(), int comnum);
extern int      Remove_IH();
extern int      writecomm(unsigned char *buf, int len);
extern int      xmit_break();
/* -- from xmutil -- */
extern int           read_comm(int *num, char *buf, int wait);
/* -- from object only files -- */
extern int getargs( int, char**, ARG *, int, int (*usage)() );

ARG Argtab[] = {
  {  'b', BOOLEAN,  &bbsmode,    "BBS mode (8,1,N) vs. T16)" },
  {  'c', INTVAR,   &comport,           "1 = COM1, 2 = COM2" },
  {  'e', INTVAR,   &eschar,            "Escape char (0x..)" },
  {  's', INTVAR,   &cur_config.speed,        "speed in bps" } };

/* ----- fgetsnn:  Gets a line from file, replacing \n with NULL.
 *       Return # chars, or EOF                                */
int fgetsnn(FILE *fp, char *s, int size)
{
  register int i;
  int c;

  for (i = 0; (i < size-1)             &&
              (c = fgetc(fp)) != EOF   &&
              (c != '\n')                 ; ++i)
    s[i] = c;
  s[i] = '\0';
  if (c == EOF)
    return(EOF);

  return(i);
}

/* ----- capture_sw:  Enables saving sessions to (PC) disc. -----
 *  Returns:  0 O.K.  1 if open fails, 2 if ESC hit.           */
int capture_sw()
{
  static char cap_fname[NAMESIZE + 1] = "capture.fil";
  char cap_temp[NAMESIZE + 1];
  static int cap_sw = 0;              /* capture on/off switch */

  if (cap_sw == 0) {              /* Open the file for capture */
    fprintf(stdout,"\n Capture to file <%s> or : ",cap_fname);
    fgetsnn (stdin, cap_temp, NAMESIZE );
    if (cap_temp[0] == eschar)
      return(2);
    if (cap_temp[0] != '\0')
      strncpy(cap_fname, cap_temp, NAMESIZE);
    if ( (cap_fptr = fopen (cap_fname, "a+t")) == NULL ) {
      printf("\n Cannot open %s.  Try again.\n", cap_fname);
      return(1);
    }
    cap_sw = 1;
  }
  else {       /* we are already capturing.  Close and get out */
    fclose( cap_fptr );
    cap_sw = 0;
  }
  return(0);
}

/* ----- keyfun:  Use to call BIOS keyboard input services ------
 *  Use instead of keypressed and bioskey to prevent DOS ^C's. */
int keyfun(int serv)
{
  rg.h.ah = serv;
  int86(BIOS_KEY, &rg, &rg);
  if (serv == KEYHIT)
    return ((rg.x.flags & 0x40) == 0);
  else
    return (rg.x.ax);
}

/* ----- term:  Emulates a dumb terminal. -------------------- */
void term()
{
  register int i;
  int  keyin;                   /* Key = scan code + ASCII val */
  char gochar;
  int redd;
  int ret_code,
      wait_ret;
  char *tail = inbuf;              /* for tail of input buffer */
  static int cap_flag = 0;        /* Is capture turned on now? */

  while (mode == M_Term) {
    redd = BUFLEN / 2;               /* Go for half at a time. */
    ret_code = read_comm (&redd, inbuf, 10);       /* 10 msecs */
    if ( (ret_code != 0) && (ret_code != TIMEOUT) )
      fprintf (stderr, "read_comm error %x\n", ret_code );

    if ( redd > 0 ) {                /* Reading was productive */
      tail[redd] = 0;                          /* plant a null */

      for ( i = 0; i < redd; i++) {      /* check for specials */
        if ( isprint( gochar = inbuf[i] & 0x7F) ||  /* zero hi */
           (    isspace(gochar)               ) ||  /* CR,LF.. */
                gochar == BS                           ) {
           putch(gochar);
           if (cap_flag)
             fputc(gochar, cap_fptr);
        }   /* printable test */
      }   /* for loop */
    }   /* end if reading was productive */

    if ( keyfun(KEYHIT) ) {
      keyin = keyfun(READNEXT);    /* Retrieve Scan Code + Key */
      gochar = keyin & 0xFF;                      /* truncates */
      if (gochar == 0) {          /* Function key or a special */
        switch (keyin) {
          case PGUP: mode = M_XSend;
                     xmodem_send();
                     mode = M_Term;
                     break;
          case PGDN: mode = M_XRecv;
                     xmodem_recv();
                     mode = M_Term;
                     break;
          case CBRK: xmit_break();
                     break;
          case INS:  if (capture_sw() == 0)
                       cap_flag = !cap_flag;
          default:   break;
        }
      }
      else {         /* Some plain old ascii character came in */
        if ( gochar != eschar ) {
          outbuf[0] = gochar;
          writecomm(outbuf, 1);
        }
        else                                 /* ESCAPE entered */
          mode = M_Cmd;                 /* leave terminal mode */
      }
    }  /* end if keypressed */
  }    /*  while mode = M_Term */
  return;
}

/* ----- off_to_dos: Prompts for command to pass to dos. ----- */
void off_to_dos()
{
  char buf[LINELEN];

  fputs("\nInput DOS Command (CR returns to menu)\nDOS>",stdout);

  while ( fgetsnn(stdin, buf, LINELEN) ) {  /* > 1 means got 1 */
    system(buf);
    fputs("\nDOS>",stdout);
  }
}

/* ----- config:  Prompts for new config (speed or default).-- */
void config()
{
  S_INIT work;
  char *cptr;
  char buf[LINELEN];
  unsigned inval;
  int inlen;
  int i = 0;

  work = Get_Conf(comport);             /* a struct assignment */

  fputs("\n Current config shows:\n", stdout);

  printf("%5u, parity %s, %s stops, %d bits/char.\n",
    work.speed,
    PARITY_LIST[work.ubits.lbits.parity],
    STOP_LIST[work.ubits.lbits.two_stops],
    work.ubits.lbits.wlen + 5);

  fputs("0 = T16 ( 7, 1, Even )\n", stdout);
  fputs("1 = BBS ( 8, 1, None )\n", stdout);
  fputs("other = new speed value\n", stdout);

  inlen = fgetsnn(stdin, buf, LINELEN);   /* Got one parameter */
  if (inlen > 0) {
    inval = atoi(buf);
    if (inval == 0) {
      work.ubits.lctrl = sev1even;                 /* 7,1,EVEN */
      bbsmode = 0;
    }
    if (inval == 1) {
      work.ubits.lctrl = ate1none;                 /* 8,1,NONE */
      bbsmode = 1;
    }
    if (inval > 1) {
      while ( SPEED_VALS[i] && SPEED_VALS[i] != inval )
        i++;
      if (SPEED_VALS[i] == 0)
        printf("\n Speed %d unavailable.\n",inval);
      else {   /* found a valid new speed */
        work.speed = inval;
      }
    }
    Config_Comm(comport, work);
    cur_config = work;                  /* Publish the results */
  }
  else
    fputs("\n Exiting Config mode.\n",stdout);

  if ( inlen == (LINELEN - 1) )
    while (fgetc(stdin) != EOF)     /* Purge garbage in buffer */
      ;

  mode = M_Cmd;
}


/* ----- prompt_wait: Prompts with string, parses command. ------
 * Returns: The (int) index number of the command entered.     */
prompt_wait ( char *prompt, char *cmds[], int n, int help  )
{
  char buffer[LINELEN];    /* used by fgetsnn */
  int i = 0;

  while (i == 0) {              /* Don't bite on CR only input */
    printf("\n%s",prompt);
    i   = fgetsnn(stdin, buffer, LINELEN );
  }

  if ( i == (LINELEN - 1) )
    while (fgetc(stdin) != EOF)     /* Purge garbage in buffer */
      ;

  for ( i = 0 ; i < n ; i++ )
    if ( *cmds[i] == toupper(buffer[0]) )  /* Match first char */
      return(i);
  return (help);           /* not found... return help default */
}

/* ----- main_help:  Shows canned help message --------------- */
void main_help()
{
static char *details[] =
  { "Config  : Set up comm parameters.\n",
    "Dos,    : Calls DOS with commands.\n",
    "Help,   : See this help info.\n",
    "Quit    : Exit program.\n",
    "Rcvx,   : Receive file using Xmodem.\n",
    "Sendx,  : Send file using Xmodem.\n",
    "Term,   : Dumb terminal mode.\n"        };

  register int i;

  fputs("\n Valid commands are:\n",stdout);
  for (i = 0 ; i < CMDDIM(details) ; i++)
    printf("%s",details[i]);
}

/* ----- main_menu:  Prompts for input, dispatches if valid -- */
void main_menu()
{
  static char *prompt     = "CTERM>";
  static char *maincmds[] =
    { "CONFIG",
      "DOS",
      "HELP",             /* used for default below (index 2 ) */
      "QUIT",
      "RCVX",
      "SENDX",
      "TERM"   };

  while (mode == M_Cmd) {
    switch ( prompt_wait(prompt, maincmds, CMDDIM(maincmds), 2 ))
    {
       case 0:  mode = M_Config;
                config();
                break;
       case 1:  off_to_dos();
                break;
       case 2:  main_help();
                break;
       case 3:  printf("\n *** Closing Comm port %d.", comport);
                close_comm();
                exit(1);
       case 4:  mode = M_XRecv;
                xmodem_recv();
                fputs("\nReturned from xmodem recv!\n",stdout);
                break;
       case 5:  mode = M_XSend;
                xmodem_send();
                fputs("\nReturned from xmodem send!\n",stdout);
                break;
       case 6:  mode = M_Term;
                eat_noise();
                term();
                break;
       default: main_help();
    }  /* end switch */
  }  /* end while */
}

/* ----- Catch_23:  Traps ^C + ^Break during user I/O. ------- */
void Catch_23()
{
  signal(SIGINT, Catch_23);      /*  Re-install self each time */
  return;
}

/* ----- Catch_24:  Traps Disk (Abort, Retry, Fail?) errors -- */
int Catch_24(int errval, int ax, int bp, int si)
{
  char msg[25];
  int drive;

  if (ax < 0) {          /* device error */
    bdosptr( 0x09, "device error$", 0);
    hardretn(-1);
  }

  drive = (ax & 0x00FF);
  sprintf(msg, "disc error on drive %c $", 'A' + drive);
  bdosptr( 0x09, msg, 0);
  hardretn(2);
}

/* ----- usage:  Give user quick help before exit. ----------- */
usage()
{
  printf("\n Defaults: T16, 1200 bps, 8,1,NONE, COM1, ESC.\n");
}

/* ----- main:  Gets the ball rolling!  ---------------------- */
main( int argc, char *argv[] )
{
  int error;

  VERSION;
  signal(SIGINT, Catch_23);
  harderr(Catch_24);

  argc = getargs( argc, argv, Argtab, CMDDIM(Argtab), usage );

  error = init_comm(comport, bbsmode);
  if (error != 0) {
    fprintf(stderr,"\n *** Comm Port %d Init FAILED!",comport);
    return(2);
  }

  fprintf(stderr,"\n *** Comm Port %d Init O.K. *** ", comport);
  main_menu();
  return(1);
}






<a name="01ff_001d"><a name="01ff_001d">
<a name="01ff_001e">
[LISTING SIX]
<a name="01ff_001e">

#Make file for cterm series Turbo C.  1/23/89 DWS
#3/20/89:  Added getargs and stoi (.obj only) from Holub
#use Make -fmakect1
#Small memory model
MDL = s
LIB = c:\turboc\lib

#implicit rules
# To add debug info: TCC (-v), TLINK (/v)
.c.obj:
   tcc -c -m$(MDL) $<

#explicit rules
cterm1.exe: commint.obj xmsend.obj xmrecv.obj xmutil.obj cterm1.obj
     tlink  ..\lib\c0s cterm1 xmsend xmrecv xmutil commint getargs stoi, \
     cterm1, , ..\lib\cs

xmsend.obj:  xmsend.c  cterm.h  commn.h

xmrecv.obj:  xmrecv.c  cterm.h  commn.h

commint.obj: commint.c          commn.h  commint.h

xmutil.obj:  xmutil.c  cterm.h  commn.h  commint.h

cterm1.obj:  cterm1.c  cterm.h  commn.h

#end makefile













Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.