/*******************************************************************************
        dhcp.c
        Z-World, 2001

        A very basic TCP/IP program, that will initilize the TCP/IP
        interface, and allow the device to be 'pinged' from another
        computer on the network.
        
        DHCP (Dynamic Host Configuration Protocol) or BOOTP (Bootstrap
        Protocol) will be used to obtain IP addresses and other
        network configuration items.
        
        As a test, after 1 1/2 minutes the IP address lease obtained
        from the DHCP server will be released then re-acquired 3
        seconds later.
        
        In addition, if your server is configured for it, then the
        bootstrap file will be downloaded (up to 16k long), then the
        same file will be uploaded to the server as a different
        file name.
*******************************************************************************/
//#define BOOTP_DEBUG		// Allow Dynamic C debugging in bootp.lib
//#define BOOTP_VERBOSE		// Print lots of detail
#define MAX_UDP_SOCKET_BUFFERS 2
#define TFTP_ALLOW_BUG		// Work-around certain TFTP server upload bug
#define DHCP_NUM_SMTP 1		// Get an SMTP server if possible

// Main define to cause BOOTP or DHCP to be used.
#define USE_DHCP

/*
 * If USE_DHCP, these values may be used as a fallback if
 * _survivebootp is set true.  Otherwise, they will be ignored.
 * Note that in a 'real' application, setting fallbacks as hard-
 * coded addresses would be unwise.
 */
#define MY_IP_ADDRESS   "10.10.6.100"
#define MY_NETMASK      "255.255.255.0"
#define MY_GATEWAY		"10.10.6.1"

// Min DHCP retry interval (secs).  If not defined, defaults to 60.
#define DHCP_MINRETRY 5

// This define causes boot image file to be downloaded.  The value
// specifies the maximum number of bytes to download.  This should
// be less than about 32k to avoid running out of extended memory.
// You will need to configure your server to specify the file to
// download.
#define DHCP_USE_TFTP (3*512+5)

// This optional string will be sent to the DHCP server as the
// 'class identifier' of this host.  This allows the server to
// "know what it's dealing with".  There is no particular standard
// for this string, but for ease of management Z-World suggests
// colon-separated fields in the form "hardware platform:vendor:
// firmware identifier:version number".  Your DHCP server can be
// configured to recognise these strings in order to select
// appropriate parameters. [NB: it is expected that this string
// be the same for all clones, i.e. not customised for each unit
// of the same product -- the ethernet hardware address is unique
// for each unit and thus identifies particular 'individuals' to
// the DHCP server.  Think of class ID as identifying the "what" but
// not the "who"].
#define DHCP_CLASS_ID "Rabbit-TCPIP:Z-World:DHCP-Test:1.0.0"

// This macro causes the MAC address to be used as a unique client
// identifier.
#define DHCP_CLIENT_ID_MAC

// These symbols cause the library to ignore incoming ethernet
// packets, in order to simulate an unreliable network.  The internet
// by its nature does not guarantee delivery of data, however on an
// ethernet LAN packets are delivered very reliably.  When developing
// new software, simulating unreliable delivery can expose bugs
// that might be otherwise undetectable. 
//#define _LOSE_PACKETS
//#define _LOSE_EVERY 2

#memmap xmem
#use dcrtcp.lib


/*
 * Print some of the DHCP or BOOTP parameters received.
 */
static void print_results(void)
{
	auto long tz;
	
	printf("Network Parameters:\n");
	printf("  My IP Address = %08lX\n", my_ip_addr);
	printf("  Netmask = %08lX\n", sin_mask);
	if (_dhcphost != ~0UL) {
		if (_dhcpstate == DHCP_ST_PERMANENT) {
			printf("  Permanent lease\n");
		} else {
			printf("  Remaining lease = %ld (sec)\n", _dhcplife - SEC_TIMER);
			printf("  Renew lease in %ld (sec)\n", _dhcpt1 - SEC_TIMER);
		}
		printf("  DHCP server = %08lX\n", _dhcphost);
		printf("  Boot server = %08lX\n", _bootphost);
	}
	if (gethostname(NULL,0))
		printf("  Host name = %s\n", gethostname(NULL,0));
	if (getdomainname(NULL,0))
		printf("  Domain name = %s\n", getdomainname(NULL,0));
	if (dhcp_get_timezone(&tz))
		printf("  Timezone (fallback only) = %ldh\n", tz / 3600);
	else
		printf("  Timezone (DHCP server) = %ldh\n", tz / 3600);
	if (_last_nameserver)
		printf("  First DNS = %08lX\n", def_nameservers[0]);
	if (_smtpsrv)
		printf("  SMTP server = %08lX\n", _smtpsrv);
}

