#include "vii.h"
/* #include "/usr/sun/include/b.out.h" */
#include "ldb.h"

struct udp {
	struct vii_hdr 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 : 3;	/* flags */
	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 */
#define	RRQ	1		/* read  request */
#define	WRQ	2		/* write request */
#define	TDATA	3		/* data packet */
#define	ACK	4		/* acknowledgement packet */
#define	ERROR	5		/* 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	69	/* TFTP's well known port */
#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[700];
char pout[700];

struct udp *pip = (struct udp *)pin;
struct udp *pop = (struct udp *)pout;

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;
int ip_uid = 1;

char *tf_data;

long in_me = 0x120a0003;

tftp_use(fhost, rmfile)
	long fhost;
	char *rmfile; {
	unsigned len;

	tf_fhost = fhost;
	tf_expected = 1;
	tf_fport = 0;
	tf_lport = 2001;
	tf_data = (char *)0x400;
	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 = 2001;
	pop->ud_dstp = TFTPPORT;

	pop->ip_prot = UDPPROT;
	pop->ip_vhl = 0x45;
	pop->ip_time = 0xff;
	pop->ip_flgs = 0;
	pop->ip_foff = 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, 700)) == 0)
			;
/*			if(pendchar()) {
				tfcndump();
				return 0;
				} */

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

		len = pip->ip_len - 28;

/*		printf("got pkt, length = %d\n", len); */

		if(pip->ip_src != tf_fhost) {
/*			dump(pip); */
			puts("bad source\n");
			continue;
			}

		if(pip->ud_dstp != tf_lport) {
/*			dump(pip); */
			puts("bad udp dst port\n");
			continue;
			}

		if(tf_fport && pip->ud_srcp != tf_fport) {
			puts("bad udp src port\n");
			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;				/* BAD. */

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

			/* Send the ack before writing the data */
		/*	tf_good();	*/
			tfsndack(tf_expected++);

			/* if it's block 1, get info from the exec header
			*/
			if(tf_expected == 2) {
				struct ldb *x;
				register char *data;
				register int i;

				x = (struct ldb *)&pip->tf_data;

				if((x->l_c1 != 1) && (x->l_c0 != 0)) {
					puts("Not a .ldb file\n");
					return 0;
					}

				tf_entry = x->l_entry;
				tf_data = (char *)x->l_load;
				nbytes = x->l_nbytes - sizeof(struct ldb);
/*				printf("nbytes = %d\n", nbytes);	*/
				data = (char *)x + sizeof(struct ldb);
				for(i = NORMLEN - sizeof(struct ldb); i > 0; i--)
					*tf_data++ = *data++;
				}
			else if(nbytes > 0) {	/* while not past code and data */
				register char *data = (char *)&pip->tf_data;
				int i, j;

				for(i=0; i<512; i++) {
/*					printf("%02x ", *data & 0xff);	*/
					*tf_data++ = *data++;
					if (nbytes-- <= 0) break;
/*					if((i+1)%16 == 0) printf("\n");
					for(j=0; j<500; j++) ;	*/
					}
				}

			/* and here we blithely drop the packet */

			if(len == NORMLEN) continue;
			else {
				puts("file loaded\n");
				puts("entry point is ");
				phex(tf_entry);
				puts("\n");
				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 = number;
/*	printf("ack %d\n", number); */
	return tf_write(tf_lastlen);
	}

/* write a tftp packet */

tf_write(len)
	unsigned len; {

/*	printf("tfwrite(%d)\n", len); */

	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 = 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 = len;
	pop->ip_id = ip_uid++;
	pop->ip_chksum = 0;
	pop->ip_chksum = ~cksum(&pop->ip_vhl, 10, 0);

	return net_write(pop, 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);
	}
 */
