/************************************************************************/
/* Copyright 1998-2017 MBARI											*/
/************************************************************************/
/* $Header: /home/cvs/oasis5/src/controller/system/ymodem.c,v 1.5 2017/06/29 22:48:24 bobh Exp $ */
/* Summary	: YModem Protocol Handler for OASIS Mooring Controller		*/
/* Filename : ymodem.c													*/
/* Author	: Robert Herlien (rah)										*/
/* Project	: OASIS Mooring												*/
/* Revision : 1.0														*/
/* Created	: 08/24/2016													*/
/* Comment	: Adapted from xmodem program by Steve Grandi, grandi@noao.edu	*/
/*																			*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																			*/
/************************************************************************/
/* Modification History:												*/
/* 20aug98 rah, created													*/
/* 24aug2016 rah, created OASIS5 version from OASIS3 version			*/
/* 09mar2017 rah, Added YModem receive									*/
/************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <ymodem.h>						/* YModem protocol definitions		*/
#include <log.h>						/* Log record definitions			*/
#include <otask.h>						/* OASIS Multitasking definitions	*/
#include <file.h>						/* OASIS File I/O Routines			*/
#include <utils.h>						/* OASIS Utility routines			*/
#include <malloc.h>						/* OASIS malloc routines			*/
#include <crc16.h>						/* CRC16-CCITT routines				*/

#include <istdio.h>						/* Our (integer) Standard I/O		*/
#include <stdio.h>						/* Standard I/O						*/
#include <string.h>						/* String functions					*/
#include <time.h>


/********************************/
/*	Module Local Data			*/
/********************************/

MLocal Byte		canStr[] = {CAN, CAN, CAN, CAN, CAN, '\0'};
MLocal char		*ytmpName = "YMODEM.$$$";


/************************************************************************/
/* Function	   : ymdmSyncProtocol										*/
/* Purpose	   : Get the protocol sync character						*/
/* Inputs	   : Time to wait in seconds								*/
/* Outputs	   : Sync char or ERROR										*/
/************************************************************************/
MLocal Errno ymdmSyncProtocol(Nat16 waitTime)
{
	Reg Int16	ch;
	Nat16		cnt;

	for(cnt = 0; cnt < waitTime; cnt++)
	{
		ch = xgetc_tmout(1);

		if ((ch == CRCCHR) || (ch == GCHR) || (ch == NAK))
			return(ch);

		if ((ch == CAN) && (xgetc_tmout(2) == CAN))
			return(ERROR);
	}

	return(ERROR);

} /* ymdmSyncProtocol() */


/************************************************************************/
/* Function	   : ymdmSendPkt											*/
/* Purpose	   : Send one YModem packet									*/
/* Inputs	   : Packet, packet number, packet size, flags word			*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno ymdmSendPkt(char *ymdmBuf, Nat16 pktNum, Nat16 pktSize,
						 Word flags, SerDesc *sdp)
{
	Nat16				errCnt, crc, cksum, i;
	Reg Int16			ch;
	Reg unsigned char	*p;

	for (errCnt = 0; errCnt < ERRORMAX; errCnt++)
	{
		xflush_ser(0);
		doputc((pktSize == SMPKTSIZE) ? SOH : STX, sdp);
		doputc(pktNum, sdp);
		doputc(~pktNum, sdp);

		crc = cksum = 0;

		for (i = 0, p = ymdmBuf; i < pktSize; i++)
		{
			doputc(*p, sdp);
			crc = updcrc((*p & 0xff), crc);
			cksum += *p++;
		}

		if (flags & CRCFLAG)
		{
			crc = updcrc(0, updcrc(0, crc));
			doputc(crc >> 8, sdp);
			doputc(crc, sdp);
		}
		else
			doputc(cksum, sdp);

		if ((flags & GFLAG) || ((ch = xgetc_tmout(2)) == ACK))
			return(OK);

		if (ch == NAK)
			return(BAD_DATA);
		
		if ((ch == CAN) && (xgetc_tmout(2) == CAN))
			return(USR_INTERRUPT);
	}

	return(ERROR);

} /* ymdmSendPkt() */


