/*************************************************************
 * vt100 terminal emulator - XMODEM protocol support
 *
 *	v2.7 870825 ACS - Make multi_xfer() non-recursive; on non-ESC in
 *			  readchar() re-do the main window's title. 
 *	v2.6 870227 DBW - bug fixes for all the stuff in v2.5
 *	v2.5 870214 DBW - more additions (see readme file)
 *	v2.4 861214 DBW - lots of fixes/additions (see readme file)
 *	v2.3 861101 DBW - minor bug fixes
 *	v2.2 861012 DBW - more of the same
 *	v2.1 860915 DBW - new features (see README)
 *	     860901 ACS - Added Parity and Word Length and support code
 *	     860823 DBW - Integrated and rewrote lots of code
 *	     860815 Steve Drew: readchar inproved with real timeouts
 *	v2.0 860809 DBW - Major rewrite
 *	v1.1 860720 DBW	- Switches, 80 cols, colors, bug fixes
 *	v1.0 860712 DBW	- First version released
 *
 *************************************************************/

#include "vt100.h"

int enablexon = TRUE;

extern struct IntuiText MyTitle;

static unsigned long parity_settings[4] = {
    0x96696996,
    0x69969669,
    0x69969669,
    0x96696996 };

/************************************************************
* Send a string (using sendchar below)
************************************************************/

void sendstring(s)
char *s;
    {
    char c;

    while ((c = *s++) != '\000') sendchar(c);
    }

/**************************************************************/
/* send char and read char functions for the xmodem function */
/************************************************************/
void sendchar(ch)
int ch;
    {
    int doxon,i,j,k;

    doxon = enablexon;
    if (doxon) No_XON();
    switch (p_parity) {
	case 0:	/* no parity */
	rs_out[0] = ch & 0xFF;
	break;

	case 1: /* mark */
	rs_out[0] = (ch & 0x7F) | 0x80;
	break;

	case 2: /* space */
	rs_out[0] = ch & 0x7F;
	break;

	case 3:	/* even */
	case 4: /* odd  */
	i     = (ch >> 5) & 0x3;
	j     = ch & 0x1F;
	k     = ((parity_settings[i] >> j) & 0x1) << 7;
	if (p_parity == 3)			/* even parity */
	    rs_out[0] = (ch & 0x7F) | k;
	else					/* odd parity */
	    rs_out[0] = (ch & 0x7F) | (k ^ 0x80);
	}
    do {
	DoIO(Write_Request);
    } while(Write_Request->IOSer.io_Error != 0);
    if (doxon) Do_XON();
    }

/* send a break to the host */
void sendbreak() {
    AbortIO(Read_Request);
    Wait(1L << Read_Request->IOSer.io_Message.mn_ReplyPort->mp_SigBit);
    WaitIO(Read_Request);
    Read_Request->IOSer.io_Command = SDCMD_BREAK;
    DoIO(Read_Request);
    Read_Request->IOSer.io_Command = CMD_READ;
    SendIO(Read_Request);
    }

int readchar()
    {
    int rd,ch;
    ULONG class, waitmask;
    USHORT code;

    Timer.tr_time.tv_secs = ttime;
    Timer.tr_time.tv_micro = 0;
    SendIO((char *) &Timer.tr_node);

    rd = FALSE;
    waitmask = ((1L << Read_Request->IOSer.io_Message.mn_ReplyPort->mp_SigBit) |
		( 1L << mywindow->UserPort->mp_SigBit) |
		( 1L << Timer_Port->mp_SigBit));
    if(reqwinup)
	waitmask |= (1L << reqwindow->UserPort->mp_SigBit);
    while (rd == FALSE) {
	Wait(waitmask);
	if (CheckIO(Read_Request)) {
	    WaitIO(Read_Request);
	    ch=rs_in[0];
	    rd = TRUE;
	    SendIO(Read_Request);
	}
	if(reqwinup &&
	  (NewMessage=(struct IntuiMessage *)GetMsg(reqwindow->UserPort))) {
	    class = NewMessage->Class;
	    ReplyMsg(NewMessage);
	    if(class == NEWSIZE)
		ReqNewSize(reqwindow->Height, reqwindow->Width);
	}
	if (NewMessage=(struct IntuiMessage *)GetMsg(mywindow->UserPort)) {
	   class = NewMessage->Class;
	   code = NewMessage->Code;
	   ReplyMsg(NewMessage);
	   if ((class == RAWKEY) && (code == 69)) {
		 AbortIO((char *) &Timer);
		 Wait (1L << Timer_Port->mp_SigBit);
		 WaitIO((char *) &Timer.tr_node);
		 InfoMsg1Line("ERROR: User aborted transfer");
		 timeout = USERABORT;
		 return('\0');
	   }
	   PrintIText(mywindow->RPort, &MyTitle, 0L, 0L);
	   continue;
	}

	if (rd == FALSE && CheckIO(&Timer)) {
	    InfoMsg1Line("ERROR: Timeout waiting for character");
	    timeout = TIMEOUT;
	    return('\0');
	}
    } /* end while */
    AbortIO((char *) &Timer);
    Wait (1L << Timer_Port->mp_SigBit);
    WaitIO((char *) &Timer.tr_node);
    timeout = GOODREAD;
    return(ch & (p_parity == 0 ? 0xFF : 0x7F));
    }

