/*******************************************************************************
        samples\tcpip\tftp\tftp.c
        Z-World, 2000

        An example using UDP, that implements a tftp server
        that can send and receive files.

*******************************************************************************/
#class auto


/***********************************
 * Configuration                   *
 * -------------                   *
 * All fields in this section must *
 * be altered to match your local  *
 * network settings.               *
 ***********************************/

/*
 * Pick the predefined TCP/IP configuration for this sample.  See
 * LIB\TCPIP\TCP_CONFIG.LIB for instructions on how to set the
 * configuration.
 */
#define TCPCONFIG 1


/*
 * This sample does not need any TCP sockets, and only 1 UDP socket.
 * Override default settings.
 */
#define MAX_TCP_SOCKET_BUFFERS 0
#define MAX_UDP_SOCKET_BUFFERS 1

/*
 * TFTP configuration
 *
 * These define the test file that we will serve up
 */
 
/* name of sample file */
#define SAMPLEFILENAME	"test"

/* size of the file (in bytes) */
#define SAMPLEFILELEN	1024

/********************************
 * End of configuration section *
 ********************************/

/*********************************************
 * Notes:
 *  - transfer type 'octet' (binary) is always assumed (for now)
 *
 *********************************************/

#memmap xmem 
#use "dcrtcp.lib"

/* The sample file we can send */
char samplefile[SAMPLEFILELEN];

udp_Socket tftpsock;

longword remoteip;
word remoteport;

#define TIMEOUT_LENGTH	10

#define PBUF_LEN	518
char pbuf[PBUF_LEN];

/* Error message types */
#define ERR_OTHER		0
#define ERR_NOTFOUND	1
#define ERR_ACCESS	2
#define ERR_NOSPACE	3
#define ERR_ILLEGAL	4
#define ERR_PORT		5
#define ERR_EXISTS	6
#define ERR_NOUSER	7

int open_udp_sock(longword addr)
{
	/*printf("Opening UDP socket\n");*/
	
	if(!udp_open(&tftpsock, 69, addr, remoteport, NULL)) {
		printf("udp_open failed!\n");
		return 1;
	}
	return 0;
}

void senderror(char type, char *msg)
{
	pbuf[0] = 0;	/* set the error opcode */
	pbuf[1] = 5;

	pbuf[2] = 0;     /* set the error type */
	pbuf[3] = type;

	strcpy(pbuf + 4, msg);

	udp_send(&tftpsock, pbuf, strlen(msg) + 5);
	printf("Error message <%d> '%s' was sent\n",type,msg);
}

void sendack(int block)
{
	pbuf[0] = 0;	/* set the error opcode */
	pbuf[1] = 4;

	pbuf[2] = ((char*)&block)[1];     /* set the error type */
	pbuf[3] = ((char*)&block)[0];     
	
	udp_send(&tftpsock, pbuf, 4);
	printf("ACK <%d> was sent\n", block);
}

/* 
 * waits for an ACK to be read; returns 0 on ACK == block, 1 on error, 2 on timeout
 */
int waitforack(int block)
{
	auto int temp, retval;
	auto unsigned long timestamp;

	timestamp = SEC_TIMER;

	while(1) {
		if(SEC_TIMER > (timestamp + TIMEOUT_LENGTH)) {
			printf("TIMEOUT on waiting for ACK; resending ACK.\n");
			return 2;
		}

		tcp_tick(&tftpsock);
		retval = udp_recv(&tftpsock, pbuf, PBUF_LEN);
		if(retval > 0) {
			switch(pbuf[1]) {
			case 4: 
				/* ACK received */
				temp = pbuf[3] + (256 * pbuf[2]);
				printf("Got ACK <%d>\n", temp);
				if( temp == block )
					return 0;

				/* wrong ACK? */
				printf("Received incorrect ACK (%d)\n",temp);
				break; /* do nothing; wait for the correct ACK */
			
			case 5: 
				/* ERROR was received. Bail out */
				printf("ERROR was received. Bailing...\n");
				return 1;
			
			default: 
				/* unknown opcode; bailing */
				senderror(ERR_ILLEGAL, "Expected ACK");
				return 1;
			}
		}
	}
}