/************************************************************************/
/* Function	   : ymdmSendFileName										*/
/* Purpose	   : Start YModem protocol, send pkt 0 with file name		*/
/* Inputs	   : File name, packet buffer								*/
/* Outputs	   : Flag word or ERROR										*/
/************************************************************************/
MLocal Word ymdmSendFileName(char *fileName, FSIZE_t fileLen,
							 char *ymdmBuf, SerDesc *sdp)
{
	Word	flags;
	char	*buff_ptr; 

	switch(ymdmSyncProtocol(NAKMAX))
	{
		case CRCCHR:
			flags = KPKT | CRCFLAG;
			break;

		case GCHR:
			flags = KPKT | CRCFLAG | GFLAG;
			break;

		case NAK:
			return(0);

		default:
			return(ERROR);
	}

	memset(ymdmBuf, 0, YBUFSIZE);
	strcpy(ymdmBuf, fileName);

	buff_ptr = ymdmBuf + strlen(fileName) + 1;

	if (fileLen)
		sprintf(buff_ptr, "%u", fileLen);

	if (ymdmSendPkt(ymdmBuf, 0, SMPKTSIZE, flags, sdp) != OK)
		return(ERROR);

	return(flags);

} /* ymdmSendFileName() */


/************************************************************************/
/* Function	   : ymdmSendFile											*/
/* Purpose	   : Send one file via YMODEM								*/
/* Inputs	   : File name, file size, buffer ptr						*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno ymdmSendFile(char *name, FSIZE_t fsize, char *ymdmBuf)
{
	FRESULT		fRes;
	Word		flags;
	INT			bytesToSend;
	char		*pktP;
	Nat16		pktNum;
	Nat16		pktSize;
	SerDesc		*sdp;
	FIL			file;
	Errno		rtn;
	int			errs;

	logFlush();
	sdp = rtosIsRunning() ? pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP) : NULL;
	
	takeFileSem();
	fRes = f_open(&file, name, (FA_READ | FA_OPEN_EXISTING));
	giveFileSem();

	if (fRes != FR_OK)
	{
		fperr(fRes, "Error on f_open");
		return(ERROR);
	}

	/* Send file name, then resync the protocol			*/
	if ((flags = ymdmSendFileName(name, fsize, ymdmBuf, sdp)) == ERROR)
		return( ERROR );

	if (flags)							/* If not XMODEM, resync		*/
		if (ymdmSyncProtocol(RESYNCMAX) == ERROR)
			return(ERROR);
	
	/* Send the data					*/
	pktNum = 1;
	errs = 0;

	takeFileSem();

	while ((f_read(&file, ymdmBuf, YBUFSIZE, &bytesToSend) == FR_OK) &&
		   (bytesToSend > 0))
	{
		giveFileSem();
		pktP = ymdmBuf;
		if ((flags & KPKT) && (bytesToSend >= (6 * 128)))
			pktSize = LGPKTSIZE;
		else
			pktSize = SMPKTSIZE;

		while (bytesToSend > 0)
		{
			rtn = ymdmSendPkt(pktP, pktNum, pktSize, flags, sdp);
			if (rtn == OK)
			{
				pktNum++;
				bytesToSend -= pktSize;
				pktP += pktSize;
			}
			else if (rtn == BAD_DATA)
			{
				if (++errs > YMAX_ERRS)
					return(ERROR);
			}
			else
				return(ERROR);
		}
		memset(ymdmBuf, 0, YBUFSIZE);
		takeFileSem();
	}

	f_close(&file);
	giveFileSem();

	/* Send EOT, wait for ACK			*/
	for (pktNum = 0; pktNum < EOTMAX; pktNum++)
	{
		doputc(EOT, sdp);
		if (xgetc_tmout(2) == ACK)
			return(OK);
	}

	return(ERROR);

} /* ymdmSendFile() */


/************************************************************************/
/* Function	   : dsySendFile											*/
/* Purpose	   : Function passed to dirScan for "YS" command			*/
/* Inputs	   : Directory entry ptr, ymodem buffer ptr					*/
/* Outputs	   : TRUE if OK, FALSE if error								*/
/************************************************************************/
MBool dsSendFile(FILINFO *de, void *ymdmBuf)
{
	Errno	rtn;

	if ((de->fattrib & (AM_DIR | AM_VOL)) == 0)
	{
		rtn = ymdmSendFile(de->fname, de->fsize, (char *)ymdmBuf);
		return(rtn == OK ? TRUE : FALSE);
	}

	return(TRUE);
}