void No_XON() {

    /* turn off XON/XOFF processing */
    enablexon = FALSE;
    Write_Request->io_SerFlags |= SERF_XDISABLED;
    Write_Request->IOSer.io_Command = SDCMD_SETPARAMS;
    DoIO(Write_Request);
    Write_Request->IOSer.io_Command = CMD_WRITE;
    }

void Do_XON() {
    /* turn on XON/XOFF processing */
    enablexon = TRUE;
    Write_Request->io_SerFlags &= ~SERF_XDISABLED;
    Write_Request->IOSer.io_Command = SDCMD_SETPARAMS;
    DoIO(Write_Request);
    Write_Request->IOSer.io_Command = CMD_WRITE;
    }

/**************************************/
/* xmodem send and recieve functions */
/************************************/

int XMODEM_Read_File(file)
char *file;
    {
    int firstchar, sectnum, sectcurr, sectcomp, errors, errorflag;
    unsigned int checksum, j, bufptr;
    char scrstr2[40];
    bytes_xferred = 0L;
    ttime = TTIME_SHORT;

    if ((fd = creat(file, 0)) < 0)
	{
	InfoMsg2Line("XMODEM Can't Open File:",file);
	return FALSE;
	}
    else
    InfoMsg1Line("XMODEM Receive, <esc> in VT100 window to abort");

    sectnum = errors = bufptr = 0;
    sendchar(NAK);
    firstchar = 0;
    No_XON();
    while (firstchar != EOT && errors != ERRORMAX)
	{
	errorflag = FALSE;

	do {                                    /* get sync char */
	    firstchar = readchar();
	    if (timeout != GOODREAD) {
		if (timeout == USERABORT || errors++ == ERRORMAX)
		    Do_XON();
		    return FALSE;
		}
	    } while (firstchar != SOH && firstchar != EOT);

	if  (firstchar == SOH)
	    {
	    sprintf(scrstr2,"Getting Block %4d...",sectnum);
	    InfoMsgNoScroll(scrstr2);
	    sectcurr = readchar();
	    if (timeout != GOODREAD) { Do_XON(); return FALSE; }
	    sectcomp = readchar();
	    if (timeout != GOODREAD) { Do_XON(); return FALSE; }
	    if ((sectcurr + sectcomp) == 255)
		{
		if (sectcurr == ((sectnum + 1) & 0xff))
		    {
		    checksum = 0;
		    for (j = bufptr; j < (bufptr + SECSIZ); j++)
			{
			bufr[j] = readchar();
			if (timeout != GOODREAD) { Do_XON(); return FALSE; }
			checksum = (checksum + bufr[j]) & 0xff;
			}
		    if (checksum == readchar() && timeout == GOODREAD)
			{
			errors = 0;
			sprintf(scrstr2,"Block %4d verified",sectnum);
			sectnum++;
			bufptr += SECSIZ;
			bytes_xferred += SECSIZ;
			InfoMsgNoScroll(scrstr2);
			if (bufptr == BufSize)
			    {
			    if (write(fd, bufr, BufSize-128) == EOF)
				{
				InfoMsg1Line("XMODEM: Error Writing File");
				Do_XON();
				return FALSE;
				}
			    bufptr = 128;
			    for (j = 0; j < 128; j++)
				bufr[j] = bufr[(BufSize-128)+j];
			    }
			sendchar(ACK);
			}
		    else
			{
			errorflag = TRUE;
			if (timeout == USERABORT) { Do_XON(); return FALSE; }
			}
		    }
		else
		    {
		    /* got a duplicate sector */
		    if (sectcurr == (sectnum & 0xff))
			{
			/* wait until we time out for 5secs */
			do {
			    readchar();
			    } while (timeout == GOODREAD);
			if (timeout == USERABORT) {
			    Do_XON();
			    return FALSE;
			    }
			InfoMsg1Line("XMODEM: Received Duplicate Sector");
			sendchar(ACK);
			}
		    else errorflag = TRUE;
		    }
		}
	    else errorflag = TRUE;
	    }
	if (errorflag == TRUE)
	    {
	    errors++;
	    InfoMsg1Line("XMODEM: Error");
	    sendchar(NAK);
	    }
	}        /* end while */
    if ((firstchar == EOT) && (errors < ERRORMAX))
	{
	sendchar(ACK);
#ifdef BUGFIXES
	/* use firstchar to remember the last char for chopping */
	if (bufptr && ((firstchar = bufr[--bufptr]) == 0 || firstchar == 0x1A))
	    {
	    while (bufptr && bufr[--bufptr] == firstchar)
		;
	    if (bufptr || bufr[0] != firstchar)	 /* check for null buffer */
		write(fd, bufr, ++bufptr);
	    }
#else
	while (bufptr > 0 && (bufr[--bufptr] == 0x00 ||
			      bufr[bufptr]   == 0x1A)) ;
	write(fd, bufr, ++bufptr);
#endif
	close(fd);
	Do_XON();
	ScrollInfoMsg(1);
	return TRUE;
	}
    Do_XON();
    return FALSE;
    }