main()
{
	longword st;
	longword rls;
	char bounced;
	char pboot;
	char putback;
	struct tftp_state ts;
	int status;
	word bflen;

	pboot = false;		// Not yet printed boot file
	putback = false;	// Not yet echoed boot file to server
	bounced = false;	// Not yet released/re-acquired DHCP parameters

	// Set runtime control for sock_init()...
	_survivebootp = 1;// Set to 1 (default) or 0 to never use fallbacks
	_bootptimeout = 6;// Short timeout for testing

	printf("Starting network (max wait %d seconds)...\n", _bootptimeout);
	
   status = sock_init();

   switch (status) {
   	default:
   		break;
   	case 1:
   		printf("Could not initialize packet driver.\n");
   		exit(1);
   	case 2:
   		printf("Could not configure using DHCP.\n");
   		break;	// continue with fallbacks
   	case 3:
   		printf("Could not configure using DHCP; fallbacks disallowed.\n");
   		exit(3);
   }
   
   if (_dhcphost != ~0UL)
   	printf("Lease obtained\n");
   else {
   	printf("Lease not obtained.  DHCP server may be down.\n");
   	printf("Using fallback parameters...\n");
   }
   print_results();

   rls = SEC_TIMER + 90;	// In 1.5 minutes relinquish lease.
   
   for (;;) {
   
   	// Check lease has not expired
      if (!tcp_tick(NULL)) {
      	printf("Network down; bringing up in 2 secs...\n");
      	st = SEC_TIMER + 2;
      	while (SEC_TIMER < st);
      	if (!(status = dhcp_acquire())) {
      		printf("Network back up\n");
   			print_results();
   		}
   		else if (status == 1) {
   			printf("Network back up but with DIFFERENT IP address\n");
   			print_results();
   		}
   		else {
   			printf("Network lost.  DHCP server probably down.\n");
   			exit(78);
   		}
      }
      
      // Some time after boot, "bounce" the network (just once).
      if (!bounced && SEC_TIMER > rls) {
      	bounced = true;
      	dhcp_release();
      	printf("Network relinquished; re-acquiring in 3 secs...\n");
      	st = SEC_TIMER + 3;
      	while (SEC_TIMER < st);
      	if (!(status = dhcp_acquire())) {
      		printf("Lease re-acquired\n");
      		print_results();
      	}
      	else if (status == 1) {
      		printf("Hmmm... lease re-acquired with DIFFERENT IP address\n");
      		print_results();
      	}
      	else {
   			printf("Whoops!  Lost network.  DHCP server probably down.\n");
   			exit(79);
   		}
      }
      
      // When we first get the boot file, print its length.  _bootpdone is
      // a global variable which gets set to 1 if the bootfile has been
      // downloaded, 2 if there was no bootfile specified, or
      // 0 if bootfile status not yet known.
      if (!pboot && _bootpdone) {
      	pboot = true;
      	if (_bootpdone == 2)
      		printf("No network boot file configured by server\n");
      	else {
      	   printf("Network boot file:\n");
      	   if (_bootperror && _bootperror != -5)
      		   printf("  Error code %d: %s\n", _bootperror,
      		     _bootperror == -1 ? "Server denied access" :
      		     _bootperror == -2 ? "Could not contact server" :
      		     _bootperror == -3 ? "Timed out" :
      		       "unknown error"
      		     );
      	   else {
      		   printf("  Length = %u\n", _bootpsize);
      		   if (_bootperror == -5)
      		   	printf("  File was truncated\n");
      		   putback = true;
      	   }
      	}
      }
      
      // Once we have the boot file, echo it back to the server.  Sometimes, for
      // a length which is not an exact multiple of 512, an error condition will
      // be returned.  This happens when the server is non-RFC compliant and
      // does not wait around to ACK the last packet.  It's not our fault!
      if (putback) {
      	putback = false;
      	ts.state = 1;	// Write
      	ts.buf_addr = _bootpdata;
      	ts.buf_len = _bootpsize;
      	ts.my_tid = 0;
      	ts.sock = NULL;	// use DHCP one
      	ts.rem_ip = 0;		// use bootp server
      	ts.mode = TFTP_MODE_OCTET;
      	strcpy(ts.file, "/tftpboot/echo");
      	printf("Sending boot file back as %s\n", ts.file);
      	
      	// This uses the non-blocking TFTP functions, but in a blocking
      	// manner.  It would be easier to use tftp_exec(), but this
      	// doesn't return the server error message.
      	if (!tftp_init(&ts)) {
      		while ((status = tftp_tick(&ts)) > 0);	// Loop until complete
      		if (!status)
      			printf("Upload completed\n");
      		else {
      			printf("Upload failed: code %d\n", status);
      			if (status == -1)
      				printf("  Message from server: %s\n", ts.file);
      		}
      	}
      	else
      		printf("Error: could not use dchp socket for tftp\n");

      	// Now we do it again, using tftp_exec().
      	//bflen = _bootpsize;
      	//status = tftp_exec(1,_bootpdata,&bflen,1,NULL,"/tftpboot/echo2",NULL);
      	//printf("echo2 uploaded: status = %d\n", status);
      }
   }
}