/* XMRECV.C: Xmodem receive state machine processing */ #include #include #include #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 ---------------------- * Does: Prompts for file to receive, attempts open. * Also clears comm port. * Pass: A pointer to the name array to make public. * Returns: 0 if successful, 1 if open fails, 2 is user abort, * 3 if comm trouble. * NOTE: This is the initial action called for xmodem receive. */ 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 --------------------------- * Does: Sends one control character, based on param passed. * Then waits for a character to come in at beginning of frame. * If virgin, will retry (initial NAK/CRC) until user aborts. * Stores: The character into global sohor variable for later examination. * Pass: INIT = post CRC and set crc flag. * RESEND = alternate CRC/NAK (if virgin), infinite retries. * NEXT = post ACK to frame and restore retries to default. * RESEND = post NAK (ask for RESEND), tests and decrement retries. * Returns: 0 if char avail., 1 if comm error, 2 if timeout, 3 if 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 ------------------------------- * Does: Looks at the first character received. Usually used * when a block is expected (beginning with SOH) * Pass: Global char sohor (read and stored by A_Frame_Wait) * Returns: 0 if SOH, 1 if CAN, 2 if EOT, 3 if 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 Function ------------------ * Does: Calculates the CRC/Checksum per flag passed. * 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 --------------------- * Does: After an SOH has been seen, validates the xmodem header. * If good (and not repeat), stores it away in prev. opened file. * Goal: Let the low level routine fill the buffer by itself without * reading every character at this level, But try to deduce a * dead line as soon as possible. * Pass: Whether to use CRC or Checksum method to validate (by ref) * Returns: 0 if alls well, 1 if bad header or seq #, * 2 if bad CRC/Checksum, 3 if timeout during char reception. */ 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 ------------------- * Does: Eats the rest of an incoming packet until two second timeout. * Then: Calls A_Frame_Wait(RESEND) which sends NAK and reads ctrl char. * Pass: An estimate of the max that might be out there * Returns: Passes thru return from A_Frame_Wait. */ 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 --------------------- * Does: Get us out of the Recv state machine (only way out). * Also: Posts an informative message regarging why, sends appropriate * final character and closes (and deletes) the file as required. */ 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 --------------- * Entered: * From terminal mode, upon PG Down key or equivalent command * Initial action: Prep_Recv which preps disk file and cleans comm. */ 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); }