/*************************************************************
 * vt100 terminal emulator - XMODEM protocol support
 *			:ts=8
 *
 *	v2.9 ACS - multi_xfer() no longer looks for $ -- kermit does,
 *		   readchar() now infers ttime of 100,000 micros if ttime == 0
 *		   (for newkermit); readchar() doesn't output a TIMED OUT msg
 *		   (because of newkermit); speed up sendstring().
 *	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 };

/* crctab calculated by Mark G. Mendel, Network Systems Corporation */
static unsigned short crctab[256] = {
    0x0000,  0x1021,  0x2042,  0x3063,  0x4084,  0x50a5,  0x60c6,  0x70e7,
    0x8108,  0x9129,  0xa14a,  0xb16b,  0xc18c,  0xd1ad,  0xe1ce,  0xf1ef,
    0x1231,  0x0210,  0x3273,  0x2252,  0x52b5,  0x4294,  0x72f7,  0x62d6,
    0x9339,  0x8318,  0xb37b,  0xa35a,  0xd3bd,  0xc39c,  0xf3ff,  0xe3de,
    0x2462,  0x3443,  0x0420,  0x1401,  0x64e6,  0x74c7,  0x44a4,  0x5485,
    0xa56a,  0xb54b,  0x8528,  0x9509,  0xe5ee,  0xf5cf,  0xc5ac,  0xd58d,
    0x3653,  0x2672,  0x1611,  0x0630,  0x76d7,  0x66f6,  0x5695,  0x46b4,
    0xb75b,  0xa77a,  0x9719,  0x8738,  0xf7df,  0xe7fe,  0xd79d,  0xc7bc,
    0x48c4,  0x58e5,  0x6886,  0x78a7,  0x0840,  0x1861,  0x2802,  0x3823,
    0xc9cc,  0xd9ed,  0xe98e,  0xf9af,  0x8948,  0x9969,  0xa90a,  0xb92b,
    0x5af5,  0x4ad4,  0x7ab7,  0x6a96,  0x1a71,  0x0a50,  0x3a33,  0x2a12,
    0xdbfd,  0xcbdc,  0xfbbf,  0xeb9e,  0x9b79,  0x8b58,  0xbb3b,  0xab1a,
    0x6ca6,  0x7c87,  0x4ce4,  0x5cc5,  0x2c22,  0x3c03,  0x0c60,  0x1c41,
    0xedae,  0xfd8f,  0xcdec,  0xddcd,  0xad2a,  0xbd0b,  0x8d68,  0x9d49,
    0x7e97,  0x6eb6,  0x5ed5,  0x4ef4,  0x3e13,  0x2e32,  0x1e51,  0x0e70,
    0xff9f,  0xefbe,  0xdfdd,  0xcffc,  0xbf1b,  0xaf3a,  0x9f59,  0x8f78,
    0x9188,  0x81a9,  0xb1ca,  0xa1eb,  0xd10c,  0xc12d,  0xf14e,  0xe16f,
    0x1080,  0x00a1,  0x30c2,  0x20e3,  0x5004,  0x4025,  0x7046,  0x6067,
    0x83b9,  0x9398,  0xa3fb,  0xb3da,  0xc33d,  0xd31c,  0xe37f,  0xf35e,
    0x02b1,  0x1290,  0x22f3,  0x32d2,  0x4235,  0x5214,  0x6277,  0x7256,
    0xb5ea,  0xa5cb,  0x95a8,  0x8589,  0xf56e,  0xe54f,  0xd52c,  0xc50d,
    0x34e2,  0x24c3,  0x14a0,  0x0481,  0x7466,  0x6447,  0x5424,  0x4405,
    0xa7db,  0xb7fa,  0x8799,  0x97b8,  0xe75f,  0xf77e,  0xc71d,  0xd73c,
    0x26d3,  0x36f2,  0x0691,  0x16b0,  0x6657,  0x7676,  0x4615,  0x5634,
    0xd94c,  0xc96d,  0xf90e,  0xe92f,  0x99c8,  0x89e9,  0xb98a,  0xa9ab,
    0x5844,  0x4865,  0x7806,  0x6827,  0x18c0,  0x08e1,  0x3882,  0x28a3,
    0xcb7d,  0xdb5c,  0xeb3f,  0xfb1e,  0x8bf9,  0x9bd8,  0xabbb,  0xbb9a,
    0x4a75,  0x5a54,  0x6a37,  0x7a16,  0x0af1,  0x1ad0,  0x2ab3,  0x3a92,
    0xfd2e,  0xed0f,  0xdd6c,  0xcd4d,  0xbdaa,  0xad8b,  0x9de8,  0x8dc9,
    0x7c26,  0x6c07,  0x5c64,  0x4c45,  0x3ca2,  0x2c83,  0x1ce0,  0x0cc1,
    0xef1f,  0xff3e,  0xcf5d,  0xdf7c,  0xaf9b,  0xbfba,  0x8fd9,  0x9ff8,
    0x6e17,  0x7e36,  0x4e55,  0x5e74,  0x2e93,  0x3eb2,  0x0ed1,  0x1ef0
};