int XMODEM_Send_File(file)
char *file;
    {
    int sectnum, bytes_to_send, size, attempts, c;
    unsigned checksum, j, bufptr;
    char scrstr2[40];
    bytes_xferred = 0;
    ttime = TTIME_LONG;

    if ((fd = open(file, 0)) < 0) {
	InfoMsg1Line("XMODEM: Cannot Open Send File");
	return FALSE;
	}
    else
    InfoMsg1Line("XMODEM Send, <esc> from VT100 window to abort");
    attempts = 0;
    sectnum = 1;
    No_XON();
    /* wait for sync char */
    j=1;
    while (((c = readchar()) != NAK) && (j++ < ERRORMAX))
	if (timeout == USERABORT) { Do_XON(); return(FALSE); }
    if (j >= (ERRORMAX))
	{
	InfoMsg1Line("XMODEM: Receiver not sending NAKs");
	Do_XON();
	return FALSE;
	}

    while ((bytes_to_send = read(fd, bufr, BufSize)) &&
	    attempts != RETRYMAX)
	{
	if (bytes_to_send == EOF)
	    {
	    InfoMsg1Line("XMODEM: Error Reading File");
	    Do_XON();
	    return FALSE;
	    }

	bufptr = 0;
	while (bytes_to_send > 0 && attempts != RETRYMAX)
	    {
	    attempts = 0;
	    sprintf(scrstr2,"Sending block %4d",sectnum);
#ifdef BUGFIXES
	    size = SECSIZ <= bytes_to_send ? SECSIZ : bytes_to_send;
	    bytes_to_send -= size;
#endif
	    do {
		InfoMsgNoScroll(scrstr2);
		sendchar(SOH);
		sendchar(sectnum);
		sendchar(~sectnum);
		checksum = 0;
#ifdef BUGFIXES
		for (j = bufptr; j < bufptr + size; j++)
		    {
		    sendchar(bufr[j]);		/* send buffer data */
#else
		size = SECSIZ <= bytes_to_send ? SECSIZ : bytes_to_send;
		bytes_to_send -= size;
		for (j = bufptr; j < (bufptr + SECSIZ); j++)
		if (j < (bufptr + size)) {
		    sendchar(bufr[j]);
#endif
		    checksum += bufr[j];
		    }
#ifdef BUGFIXES
		if( size < SECSIZ )		/* check if we need to pad */
		    {
		    c = bufr[j-1] ? 0 : 0x1A;	/* choose correct padding */
		    j = SECSIZ - size;
		    checksum += j * c;
		    while ( j-- )
			sendchar(c);		/* send padding */
		    }
#else
		else sendchar(0);
#endif
		sendchar(checksum);
		attempts++;
		c = readchar();
		if (timeout == USERABORT) {
		    InfoMsg1Line("XMODEM: ABORTED");
		    Do_XON();
		    return FALSE;
		    }
		} while ((c != ACK) && (attempts != RETRYMAX));
	    bufptr += size;
	    bytes_xferred += size;
	    sprintf(scrstr2,"Sent    block %4d",sectnum);
	    InfoMsgNoScroll(scrstr2);
	    sectnum++;
	    }
	}
    close(fd);
    if (attempts == RETRYMAX)
	{
	InfoMsg1Line("XMODEM: No Acknowledgment, ABORTING");
	Do_XON();
	return FALSE;
	}
    else
	{
	attempts = 0;
	do {
	    sendchar(EOT);
	    attempts++;
	    } while ((readchar() != ACK) &&
		     (attempts != RETRYMAX) &&
		     (timeout != USERABORT)) ;
	if (attempts == RETRYMAX)
	    InfoMsg1Line("XMODEM: No end of file");
	}
    Do_XON();
    ScrollInfoMsg(1);
    return TRUE;
    }

/* allow for multi file xfers separated by commas under
    kermit and XMODEM */

void multi_xfer(name,mode,do_send)
char *name;
int (*mode)();
int do_send;
    {
    int done = 0;
    int status;
    char *p, *name_start;

    timeout = USERABORT - 1;
    for(p=name_start=name; !done && timeout != USERABORT; name_start=++p)
	{
	if (*(name_start) == '$' && *(name_start+1) == '\0') {
	    saybye();
	    return;
	    }
	while(*p == ' ') p++;
	while(*p && *p != ',' && *p != ' ') p++;
	if (*p == '\0') {
	    done = TRUE;
	    multi = 0;
	}
	else
	    multi = 1;
	*p = '\0';

	status = ((*mode)(name_start, multi));
	if (status == FALSE) close(fd);
	}
    server = 0;
    multi = 0;
    }

