/*
 * Protocol module for Windows Kermit
 * 
 * Copyright (c) 1990, 1991 by
 * William S. Hall
 * 3665 Benton Street  #66
 * Santa Clara, CA 95051
 *
 * This module must be preprocessed by wart
 */

#define NOCOMM
#define NOKANJI
#define NOSOUND
#define NOATOM
#include <windows.h>
#include <stdio.h>
#include <string.h>
#ifdef COLUMBIA
#include "wkkerm.h"
#else
#include "wnkerm.h"
#endif

/* The state machine returns 0 to show completion or 1 if more input
 * is expected.  Currently, these return values are not used.
 */
#define RESUME return(0)
#define CONTINUE return(1)

/* local function prototypes */
static int near input(void);

static char vcmd;
static int vstate;

/* protocol states */
%states get rsfile rsdata ipkt rgen
%states ssinit ssfile ssdata sseof sseot

/* Protocol description.
 */
%%

/* Begin file send
 */
s {		
    krm_tinit();			/* initialize */
    Kermit.start = 'w';			/* switch to wait */
}

/* Begin get
 */
r {
    krm_tinit();
    vstate = get;
    vcmd = 0;
    krm_sinit('I');
    BEGIN ipkt;
}

/* Begin host command
 */
c {
    krm_tinit();
    vstate = rgen;
    vcmd = 'C';
    krm_sinit('I');
    BEGIN ipkt;
}

/* Begin generic command
 */
g {
    krm_tinit();
    vstate = rgen;
    vcmd = 'G';
    krm_sinit('I');
    BEGIN ipkt;
}

/* Wait to send first packet
 */
w {
    if (Kermit.delay) {			/* timer will reset this parameter */
	Kermit.start = 'w';		/* wait to send first packet */
        CONTINUE;
    } else {
        if (krm_sinit('S') < 0) {		/* transmit sendinit packet */
            krm_err(IDS_KRM_SENDINIT_ERROR);	/* error; inform remote */
            RESUME;				/* quit */
        }	
	krmFlushQue();		/* flush any pending input in local buffer */
        BEGIN ssinit;		/* prepare to open file and send file name */
    }	
}

/* Begin receive file
 */
v {
    krm_tinit();			/* initialize */
    BEGIN get;
}

<ipkt>Y {
    krm_spar(krm_rcvpkt.data, krm_rcvpkt.len);	/* read send-init data */
    if (vcmd) {
        if (krm_sendcmd(vcmd, Kermit.pFilelist) < 0) {
            krm_err(IDS_KRM_SENDCMD_ERROR);	/* error; inform remote */
            RESUME;				/* quit */
        }	
	vcmd = 0;
    }
    if (vstate == get)
        if (krm_srinit(Kermit.pFilelist) < 0) {
	    krm_err(IDS_KRM_SENDR_ERROR);
	    RESUME;
	}
    BEGIN vstate;
}

<ipkt>E {
    if (vcmd)
        if (krm_sendcmd(vcmd, Kermit.pFilelist) < 0) {
            krm_err(IDS_KRM_SENDCMD_ERROR);	/* error; inform remote */
            RESUME;				/* quit */
        }	
    vcmd = 0;
    if (vstate == get)
        if (krm_srinit(Kermit.pFilelist) < 0) {
	    krm_err(IDS_KRM_SENDR_ERROR);
	    RESUME;
	}
    BEGIN vstate;
}
        
/* got ACK of sendinit packet
 */
<ssinit>Y {
    krm_spar(krm_rcvpkt.data, krm_rcvpkt.len);	/* set init data */
    Kermit.bctu = Kermit.bctr;			/* select block check type */
    Kermit.pFile = krm_getnextfile(TRUE);	/* get file to send */
    if (krm_sfile() < 0) {			/* send file name packet */
        krm_err(IDS_KRM_SENDFILENAME_ERROR);	/* error; inform remote */
        RESUME;					/* quit */
    }
    BEGIN ssfile;		       /* prepare to send first data packet */
}

