/*
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 <windows.h>
#endif
#include <string.h>
#include <comm.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <xfer.h>
#include <kermit.h>
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; i<len-3;i++) {
if ((ch = (*k->c_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);
}