
/*****************************************************************************
* Copyright (c) 2002 Digi International Inc., All Rights R
* This software contains proprietary and confidential information of Digieserved
* 
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
* 
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*****************************************************************************/
/*
 * $Log: tftpc.c,v $
 * Revision 1.6  2008/03/28 19:46:41  jwang
 * changes all members of the tftpc_conn_t structure to "volatile" as this structure is accessed both from the bootloader code and the ethernet interrupt level.
 *
 * Revision 1.5  2005/09/29 17:19:07  lming
 * Changed include <> back to " "
 *
 * Revision 1.4  2005/09/22 20:01:25  lming
 * Changed all includes from <> to " "
 *
 * Revision 1.3  2005/01/28 19:39:43  vshankar
 * Added error handling
 *
 * Revision 1.2  2003/06/25 14:45:43  jwolf
 *  replace all c++-style comments with c-style comments
 *
 * Revision 1.1  2003/04/19 21:40:44  cgordon
 * Restructured BSP so that multiple development boards could be supported easily.  This mainly meant moving the contents of the customize directory into platform specific directories.  Header files in the platform directory were changed so that external applications would no longer refer to them.
 *
 * Revision 1.2  2002/11/04 18:27:23  cgordon
 * Modified to work with GNU.  Fixed issues with GNU's nonhandling of packed structures and bit fields.
 *
 * Revision 1.1  2002/10/07 22:23:51  cgordon
 * Added bootloader and customization hooks into BSP
 *
 *
 */
/*
 * TFTP CLIENT
 *
 * TFTP protocol  revision 2 - RFC 1350.
 */
/*#define UNIT_TEST */
/*#include <stdio.h> */

/* #include <assert.h> : Assert pulls in Thread-X and therefore cannot
 * be used in the boot code.
 */
#include <stddef.h>
#include <string.h>
#include "tftpc.h"
#include "udp.h"
#include "bele.h"
#include "log.h"
#include "timer.h"
#include <bootldr.h>
#include <blerror.h>

/*
 * Maximum number of retries we will allow in a connection.
 */
#define TFTP_MAX_RETRIES        100

unsigned long delay_ms(unsigned long ms);    /* narmbase.c */

typedef struct {
    int send_noalloc;
    int udpsend_failed;
    int datasend_failed;
    int acksend_failed;
    int errsend_failed;
} tftpcstats_t;

tftpcstats_t tftpcstats;

typedef struct {
    volatile WORD16 tftp_port; /* Installed TFTP port. */
    volatile int open;	    /* Is the connection closed, halfopen, or open? */
    volatile WORD16 rw;	    /* TFTP_RRQ or TFTP_WRQ */
    volatile WORD32 destip;   /* IP of connected peer. */
    volatile WORD16 stid;	    /* Source TID */
    volatile WORD16 dtid;	    /* Dest TID */
    volatile WORD32 address;  /* Destination address of file. */
    volatile WORD16 block_number; /* Block number of expected next block. */
    volatile WORD32 remaining_length; /* Number of bytes remaining to be sent. */
    volatile PACKET *last_packet; /* Last packet.  Copy in, copy out.  Don't send it! */
    volatile int retries;        /* number of retries in this connection so far*/
} tftpc_conn_t;

/* Currently we only support 1 connection at a time. */
tftpc_conn_t tftpcconn;

/*
 * Size of buffer to receive file, and number of bytes received so far.
 */
static WORD32 bufferSize, bytesReceived;
static char *buffer;
static int getResult;

void TftpCQuitLastPacket(void)
{
    tftpcconn.last_packet->length = 0;
}

BOOLEAN TftpCData(BYTE *data, int length)
{
    int bytesToCopy = bufferSize - bytesReceived;

    if (bytesToCopy > length)
    {
        bytesToCopy = length;
    }

    if (bytesToCopy)
    {
        memcpy (buffer, data, bytesToCopy);
        buffer += bytesToCopy;
        bytesReceived += length;
    }

   /*
    * Return TRUE if buffer overflow occurred.
    */
    return ((length - bytesToCopy) != 0);
}


