/* Copyright (C) Magna Carta Software, Inc. 1990, 1991. All Rights Reserved. C COMMUNICATIONS TOOLKIT v1.0 XMODEM.C -- ROUTINES TO HANDLE XMODEM/XMODEM-CRC/XMODEM-1K/YMODEM/YMODEM-g FILE TRANSFER. */ #define CCT_DEVELOPMENT #if (defined(CCTW) || defined(_WINDOWS)) #include #endif #include #include #include #include #include #include static long cct_serial_num = 1234; short x_tx__(COMM_PORT *p, XFER *x); short yhdr_build_(XFER *x, DWORD paknum, char *buf); /* RX_CAN -- Check for a sequence of X_CAN CAN characters (indicating a user abort while we are sending a file). */ static short rx_can_(COMM_PORT *p) { short i; for (i=0; i < XCAN; i++) if (c_inchar(p) != CAN) return (FALSE); return (TRUE); } short x_pkt_build_(XFER *x, DWORD paknum, const char *buf) { WORD i; short echk = 0, pkt_result = FALSE, ch = 0; char *p = (char *) buf; x->soh = (x->blocksize == 1024) ? STX : SOH; *p++ = (char) x->soh; /* SOH or STX */ *p++ = (char) (paknum % 256); /* packet number */ *p++ = (char) ~(paknum % 256); /* 1's complement of packet no. */ echk = 0; /* initialize error accumulator */ for (i=0; i< x->blocksize && ch != EOF; i++) { if ((ch = c_fgetc_(x)) != EOF) { *p++ = (char) ch; echk = (*x->chk)(echk, ch); } else { /* COME HERE WHEN END OF FILE REACHED */ *p++ = CTRL_Z; echk = (*x->chk)(echk, CTRL_Z); i++; /* pad with NUL bytes */ for (; i < x->blocksize; i++) { *p++ = (char) NUL; echk = (*x->chk)(echk, NUL); } pkt_result = EOF; } } /* END OF BLOCK -- SEND ERROR CHECKING BYTE(S) */ if (x->chk == checksum) *p++ = (char) (echk % 256); else { *p++ = (char) (echk >> 8); *p++ = (char) echk; } return (pkt_result); } /* X_GET_PROTOCOL -- Wait for the starting character, to determine which variety of X/YMODEM the receiver wants. */ short x_get_protocol_(COMM_PORT *p, XFER *x) { WORD done = FALSE, retries = 0; do { cct_xfer_status = FALSE; switch(c_waitc(p, EOF, x->ibdelay)) { case 'C': /* XMODEM-CRC requested */ x->chk = crc_ccitt; if (x->p_user != NULL) (*x->p_user)(p, USING_CRC, 0L); done = TRUE; break; case 'G': /* YMODEM-g requested */ x->chk = crc_ccitt; x->protocol = YMODEM_G; if (x->p_user != NULL) (*x->p_user)(p, YMODEM_G, 0L); /* IF NO FORM OF FLOW CONTROL IS BEING ACCEPTED, USE XONXOFF */ if (!(p->flowctl & RX)) set_rx_xlat(p, FLOWCTL, XONXOFF); done = TRUE; break; case NAK: /* XMODEM-Checksum requested */ x->chk = checksum; if (x->p_user != NULL) (*x->p_user)(p, USING_CHECKSUM, 0L); done = TRUE; break; case CAN: /* cancel sent */ if (x->p_user != NULL) (*x->p_user)(p, RX_CANCELLED, 0L); done = cct_xfer_status = CAN; break; case USER_CANCELLED: if (x->p_user != NULL) (*x->p_user)(p, USER_CANCELLED, 0L); done = cct_xfer_status = USER_CANCELLED; break; default: if (x->p_user != NULL) (*x->p_user)(p, NO_RX_START, (DWORD) retries); cct_xfer_status = NO_RX_START; break; } } while (retries++ < x->retries && !done); return (cct_xfer_status); } /* X_TX_ -- Setup XMODEM routine. Save status and call x_tx__(). Restore status at end. */ short x_tx_(COMM_PORT *p, XFER *x, short protocol, unsigned len, short (CDECL_ *progress)(struct comm_port *, short, DWORD)) { char sxlat[XLAT_SIZE]; short save_kbd_clear, old_databits, old_parity, old_stopbits, ret; p->x = x; x->maxlen = x->len = max(len, MIN_BUFFER_SIZE); x->protocol = protocol; if (x->retries == 0) x->retries = XMODEM_RETRIES; if (x->ibdelay == 0) x->ibdelay = XMODEM_IBDELAY; /* initialize interbyte delay */ x->p_user = progress; /* enable progress reports */ x->error = a_xfer_errors; /* enable error recording */ if (protocol == YMODEM || protocol == YMODEM_G || protocol == XMODEM_1K) x->blocksize = 1024; else x->blocksize = 128; save_kbd_clear = kbd_clear; kbd_clear = TRUE; /* clear kbd. to detect exit */ /* SAVE THE EXISTING DATA FORMAT AND SET THE NEW ONE FOR XMODEM (8N1) */ old_databits = p->rxdatabits; old_parity = p->parity; old_stopbits = p->stopbits; if (p->rxdatabits != DATABITS8) set_databits(p, 8); if (p->parity != PARITY_NONE) set_parity(p, PARITY_NONE); if (p->stopbits != STOPBITS1) set_stopbits(p, 1); /* SAVE THE DATA TRANSLATION PARAMETERS AND SET NEW ONES FOR XMODEM */ save_xlat(p, sxlat); p->tx_xlat = p->rx_xlat = TRUE; p->tx_eol = p->tx_case_convert = p->tx_ascii_only = FALSE; ret = x_tx__(p, x); /* this is all we wanted to do! */ restore_xlat(p, sxlat); /* SET DATA FORMAT TO ORIGINAL VALUES */ set_databits(p, old_databits); set_parity(p, old_parity); set_stopbits(p, old_stopbits); kbd_clear = save_kbd_clear; ffreebuf_(x); /* free memory used by the file buffer */ return (ret); } /* X_TX__ -- Main X/Ymodem send loop. Assumes that we are waiting for the start character (a NAK or a 'C') from the file receiver. */ short x_tx__(COMM_PORT *p, XFER *x) { static char xbuf[1029]; short donefile, cancount, xbuflen, ret, ch; WORD retries; DWORD paknum; short f_eof = FALSE, donexfer = FALSE;; if (x->num_files) do { if (x_get_protocol_(p, x) != 0) return (EOF); /* SET FLAG TO INDICATE START OF FILE */ donefile = FALSE; paknum = 1; /* ALLOCATE BUFFER FOR FILE TRANSFERS */ if ((ret = fmakebuf_(x, x->len)) != 0) return (ret); if (x->p_user != NULL) (*x->p_user)(p, FILE_SOURCE, (long) x->origin); if (x->protocol == YMODEM || x->protocol == YMODEM_G) { lastfile: cancount = retries = 0; paknum = 0L; yhdr_build_(x, paknum, xbuf); resend0: if (retries > x->retries) donefile = TRUE; else { c_putn(p, Y_PKT_0_SIZE+3+((x->chk==checksum) ? 1 : 2), xbuf); waitagain0: retries++; switch(ch = c_waitc(p, EOF, x->ibdelay)) { case ACK: paknum++; if (f_eof) return (0); if (c_waitc(p, 'C', x->ibdelay) != 'C') donefile = TRUE; break; case CAN: if (cancount++ > 1) { x_can(p); donexfer = donefile = TRUE; if (x->p_user != NULL) (*x->p_user)(p, RX_CANCELLED, 0L); } else goto waitagain0; break; case 'G': paknum++; if (f_eof) return (0); break; case NAK: c_rxflush(p, 0); if (x->p_user != NULL) (*x->p_user)(p, NAK, 0L); goto resend0; case USER_CANCELLED: donexfer = donefile = TRUE; if (x->p_user != NULL) (*x->p_user)(p, USER_CANCELLED, 0L); break; default: goto resend0; } } } /* XMODEM LOOP */ if (!donefile) do { cancount = retries = 0; xbuflen = x->blocksize + 3 + ((x->chk == checksum) ? 1 : 2); ret = x_pkt_build_(x, paknum, xbuf); resend1: if (retries > x->retries) donefile = TRUE; else { c_putn(p, xbuflen, xbuf); retries++; waitagain1: if (x->protocol == YMODEM_G) { if (rx_can_(p)) { donefile = donexfer = TRUE; (*x->p_user)(p, RX_CANCELLED, 0L); } else paknum++; } else switch(c_waitc(p, EOF, x->ibdelay)) { case ACK: if (x->p_user != NULL) (*x->p_user)(p, XFER_POSITION, (long) paknum * x->blocksize); paknum++; break; case NAK: c_rxflush(p, 0); if (x->p_user != NULL) (*x->p_user)(p, NAK, 0L); goto resend1; case CAN: if (cancount++ > 1) { x_can(p); donexfer = donefile = TRUE; if (x->p_user != NULL) (*x->p_user)(p, RX_CANCELLED, 0L); } else goto waitagain1; break; case USER_CANCELLED: donexfer = donefile = TRUE; if (x->p_user != NULL) (*x->p_user)(p, USER_CANCELLED, 0L); break; default: goto resend1; } } if (ret == EOF) { /* BLOCK FOR EOT */ cancount = retries = 0; resend2: if (retries++ > x->retries) donefile = TRUE; else { c_putc(p, EOT); waitagain2: switch(c_waitc(p, EOF, x->ibdelay)) { case ACK: ret = 0; donefile = TRUE; if (x->p_user != NULL) (*x->p_user)(p, EOT, 0L); break; case NAK: c_rxflush(p, 0); if (x->p_user != NULL) (*x->p_user)(p, NAK, 0L); goto resend2; case CAN: if (cancount++ > 1) { x_can(p); donexfer = donefile = TRUE; if (x->p_user != NULL) (*x->p_user)(p, RX_CANCELLED, 0L); } else goto waitagain2; break; case USER_CANCELLED: donexfer = donefile = TRUE; if (x->p_user != NULL) (*x->p_user)(p, USER_CANCELLED, 0L); break; default: goto resend2; } } } } while (!donefile); cct_file_close_(x->fh); /* close the file/stream */ ffreebuf_(x); funqueue(x, x->fspec); /* if TX in progress, return 1 */ if (!x->num_files) { donexfer = TRUE; if (p->f_txbusy) ret = 1; } else while (p->f_txbusy); /* wait for YMODEM-g */ if (donexfer && (x->protocol == YMODEM || x->protocol == YMODEM_G)) { f_eof = TRUE; goto lastfile; } } while (!donexfer); return (ret); } /* YMODEM_HDR_BUILD_ -- Build a YMODEM header (excluding SOH, pkt. number, and CRC. */ short ymodem_hdr_build_(XFER *x, char buf[]) { char *p = buf; memset(buf, 0, Y_PKT_0_SIZE); if (x->xf != NULL) { /* SEARCH FOR A PATH OR DRIVE DELIMITER */ p = x->fspec + 79; while (p > x->fspec) { if (*p == ':' || *p == '\\') { ++p; break; } --p; } strcpy(buf, p); p = buf + strlen(buf) + 1; /* COPY FILE LENGTH TO BUFFER */ ltoa(x->stat.st_size, p, 10); while (*p != '\0') p++; /* COPY FILE MODIFICATION DATE TO BUFFER */ ltoa(x->stat.st_mtime, ++p, 8); while (*p != '\0') p++; /* COPY FILE MODE TO BUFFER */ itoa(x->stat.st_mode, ++p, 8); while (*p != '\0') p++; /* COPY SERIAL NUMBER TO BUFFER */ ltoa(cct_serial_num, ++p, 8); } return (0); } short yhdr_build_(XFER *x, DWORD paknum, char *buf) { short i; short echk = 0; char *p = buf; memset(buf, 0, Y_PKT_0_SIZE+5); *p++ = (Y_PKT_0_SIZE == 128) ? SOH : STX; /* SOH or STX */ *p++ = (char) (paknum % 256); /* packet number */ *p++ = (char) ~(paknum % 256); /* 1's complement of packet no. */ ymodem_hdr_build_(x, p); /* CALCULATE CRC */ for (i=0, p=buf+3; i < Y_PKT_0_SIZE; i++) echk = (*x->chk)(echk, *p++); /* END OF BLOCK -- SEND ERROR CHECKING BYTE(S) */ if (x->chk == checksum) buf[Y_PKT_0_SIZE+3] = (char) (echk % 256); else { buf[Y_PKT_0_SIZE+3] = (char) (echk >> 8); buf[Y_PKT_0_SIZE+4] = (char) echk; } return (0); } /* RX_XMODEM_ -- This is the XMODEM, XMODEM_CRC, XMODEM_1K, YMODEM/g receive routine (this flexibility is the reason for its large size). If a packet is not received successfully, "pkt_error" is non-zero. If the transmission must be aborted, "file_error" is non-zero. Return value: 0 -- success; */ short rx_xmodem_(COMM_PORT *p, XFER *x) { short file_error, DONE_SOH, pkt_error, ch, f_ymodem, ret; short msgnum = 0; WORD paknum, i, echk, retries, ibdelay; char *pfbuf; unsigned long bytes = 0L; char sxlat[XLAT_SIZE]; short old_databits, old_parity, old_stopbits; short (CDECL_ *p_user)(struct comm_port *, short, unsigned long) = x->p_user; /* SAVE THE CURRENT DATA FORMAT AND SET IT FOR XMODEM (8N1) */ old_databits = p->rxdatabits; old_parity = p->parity; old_stopbits = p->stopbits; if (p->rxdatabits != DATABITS8) set_databits(p, 8); if (p->parity != PARITY_NONE) set_parity(p, PARITY_NONE); if (p->stopbits != STOPBITS1) set_stopbits(p, 1); /* SAVE THE CURRENT DATA TRANSLATION STATE AND SET IT FOR XMODEM */ save_xlat(p, sxlat); /* SET INTERBYTE DELAYS */ if (x->ibdelay < p->rx_ibdelay) ibdelay = x->ibdelay = p->rx_ibdelay; else ibdelay = x->ibdelay; /* SET RETRIES */ if (x->retries < XMODEM_RETRIES) x->retries = XMODEM_RETRIES; /* INITIALIZE SOME LOCAL VARIABLES */ for (i=0; i < MAX_XFER_ERROR_TYPES; i++) x->error[i] = 0; f_ymodem = file_error = retries = paknum = 0; c_rxflush(p, 0); /* THE FOLLOWING LOOP RECEIVES ONE PACKET (XMODEM/CRC/1K, YMODEM/G) */ do { DONE_SOH = pkt_error = FALSE; #if XDEBUG printf("Paknum=%d", paknum); #endif /* THE FOLLOWING LOOP WAITS FOR A SOH/STX */ do { if (paknum < 2) c_putc(p, x->poll); /* send NAK or 'C' */ switch (ch = c_waitc(p, EOF, ibdelay)) { case SOH: x->blocksize = 128; DONE_SOH = TRUE; pkt_error = 0; if (p_user != NULL) (*p_user)(p, SOH, (long) paknum); break; case STX: x->blocksize = 1024; DONE_SOH = TRUE; if (p_user != NULL) (*p_user)(p, STX, 0L); break; case CAN: x->error[-TX_CANCELLED - XERROR_OFFSET]++; file_error = TX_CANCELLED; pkt_error = CAN; if (p_user != NULL) (*p_user)(p, CAN, 0L); break; case EOT: /* END OF TRANSMISSION */ if (p_user != NULL) (*p_user)(p, EOT, 0L); if (f_ymodem) x->pb -= (unsigned) (bytes - fsize); if (x->pb - x->buf > 0) file_error = c_fwrite_(p, x); else x->pb = x->buf; ret = cct_file_close_(x->fh); file_error = (file_error) ? file_error : ret; if (!file_error) { c_putc(p, ACK); if (f_ymodem) { if (!file_error) { DONE_SOH = file_error = pkt_error = retries = paknum = 0; bytes = 0L; mspause(1000); /* delay (why?) */ continue; } } else file_error = ACK; } break; case USER_CANCELLED: if (p_user != NULL) (*p_user)(p, USER_CANCELLED, 0L); x->error[-USER_CANCELLED - XERROR_OFFSET]++; file_error = USER_CANCELLED; pkt_error = CAN; break; case EOF: if (p_user != NULL) (*p_user)(p, NO_TX_START, (DWORD) retries); x->error[-NO_TX_START - XERROR_OFFSET]++; pkt_error = EOF; break; default: if (paknum < 2 && p_user != NULL) (*p_user)(p, GARBAGE_RECEIVED, (unsigned long) ch); pkt_error = GARBAGE_RECEIVED; if (retries) retries--; break; } if (retries == x->retries) { if (p_user != NULL) (*p_user)(p, NO_TX_START, 0L); file_error = pkt_error = NO_TX_START; } if (pkt_error) retries++; } while (!DONE_SOH && !pkt_error && !file_error); if (file_error) break; /* PACKET NUMBER BYTE */ if (!pkt_error) { ch = c_waitc(p, EOF, ibdelay); if (ch == USER_CANCELLED) { /* no response */ msgnum = USER_CANCELLED; file_error = USER_CANCELLED; pkt_error = CAN; } else if (ch == EOF) { /* no response */ msgnum = RX_TIMEOUT; pkt_error = NAK; } /* packet # = 0 => YMODEM/SEAlink */ else if ((paknum == 0) && ch == NUL) { f_ymodem = TRUE; if (p_user != NULL) (*p_user)(p, YMODEM, 0L); } else if (paknum == 0 && ch == 0X1) { if (p_user != NULL) { if (x->blocksize == 1024) (*p_user)(p, XMODEM_1K, 0L); else { if (x->chk == checksum) (*p_user)(p, XMODEM, 0L); else (*p_user)(p, XMODEM_CRC, 0L); } } paknum++; } /* PACKET NUMBER SAME AS PREVIOUS -- ACK AND IGNORE */ else if (paknum && ch == (short) ((paknum-1) % 256)) { #if XDEBUG printf("\nPacket number same as previous"); #endif for (i = x->blocksize + 1; i > 0; --i) { ch = c_waitc(p, EOF, ibdelay); if (ch < 0) break; } pkt_error = ACK; } else if ((short)(paknum % 256) != ch) { msgnum = BAD_PACKET_NUMBER; pkt_error = NAK; } } /* 1's COMPLEMENT OF PACKET NUMBER BYTE */ if (!pkt_error) { #if XDEBUG printf("\nWaiting for 1's complement of packet number"); #endif ch = c_waitc(p, EOF, ibdelay); if (ch == EOF) { /* no response */ msgnum = RX_TIMEOUT; pkt_error = NAK; } else if (ch == USER_CANCELLED) { /* keyboard cancel */ msgnum = USER_CANCELLED; file_error = USER_CANCELLED; pkt_error = CAN; } /* packet # = 0 => YMODEM/SEAlink */ else if (!paknum && f_ymodem && ch != (BYTE) ~NUL) { msgnum = BAD_PACKET_NUMBER; pkt_error = NAK; } else if (paknum % 256 != (BYTE) ~ch && !f_ymodem) { msgnum = BAD_PACKET_NUMBER; pkt_error = NAK; } } /* THE DATA (AT LAST!) */ if (!pkt_error) { #if XDEBUG printf("\nWaiting for data"); printf("\nx->buf = %Fp, x->pb=%Fp, Dif=%d", x->buf, x->pb, x->pb - x->buf); #endif pfbuf = x->pb; /* mark start of pkt. in buffer */ echk = 0; /* initialize checksum/CRC */ for (i = x->blocksize; i > 0; --i) { ch = c_waitc(p, EOF, ibdelay); if (ch == USER_CANCELLED) { msgnum = USER_CANCELLED; file_error = USER_CANCELLED; pkt_error = CAN; break; } else if (ch == EOF) { x->pb = pfbuf; msgnum = RX_TIMEOUT; pkt_error = NAK; break; } else { *x->pb = (BYTE) ch; x->pb++; /* TC v2.0 won't handle increment with assignment */ echk = (*x->chk)(echk, ch); } } if (!pkt_error && p_user != NULL) (*p_user)(p, XFER_POSITION, (long) paknum * x->blocksize); } /* ERROR CHECKING (CHECKSUM OR CRC) */ if (!pkt_error) { if (x->poll == NAK) { if ((ch = c_waitc(p, EOF, ibdelay)) != (short) (echk % 256)) { x->pb = pfbuf; msgnum = CRC_ERROR; pkt_error = NAK; } } else if (x->poll == 'C' || x->poll == 'G') { ch = c_waitc(p, EOF, ibdelay) << 8; /* high byte of CRC */ ch |= c_waitc(p, EOF, ibdelay); /* low byte of CRC */ if ((WORD) ch != echk) { x->pb = pfbuf; msgnum = CRC_ERROR; pkt_error = NAK; } } } /* PACKET RECEIVED WITHOUT ERROR -- OPEN FILE TO RECEIVE DATA */ if (!pkt_error) { if (f_ymodem == 1 && !paknum) { /* YMODEM REQUESTED -- ACCESS THE FILE INFORMATION */ pkt_error = parse_ymodem_hdr_((char FAR_ *)x->buf, x->blocksize, fname, &fsize, &fdate); if (pkt_error) x_can(p); else { msgnum = FILE_INFO_RECEIVED; if (fname[0] == '\0') { file_error = ACK; /* finished YMODEM */ x_ack(p); } else { /* CREATE FILE TO RECEIVE DATA */ /* get disk size */ x->fh = cct_file_open_(fname,O_CREAT|O_RDWR|O_BINARY); if (!x->fh) file_error = TRUE; if (file_error) { ffreebuf_(x); msgnum = file_error = DISK_FULL; } } x->pb = x->buf; } } else { if (f_ymodem == 0 && paknum == 1) { x->fh = cct_file_open_(ftempname,O_CREAT|O_RDWR|O_BINARY); if (!x->fh) file_error = TRUE; if (file_error) msgnum = DISK_FULL; } bytes += x->blocksize; } /* WRITE FILE TO DISK */ if (x->pb > x->high) { /* ADJUST TO EXACT FILE LENGTH */ if (f_ymodem && bytes > fsize) x->pb -= (unsigned) (bytes - fsize); if (x->pb - x->buf > 0) file_error = c_fwrite_(p, x); else x->pb = x->buf; } paknum++; retries = 0; if (!file_error && x->protocol != YMODEM_G) { c_rxflush(p, 0); /* clear any noise from RX buffer */ x_ack(p); } } /* SEND MESSAGE TO USER AND UPDATE ERROR COUNTER */ if (msgnum && p_user != NULL) { (*p_user)(p, msgnum, 0L); x->error[-msgnum - XERROR_OFFSET]++; msgnum = 0; } /* HANDLE PACKET ERRORS */ switch (pkt_error) { case ACK: x_ack(p); break; case CAN: x_can(p); break; case NAK: x_nak(p); break; default: break; } if (pkt_error && (f_ymodem && x->poll == 'G')) file_error = GARBAGE_RECEIVED; } while (!file_error); if (file_error != ACK) x_can(p); c_rxflush(p, 0); cct_file_close_(x->fh); rest_xlat(p, sxlat); /* SET DATA FORMAT TO ORIGINAL VALUES */ set_databits(p, old_databits); set_parity(p, old_parity); set_stopbits(p, old_stopbits); return (file_error); }