/*
 * ymodem.c
 *
 *  Created on: Feb 5, 2014
 *      Author: rob
 */

/* ymodem for RTD Serial Recovery (rtdsr)
 *
 * copyright (c) 2011 Pete B. <xtreamerdev@gmail.com>
 *
 * based on ymodem.c for bootldr, copyright (c) 2001 John G Dorsey
 * baded on ymodem.c for reimage, copyright (c) 2009 Rich M Legrand
 * crc16 function from PIC CRC16, by Ashley Roll & Scott Dattalo
 * crc32 function from crc32.c by Craig Bruce
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * These Ymodem calls are aimed at embedded software and tailored to
 * work against Microsoft's HyperTerminal. Some of the Ymodem protocol
*  operations have been voluntarily left out.
 *
 * To be able to use these functions, you must provide:
 * o int _getchar(int timeout): A serial getchar() call, with a
 *   timeout expressed in seconds. Negative means infinite timeout.
 *   should return the read character, as an int, or negative on
 *   error/timeout.
 * o void _putchar(int c): A serial putchar() call
 * o printf(), thought all the printfs can be removed if needed.
 *
 * **********************
 *2/2014 Robert Glatts
 * rglatts@ucsd.edu
 *
 * Modified source code to work with SeapHOx Tivaware and FatFs function calls.
 */

#include "system.h"
#include "ymodem.h"
#include "user_io.h"
#include "microsd.h"
#include "uartstdio.h"	// User local version with larger RX buffer

// Declarations
enum ymodem_error {FILE_OPEN_ERROR, FILE_WRITE_ERROR, FILE_SIZE_ERROR, PACKET_TIMEOUT_ERROR, PACKET_CRC_ERRORS};

// Global variables
FIL FileObject;
static unsigned char packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];


// Function prototypes
unsigned short crc16(unsigned char *buf, unsigned long count);
const char *u32_to_str(unsigned int val);
unsigned long str_to_u32(char* str);
int receive_packet(int packet_size);
void send_packet(unsigned char *data, int block_no);
void send_packet0(char* filename, unsigned long size);
int send_data_packets(unsigned long file_size);



/* http://www.ccsinfo.com/forum/viewtopic.php?t=24977 */
unsigned short crc16(unsigned char *buf, unsigned long count)
{
        unsigned short crc = 0;
        int i;

        while(count--) {
                crc = crc ^ *buf++ << 8;

                for (i=0; i<8; i++) {
                        if (crc & 0x8000) {
                                crc = crc << 1 ^ 0x1021;
                        } else {
                                crc = crc << 1;
                        }
                }
        }
        return crc;
}

const char *u32_to_str(unsigned int val)
{
        /* Maximum number of decimal digits in u32 is 10 */
        static char num_str[11];
        int  pos = 10;
        num_str[10] = 0;

        if (val == 0) {
                /* If already zero then just return zero */
                return "0";
        }

        while ((val != 0) && (pos > 0)) {
                num_str[--pos] = (val % 10) + '0';
                val /= 10;
        }

        return &num_str[pos];
}

unsigned long str_to_u32(char* str)
{
        const char *s = str;
        unsigned long acc;
        int c;

        /* strip leading spaces if any */
        do {
                c = *s++;
        } while (c == ' ');

        for (acc = 0; (c >= '0') && (c <= '9'); c = *s++) {
                c -= '0';
                acc *= 10;
                acc += c;
        }
        return acc;
}



