/* XMSEND.C Xmodem Send state machine processing. */ #include /* for putch call */ #include #include /* for filelength call */ #include #include #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 ---------------------- * Does: Prompts for file to transmit, attempts open. * Returns: 0 if successful, 1 if open fails, 2 is 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 ------------------ * Does: Waits for initial sync character. * Pass: CRC if that's what is desired. * NAK if Checksum is desired. * NEXT if alternation is desired (will try 2x each). * Why alternate 2x each? Because receivers alternate 1x each * when they are "hunting". If we wait twice for CRC first, * we'll have a good change to catch the receiver hunting CRC. * 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 --------------------------- * Does: Waits for global timeout value for a character. * Pass: The character desired. * Returns: 0 if match, 1 if other, 2 if timeout, 3 if 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 ------------------- * Does: Reads from disk as required, formats packet. * Pass: Defines INIT (1), NEXT (2) * Returns: 0 if alls well, 1 if disk trouble, 2 if EOF found, 3 to give up. * Note: 1 and 3 are not implemented yet. */ 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 ----------------------- * Does: Send a previously created packet out the comm port. * Pass: NEXT if normal, NAK or TIMEOUT if thats why, RESEND if comm retry. * Returns: 0 if O.K., 1 if write error, 2 if no retries, 3 if 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 --------------------- * Does: Get us out of the Send state machine (only way out). * Also: Posts an informative message regarging why, sends appropriate * final character and closes the file we were sending. */ 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 ------------------ * Entered: * From terminal mode, upon PG Up key or equivalent command * Initial action: Get_Fname (and possibly other params) */ 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); }