home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS - Coast to Coast
/
simteldosarchivecoasttocoast2.iso
/
ddjmag
/
ddj8910.zip
/
SMITH.LST
< prev
next >
Wrap
File List
|
1989-09-07
|
40KB
|
1,184 lines
_Finite State Machines for XModem_
by Donald W. Smith
[LISTING ONE]
/* 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 */
[LISTING TWO]
#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;
[LISTING THREE]
/* 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);
}
[LISTING FOUR]
/* 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);
}
[LISTING FIVE]
/* 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);
}
[LISTING SIX]
#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