int ymodem_receive(int argc, char *argv[])
{
	unsigned char *file_ptr;
	int c, i, cancan;
	unsigned int packet_size, packets_received;
	char file_name[FILE_NAME_LENGTH], file_size[FILE_SIZE_LENGTH];
	unsigned long size = 0;
	enum ymodem_error error;
	FRESULT fresult;
	UINT bytes_written;

	uprintf("Ymodem rcv:\r\n");
	file_name[0] = 0;
	i = 0;
	cancan = 0;
	packets_received = 0;

	UARTEchoSet(false);		// Disable console echo

	while(1)
	{
		UARTCharPut(UART0_BASE, CRC);
		ROM_SysCtlDelay(MILLISECOND*1000);
		if( UARTRxBytesAvail() ) break;
		if( i > 60 ) goto abort;		// Give sender one minute to start
	}

	// Receive packets
	while(1)
	{
        // Get the first character
		while(1)
		{
			c = getchar_timed(PACKET_TIMEOUT);
			if (c < 0)
			{
				error = PACKET_TIMEOUT_ERROR;
				goto abort;	// Timeout
			}

			// Look at the first character and decide what to do
			switch(c)
			{
				case SOH:
					packet_size = PACKET_SIZE;
					break;

				case STX:
					packet_size = PACKET_1K_SIZE;
					break;

				case EOT:
					UARTCharPut(UART0_BASE, NAK);	// NAK first EOT character
					c = getchar_timed(PACKET_TIMEOUT);
					if (c == EOT)
					{
						UARTCharPut(UART0_BASE, ACK);	// ACK second EOT character
						f_close(&FileObject);			// Got a good file, close it

						UARTCharPut(UART0_BASE, CRC);	// Send a 'C' to request second file if their is one
						packets_received = 0;			// Reset packet counter since next packet should be filename type
						continue;
					}
					else
					{
						UARTCharPut(UART0_BASE, NAK);	// Unexpected character, NAK it
						continue;
					}

				case CAN:
					c = getchar_timed(PACKET_TIMEOUT);
					if (c == CAN) cancan++;
					if (cancan >= 2) goto abort;	// Got two CAN's in a row
					else
					{
						UARTCharPut(UART0_BASE, NAK);	// Unexpected character, NAK it
						continue;
					}

			}	// End of switch

			if(c == SOH || c == STX) break; // End first character loop if a packet is coming
		}

        packet_data[0] = (unsigned char)c;  // Store the first character

        // Get the rest of the packet
    	for(i = 1; i < (packet_size + PACKET_OVERHEAD); i++)
    	{
    		c = getchar_timed(PACKET_TIMEOUT);
    		if (c < 0) {
				error = PACKET_TIMEOUT_ERROR;
   				goto abort;
    		}

    		else packet_data[i] = (unsigned char)c;
    	}

    	// Check the packet CRC
    	if (crc16(packet_data + PACKET_HEADER, packet_size + PACKET_TRAILER) != 0)
    	{
			UARTCharPut(UART0_BASE, NAK);	// Bad CRC, ask for a resend
    	}

		// Check if sequence number matches packets received so far
		else if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))
		{
			UARTCharPut(UART0_BASE, NAK);	// Bad sequence number, ask for a resend
		}

    	// Good packet and sequence number
    	else
		{
			packets_received++;		// Got a good packet

			// Is this the first packet?
			if (packets_received == 1)
			{
				/* The spec suggests that the whole data section should
				 * be zeroed, but I don't think all senders do this. If
				 * we have a NULL filename and the first few digits of
				 * the file length are zero, we'll call it empty.
				 */

				// Count the number of zeros in packet
				for (i = PACKET_HEADER; i < PACKET_HEADER + 4; i++)
				{
					if (packet_data[i] != 0)
					{
						break;
					}
				}

				// Filename packet has data
				if (i < PACKET_HEADER + 4)
				{
					// Get the file name
					for (file_ptr = packet_data + PACKET_HEADER, i = 0; *file_ptr && i < FILE_NAME_LENGTH; )
					{
						file_name[i++] = *file_ptr++;
					}
					file_name[i++] = '\0';

					// Get the file size string
					for (++file_ptr, i = 0; *file_ptr != ' ' && i < FILE_SIZE_LENGTH; )
					{
						file_size[i++] = *file_ptr++;
					}
					file_size[i++] = '\0';

					// Convert file size string to unsigned long
					size = str_to_u32(file_size);

					// File size too big?
					if (size > MAX_RX_FILE_SIZE * 1024)
					{
						error = FILE_SIZE_ERROR;
						goto abort;
					}

					// Good file size, open the file
					else
					{
						printf("\n\nOpening filename = %s\n", file_name);

						// Open the file for writing. Overwrite existing file.
						fresult = f_open(&FileObject, file_name, FA_WRITE | FA_READ | FA_CREATE_ALWAYS);
						if(fresult != FR_OK)
						{
							printf("f_open error: %s\n", StringFromFresult(fresult) );
							error = FILE_OPEN_ERROR;
							goto abort;
						}

					}

					UARTCharPut(UART0_BASE, ACK);
					UARTCharPut(UART0_BASE, CRC);
				}

				// Filename packet is empty; end session
				else
				{
						UARTCharPut(UART0_BASE, ACK);	// Acknowledge the end of session

						// Close and open file to avoid FR_INVALID_OBJECT error from f_lseek()
						f_close(&FileObject);

						fresult = f_open(&FileObject, file_name, FA_WRITE | FA_READ | FA_CREATE_ALWAYS);
						if(fresult != FR_OK)
						{
							printf("f_open error: %s\n", StringFromFresult(fresult) );
							error = FILE_OPEN_ERROR;
							goto abort;
						}

						// Trucate file to correct size indicated by sender
						fresult = f_lseek(&FileObject, size);	// Move the file pointer to correct end
						if(fresult != FR_OK)
						{
							printf("f_lseek error: %s\n", StringFromFresult(fresult) );
						}

						fresult = f_truncate(&FileObject);	// Get rid of the padding
						if(fresult != FR_OK)
						{
							printf("f_truncate error: %s\n", StringFromFresult(fresult) );
						}

						goto success;	// Close file and return
				}

			}	// End first packet

			// It's a data packet, so store it
			else
			{
				f_write (&FileObject, &packet_data[PACKET_HEADER], (UINT)packet_size, &bytes_written);
				if(fresult != FR_OK)
				{
					printf("f_write error: %s\n", StringFromFresult(fresult) );
					error = FILE_WRITE_ERROR;
					goto abort;
				}

				UARTCharPut(UART0_BASE, ACK);	// Tell the sender to send next packet
			}

		}  // End good sequence number

	}  /* End of receive packets loop */


