/* Copyright (C) Magna Carta Software, Inc. 1988-1990. All Rights Reserved C COMMUNICATIONS TOOLKIT KERMIT.C -- ROUTINES TO HANDLE KERMIT FILE TRANSFERS. Based on "Kermit Protocol Manual" 5ed. & DaCruz (1987). Note: If you port this to other hardware architectures or non MSDOS based opertating systems, we would appreciate knowing of any problems due to the code constructs used below. Capabilities: Basic Kermit specification Eight-bit quoting; Repeated character quoting; Explanation of debug codes: if XDEBUG=1 general purpose debug information is displayed; */ #define CCT_DEVELOPMENT #if (defined(CCTW) || defined(_WINDOWS)) #include #endif #include #include #include #include #include #include #include static char k_error_msg[PATHLEN]; /* K_INIT -- Prepare for Kermit transfers a) allocate memory for the transit buffer; b) allocate memory for the parameter structure; c) initialize the parameter structure; Return code: 0 -- normal return; EOF -- Kermit already initialized; NO_RAM -- insufficient memory for internal structures or buffers; */ short k_init(void *p, XFER *x, KERMIT_PARMS *k) { /* IF x->k IS ALREADY INITIALIZED, EXIT */ if (x->k != NULL) return (EOF); if (k == NULL) { if ((k = x->k = (KERMIT_PARMS *) memcalloc(1, sizeof(KERMIT_PARMS))) == NULL) return (NO_RAM); ((KERMIT_PARMS *)x->k)->flags |= CCT_DYNAMIC; /* tell CCT it is dynamically allocated */ } else x->k = k; if (!k->iobufsize) k->iobufsize = KERMIT_PAK_SIZE; /* ALLOCATE MEMORY FOR THE RECEIVE BUFFER */ k->rxbuf = (char *) memcalloc(1, k->iobufsize); if (k->rxbuf == NULL) { if (isdynamic(k)) memfree(k); return (NO_RAM); } /* ALLOCATE MEMORY FOR THE TRANSMIT BUFFER */ k->txbuf = (char *) memcalloc(1, k->iobufsize); if (k->txbuf == NULL) { memfree(k->rxbuf); k->rxbuf = NULL; if (isdynamic(k)) memfree(k); return (NO_RAM); } /* INITIALIZE THE KERMIT_PARMS STRUCTURE WITH DEFAULTS */ if (!k->mark) k->mark = K_MARK; if (!k->maxl) k->maxl = K_MAXL; if (!k->timeout) k->timeout = K_TIMEOUT; if (!k->eol) k->eol = K_EOL; if (!k->qctl) k->qctl = K_QCTL; if (!k->qbin) k->qbin = K_QBIN; if (!k->chkt) k->chkt = K_CHKT; if (!k->rept) k->rept = K_REPT; if (!k->mmark) k->mmark = K_MARK; if (!k->mmaxl) k->mmaxl = K_MAXL; if (!k->mtimeout) k->mtimeout = K_TIMEOUT; if (!k->meol) k->meol = K_EOL; if (!k->mqctl) k->mqctl = K_QCTL; if (!k->mqbin) k->mqbin = K_QBIN; if (!k->mchkt) k->mchkt = K_CHKT; if (!k->mrept) k->mrept = K_REPT; if (!k->max_retry) k->max_retry = 5; k->parity = get_parity(p); if (k->port == NULL) k->port = p; /* the CCT COMM_PORT structure */ if (k->x == NULL) k->x = x; /* the CCT XFER structure */ /* THE PORT STRING WRITE FUNCTION */ if (k->c_puts == NULL) k->c_puts = (short (*)(void *, char *)) c_puts; /* THE WAIT FOR CHAR FN. */ if (k->c_waitc == NULL) k->c_waitc = (short (*)(void *, short, WORD)) c_waitc; /* THE FN. TO CLEAR THE RECEIVE BUFFER */ if (k->c_rxflush == NULL) k->c_rxflush = (short (*)(void *, WORD)) c_rxflush; /* THE FN. TO LOAD THE FILE TO SEND */ if (k->fmakebuf_ == NULL) k->fmakebuf_ = (short (*)(void *)) fmakebuf_; /* THE FN. TO WRITE THE RECEIVED FILE TO DISK */ if (k->c_fwrite_ == NULL) k->c_fwrite_ = (short (*)(void *, void *)) c_fwrite_; /* THE FN. TO TELL USER WHAT IS GOING ON */ /* if (k->p_user == NULL) k->p_user = (short (*)(void *, void *)) c_fwrite_; */ k->flags |= CCT_INIT_OK; return (0); } /* K_BUILD_INIT_ -- Build a Kermit 'send_init' packet. */ void k_build_init_(KERMIT_PARMS *k) { /* WORD chk; */ k->paknum = 0; /* initialize sequence counter */ memset(k->txbuf, 0X0, k->iobufsize); #if XDEBUG k->txbuf[0] = (char) k->mmark; /* header */ k->txbuf[1] = (char) SP; k->txbuf[2] = (BYTE) tochar(0); /* packet sequence number */ k->txbuf[3] = 'S'; #endif /* DATA FIELD */ k->txbuf[4] = (char) tochar(k->mmaxl); /* maximum packet length */ k->txbuf[5] = (char) tochar(k->mtimeout); /* you time me out after... */ k->txbuf[6] = (char) tochar(k->mnpad); /* no. of pad chars */ k->txbuf[7] = (char) ctl(k->mpadc); /* pad character */ k->txbuf[8] = (char) tochar(k->meol); /* end-of-line character */ k->txbuf[9] = (char) k->mqctl; /* control quote character */ k->txbuf[10] = (char) k->mqbin; /* 8th-bit prefix character */ k->txbuf[11] = (char) k->mchkt; /* type of error checking */ k->txbuf[12] = (char) k->mrept; /* repeat quote character */ #if XDEBUG printf("\nSend parms: MAXL=%d, TIME=%d, NPAD=%d, PADC=%d, EOL=%d, QCTL=%c " "QBIN=%c, REPT=%c\n", k->mmaxl, k->mtimeout, k->mnpad, k->mpadc, k->meol, k->mqctl, k->mqbin, k->mrept); #endif } /* K_CHECK_ -- Compute the checksum of a buffer starting at "buf" and extending for "count" characters. */ WORD k_check_(KERMIT_PARMS *k, char *buf, WORD count) { WORD chk; short andval; andval = (k->parity != PARITY_NONE) ? 0X7F : 0XFF; for(chk=0; count > 0; count--, buf++) chk += *buf & andval; return (chk); } /* K_BUILD_PKT_ -- Build a Kermit packet of the specified type. 's' is a pointer to the data field. */ short k_build_pkt_(KERMIT_PARMS *k, char *s, short type) { short chk; if (s != NULL) { memset(&k->txbuf[4], 0X0, k->iobufsize-5); strcpy(&k->txbuf[4], s); /* data field */ } k->txbuf[0] = (char) k->mark; /* header */ /* CALCULATE THE PACKET LENGTH */ k->txbuf[1] = (char) tochar(2 + strlen(&k->txbuf[4]) + (k->chkt & 0X0F)); k->txbuf[2] = (char) tochar(k->paknum % 64); /* packet sequence number */ k->txbuf[3] = (char) type; /* packet type */ /* ERROR CHECK VALUE */ if (k->chkt == '1' || type == 'S') { chk = (char) k_check_(k, &k->txbuf[1], unchar(k->txbuf[1])); k->txbuf[strlen(k->txbuf)] = (char) tochar((chk + ((chk & 192)/64)) & 63); /* error check value */ } else if (k->chkt == '2') { chk = checksumn(&k->txbuf[1], unchar(k->txbuf[1])); /* ERROR CHECK VALUE BYTE 1 */ k->txbuf[strlen(k->txbuf)] = (char) tochar((chk >> 6) & 0X3F); k->txbuf[strlen(k->txbuf)] = (char) tochar(chk & 0X3F); } /* PACKET TERMINATOR */ k->txbuf[2 + unchar(k->txbuf[1])] = (char) k->eol; /* TERMINATE THE STRING */ k->txbuf[2 + unchar(k->txbuf[1]) + 1] = '\0'; return(0); } /* K_RECEIVE_PKT_ -- Try to receive a packet from the remote system. Return Value: EOF -- character not recieved; >0 -- packet type; */ short k_receive_pkt_(KERMIT_PARMS *k) { short chksum, len, i, ch; char *pk; k->ptype = 0; pk = k->rxbuf; memset(k->rxbuf, 0X0, k->iobufsize); /* START OF PACKET HEADER */ if ((ch = (*k->c_waitc)(k->port, k->mark, 1000*k->timeout)) < 0) return (ch); /* PKT. LENGTH */ if ((ch = (*k->c_waitc)(k->port, EOF, 1000*k->timeout)) < 0) return (ch); #if XDEBUG printf("LEN=%d ", unchar(ch)); #endif len = unchar(ch); k->dlen = len - 3; chksum = (char) ch; /* initialize checksum */ /* PACKET SEQUENCE NUMBER */ if ((ch = (*k->c_waitc)(k->port, EOF, 1000*k->timeout)) < 0) return (ch); k->num = unchar(ch); #if XDEBUG printf("SEQ=%d ", unchar(ch)); #endif chksum += (char) ch; /* TYPE FIELD */ k->ptype = ch = (*k->c_waitc)(k->port, EOF, 1000*k->timeout); /* type field */ chksum += (char) ch; #if XDEBUG printf("TYPE=%c\n", ch); #endif /* RECEIVE THE DATA FIELD */ for (i=0; ic_waitc)(k->port, EOF, 1000*k->timeout)) < 0) break; chksum += (char) ch; *pk++ = (char) ch; } if (ch == USER_CANCELLED) return (ch); if (i < len-3) return (EOF); /* ERROR CHECK VALUE */ if ((ch = (*k->c_waitc)(k->port, EOF, 1000*k->timeout)) < 0) return (ch); chksum = tochar((chksum + ((chksum & 192)/64)) & 63); /* error check value */ if (chksum != ch) return (EOF); /* invalid checksum */ #if XDEBUG printf("CHECK=%d ", ch); set_rx_xlat(k->port, LOCAL_ECHO, OFF); #endif return (k->ptype); } /* ISPREFIX_ -- Returns TRUE if a character is in the range of valid Kermit prefix characters. */ short isprefix_(short ch) { if (ch >= 33 && ch <= 63) return (TRUE); if (ch >= 96 && ch <= 126) return (TRUE); return (FALSE); } /* K_NEG_PARMS_ -- Negotiate Kermit parameters. Called after response from k_send_init() or receipt of send_init(). In each case, no response or an invalid response from the remote initiates the use of Kermit defaults. */ short k_neg_parms_(KERMIT_PARMS *k) { short len, x; /* MAXIMUM PACKET LENGTH */ len = strlen(&k->rxbuf[0]); x = (len >= 1) ? unchar(k->rxbuf[0]) : 80; k->maxl = (x < 10 || x > 94) ? 80 : x; /* TIMEOUT BETWEEN CHARACTERS */ x = (len >= 2) ? unchar(k->rxbuf[1]) : 5; k->timeout = (x < 0) ? 5 : x; /* NUMBER OF PADDING CHARACTERS */ x = (len >= 3) ? unchar(k->rxbuf[2]) : 0; k->npad = (x < 0) ? 0 : x; /* PADDING CHARACTER */ k->padc = (len >= 4) ? ctl(k->rxbuf[3]) : '\0'; /* END-OF-LINE SYMBOL */ x = (len >= 5) ? (WORD) unchar(k->rxbuf[4]) : '\r'; k->eol = ((x < 2) || (x > 31)) ? '\r' : x; /* CONTROL QUOTE CHARACTER */ x = (len >= 6) ? (WORD) k->rxbuf[5] : '#'; k->qctl = (isprefix_(x)) ? x : k->qctl; /* 8th BIT QUOTING (k->qbin is initialized to 'Y' if no parity, else '&') */ if (k->mqbin == 'N') k->qbin = FALSE; else { x = (len >= 7) ? k->rxbuf[6] : FALSE; #if XDEBUG printf("\nRequested QBIN=%c", x); #endif if (x == 'Y' && isprefix_(k->mqbin)); else if (isprefix_(x)) { if (x == (short) k->mqbin || k->mqbin == 'Y') k->mqbin = k->qbin = x; } else k->qbin = FALSE; } /* TYPE OF ERROR CHECKING */ x = (len >= 8) ? k->rxbuf[7] : '1'; /* k->chkt = (x == '2' || x == '3') ? x : '1'; NOT SUPPORTED (YET) */ k->chkt = '1'; /* RUN-LENGTH ENCODING */ x = (len >= 9) ? k->rxbuf[8] : FALSE; #if XDEBUG printf("\nRequested REPT=%#X, %c", x, x); #endif if (isprefix_(x) && isprefix_(k->mrept)) k->mrept = k->rept = x; else k->rept = FALSE; k->dlen = k->maxl - 2 - (k->chkt & 0XF); #if XDEBUG printf("\nNEG: MAXL=%d, TIME=%d, NPAD=%d, PADC=%d, EOL=%d, QCTL=%c " "QBIN=%c, REPT=%c\n", k->maxl, k->timeout, k->npad, k->padc, k->eol, k->qctl, k->qbin, k->rept); #endif return (0); } /* K_PKT_ -- Send a Kermit packet that requires no character in the data field. (e.g. ACK, NAK, BREAK). */ void k_pkt_(KERMIT_PARMS *k, char ch) { k_build_pkt_(k, "", ch); (*k->c_puts)(k->port, k->txbuf); /* send ACK */ } /* K_SEND_PKT_ -- Send a packet and get the response. */ short k_send_pkt_(KERMIT_PARMS *k) { #if XDEBUG printf("\nSending %c packet", k->txbuf[3]); #endif c_rxflush(k->port, 0); (*k->c_puts)(k->port, k->txbuf); /* send it */ return (k_receive_pkt_(k)); } /* K_S_ -- State S. Transmit the send_init packet and get the response. Return Value: EOF -- No response; 'F' -- Success. Go to state 'F'. */ short k_s_(KERMIT_PARMS *k) { short ret; short DONE = FALSE; WORD i; if (k->p_user != NULL) (*k->p_user)(k->port, K_SINIT | 0X1000, 0L); k_build_init_(k); /* build a send_init packet */ k_build_pkt_(k, NULL, 'S'); for (i=0; i < k->max_retry && !DONE; i++) { ret = k_send_pkt_(k); switch (ret) { case EOF: case 'N': continue; case USER_CANCELLED: case 'A': case 'E': DONE = TRUE; break; case 'Y': k_neg_parms_(k); k->paknum++; ret = 'F'; DONE = TRUE; break; } } if (i == k->max_retry) ret = EOF; return (ret); } /* K_SF_ -- State SF. Send the file header. */ short k_sf_(KERMIT_PARMS *k) { short ret; short DONE = FALSE; WORD i; char *p; /* ELIMINATE ANY PATH/DRIVE SPEC FROM THE FILE NAME WE SEND */ p = k->x->xf->fspec + strlen(k->x->xf->fspec); while (*p != ':' && *p != '\\' && p >= k->x->xf->fspec) p--; k_build_pkt_(k, ++p, 'F'); for (i=0; i < k->max_retry && !DONE; i++) { ret = k_send_pkt_(k); switch (ret) { case EOF: case 'N': continue; case USER_CANCELLED: case 'A': case 'E': DONE = TRUE; break; case 'Y': if (k->num != k->paknum % 64) continue; else { if (k->p_user != NULL) (*k->p_user)(k->port, K_FHEAD | 0X1000, 0L); ret = 'D'; k->paknum++; DONE = TRUE; } } } if (i == k->max_retry) ret = EOF; return (ret); } /* K_ENCODE_ -- Get data from the XFER send buffer and encode it as a Kermit data pkt. ready for transmission. Return value: len -- The length of the packet (after encoding); */ short k_encode_(KERMIT_PARMS *k, XFER *x, char *pb) { WORD i; short temp, ch = EOF, len = 0; /* EACH ITERATION OF LOOP ENCODES ONE CHARACTER AND ADDS IT TO k->txbuf */ do { temp = (ch == EOF) ? c_fgetc_(x) : ch; /* read from file */ ch = EOF; /* AT END-OF-FILE WE EXIT HERE */ if (temp == EOF) break; len++; /* DO RUN-LENGTH ENCODING */ if (k->rept) { i = 1; while (c_fpeekc_(x) == temp && i < 94) { ch = c_fgetc_(x); i++; } if (i > 2) { *pb++ = (char) k->rept; /* add the repeat prefix */ *pb++ = (char) tochar(i); /* add the (encoded) count */ ch = EOF; len += i - 1; } } /* EIGHTH-BIT PREFIXING */ if (k->qbin) { /* IF HIGH BIT QUOTING IS IN EFFECT -- DO IT */ if (temp & 0X80) { *pb++ = (char) k->qbin; /* high bit prefix */ temp &= 0X7F; } if (temp == (short) k->qbin) { /* is "temp" the high bit prefix */ *pb++ = (char) k->qctl; /* prefix with the ctrl prefix */ } } /* SEE IF IT IS A CONTROL CHARACTER */ if (iscntrl(temp & 0X7F)) { *pb++ = (char) k->qctl; /* prefix it */ *pb++ = (char) ctl(temp); /* do it */ } else { if ((temp & 0X7F) == (short) k->qctl) *pb++ = (char) k->qctl; else if (k->rept && (temp & 0X7F) == (short) k->rept) *pb++ = (char) k->qctl; *pb++ = (char) temp; } } while (pb < k->txbuf + k->maxl - 5 || ch != EOF); return (len); } /* K_SD_ -- State SD. Send the data packets. */ short k_sd_(KERMIT_PARMS *k) { short count, ret, DONE, FINISHED; WORD i; unsigned long tlen = 0; FINISHED = FALSE; if (k->p_user != NULL) (*k->p_user)(k->port, K_DATA | 0X1000, 0L); do { DONE = FALSE; /* BUILD A DATA PACKET DATA FIELD */ memset(k->txbuf, 0X0, k->iobufsize); /* KERMITIZE THE DATA TO SEND */ count = k_encode_(k, k->x, &k->txbuf[4]); if (!count) { ret = 'Z'; /* FINISHED OK */ FINISHED = TRUE; } else { k_build_pkt_(k, NULL, 'D'); for (i=0; i < k->max_retry && !DONE; i++) { ret = k_send_pkt_(k); switch (ret) { case EOF: case 'N': continue; case USER_CANCELLED: strncpy(k_error_msg, "User Intervention", 80); case 'A': case 'E': DONE = FINISHED = TRUE; break; case 'Y': if (k->num != k->paknum % 64) continue; else { if (k->p_user != NULL) (*k->p_user)(k->port, XFER_POSITION, tlen+=count); k->paknum++; DONE = TRUE; } } } if (i == k->max_retry) { ret = EOF; FINISHED = TRUE; } } } while (!FINISHED); return (ret); } /* K_Z_ -- State SZ. Send the end-of-file (Z) packet. */ short k_z_(KERMIT_PARMS *k) { short ret; if (k->p_user != NULL) (*k->p_user)(k->port, K_EOF | 0X1000, (DWORD) k->paknum); k_build_pkt_(k, "", 'Z'); ret = k_send_pkt_(k); if (ret == EOF) ret = 'A'; else { k->paknum++; ret = 'F'; } return (ret); } /* K_SB_ -- State SB. Send the Break (B) packet (indicates end of transmission). */ short k_sb_(KERMIT_PARMS *k) { short ret; if (k->p_user != NULL) (*k->p_user)(k->port, K_BREAK | 0X1000, (DWORD) k->paknum); k_build_pkt_(k, "", 'B'); ret = k_send_pkt_(k); if (ret == EOF) ret = 'A'; else k->paknum++; return (ret); } /* K_SE_ -- State SE. Send the ERROR (E) packet (indicates fatal error). Parameter: KERMIT_PARMS *k -- a pointer to the Kermit structure; const char *s -- the string to send in the ERROR pkt. data field. */ short k_se_(KERMIT_PARMS *k, const char *s) { short ret; if (k->p_user != NULL) (*k->p_user)(k->port, K_ERROR | 0X1000, (DWORD) k->paknum); k_build_pkt_(k, (char *) s, 'E'); ret = k_send_pkt_(k); if (ret == EOF) ret = 'A'; else k->paknum++; return (ret); } /* K_SEND_ -- This is the main state table for a Kermit 'send' transaction. Return value: Responses obtained from fload(): -1 -- no files queued to send; -2 -- unable to open file; NO_RAM -- insufficient memory for file fransfer buffer (less than MIN_BUFFER_SIZE) -4 -- file length does not match number of bytes read (short count); 'A' -- aborted; 'B' -- success; 'C' -- 0 -- unknown state requested; */ short k_send_(KERMIT_PARMS *k, XFER *x, WORD len, short (CDECL_ *progress)(struct comm_port *, short, unsigned long)) { short state = 'S', DONE = FALSE; if (k == NULL) return (EOF); if (progress != NULL) k->p_user = progress; memset(k_error_msg, 0X0, PATHLEN); /* LET THE RECEIVER CHOOSE 8TH-BIT PREFIXING */ if (k->parity == PARITY_NONE) k->mqbin = 'Y'; (*k->c_rxflush)(k->port, 0); do { switch (state) { case 'A': if (x->fh) cct_file_close_(x->fh); funqueue(x, x->fspec); ffreebuf_(x); DONE = TRUE; break; case 'B': /* BREAK - end of transaction */ k_sb_(k); DONE = TRUE; break; case 'C': DONE = TRUE; break; case 'D': /* FILE DATA - goes to 'Z' if OK */ state = k_sd_(k); cct_file_close_(x->fh); break; case 'E': /* ERROR - send error packet */ k_se_(k, k_error_msg); state = 'A'; break; case 'F': /* FILE INFO - goes to 'D' if OK */ if (fmakebuf_(x, len) != 0) state = 'B'; else state = k_sf_(k); break; case 'S': /* SEND-INIT - goes to 'F' if OK */ state = k_s_(k); break; case 'Z': /* END OF FILE - goes to 'F' if OK */ state = k_z_(k); funqueue(x, x->fspec); ffreebuf_(x); if (!x->num_files && state == 'F') state = 'B'; break; default: state = 0; DONE = TRUE; break; } } while (!DONE); return (state); } /* K_DECODE_ -- Decodes an encoded Kermit packet data field and stores the decoded form in the XFER receive buffer. */ short k_decode_(KERMIT_PARMS *k, WORD len) { WORD i, j, l, count; short f_qbin, f_qctl, ch; /* EACH ITERATION OF THIS LOOP TRANSFORMS ONE CHARACTER AND STORES IT */ for (i=0, l=0; i < len;) { f_qbin = f_qctl = FALSE; /* reset flags */ count = 1; ch = k->rxbuf[i++]; /* REPEAT CHARACTER PROCESSING */ if (k->rept && (WORD) ch == k->rept) { count = unchar(k->rxbuf[i++]); ch = k->rxbuf[i++]; } /* HIGH BIT FLAG */ if (k->qbin && (WORD) ch == k->qbin) { f_qbin = TRUE; ch = k->rxbuf[i++]; } /* CONTROL CHARACTER FLAG */ if (k->qctl && (WORD) ch == k->qctl) { f_qctl = TRUE; ch = k->rxbuf[i++]; } /* SPECIAL CASES TO DEAL WITH POTENTIAL CONTROL CHARACTERS */ /* THESE LINES ALLOW US TO TREAT PREFIX CHARS AS LITERALS */ if (f_qctl && ((WORD)(ch & 0X7F) == k->qctl)) f_qctl = FALSE; if (f_qctl && ((WORD)(ch & 0X7F) == k->qbin)) f_qctl = FALSE; if (f_qctl && ((WORD)(ch & 0X7F) == k->rept)) f_qctl = FALSE; /* FINALLY, WE CAN DECODE THE CHARACTER */ if (f_qctl) ch = ctl(ch); /* restore ctl chars */ if (f_qbin) ch |= 0X80; /* restore high bit */ for (j=0; j < count; j++) c_fputc_(k->port, ch); l += count; } return (l); } /* K_RD_ -- Receive data packets. */ short k_rd_(KERMIT_PARMS *k) { short ret; WORD i, nlen; short DONE = FALSE; DWORD len = 0; for (i=0; i < k->max_retry && !DONE; i++) { ret = k_receive_pkt_(k); if (ret == EOF) k_nak_(k); /* invalid packet -- ABORT */ /* valid packet format -- what type is it? */ /* PACKET NUMBER SAME AS PREVIOUS -- ACK AND TRY AGAIN */ else if (ret != USER_CANCELLED && k->num == ((k->paknum - 1) % 64)) { k->paknum--; k_ack_(k); k->paknum++; } else switch (ret) { case 'A': /* ABORT packet received */ ret = 'A'; DONE = TRUE; if (k->p_user != NULL) (*k->p_user)(k->port, K_ABORT | 0X1000, (DWORD) k->paknum); break; case 'D': /* good data */ k_ack_(k); nlen = k_decode_(k, k->dlen); /* decode data packet, write to disk */ k->paknum++; if (k->p_user != NULL) (*k->p_user)(k->port, XFER_POSITION, len+=nlen); i = 0; ret = 'D'; break; case 'E': /* ERROR packet received */ k_ack_(k); ret = 'A'; DONE = TRUE; if (k->p_user != NULL) (*k->p_user)(k->port, K_ERROR | 0X1000, (DWORD) k->paknum); break; case 'Z': /* EOF packet received */ k_ack_(k); k->paknum++; if ((*k->c_fwrite_)(k->port, k->x) == DISK_FULL) { global_com_error = DISK_FULL; ret = 'A'; cct_file_close_(k->x->fh); } else if (cct_file_close_(k->x->fh) < 0) { global_com_error = EOF; ret = 'A'; } else ret = 'F'; DONE = TRUE; break; case USER_CANCELLED: /* abort key pressed */ k_se_(k, "User intervention"); k->paknum++; if ((*k->c_fwrite_)(k->port, k->x) == DISK_FULL) { global_com_error = DISK_FULL; cct_file_close_(k->x->fh); } else if (cct_file_close_(k->x->fh) < 0) global_com_error = EOF; ret = USER_CANCELLED; DONE = TRUE; break; default: ret = 'A'; DONE = TRUE; } } return (ret); } /* K_RF_ -- In state RF. Wait for FILE HEADER packet. */ short k_rf_(KERMIT_PARMS *k) { short ret, DONE = FALSE; WORD i = 0; for (i=0; i < k->max_retry && !DONE; i++) { ret = k_receive_pkt_(k); if (ret < 0) k_nak_(k); /* invalid packet -- NAK */ /* valid packet format -- what type is it? */ else switch (ret) { case 'B': /* BREAK pkt. => last file received */ k_ack_(k); DONE = TRUE; ret = 'A'; if (k->p_user != NULL) (*k->p_user)(k->port, K_BREAK | 0X1000, (DWORD) k->paknum); break; case 'E': /* ERROR pkt. => abort */ k_ack_(k); DONE = TRUE; ret = 'A'; if (k->p_user != NULL) (*k->p_user)(k->port, K_ERROR | 0X1000, (DWORD) k->paknum); break; case 'F': /* File header received -- open file */ if (k->p_user != NULL) (*k->p_user)(k->port, K_FHEAD | 0X1000, (DWORD) k->paknum); if (cct_file_open_(k->rxbuf, O_CREAT|O_RDWR|O_BINARY) == 0) ret = 'A'; else { /* SAVE THE FILE NAME FOR THE PROGRESS FUNCTION */ strncpy(fname, k->rxbuf, 12); fname[12] = '\0'; fsize = (long) EOF; if (k->p_user != NULL) (*k->p_user)(k->port, FILE_INFO_RECEIVED, 0L); ret = 'D'; k_ack_(k); k->paknum++; } DONE = TRUE; break; case 'S': /* ACK with local parms */ (*k->c_puts)(k->port, k->txbuf); /* ACK with local parms */ i = 0; break; case 'Z': k_ack_(k); /* ACK a lost ACK */ DONE = TRUE; ret = 'F'; } } return (ret); } /* K_R_ -- In state R. Wait to receive a send_init packet from other system. */ short k_r_(KERMIT_PARMS *k) { short ret; WORD i; short DONE = FALSE; for (i=0; i < k->max_retry; i++) { ret = k_receive_pkt_(k); if (ret == EOF) continue; if (ret == USER_CANCELLED) { DONE = TRUE; break; } else break; } if (i < k->max_retry && !DONE) { if (k->ptype == 'S') { if (k->p_user != NULL) (*k->p_user)(k->port, K_SINIT | 0X1000, (DWORD) k->paknum); k_neg_parms_(k); k_build_init_(k); k_build_pkt_(k, NULL, 'Y'); (*k->c_puts)(k->port, k->txbuf); /* ACK with local parms */ k->paknum++; ret = 'F'; } } else ret = 'A'; return (ret); } /* K_RCV_ -- A Kermit 'receive' transaction. */ short k_rcv_(KERMIT_PARMS *k) { short state = 'R', DONE = FALSE; k->mqbin = (k->parity == PARITY_NONE) ? 'Y' : '&'; if (get_tx_xlat(k->port, FLOWCTL) & XONXOFF) c_putc(k->port, XON); k->p_user = k->p_user; do { switch (state) { case 'A': DONE = TRUE; break; case 'C': DONE = TRUE; break; case 'D': /* goes to 'F' if successful */ state = k_rd_(k); break; case 'F': /* goes to 'D' or 'A' (last file) if successful */ state = k_rf_(k); break; /* START HERE (do not pass go, do not collect $200... */ case 'R': /* goes to 'F' if successful */ state = k_r_(k); break; default: state = EOF; DONE = TRUE; break; } if (is_key(((COMM_PORT *) k->port)->abort_key)) { k_se_(k, "User intervention"); state = USER_CANCELLED; DONE = TRUE; } } while (!DONE); return (state); }