/************************************************************************/
/* Copyright 1998-2018 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	: BEDS2														*/
/* 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									*/
/* 13mar2018 rah, created BEDS2 version from OASIS5 version				*/
/************************************************************************/

#include <mbariTypes.h>				/* MBARI type definitions			*/
#include <beds.h>					/* BEDS2 controller definitions		*/
#include <ymodem.h>					/* YModem protocol definitions		*/
#include <clock.h>
#include <fatFs/ff.h>				/* FatFs definitions					*/
#include <file.h>					/* BEDS File I/O Routines			*/
#include <fileUtils.h>				/* BEDS file utilities definitions	*/
#include <serial.h>					/* BEDS serial I/O					*/
#include <utils.h>					/* BEDS utility functions			*/
#include <crc16.h>					/* CRC16-CCITT routines				*/

#include <stdio.h>					/* Standard I/O						*/
#include <string.h>					/* String functions					*/
#include <stdlib.h>					/* Standard C library, for malloc	*/
#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(Nat32 waitTime, SerPort port)
{
	int		ch;
	Nat32	cnt;

	for(cnt = 0; cnt < waitTime; cnt++)
	{
		ch = ser_getc_tmout(port, TICKS_PER_SEC);

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

		if ((ch == CAN) && (ser_getc_tmout(port, 2*TICKS_PER_SEC) == 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, SerPort port)
{
	Nat16				crc, cksum, errCnt, i;
	Reg int				ch;
	Reg unsigned char	*p;

	for (errCnt = 0; errCnt < ERRORMAX; errCnt++)
	{
		serRxFlush(port);
		_mon_putc((pktSize == SMPKTSIZE) ? SOH : STX);
		_mon_putc(pktNum);
		_mon_putc(~pktNum);

		crc = cksum = 0;

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

		if (flags & CRCFLAG)
		{
			crc = updcrc(0, crc);
			crc = updcrc(0, crc);
			_mon_putc(crc >> 8);
			_mon_putc(crc);
		}
		else
			_mon_putc(cksum);
		
		if ((flags & GFLAG) || 
			((ch = ser_getc_tmout(port, 2*TICKS_PER_SEC)) == ACK))
			return(OK);

		if (ch == NAK)
			return(BAD_DATA);
		
		if ((ch == CAN) && (ser_getc_tmout(port, 2*TICKS_PER_SEC) == CAN))
			return(ABORT);
	}

	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, SerPort port)
{
	Word	flags;
	char	*buff_ptr; 

	switch(ymdmSyncProtocol(NAKMAX, port))
	{
		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, port) != 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											*/
/************************************************************************/
Errno ymdmSendFile(char *name, FSIZE_t fsize, char *ymdmBuf)
{
	FRESULT		fRes;
	SerPort		port;
	Word		flags;
	int			bytesToSend;
	char		*pktP;
	Nat16		pktNum;
	Nat16		pktSize;
	FIL			file;
	Errno		rtn;
	int			errs;

	port = getConsole();

	fRes = f_open(&file, name, (FA_READ | FA_OPEN_EXISTING));

	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, port)) == ERROR)
		return( ERROR );

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

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

		while (bytesToSend > 0)
		{
			rtn = ymdmSendPkt(pktP, pktNum, pktSize, flags, port);
			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);
	}

	f_close(&file);

	/* Send EOT, wait for ACK			*/
	for (pktNum = 0; pktNum < EOTMAX; pktNum++)
	{
		_mon_putc(EOT);
		if (ser_getc_tmout(port, 2*TICKS_PER_SEC) == ACK)
			return(OK);
	}

	return(ERROR);

} /* ymdmSendFile() */


