/*
 *------------------------------------------------------------------
 *
 * $Source: /mit/cgw/src/tftp.vax/RCS/tftp.c,v $
 * $Revision: 1.3 $
 * $Date: 88/07/11 19:08:24 $
 * $State: Exp $
 * $Author: jon $
 * $Locker: jon $
 *
 * $Log:	tftp.c,v $
 * Revision 1.3  88/07/11  19:08:24  jon
 * Sommerfeld's changes.
 * 
 * Revision 1.2  88/05/31  21:12:20  wesommer
 * [jon] random changes.
 * 
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_tftp_c = "$Header: tftp.c,v 1.3 88/07/11 19:08:24 jon Locked $";
#endif	lint

#ifdef ETHER
#include "ether.h"
#else
#include "vii.h"
#endif
#include <a.out.h>

struct udp {
	ln_hdr_type ln_hdr;	/* local net header */
	char ip_vhl;    	/* Internet header length in 32 bit words */
	char ip_tsrv;		/* Type of service */
	short ip_len;		/* Total packet length including header */
	short ip_id;		/* ID for fragmentation */
	short ip_flgs;		/* flags and fragment offset */
/*	short ip_foff : 13;	/* Fragment offset */
	char ip_time;		/* Time to live (secs) */
	char ip_prot;		/* protocol */
	short ip_chksum;	/* Header checksum */
	long ip_src;		/* Source name */
	long ip_dst;		/* Destination name */
	short ud_srcp;		/* source port */
	short ud_dstp;		/* dest port */
	short ud_len;		/* length of UDP packet */
	short ud_cksum;		/* UDP checksum */
	short tf_op;		/* tftp opcode */
	short tf_block;		/* block or error code */
	short tf_data;		/* take the address of this */
	};

struct ph {
	long ph_src;		/* source address */
	long ph_dest;	/* dest address */
	char	ph_zero;	/* zero (reserved) */
	char	ph_prot;	/* protocol */
	short	ph_len;		/* udp length */
	};

/* Some goodly constants, macros and an external */
#define	UDPPROT	17	/* UDP Internet protocol number */
#define	UDPHDRSIZE	(sizeof(struct udp)-sizeof(struct ip))


/* TFTP opcodes (byte swapped by hand) */
#define	RRQ	0x0100		/* read  request */
#define	WRQ	0x0200		/* write request */
#define	TDATA	0x0300		/* data packet */
#define	ACK	0x0400		/* acknowledgement packet */
#define	ERROR	0x0500		/* error packet */

/* TFTP error codes */
#define	ERRTXT		0	/* see the enclosed text */
#define	FNOTFOUND	1	/* file not found */
#define	ACCESS		2	/* access violation */
#define	DISKFULL	3	/* don't even ask. */
#define	ILLTFTP		4	/* illegal TFTP operation */
#define	BADTID		5	/* unkown transfer ID */
#define	FEXISTS		6	/* file already exists */
#define	NOUSER		7	/* no such user */

/* TFTP states */
#define	DATAWAIT	1
#define	ACKWAIT		2
#define	DEAD		3
#define	TIMEOUT		4
#define	RCVERR		5
#define	RCVACK		6
#define	RCVDATA		7
#define	RCVLASTDATA	8
#define	TERMINATED	9

#define	TFTPPORT	0x4500	/* TFTP's well known port (69) */
#define	TFTPTRIES	20	/* # of retries on packet transmission */
#define	REQTRIES	4	/* # of retries on initial request */
#define	REQLEN		512	/* stupid, stupid... */
#define	NORMLEN		512	/* normal length of received packet */

/*  Constants for round trip time estimation and retry timeout */
/* All calculation is done in clock ticks (at a rate of 18/second) but
 * only the initial estimate and the upper limit are specified in
 * ticks; the rest of the algorithm uses dimensionless multipliers.
 */

#define	Kinit	3	/* Initial divisor for (1+1/K) estimate multiplier. */
#define	Kinc	1	/* Reduce K by this if previous packet lost.  */
#define	T0	15	/* Initial value for round trip time estimate.  */
#define	MAXTMO	216	/* upper limit on retry timeout timer, in ticks.  */
#define	TMMULT  3	/* multiplier to get retry timeout from round trip
			   estimate.  */