abort:
	UARTCharPut(UART0_BASE, CAN);
	UARTCharPut(UART0_BASE, CAN);
	ROM_SysCtlDelay(MILLISECOND*1000);
	uprintf("\r\nYmodem aborted: ");
	switch(error)
	{
		case FILE_OPEN_ERROR:
			uprintf("File open error\r\n");
			break;

		case FILE_WRITE_ERROR:
			uprintf("File write error\r\n");
			break;

		case FILE_SIZE_ERROR:
			uprintf("File size larger than: %d kB\r\n", MAX_RX_FILE_SIZE);
			break;

		case PACKET_TIMEOUT_ERROR:
		uprintf("Packet timeout error\r\n");

	}

success:
	f_close(&FileObject);
	UARTEchoSet(true);		// Enable console echo
	return 0;
}

void send_packet(unsigned char *data, int block_no)
{
        int count, crc, packet_size;

        printf("sending block %d\n", block_no);

        /* We use a short packet for block 0 - all others are 1K */
        if (block_no == 0) {
                packet_size = PACKET_SIZE;
        } else {
                packet_size = PACKET_1K_SIZE;
        }
        crc = crc16(data, packet_size);
        /* 128 byte packets use SOH, 1K use STX */
        UARTCharPut(UART0_BASE, (block_no==0)?SOH:STX);
        UARTCharPut(UART0_BASE, block_no & 0xFF);
        UARTCharPut(UART0_BASE, ~block_no & 0xFF);

        for (count=0; count<packet_size; count++) {
        	UARTCharPut(UART0_BASE, data[count]);
        }
        UARTCharPut(UART0_BASE, (crc >> 8) & 0xFF);
        UARTCharPut(UART0_BASE, crc & 0xFF);
}

/* Send block 0 (the filename block). filename might be truncated to fit. */
void send_packet0(char* filename, unsigned long size)
{
        unsigned long count = 0;
        unsigned char block[PACKET_SIZE];
        const char* num;

        if (filename) {
                while (*filename && (count < PACKET_SIZE-FILE_SIZE_LENGTH-2)) {
                        block[count++] = *filename++;
                }
                block[count++] = 0;

                num = u32_to_str(size);
                while(*num) {
                        block[count++] = *num++;
                }
        }

        while (count < PACKET_SIZE) {
                block[count++] = 0;
        }

        send_packet(block, 0);
}