void TftpCRetransmit(WORD32 context)
{
    PACKET *packet;
    if (tftpcconn.last_packet->length)
    {
        if (tftpcconn.retries < TFTP_MAX_RETRIES)
        {
        	TimerSchedule(TIMER_TICKS_PER_SEC, TftpCRetransmit, 0);
        	packet = PacketAllocate();
        	if (!packet)
        	{
        	    ++tftpcstats.send_noalloc;
        	    return;
        	}
        	memcpy(&packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE],
			(const void *)&tftpcconn.last_packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE],
        	       tftpcconn.last_packet->length);
        	packet->length = tftpcconn.last_packet->length;
        
        	if (!UdpSend(packet, tftpcconn.destip, tftpcconn.stid, tftpcconn.dtid))
        	{
        	    ++tftpcstats.udpsend_failed;
        	    PacketFree(packet);
        	}
            tftpcconn.retries++;
        }
        else
        {
            TftpCQuitLastPacket();
            UdpUnbindPort(tftpcconn.stid);
            getResult = BL_TFTPC_RETRANSMIT_ERROR;
            tftpcconn.open = FALSE;
        }
            
    }
}

BOOLEAN TftpCSend(PACKET *packet)
{
    /* TODO: It would be smarter to load the packet to send into the last
       packet, and then try to allocate one to transmit.  In this way,
       allocation failures can be gracefully handled when packet buffers
       are available, instead of dropping back to a previous packet.
    */
    /* Save packet for possible retransmission. */
    memcpy((void *)&tftpcconn.last_packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE],
	   &packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE], packet->length);
    tftpcconn.last_packet->length = packet->length;
    TimerSchedule(TIMER_TICKS_PER_SEC, TftpCRetransmit, 0);

    return UdpSend(packet, tftpcconn.destip, tftpcconn.stid, tftpcconn.dtid);
}

BOOLEAN tftpc_rq(char *filename, char *mode)
{
    PACKET *packet = PacketAllocate();
    tftp_rq_header_t *tftpcframe;
    char *p;
    
    if (!packet)
    {
	++tftpcstats.send_noalloc;
	return FALSE;
    }
    tftpcframe = (tftp_rq_header_t *)&packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE];
    tftpcframe->opcode = tftpcconn.rw;
    strcpy(tftpcframe->str, filename);
    p = tftpcframe->str + strlen(tftpcframe->str) + 1;
    strcpy(p, mode);
    packet->length = offsetof(tftp_rq_header_t, str)
		     + (p - (tftpcframe->str)) + strlen(p) + 1;

    if (!TftpCSend(packet))
    {
	++tftpcstats.acksend_failed;
	PacketFree(packet);
	return FALSE;
    }
    /* ArmEth owns packet; don't free it! */
    return TRUE;
}

BOOLEAN tftpc_data(BYTE *data, WORD32 length)
{
    PACKET *packet = PacketAllocate();
    tftp_header_t *tftpcframe;
    
    if (!packet)
    {
	++tftpcstats.send_noalloc;
	return FALSE;
    }
    tftpcframe = (tftp_header_t *)&packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE];
    tftpcframe->opcode = TFTP_DATA;
    tftpcframe->blockNumberOrErrorCode = tftpcconn.block_number;
    memcpy(tftpcframe->data, data, length);
    packet->length = offsetof(tftp_header_t, data) + length;

    if (!TftpCSend(packet))
    {
	++tftpcstats.acksend_failed;
	PacketFree(packet);
	return FALSE;
    }
    /* ArmEth owns packet; don't free it! */
    return TRUE;
}

BOOLEAN tftpc_ack(void)
{
    PACKET *packet = PacketAllocate();
    tftp_header_t *tftpcframe;
    
    if (!packet)
    {
	++tftpcstats.send_noalloc;
	return FALSE;
    }
    tftpcframe = (tftp_header_t *)&packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE];
    tftpcframe->opcode = TFTP_ACK;
    tftpcframe->blockNumberOrErrorCode = tftpcconn.block_number;
    packet->length = offsetof(tftp_header_t, blockNumberOrErrorCode) + sizeof(tftpcframe->blockNumberOrErrorCode);

    if (!TftpCSend(packet))
    {
	++tftpcstats.acksend_failed;
	PacketFree(packet);
	return FALSE;
    }
    /* ArmEth owns packet; don't free it! */
    return TRUE;
}

BOOLEAN tftpc_err(int error)
{
    PACKET *packet = PacketAllocate();
    tftp_header_t *tftpcframe;
    if (!packet)
    {
	++tftpcstats.send_noalloc;
	return FALSE;
    }
    tftpcframe = (tftp_header_t *)&packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE];
    tftpcframe->opcode = TFTP_ERROR;
    tftpcframe->blockNumberOrErrorCode = error;
    tftpcframe->data[0] = 0;
    packet->length = offsetof(tftp_header_t, data) + sizeof(tftpcframe->data[0]);
    if (!TftpCSend(packet))
    {
	TftpCQuitLastPacket();
	++tftpcstats.errsend_failed;
	PacketFree(packet);
	return FALSE;
    }
    TftpCQuitLastPacket();
    /* ArmEth owns packet; don't free it! */
    return TRUE;
}


    
/* The well known port number for TFTP is 69. */
BOOLEAN TftpCInitialize(WORD16 port)
{
    memset(&tftpcstats, 0, sizeof(tftpcstats));
    memset(&tftpcconn, 0, sizeof(tftpcconn));
    tftpcconn.tftp_port = port;
    tftpcconn.last_packet = PacketAllocate();
    if (!tftpcconn.last_packet)
	return FALSE;
    tftpcconn.last_packet->length = 0;
    return TRUE;
}