/************************************************************************/
/* Function	   : dsSendFile												*/
/* 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(int argc, char **argv)
{
	char		*ymdmBuf, *fileName;
	int			i;
	SerPort		port = getConsole();

	if (argc <= 1)
		return(ERROR);

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

	for (i = 1; i < argc; i++)
	{
		fileName = argv[i];
		printf("Sending %s via YMODEM protocol\r\n", fileName);
		printf("Please start download on receiver with YMODEM protocol\n");
		sysLogPrintf("Sending %s via YModem", fileName);

		memset(ymdmBuf, 0, YBUFSIZE);

		if (dirScan(fileName, dsSendFile, ymdmBuf) > 0)
			ymdmSendFileName("", 0, ymdmBuf, port);
		else
			printf("\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, SerPort port)
{
	Int32	i, c;
	Nat32	pktSize;
	Byte	*p;
	Nat16	crc;

	if ((c = ser_getc_tmout(port, tmout*TICKS_PER_SEC)) == ERROR)
		return(TMOUT);
	
	switch(c)
	{
	  case SOH:
		  pktSize = SMPKTSIZE;
		  break;
	  case STX:
		  pktSize = LGPKTSIZE;
		  break;
	  case EOT:
		  return(0);
	  case CAN:
		  if (ser_getc_tmout(port, PKT_TMOUT*TICKS_PER_SEC) == CAN)
			  return(ABORT);
		  return(BAD_DATA);
	  case CTLC:
		  if (ser_getc_tmout(port, PKT_TMOUT*TICKS_PER_SEC) == CTLC)
			  return(ABORT);
	  default:
		  return(BAD_DATA);
	}

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

	for (i = 1; i < (pktSize + RCV_OVHD); i++)
	{
		if ((c = ser_getc_tmout(port, tmout*TICKS_PER_SEC)) == ERROR)
			return(TMOUT);
		*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, SerPort port)
{
	Byte	*p;
	int		i, tries;
	Int32	rtn;
	FRESULT fres;
	FILINFO	finfo;

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

		rtn = ymdmRcvPkt(fsp->ybuf, CONNECT_TMOUT, port);
		if (rtn >= 0)
		{
			serRxFlush(port);
			_mon_putc(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);

			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));

			if (fres != FR_OK)
			{				
				printf(canStr);
				serWaitTxComplete(port);
				printf("Cannot open %s\r\n", ytmpName);
				fperr(fres, NULL);
				return(0);
			}

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

	printf(canStr);
	serWaitTxComplete(port);
	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, SerPort port)
{
	int		i, nPkts, errors;
	Nat32	bytesRcvd, bw;
	Int32	rtn;
	FRESULT fres;

	while ((rtn = ymdmRcvGetFirstPkt(fsp, port)) > 0)
	{
		nPkts = 1;
		errors = 0;
		bytesRcvd = 0;
		while(TRUE)
		{
			rtn = ymdmRcvPkt(fsp->ybuf, PKT_TMOUT, port);
			serRxFlush(port);
			switch(rtn)
			{
			  case BAD_DATA:
			  case TMOUT:
				  if (++errors >= YMAX_ERRS)
				  {
					  printf(canStr);
					  f_unlink(ytmpName);
					  return(rtn);
				  }
				  _mon_putc(NAK);
				  break;
				  
			  case ABORT:
				  printf(canStr);
				  f_unlink(ytmpName);
				  return(rtn);

			  case 0:
				  _mon_putc(NAK);

				  /* If got correct number of pkts for bytesRcvd, truncate file */
				  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);

				  if (ser_getc_tmout(port, CONNECT_TMOUT*TICKS_PER_SEC) == EOT)
				  {
					  _mon_putc(ACK);
					  return(1);
				  }
				  return(0);

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

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

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

	return(0);
}


/************************************************************************/
/* Function	   : ymdmRcv												*/
/* Purpose	   : Receive File(s) in YModem format						*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn	ymdmRcv(int argc, char **argv)
{
	YFileStruct *fsp;
	SerPort		port = getConsole();
	int			fileCnt = 0;
	MBool		flow = FALSE;

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

	printf("Please start YMODEM send file on your terminal emulator\r\n");
	serWaitTxComplete(port);
	serRxFlush(port);

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

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

	serRxFlush(port);
	printf("Received %d file(s)\r\n", fileCnt);

	free(fsp);
	return(OK);
}