int send_data_packets(unsigned long file_size)
{
        int blockno = 1;
        int ch, tries;
        unsigned int send_size;
        UINT br;		// Bytes read
        FRESULT fresult;

        // Send the file packets
        do
        {
        	fresult = f_read(&FileObject, packet_data, PACKET_1K_SIZE, &br);	// Read a packet from file
        	if(fresult != FR_OK)
        	{
        		uprintf("f_read error: %s\r\n", StringFromFresult(fresult) );
        		return -1;
        	}

        	if (br == PACKET_1K_SIZE)		// Send full sized packet
        	{
        		send_size = PACKET_1K_SIZE;
            }

        	else if(br < PACKET_1K_SIZE)
        	{
        		send_size = br;			// Send small packet
            }

        	else
        	{
        		printf("br out of range\n");
        		return -1;
        	}

			// Send packet until ACK received.
        	tries = 0;

        	do
			{
				send_packet(packet_data, blockno);		// Send the packet
				printf("sent block %d\n", blockno);
				ch = getchar_timed(PACKET_TIMEOUT);
				printf("response = %02X\n", ch);

				if (ch == ACK)				// Receiver got the packet okay, send next packet
				{
					blockno++;
			    	break;
				}
				else
				{
					if((ch == CAN) || (tries++ > 10))	// Receiver sent CANCEL command or didn't respond
					{
						return -1;
					}
				}
			} while(1);

        } while(send_size == PACKET_1K_SIZE);	// Last packet sent was less than full sized, so end

        // Send two EOTs to signal end of data packets. Receiver should NAK first EOT and ACK C second EOT
        do
        {
        	UARTCharPut(UART0_BASE, EOT);			// Send End of Transmission command
            ch = getchar_timed(PACKET_TIMEOUT);
        } while((ch != ACK) && (ch != -1));		// Keep sending until receiver ACKs or doesn't respond

        /* Send last data packet */
        if (ch == ACK) {
                ch = getchar_timed(PACKET_TIMEOUT);
                if (ch == CRC) {
                        do {
                                send_packet0(0, 0);
                                ch = getchar_timed(PACKET_TIMEOUT);
                        } while((ch != ACK) && (ch != -1));
                }
        }

        return 0;
}

// Send file. Returns -1 on error
int ymodem_send(int argc, char *argv[])
{
        int ch, tries=0, error=0;
        unsigned long file_size;
        FRESULT fresult;
        char filename[30];

    	UARTEchoSet(false);		// Disable console echo

        strncpy(filename, argv[1], sizeof(filename));
		printf("\n\nfilename = %s\n", filename);

    	// Open the file for reading.
    	fresult = f_open(&FileObject, filename, FA_READ);
    	if(fresult != FR_OK)
    	{
    		printf("f_open error: %s\n", StringFromFresult(fresult) );
    		error = -1;
    		goto abort;
    	}

    	file_size = (unsigned long)(FileObject.fsize);  // Set file size
    	printf("File size = %lu\n", file_size);

    	uprintf("Ymodem sending %s, %lu bytes...\r\n", filename, file_size);    //uprintf("Ymodem sending %s...\r\n", filename);
    	UARTFlushTx(false);

        /* Flush the RX FIFO, after a cool off delay */
        MAP_SysCtlDelay(MILLISECOND*1000);
        UARTFlushRx();

        /* Send 'C' and look for 'C' response */
        do
        {
        	UARTCharPut(UART0_BASE, CRC);	// Sending 'C' is not in YMODEM spec, but lets us know sender is waiting
            ch = getchar_timed(1);
			tries++;
           if(tries >= 30) goto abort;

        } while (ch != CRC);		// Look for response for 30 seconds

		// Got the right response, send the file
		tries = 0;

		do
		{
			send_packet0(filename, file_size);
			/* When the receiving program receives this block and successfully
			 * opened the output file, it shall acknowledge this block with an ACK
			 * character and then proceed with a normal XMODEM file transfer
			 * beginning with a "C" or NAK tranmsitted by the receiver.
			 */
			ch = getchar_timed(PACKET_TIMEOUT);

			if (ch == ACK)
			{
				ch = getchar_timed(PACKET_TIMEOUT);

				if (ch == CRC)	// Receiver got packet 0 okay, send the rest
				{
					error = send_data_packets(file_size);
					if(error)
					{
						goto abort;
					}

					else
					{
						uprintf("\r\nSent: %s\r\n", filename);
						goto success;
					}
				}
			}

		} while(tries++ < 10);	// Try sending packet0 for ten seconds

abort:
		UARTCharPut(UART0_BASE, CAN);
		UARTCharPut(UART0_BASE, CAN);
        ROM_SysCtlDelay(MILLISECOND*1000);
        uprintf("\nYmodem aborted\n");

success:
        f_close(&FileObject);
    	UARTEchoSet(true);		// Enable console echo
		return error;
}