void TftpCFinalize(void)
{
    TftpCQuitLastPacket();
    TimerStop(TftpCRetransmit, 0);
    PacketFree((PACKET *)tftpcconn.last_packet);
}

void TftpCReceive(PACKET *packet, WORD32 header_offset)
{
    BOOLEAN bufferOverflow;

    tftp_header_t *tftpframe = (tftp_header_t *)&packet->frame[header_offset+sizeof(udp_header_t)];
    udp_header_t *udpframe = (udp_header_t *)&packet->frame[header_offset];
    if ((tftpcconn.open && tftpcconn.stid != udpframe->dport)
	    || (tftpcconn.open == TFTP_OPEN
		&& tftpcconn.dtid != udpframe->sport))
    {
	/* Don't send message or Cygwin TFTP cancels send! */
/*	tftpc_err(TFTP_ERROR_UNKNOWN_TID); */
	/* Maintain connection. */
	return;	    /* Ignore the duplicate message */
    }
    switch (tftpframe->opcode)
    {
	case TFTP_RRQ:
	case TFTP_WRQ:
	    tftpc_err(TFTP_ERROR_NO_SUCH_USER);
	    break;
	case TFTP_DATA:
	{
	    if (!tftpcconn.open)
	    {
		tftpc_err(TFTP_ERROR_UNKNOWN_TID);
		break;
	    }
	    if (tftpcconn.rw != TFTP_RRQ)
	    {
		tftpc_err(TFTP_ERROR_ILLEGAL_TFTP_OP);
		break;
	    }
	    if (tftpframe->blockNumberOrErrorCode == tftpcconn.block_number)
	    {
		/* Expected this one. */
		int length = udpframe->length -sizeof(udp_header_t) - offsetof(tftp_header_t, data);

		if (tftpcconn.open == TFTP_HALFOPEN)
		{
		    tftpcconn.open = TFTP_OPEN;
		    tftpcconn.dtid = udpframe->sport;
		}

		bufferOverflow = TftpCData(tftpframe->data, length);
        if (bufferOverflow)
        {
            tftpc_err(TFTP_ERROR_DISK_FULL);
            TftpCQuitLastPacket();
            UdpUnbindPort(tftpcconn.stid);
            getResult = BL_TFTPC_BUFFER_TOO_SMALL;
            tftpcconn.open = FALSE;
        }
		else 
        {
            if (length < 512)
    		{
    		    /* Last packet */
    		    UdpUnbindPort(tftpcconn.stid);
    		    tftpcconn.open = FALSE;
                getResult = BL_SUCCESS;
    		}
    		tftpc_ack();
    		if (length < 512)
    		    TftpCQuitLastPacket();
    		++tftpcconn.block_number;
    	    }
        }
	    else if (tftpframe->blockNumberOrErrorCode == tftpcconn.block_number-1)
	    {
		/* They missed our data.  Send it again. */
		TftpCRetransmit(0);
	    }
	    else
	    {
		/* Bad block number. */
		tftpc_err(TFTP_ERROR_ILLEGAL_TFTP_OP);
	    }
	    break;
	}
	case TFTP_ACK:
	{
	    if (!tftpcconn.open)
	    {
		tftpc_err(TFTP_ERROR_UNKNOWN_TID);
		break;
	    }
	    if (tftpcconn.rw != TFTP_WRQ)
	    {
		tftpc_err(TFTP_ERROR_ILLEGAL_TFTP_OP);
		break;
	    }
	    if (tftpframe->blockNumberOrErrorCode == tftpcconn.block_number)
	    {
		/* Expected this one. */
		int length;

		if (tftpcconn.open == TFTP_HALFOPEN)
		{
		    tftpcconn.open = TFTP_OPEN;
		    tftpcconn.dtid = udpframe->sport;
		}
		length = tftpcconn.remaining_length;
		if (length > 512)
		    length = 512;
		if (tftpcconn.remaining_length == 0)
		{
		    /* We're done. */
		    TftpCQuitLastPacket();
		    UdpUnbindPort(tftpcconn.stid);
		    tftpcconn.open = FALSE;
		    break;
		}
		tftpcconn.remaining_length -= length;
		++tftpcconn.block_number;

		length = tftpcconn.remaining_length;
		if (length > 512)
		    length = 512;

		tftpc_data((BYTE *)tftpcconn.address, length);
	    }
	    else if (tftpframe->blockNumberOrErrorCode == tftpcconn.block_number-1)
	    {
		/* They missed our data.  Send it again. */
		TftpCRetransmit(0);
	    }
	    else
	    {
		/* Bad block number. */
		tftpc_err(TFTP_ERROR_ILLEGAL_TFTP_OP);
	    }
	    break;
	}
	case TFTP_ERROR:
	{
	    TftpCQuitLastPacket();
	    UdpUnbindPort(tftpcconn.stid);
	    tftpcconn.open = FALSE;
	    break;
	}
	default:
	{
	    tftpc_err(TFTP_ERROR_ILLEGAL_TFTP_OP);
	}
    }
}