int doread(void)
{
	auto long length, count;
	auto unsigned long file;
	auto int block, retval;
	auto char *f;

	/* handle the RRQ; put the file to the client */
	if(0 == strcmp(SAMPLEFILENAME, pbuf + 2) ) {
		/* the file requested was good; send it */

		/*file = samplefile;
		 *xmem2root(&length, file, 4);
		 *file += 4; */
		f = samplefile;
		length = sizeof(samplefile);
		 
		printf("File had length %ld\n",length);

		count = 0; block = 0;
		while(length >= 512) {
			block++;
			
			/* write out the full length data packets */

			do {
				pbuf[0] = 0;		/* set the opcode */
				pbuf[1] = 3;				
				
				pbuf[2] = ((char*)&block)[1];     /* set the block # */
				pbuf[3] = ((char*)&block)[0];     

				/*xmem2root(pbuf + 4, file + count, 512);*/ /* fill the packet with data */
				memcpy(pbuf + 4, f + (int)count, 512);
											
			
				udp_send(&tftpsock, pbuf, 516);
				printf("Wrote data block <%d> with size 512\n",block);
				tcp_tick(&tftpsock);
			
				retval = waitforack(block);
				if(1 == retval) return 1;
			} while(2 == retval);
			
			count += 512;
			length -= 512;
		}

		/* finish up the remaining bytes */
		block++;

		do {
			pbuf[0] = 0;		/* set the opcode */
			pbuf[1] = 3;				

			pbuf[2] = ((char*)&block)[1];     /* set the block # */
			pbuf[3] = ((char*)&block)[0];     

		
			/*xmem2root(pbuf + 4, file + count, (int)length);*/
			memcpy(pbuf + 4, f + (int)count, (int)length);
				
			udp_send(&tftpsock, pbuf, (int)length + 4);
			printf("Wrote data block <%d> with size %d\n", block, (int)length);
			
			retval = waitforack(block);
			if(1 == retval) {
				return 1;
			}
		} while(2 == retval);

		printf("All done.\n\n");
		return 0;
	} else {
		/* unknown file; error out */
		senderror(ERR_NOTFOUND, "File was not found");
		return 1;
	}
}

int getdatapkt(int block)
{
	auto int temp, retval;

	auto unsigned long timestamp;

	timestamp = SEC_TIMER;
			
	while(1) {
		if(SEC_TIMER > (timestamp + TIMEOUT_LENGTH)) {
			printf("TIMEOUT on reading data packet; resending ACK.\n");
			sendack(block - 1);
			timestamp = read_rtc();
		}
	
		tcp_tick(&tftpsock);
		retval = udp_recv(&tftpsock, pbuf, PBUF_LEN);
		if(retval > 0) {
			switch(pbuf[1]) {
			case 3:
				/* DATA received */
				temp = pbuf[3] + (256 * pbuf[2]);
				printf("Got data pkt <%d>\n", temp);
				if( temp == block ) {
					/* correct data file was read */
					sendack(block);	/* ACK the block */
					return retval;			/* return the length read */
				}

				/* wrong DATA? */
				break; /* do nothing; wait for the correct data pkt */
			
			case 5:
				/* ERROR was received. Bail out */
				return -1;
			
			default:
				/* unknown opcode; bailing */
				senderror(ERR_ILLEGAL, "Expected DATA");
				return -1;
			}
		}
	}	
}

/* temporary storage for the received file */
char buf[2048];

int dowrite(void)
{
	auto char *file;
	auto int buflen, block, retval;

	/* handle the WRQ; get the file from the user */

	/* get a place to put the file */
	file = buf;
	buflen = 2048;

	printf("Receiving '%s'...\n",pbuf+2);
	
	sendack(0); /* ACK the WRQ, wait for the first data packet */
	
	block = 0;
	while(1) {
		block++;
		
		/* get a data packet */
		retval = getdatapkt(block);
		if(-1 == retval) return 1;

		retval -= 4;	/* strip off header */
				
		if(retval > buflen) { /* check for storage space */
			senderror(ERR_NOSPACE, "Out of room to store file");
			return 1;
		}
		
		memcpy(file, pbuf+4, retval); /* copy the file to dest */
		file += retval;
		buflen -= retval;
		
		if(retval < 512) {
			/* all done */
			printf("RECEIVED FILE-->\n");
			for(retval = 0; retval < (2048-buflen); retval++)
				putchar(buf[retval]);
			printf("<--END OF RECEIVED FILE\n\n");
			return 0;
		}
	}
}

int do_tftp(void)
{
	auto int retval;

	remoteip = remoteport = 0;
	if(open_udp_sock(0)) /* open listen port */	
		return 0;
	
	while(tcp_tick(&tftpsock)) {
			
		retval = udp_recvfrom(&tftpsock, pbuf, PBUF_LEN, &remoteip, &remoteport);
		// Must have at least two bytes
		if(retval >= 2) {
			printf("REMOTEIP == 0x%lx, REMOTEPORT == 0x%x\n", remoteip, remoteport);

			/* possible request was received */
			switch(pbuf[1]) {
			case 1: {
				/* RRQ */
				printf("Read request received for file '%s'\n",pbuf+2);
				doread();
				break;
			}
			case 2: {
				/* WRQ */
				printf("Write request received for file '%s'\n",pbuf+2);
				dowrite();
				break;
			}
			case 4: {
				printf("ACK <%d> was received (error)\n",pbuf[3] + (256 * pbuf[2]));
				break;
			}
			default:
				/* error? */
				printf("Bad opcode was received: %d\n", pbuf[1]);
				senderror(ERR_ILLEGAL, "opcode was not RRQ or WRQ");
			}

			/* reset the udp socket */
			remoteip = remoteport = 0;
			udp_close(&tftpsock);
			if(open_udp_sock(0))
				return 0;
		}
	}
			
	sock_close(&tftpsock);
	return 0;
}

void main()
{
	auto int i;
	
	sock_init(); /* init tcp/ip */

	for(i = 0; i < SAMPLEFILELEN; i++) {
		samplefile[i] = 'a';
	}
		
	while(1) {
		do_tftp();
	}
}