/*
 * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. 
 *  NOTE: First srgument must be in range 0 to 255.
 *        Second argument is referenced twice.
 * 
 * Programmers may incorporate any or all code into their programs, 
 * giving proper credit within the source. Publication of the 
 * source routines is permitted so long as proper credit is given 
 * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, 
 * Omen Technology.
 */

#define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp)

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

void sendstring(s)
register char *s;
{
    char	data[20];
    register char *cp = data;
    int		i;
    LONG	oldlength = Write_Request->IOSer.io_Length;
    APTR	saveaddr =  Write_Request->IOSer.io_Data;

    Write_Request->IOSer.io_Length = sizeof(data)-1;
    Write_Request->IOSer.io_Data = (APTR) &(data[0]);

    if (enablexon)
	No_XON();

    while(i = *(s++)) {
	*(cp++) = addparity(i);
	if( (cp - data) == sizeof(data)-1) {
	    *cp = '\0';
	    do
		DoIO((struct IORequest *)Write_Request);
	    while(Write_Request->IOSer.io_Error != 0);
	    cp = data;
	}
    }
    if(cp > data) {
	*(cp++) = '\0';
	Write_Request->IOSer.io_Length = strlen(data);
	do
	    DoIO((struct IORequest *)Write_Request);
	while(Write_Request->IOSer.io_Error != 0);
    }

    Write_Request->IOSer.io_Length = oldlength;
    Write_Request->IOSer.io_Data   = saveaddr;

    if (enablexon)
	No_XON();
}

/**************************************************************/
/* send char and read char functions for the xmodem function */
/************************************************************/
void sendchar(ch)
int ch;
{
    if (enablexon)
	No_XON();

    rs_out[0] = addparity(ch);

    do {
	DoIO((struct IORequest *)Write_Request);
    } while(Write_Request->IOSer.io_Error != 0);

    if (enablexon)
	Do_XON();
}