BOOLEAN TftpCPut(WORD32 destip, char *filename, char *mode, WORD32 address)
{
    /* We want to write a file. */

    if (tftpcconn.open)
	return FALSE;
    if (strcmp(mode, "octet") != 0 && strcmp(mode, "OCTET") != 0)
	return FALSE;

    tftpcconn.open = TRUE;
    tftpcconn.rw = TFTP_WRQ;
    tftpcconn.destip = destip;
    tftpcconn.stid = UdpChooseFreePort(); /* TODO: Add random. */
    tftpcconn.dtid = tftpcconn.tftp_port;
    tftpcconn.block_number = 0;
    UdpBindPort(tftpcconn.stid, TftpCReceive);
    if (!tftpc_rq(filename, mode))
    {
	/* Let it retry. */
    }
    return TRUE;
}

BOOLEAN TftpCGet(WORD32 destip, char *filename, char *mode, WORD32 address)
{
    /* We want to read a file. */
    if (tftpcconn.open)
	return FALSE;
    if (strcmp(mode, "octet") != 0 && strcmp(mode, "OCTET") != 0)
	return FALSE;

    tftpcconn.open = TRUE;
    tftpcconn.rw = TFTP_RRQ;
    tftpcconn.destip = destip;
    tftpcconn.stid = UdpChooseFreePort(); /* TODO: Add random. */
    tftpcconn.dtid = tftpcconn.tftp_port;
    tftpcconn.block_number = 1;
    tftpcconn.retries = 0;
    UdpBindPort(tftpcconn.stid, TftpCReceive);
    if (!tftpc_rq(filename, mode))
    {
	/* Let it retry. */
    }
    return TRUE;
}



/*
 *
 *  Function: int blTftpGetFile(char *destination, WORD32 maxSize, WORD32 serverAddress, char *filename, WORD32 *bytesReceived)
 *
 *  Description:
 *
 *      This function downloads a file from a TFTP server.  The contents of the
 *      file are copied to a buffer in RAM.  If the buffer is too small, then
 *      as much of the file as possible is copied into it and the transfer
 *      is aborted.
 *
 *  Parameters:
 *
 *      destination     pointer to buffer to receive file
 *      maxSize         size of buffer in bytes
 *      serverAddress   IP address as a WORD32 in network order
 *      filename        pathname of file to download
 *      bytesReceived   number of bytes actually received
 *
 *  Return Values:
 *
 *  BL_SUCCESS                  file has been downloaded
 *  BL_TFTPC_INITIALIZE_ERROR   TftpCInitialize() failed
 *  BL_TFTPC_GET_ERROR          TftpCGet() failed
 *  BL_TFTPC_RETRANSMIT_ERROR   TftpCRetransmit() failed
 *  BL_TFTPC_BUFFER_TOO_SMALL   buffer was too small for entire file
 *
 */

int blTftpGetFile(char *destination, WORD32 maxSize, WORD32 serverAddress, char *filename, WORD32 *bReceived)

{
    static BOOLEAN tftpInitialized = FALSE;

    if (!tftpInitialized)
    {
        if (TftpCInitialize(69) == FALSE)
        {
            return BL_TFTPC_INITIALIZE_ERROR;
        }
        tftpInitialized = TRUE;
    }

    bufferSize = maxSize;
    bytesReceived = 0;
    buffer = destination;
    getResult = BL_FAILURE;

    if (TftpCGet(serverAddress, filename, "octet", (WORD32) destination) == FALSE)
    {
        return BL_TFTPC_GET_ERROR;
    }

    while (tftpcconn.open)
    {
    }

    *bReceived = bytesReceived;

    return getResult;
}