/************************************************************************/
/* Function	   : ymdmSend												*/
/* Purpose	   : Send File(s) in YModem format							*/
/* Inputs	   : Parm Mask, File name(s)								*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn ymdmSend(ParmMask_t pmask, char *fileName)
{
	char		*ymdmBuf;
	SerDesc		*sdp;

	if ((pmask & 1) == 0)
		return(ERROR);

	if ( (ymdmBuf = pmalloc(YBUFSIZE)) == NULL )
		return(ERROR);

	xprintf("Sending %s via YMODEM protocol\n", fileName);
	xprintf("Please start download on receiver with YMODEM protocol\n");

	memset(ymdmBuf, 0, YBUFSIZE);

	if (dirScan(fileName, dsSendFile, ymdmBuf) > 0)
	{
		sdp = rtosIsRunning() ? pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP) : NULL;
		ymdmSendFileName("", 0, ymdmBuf, sdp);
	}
	else
		xputs("\030\030\030\030\030");

	free(ymdmBuf);
	return(OK);

} /* ymdmSend() */


/************************************************************************/
/* Function	   : ymdmRcvPkt												*/
/* Purpose	   : Receive one YModem packet								*/
/* Inputs	   : Packet buffer, length ptr for return					*/
/* Outputs	   : Packet size (128 or 1024), 0 for EOT, or Error type < 0*/
/************************************************************************/
MLocal Int32 ymdmRcvPkt(Byte *dp, Nat32 tmout)
{
	Int32	i, c;
	Nat32	pktSize;
	Nat16	crc;
	Byte	*p;

	if ((c = xgetc_tmout(tmout)) == ERROR)
		return(TMOUT_ERR);

	switch(c)
	{
	  case SOH:
		  pktSize = SMPKTSIZE;
		  break;
	  case STX:
		  pktSize = LGPKTSIZE;
		  break;
	  case EOT:
		  return(0);
	  case CAN:
		  if (xgetc_tmout(PKT_TMOUT) == CAN)
			  return(USR_INTERRUPT);
		  return(BAD_DATA);
	  case CTLC:
		  if (xgetc_tmout(PKT_TMOUT) == CTLC)
			  return(USR_INTERRUPT);
	  default:
		  return(BAD_DATA);
	}

	p = dp;
	*p++ = (Byte)c;
	crc = 0;

	for (i = 1; i < (pktSize + RCV_OVHD); i++)
	{
		if ((c = xgetc_tmout(tmout)) == ERROR)
			return(TMOUT_ERR);
		*p++ = (Byte)c;
		if (i >= PKT_HDR)
			crc = updcrc((c & 0xff), crc);
	}

	if ((crc != 0) || (dp[1] != ((dp[2] ^ 0xff) & 0xff)))
		return(BAD_DATA);

	return(pktSize);
}


/************************************************************************/
/* Function	   : ymdmRcvGetFirstPkt										*/
/* Purpose	   : Start YModem receive, get 1st packet with file name, open file*/
/* Inputs	   : Buffer pointer											*/
/* Outputs	   : 0 for end of protocol, >0 for good file, <0 for error	*/
/************************************************************************/
MLocal Int32 ymdmRcvGetFirstPkt(YFileStruct *fsp)
{
	Byte	*p;
	int		i, tries;
	Int32	rtn;
	FRESULT fres;
	FILINFO	finfo;

	for (tries = 0; tries < YTRIES; tries++)
	{
		fsp->fileSize = 0;
		xputc(CRCCHR);

		rtn = ymdmRcvPkt(fsp->ybuf, CONNECT_TMOUT);
		if (rtn >= 0)
		{
			xflush_ser(0);
			xputc(ACK);
			if ((rtn == 0) || fsp->ybuf[1])			/* End or not pkt 0		*/
				return(0);

			/* Check first 4 payload bytes for zeroes; assume blank pkt if so*/
			for (i = PKT_HDR; i < PKT_HDR+4; i++)
				if (fsp->ybuf[i])
					break;

			if (i >= PKT_HDR + 4)				/* If >=4 zeroes, blank pkt*/
				return(0);

			/* Got valid file packet.  Check name for validity			*/
			p = fsp->ybuf + PKT_HDR;
			strncpy(fsp->fname, p, FNAME_SIZE);

			takeFileSem();
			if ((fres = f_stat(p, &finfo)) == FR_NO_FILE)
				fres = FR_OK;
			if (fres == FR_OK)
				fres = f_open(&fsp->fp, ytmpName, (FA_WRITE | FA_CREATE_ALWAYS));
			giveFileSem();

			if (fres != FR_OK)
			{				
				xputs(canStr);
				xdrain_ser(TICKS_PER_SEC);
				xprintf("Cannot open %s\n", ytmpName);
				fperr(fres, NULL);
				return(0);
			}

			fsp->fileSize = fsp->fileTime = 0;
			sscanf(p+strlen(p)+1, " %u %o", &fsp->fileSize, &fsp->fileTime);
			xputc(CRCCHR);
			return(rtn);
		}
		else if (rtn == USR_INTERRUPT)
		{
			xputs(canStr);	
			return(USR_INTERRUPT);
		}
		else if (rtn == BAD_DATA)
			xputc(NAK);
	}

	xputs(canStr);
	xdrain_ser(TICKS_PER_SEC);
	return(rtn);
}