static int
addparity(ch)
register int ch;
{
    int		i, j, k;

    if(p_parity > 0)
	switch (p_parity) {
	case 1: /* mark */
	    ch = (ch & 0x7F) | 0x80;
	    break;

	case 2: /* space */
	    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 */
		ch = (ch & 0x7F) | k;
	    else					/* odd parity */
		ch = (ch & 0x7F) | (k ^ 0x80);
	}
    return(ch & 0xFF);
}

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

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

    if(ttime == 0)
	Timer.tr_time.tv_micro = 100000;
    else
	Timer.tr_time.tv_micro = 0;
    Timer.tr_time.tv_secs = ttime;
    SendIO((struct IORequest *)&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((struct IORequest *)Read_Request)) {
	    WaitIO((struct IORequest *)Read_Request);
	    ch=rs_in[0];
	    rd = TRUE;
	    SendIO((struct IORequest *)Read_Request);
	}
	if(reqwinup &&
	  (NewMessage=(struct IntuiMessage *)GetMsg(reqwindow->UserPort))) {
	    class = NewMessage->Class;
	    ReplyMsg((struct Message *)NewMessage);
	    if(class == NEWSIZE)
		ReqNewSize(reqwindow->Height, reqwindow->Width);
	}
	if (NewMessage=(struct IntuiMessage *)GetMsg(mywindow->UserPort)) {
	   class = NewMessage->Class;
	   code = NewMessage->Code;
	   ReplyMsg((struct Message *)NewMessage);
	   if ((class == RAWKEY) && (code == 69)) {
		if(!CheckIO((struct IORequest *)&Timer))
		    AbortIO((struct IORequest *)&Timer);
		Wait (1L << Timer_Port->mp_SigBit);
		WaitIO((struct IORequest *)&Timer.tr_node);
		InfoMsg1Line("ERROR: User aborted transfer");
		timeout = USERABORT;
		return('\0');
	   }
	   PrintIText(mywindow->RPort, &MyTitle, 0L, 0L);
	}

	if (rd == FALSE && CheckIO((struct IORequest *)&Timer)) {
/*	    InfoMsg1Line("ERROR: Timeout waiting for character"); */
	    timeout = TIMEOUT;
	    return('\0');
	}
    } /* end while */
    if(!CheckIO((struct IORequest *)&Timer))
	AbortIO((struct IORequest *)&Timer);
    Wait (1L << Timer_Port->mp_SigBit);
    WaitIO((struct IORequest *)&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((struct IORequest *)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((struct IORequest *)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,
		c, good_sect, nak_char, retval = FALSE;
    unsigned int   checksum, j, bufptr;
    unsigned short crc;
    char	scrstr2[40];

    bytes_xferred = 0L;
    ttime = TTIME_SHORT;

    if( (bufr = AllocMem((long)BufSize, MEMF_PUBLIC|MEMF_CLEAR)) == NULL) {
	InfoMsg1Line("XMODEM: Can't get a buffer.");
	return FALSE;
    }

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

    sectnum = errors = bufptr = firstchar = 0;
    if(p_xproto == 2)
	nak_char = 'C';
    else
	nak_char = NAK;
    No_XON();
    sendchar(nak_char);
    while (firstchar != EOT && errors != ERRORMAX) {
	errorflag = FALSE;

	while( (firstchar = readchar()) != SOH && firstchar != EOT) {
	    if (timeout != GOODREAD) {
		if (timeout == USERABORT || errors++ == ERRORMAX)
		    goto exit;
	    }
	    sendchar(nak_char);
	}

	if  (firstchar == SOH) {
	    sprintf(scrstr2,"%s: Block: %4d Bytes: %d",
		    p_xproto==2?"XmodemCRC":"Xmodem", sectnum, sectnum*SECSIZ);
	    InfoMsgNoScroll(scrstr2);
	    sectcurr = readchar();
	    if (timeout != GOODREAD)
		goto exit;
	    sectcomp = readchar();
	    if (timeout != GOODREAD)
		goto exit;
	    if ((sectcurr + sectcomp) == 255) {
		if (sectcurr == ((sectnum + 1) & 0xff)) {
		    checksum = 0; crc = 0;
		    for (j = bufptr; j < (bufptr + SECSIZ); j++) {
			bufr[j] = readchar();
			if (timeout != GOODREAD)
			    goto exit;
			checksum = (checksum + bufr[j]) & 0xff;
			crc = updcrc(((unsigned int)bufr[j] & 0xff),  crc);
		    }
		    c = readchar();
		    if(timeout != GOODREAD) {
		    	errorflag = TRUE;
			if(timeout == USERABORT)
			    goto exit;
		    }
		    if(p_xproto == 2) {
			crc = updcrc(((unsigned int)c & 0xff), crc);
			c = readchar();
			if(timeout != GOODREAD) {
			    errorflag = TRUE;
			    if(timeout == USERABORT)
				goto exit;
			}
			crc = updcrc(((unsigned int)c & 0xff), crc);
			good_sect = (crc == 0);
		    } else
			good_sect = (checksum == c);
		    if (!good_sect) {
		    	errorflag = TRUE;
			if(timeout == USERABORT)
			    goto exit;
		    } else {
			errors = 0;
/*			sprintf(scrstr2,"Block %4d verified",sectnum); */
			sectnum++;
			bufptr += SECSIZ;
			bytes_xferred += SECSIZ;
/*			InfoMsgNoScroll(scrstr2); */
			if (bufptr == BufSize) {
			    if (write(fd, bufr, BufSize-SECSIZ) == EOF) {
				InfoMsg1Line("XMODEM: Error Writing File");
				goto exit;
			    }
			    bufptr = SECSIZ;
			    for (j = 0; j < SECSIZ; j++)
				bufr[j] = bufr[(BufSize-SECSIZ)+j];
			}
			sendchar(ACK);
		    }
		} else {
		    /* got a duplicate sector */
		    if (sectcurr == (sectnum & 0xff)) {
			/* wait until we time out for 5secs */
			do {
			    readchar();
			} while (timeout == GOODREAD);
			if (timeout == USERABORT)
			    goto exit;
			InfoMsg1Line("XMODEM: Received Duplicate Sector");
			sendchar(ACK);
		    }
		    else errorflag = TRUE;
		}
	    } else errorflag = TRUE;
	}
	if (errorflag == TRUE) {
	    errors++;
	    InfoMsg1Line("XMODEM: Error");
	    sendchar(nak_char);
	}
    }        /* end while */
    if ((firstchar == EOT) && (errors < ERRORMAX)) {
	sendchar(ACK);
	if (bufptr) {
	    if(p_autochop) {
		/* use firstchar to remember the last char for chopping */
		if((firstchar = bufr[--bufptr]) == 0 || firstchar == 0x1A)
		    while (bufptr && bufr[--bufptr] == firstchar)
			;
		bufptr++;
	    }
	    write(fd, bufr, bufptr);
	}
	close(fd);
	ScrollInfoMsg(1);
	retval = TRUE;
    }
exit:
    Do_XON();
    FreeMem(bufr, (long)BufSize);
    bufr = NULL;
    return retval;
}

int XMODEM_Send_File(file)
char *file;
{
    int		sectnum, bytes_to_send, size, attempts, c, use_crc = 0,
		retval = FALSE;
    unsigned	checksum, j, bufptr;
    unsigned short crc;
    char	scrstr2[40];

    bytes_xferred = 0;
    ttime = TTIME_LONG;

    if( (bufr = AllocMem((long)BufSize, MEMF_PUBLIC|MEMF_CLEAR)) == NULL) {
	InfoMsg1Line("XMODEM: Can't get a buffer.");
	return FALSE;
    }

    if ((fd = open(file, 0)) < 0) {
	InfoMsg1Line("XMODEM: Cannot Open Send File");
	FreeMem(bufr, (long)BufSize);
	bufr = NULL;
	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) && (c != 'C') && (j++ < ERRORMAX))
	if (timeout == USERABORT)
	    goto bad_exit;

    if (j >= (ERRORMAX)) {
	InfoMsg1Line("XMODEM: Receiver not sending");
	goto bad_exit;
    }

    if(c == 'C')
	use_crc = 1;
    while ((bytes_to_send = read(fd, bufr, BufSize)) &&
	    attempts != RETRYMAX) {
	if (bytes_to_send == EOF) {
	    InfoMsg1Line("XMODEM: Error Reading File");
	    goto bad_exit;
	}

	bufptr = 0;
	while (bytes_to_send > 0 && attempts != RETRYMAX) {
	    attempts = 0;
	    sprintf(scrstr2,"%s: Sending Block: %4d Bytes: %d",
		    use_crc?"XmodemCRC":"Xmodem", sectnum, sectnum*SECSIZ);
	    size = SECSIZ <= bytes_to_send ? SECSIZ : bytes_to_send;
	    bytes_to_send -= size;
	    do {
		InfoMsgNoScroll(scrstr2);
		sendchar(SOH);
		sendchar(sectnum);
		sendchar(~sectnum);
		checksum = 0; crc = 0;
		for (j = bufptr; j < bufptr + size; j++) {
		    sendchar(bufr[j]);		/* send buffer data */
		    checksum += bufr[j];
		    crc = updcrc(((unsigned int)bufr[j] & 0xff), crc);
		}
		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-- ) {
			if(use_crc)
			    crc = updcrc(c, crc);
			sendchar(c);		/* send padding */
		    }
		}
		if(use_crc) {
		    crc = updcrc(0, updcrc(0, crc));
		    sendchar(crc >> 8);
		    sendchar(crc & 0xff);
		} else
		    sendchar(checksum);
		attempts++;
		c = readchar();
		if (timeout == USERABORT) {
		    InfoMsg1Line("XMODEM: ABORTED");
		    goto bad_exit;
		}
	    } 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");
	goto bad_exit;
    } else {
	attempts = 0;
	do {
	    sendchar(EOT);
	    attempts++;
	} while ((readchar() != ACK) &&
		     (attempts != RETRYMAX) &&
		     (timeout != USERABORT)) ;
	if (attempts == RETRYMAX)
	    InfoMsg1Line("XMODEM: No end of file");
    }
    ScrollInfoMsg(1);
    retval = TRUE;

bad_exit:
    Do_XON();
    FreeMem(bufr, (long)BufSize);
    bufr = NULL;
    return retval;
}

/* 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+1) == '\0')
	    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;
    if(p_xbeep)
	cmd_beep(0L);
    }