/* Got ACK of file name packet
 */
<ssfile>Y {
    int x;
    krm_savename();			/* post the remote's name for file */
    if ((x = krm_sdata()) == 0) {	/* no data in file */
        if (krm_seof("") < 0) {		/* send end of file */
           krm_err(IDS_KRM_SENDEOF_ERROR);   /* error; inform remote */
           RESUME;			/* quit */
        }
        BEGIN sseof;
    }
    else if (x < 0) {			/* error in sending file */
        krm_rclose(FALSE);		/* close the file */
        krm_err(IDS_KRM_SENDDATA_ERROR); /* post error to remote and quit */
        RESUME;
    }
    else
	BEGIN ssdata;			/* prepare to receive data */
}

/* Got ACK to data packet
 */
<ssdata>Y {				
    krm_checkcnx();			/* check if transaction cancelled */
    if (Kermit.abort) {
	if (Kermit.abort == KRM_FILEABORT)  /* cancel file send */
	    Kermit.abort = 0;
	if (krm_seof("D") < 0) {	    /* tell remote about cancellation */
	   krm_err(IDS_KRM_SENDEOF_ERROR);  /* error; inform remote */
	   RESUME;			    /* quit */
	}
        BEGIN sseof;			/* prepare to send end of file */
    }
    else {
        int x;
	if ((x = krm_sdata()) == 0) {		/* no more data */
	    if (krm_seof("") < 0) {		/* send end of file */
	       krm_err(IDS_KRM_SENDEOF_ERROR);  /* error; inform remote */
	       RESUME;				/* quit */
	    }
            BEGIN sseof;		/* prepare to send end of file */
        }
	else if (x < 0) {			/* error */
	    krm_rclose(FALSE);			/* close the file */
	    krm_err(IDS_KRM_SENDDATA_ERROR);	/* inform remote */
	    RESUME;				/* quit */
        }
    }
}

/* Got ACK to end of file packet
 */
<sseof>Y {
    if (Kermit.pFile = krm_getnextfile(FALSE)) {  /* any more files? */
        if (krm_sfile() < 0) {			  /* send next file name */
            krm_err(IDS_KRM_SENDFILENAME_ERROR);  /* error, inform remote */
            RESUME;				  /* quit */
        }
        BEGIN ssfile;				/* prepare for next file */
    }	
    else {					/* no more files to send */
	if (krm_seot() < 0) {			/* send break */
	    krm_err(IDS_KRM_SENDEOT_ERROR);	/* error, inform remote */
	    RESUME;				/* quit */
	}
	BEGIN sseot;				/* prepare to exit file send */
    }
}

/* Got ACK to end of transmission packet
 */
<sseot>Y {
    krm_tend(IDS_KRM_TRANSACTION_DONE);		/* clean up */
    RESUME;					/* quit */
}

/* resend of previous I packet ACK, 
same sequence number */
<get>Y {

    krm_srinit(Kermit.pFilelist);

}

<rgen>Y {
    if (krm_rcvpkt.len > 0)
	krm_tend(KRM_DATA_PACKET);
    else
        krm_tend(IDS_KRM_TRANSACTION_DONE);	/* clean up */
    RESUME;					/* quit */
}

/* Got sendinit packet
 */
<rgen,get>S {
    BYTE data[KRM_MAXDATALEN + 1];
    krm_spar(krm_rcvpkt.data, krm_rcvpkt.len);	/* read send-init data */
    krm_ack(krm_rpar(data), data);		/* return init data in ACK */
    Kermit.bctu = Kermit.bctr;			/* set block check type */
    BEGIN rsfile;			        /* prepare for file name */
}

/* Got file name packet
 */