#define	max(a,b)	((a) > (b) ? (a) : (b))
#define	min(a,b)	((a) < (b) ? (a) : (b))

/* This is the source code for TFTP. I've tried to write it so that it
	may be called from inside a program with minimal hassle... */

char pin[1600];
char pout[1600];

struct udp *pip;
struct udp *pop;

int tf_ous, tf_tmo, tf_rsnd, tf_size, tf_K, tf_trt, tf_rt;
int tf_NR, tf_NR_last, tf_expected, tf_lport, tf_fport, tf_fhost;
int tf_tries, tf_lastlen, tf_sent, tf_entry;
int nbytes, nskip, ntbytes;
int ip_uid = 1;

extern char *load_start;
char *tf_data;
int tf_bss;

extern long in_me;
extern long gateway;

tftp_use(fhost, rmfile)
	long fhost;
	char *rmfile;
{
	unsigned len;
	register char *data;
	struct exec *x;
	register int i, j;

	pip = (struct udp *)pin;
	pop = (struct udp *)pout;
	tf_fhost = fhost;
	tf_expected = 1;
	tf_fport = 0;
	tf_lport = 0x2020;
	tf_ous = 0;
	tf_tmo = 0;
	tf_rsnd = 0;
	tf_size = 0L;
	tf_K = Kinit;
	tf_trt = T0;
	tf_rt = min( tf_trt*TMMULT , MAXTMO);
	tf_NR = 0;
	tf_NR_last = 1;

	pop->ud_srcp = 0x2020;
	pop->ud_dstp = TFTPPORT;

	pop->ip_prot = UDPPROT;
	pop->ip_vhl = 0x45;
	pop->ip_time = 0xff;
	pop->ip_flgs = 0;
	pop->ip_id = ip_uid++;
	pop->ip_src = in_me;
	pop->ip_dst = fhost;
	pop->ip_tsrv = 0;

	tfsndreq(rmfile);

	while(1) {
		while((len = net_read(pin, 1600)) == 0)
			;
		/*			if(pendchar()) {
		 * tfcndump();
		 * return 0;
		 * } */

		if(pip->ip_dst != in_me) continue;

		len = swab(pip->ip_len) - 28;

#ifdef DEBUG
		printf("got pkt, length = %d\n", len);
#endif

		if(pip->ip_src != tf_fhost) {
			/*			dump(pip); */
			continue;
		}

		if(pip->ud_dstp != tf_lport) {
			/*			dump(pip); */
			continue;
		}

		if(tf_fport && pip->ud_srcp != tf_fport) {
			continue;
		}

		if(pip->tf_op == TDATA) {
			if(tf_fport == 0) {
				tf_fport = pip->ud_srcp;
				pop->ud_dstp = tf_fport;
			}

			if(len < 4) {
				puts("TFDODATA: Died of CSR disease.\n");
				return 0;
			}

			len -= 4; /* sizeof(tftp header).  BAD. */

			if(swab(pip->tf_block) != tf_expected) {
				/*
				 * puts("TFTP: Got block ");
				 * phex(swab(pip->tf_block));
				 * puts(", expecting ");
				 * phex(tf_expected);
				 * puts(".\n");
				 */
				tfsndack(tf_expected - 1);
				continue;
			}

			/* Send the ack before writing the data */
			/*	tf_good();	*/
			puts("#");

			/* if it's block 1, get info from the exec header
			 */
			if(tf_expected == 1) {
				x = (struct exec *)&pip->tf_data;
				if (x->a_magic != ZMAGIC
				    && x->a_magic != NMAGIC) {
					puts("Not a ZMAGIC or NMAGIC file\n");
					return 0;
				}

				tf_entry = x->a_entry & 0x7fffffff;
				tf_bss = x->a_bss;
				tf_data = load_start;
				nskip = N_TXTOFF(*x);
				ntbytes = x->a_text;
				nbytes = ntbytes + x->a_data;
			}
			if (tf_expected >= 1  && nbytes > 0) {
				data = (char *)&pip->tf_data;
#ifdef DEBUG
				puts ("tf_data = ");
				phex (tf_data);
				puts ("\n");
#endif
				for(i=0; i<512; i++) {
					if (nskip-- > 0) data++;
					else {
						--nbytes;
						if (nbytes <= 0) break;
						if (ntbytes-- == 0)
							tf_data = (char *)(((int)tf_data + 1023) & ~0x3ff);
						*tf_data++ = *data++;
					}
				}
			}
			tfsndack(tf_expected++);

			/* and here we blithely drop the rest of the packets */

			if(len == NORMLEN) continue;
			else {
				while (tf_bss--)
					*tf_data++ = 0;
				puts("file loaded\n");
				puts("entry point is ");
				phex(tf_entry);
				puts("\n");
				launch (tf_entry, tf_data-load_start);
				return 1;
			}
		}

		else if(pip->tf_op == ERROR) {
			tfdoerr(len);
			return 0;
		}
		else {
			puts("TFTPRCV: Got bad opcode %d.\n", pip->tf_op);
			continue;
		}

	}
}