/************************************************************************/
/* Function	   : ymdmRcvFile											*/
/* Purpose	   : Receive one file in YModem format						*/
/* Inputs	   : Buffer pointer											*/
/* Outputs	   : 0 for end, 1 to continue, or ERROR code				*/
/************************************************************************/
MLocal int ymdmRcvFile(YFileStruct *fsp)
{
	int		i, nPkts, errors;
	Nat32	bytesRcvd, bw;
	Int32	rtn;
	FRESULT fres;

	while ((rtn = ymdmRcvGetFirstPkt(fsp)) > 0)
	{
		nPkts = 1;
		errors = 0;
		bytesRcvd = 0;
		while(TRUE)
		{
			rtn = ymdmRcvPkt(fsp->ybuf, PKT_TMOUT);
			xflush_ser(0);
			switch(rtn)
			{
			  case BAD_DATA:
			  case TMOUT_ERR:
				  if (++errors >= YMAX_ERRS)
				  {
					  xputs(canStr);
					  f_unlink(ytmpName);
					  return(rtn);
				  }
				  xputc(NAK);
				  break;
				  
			  case USR_INTERRUPT:
				  xputs(canStr);
				  f_unlink(ytmpName);
				  return(rtn);

			  case 0:
				  xputc(NAK);

				  /* If got correct number of pkts for bytesRcvd, truncate file */
				  takeFileSem();
				  if (fsp->fileSize && (bytesRcvd >= fsp->fileSize) &&
					  (bytesRcvd < (fsp->fileSize + LGPKTSIZE)))
				  {
					  f_lseek(&fsp->fp, fsp->fileSize);
					  f_truncate(&fsp->fp);
				  }
				  f_close(&fsp->fp);
				  f_unlink(fsp->fname);
				  f_rename(ytmpName, fsp->fname);
				  if (fsp->fileTime)
					  setFileTime(fsp->fname, fsp->fileTime);
				  giveFileSem();

				  if (xgetc_tmout(CONNECT_TMOUT) == EOT)
				  {
					  xputc(ACK);
					  return(1);
				  }
				  return(0);

			   default:
				   if (nPkts != fsp->ybuf[1])			/* Wrong packet number!		*/
					   xputc(NAK);
				   else
				   {
					   nPkts++;
					   bytesRcvd += rtn;

					   takeFileSem();
					   fres = f_write(&fsp->fp, &fsp->ybuf[PKT_HDR], rtn, &bw);
					   giveFileSem();

					   if ((fres != FR_OK) || (bw != rtn))
					   {
						   xputs(canStr);
						   f_unlink(ytmpName);
						   return(WRITE_ERROR);
					   }
					   xputc(ACK);
				   }
				   break;
			}
		}
	}

	return(0);
}


/************************************************************************/
/* Function	   : ymdmRcv												*/
/* Purpose	   : Receive File(s) in YModem format						*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn ymdmRcv(void)
{
	YFileStruct *fsp;
	int			fileCnt = 0;
	MBool		flow = FALSE;

	if ((fsp = (YFileStruct *)pmalloc(sizeof(YFileStruct))) == NULL)
		return(ERROR);

	xprintf("Please start YMODEM send file on your terminal emulator\n");
	xdrain_ser(TICKS_PER_SEC/2);
	xflush_ser(0);
	xser_flowCtl(&flow);

	memset(fsp, 0, sizeof(YFileStruct));

	while (ymdmRcvFile(fsp) > 0)
		fileCnt++;

	xflush_ser(TICKS_PER_SEC);
	xprintf("Received %d file(s)\n", fileCnt);
	xser_flowCtl(&flow);

	free(fsp);
	return(OK);
}
