Metropoli BBS
VIEWER: kermit.c MODE: TEXT (ASCII)
/*
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);

}
[ RETURN TO DIRECTORY ]