#define MINTICKS 20

/* Utility routines */
/*
tftptmo() {
	puts("TFTP: Timeout.\n");

	if(--tf_tries) {
		tf_rsnd++;
		tf_NR++;
		udp_write(pout, tf_lastlen, tf_fhost,
				tf_fport ? tf_fport : TFTPPORT, tf_lport);
		}
	}
*/
tf_good() {
	long trtM;

/*	trtM = cticks - tf_sent;  /*  Measured round trip time  */
	if(tf_NR_last == 1) tf_K = Kinit;

	if(tf_NR == 1)
		tf_trt = (trtM+tf_trt)/2;
	else {
		if((tf_NR_last > 1) && (tf_K >1) )
			tf_K -= Kinc;

		tf_trt += tf_trt/tf_K;
		}
	tf_rt = max(min( tf_trt*TMMULT , MAXTMO), MINTICKS);
	tf_NR_last = tf_NR;
	}

/* Format up and send out an initial request for a tftp connection. */

tfsndreq(fname)
	char *fname; {

	pop->tf_op = RRQ;

	strcpy((char *)&(pop->tf_block), fname);
	strcpy((char *)&(pop->tf_block)+strlen(fname)+1, "image");

	puts("TFTP:  sending initial request\n");
	tf_write(strlen(fname)+9);
	}

/* Process an incoming error packet */

tfdoerr(len)
	unsigned len; {

	puts("TFTP: Error from foreign host:\n");
	puts(&(pip->tf_data));
	}

/* ack a certain block number */

tfsndack(number)
	unsigned number; {

	tf_lastlen = 4;
	pop->tf_op = ACK;
	pop->tf_block = swab(number);
#ifdef DEBUG
	printf("ack %d\n", number);
#endif
	return tf_write(tf_lastlen);
	}

/* write a tftp packet */

tf_write(len)
	unsigned len; {

#ifdef DEBUG
	printf("tfwrite(%d)\n", len);
#endif

	if(pop->tf_op != RRQ) tf_tries = TFTPTRIES;
	else tf_tries = REQTRIES;

	tf_lastlen = len;

	len += 8;
	if(len & 1) ((char *)&pop->ud_srcp)[len] = 0;

	pop->ud_len = swab(len);

/*	php.ph_src = in_me;
	php.ph_dest = fhost;
	php.ph_zero = 0;
	php.ph_prot = UDPPROT;
	php.ph_len = pup->ud_len;
	pop->ud_cksum = cksum(&php, sizeof(struct ph)>>1, 0);
	pop->ud_cksum = ~cksum(&pup->ud_srcp, (len+1)>>1, 0);
*/
	pop->ud_cksum = 0;

	len += 20;
	pop->ip_len = swab(len);
	pop->ip_id = ip_uid++;
	pop->ip_chksum = 0;
	pop->ip_chksum = ~cksum(&pop->ip_vhl, 10, 0);

	return net_write(pop, gateway ? gateway : tf_fhost, len);
/*	tf_sent = cticks;
	tf_NR = 1;
*/
	}


/* Dump a connection block for debugging purposes. */
/*
tfcndump() {

	printf("lastlen = %d\texpected = %d\n",
				tf_lastlen, tf_expected);
	printf("ous = %d\tmo = %d\trsnd = %d\n\n", tf_ous,tf_tmo, tf_rsnd);
	printf("round trip delay = %U\tK = %d\tcurnt tmo = %U\n",
			tf_trt, tf_K, tf_rt);
	}
 */