<rgen,rsfile>F {
    if (krm_rcvfil()) {				/* try to open file */
	char buf[KRM_MAXDATALEN + 1];			
	int inlen, outlen;
	inlen = strlen(Kermit.pFile);		/* return our name to remote */
	outlen = krm_encode(buf, Kermit.pFile, sizeof(buf) - 1, &inlen);
        krm_ack(outlen, buf);			/* send the name in ACK */
        BEGIN rsdata;				/* prepare to receive data */
    }
    else {
	krm_err(IDS_KRM_FILE_OPEN_ERROR);	/* error, inform remote */
	RESUME;					/* quit */
    }
}

<rgen,rsfile>X {
    if (krm_opent()) {
	krm_ack(0, "");
	BEGIN rsdata;
    }
    else {
	krm_err(IDS_KRM_TERMINAL_OPEN_ERROR);
	RESUME;
    }
}

/* Got data packet
 */
<rsdata>D {
    if (krm_rcvdata()) {
	switch(Kermit.abort) {			/* see if abort flag is on */
	    case KRM_BATCHABORT:
		krm_ack(1, "Z");		/* cancel remaining files */
		Kermit.abort = 0;
		break;
	    case KRM_FILEABORT:
		krm_ack(1, "X");		/* cancel current file */
		Kermit.abort = 0;
		break;
	    default:
		krm_ack(0, "");				/* continue */
	}
    }
    else {
	krm_rclose(TRUE);			/* error in storing data */
	krm_err(IDS_KRM_FILE_WRITE_ERROR);	/* inform host */
	RESUME;					/* quit */
    }
}

/* Got end of file packet
 */
<rsdata>Z {		/* see if remote wants us to delete file */
    if (krm_rclose(krm_rcvpkt.len && 
	(krm_rcvpkt.data[0] == 'D') ? TRUE : FALSE)) {
        krm_ack(0, "");				/* acknowledge */
        BEGIN rsfile;				/* prepare for next file */
    }
    else {
	krm_err(IDS_KRM_FILE_CLOSE_ERROR);	/* error; inform remote */
	RESUME;					/* quit */
    }
}

/* Got end of transmission packet
 */
<rsfile>B {
    krm_ack(0, "");			/* acknowledge */
    krm_tend(IDS_KRM_TRANSACTION_DONE);	/* clean up */
    RESUME;				/* quit */
}

/* Packet is incomplete; return.
 */
$ {
    CONTINUE;			/* continue until packet is filled */
}

/* Got error packet
 */
E {
    krm_rclose(TRUE);		/* close any open files */
    krm_tend(KRM_DATA_PACKET);	/* clean up */
    RESUME;			/* quit */
}

/* Unrecognized packet
 */
. {
    krm_rclose(TRUE);			/* close any open files */
    krm_err(IDS_KRM_UNKNOWN_PACKET);	/* post error to remote */
    RESUME;				/* quit */
}

%%

/* input
 *
 * provide packet type to state machine
 */
static int near input()
{

    register int type;

    if (Kermit.start) {
	type = Kermit.start;
	Kermit.start = 0;
    }
    else {
	type = krm_rpack();		/* read input */
	if ((type != '$') && (type != 'E'))  {
	  /* packet is complete but is not an error packet */
	    if ((Kermit.seq != krm_rcvpkt.seq) || strchr("NQT", type)) {
	      /* something is wrong with packet */
	        if (Kermit.retries >= KermParams.RetryLimit) {
		  /* pretend we have an error packet */
		    krmCreatePseudoPacket('E', PS_DONE, IDS_KRM_TOOMANYRETRIES);
		    type = 'E';
	        }
	        else if ((type == 'N') && 
		 	 (krm_rcvpkt.seq == ((Kermit.seq + 1) & 63))) {
		  /* this is OK since NAK for next packet is ACK for previous */
		    type = 'Y';
		    Kermit.retries = 0;
	        }
	        else {
		  /* resend previous packet and try again */
		    Kermit.retries += 1;
		    Kermit.totalretries += 1;
		    krm_resend();
		    type = '$';
	        }
	    }
	    else	/* packet is OK; reset retry count */
	        Kermit.retries = 0;
	}
    }
    return (type);
}
