/*******************************************************************************
        Samples\TCPIP\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.
*******************************************************************************/
#class auto

/*
 * Pick the predefined TCP/IP configuration for this sample.  See
 * LIB\TCPIP\TCP_CONFIG.LIB for instructions on how to set the
 * configuration.
 * Config #5 = simple blank-slate DHCP, with fallbacks
 */
#define TCPCONFIG		5

/*
 * Define this symbol to force BOOTP protocol (not DHCP).  This is useful
 * when code space is tight.  You may need to specially configure your
 * DHCP server to respond to BOOTP-only clients.
 */
//#define BOOTP_ONLY

/*
 * General debugging options...
 */
//#define BOOTP_DEBUG		// Allow Dynamic C debugging in bootp.lib
//#define BOOTP_VERBOSE		// Print lots of detail
//#define ICMP_VERBOSE

/*
 * Specify information to obtain from DHCP server...
 */
#define DHCP_NUM_SMTP 1		// Get an SMTP server if possible
#define DHCP_NUM_ROUTERS 2	// Get up to 2 routers (gateways) if possible
#define DHCP_NUM_DNS  2		// Get up to 2 DNS (Domain Name System) servers if possible
#define DHCP_NUM_QOTD 2		// Get up to 2 cookie (Quote Of The Day) servers if possible
#define MAX_COOKIES 2		// Also need to tell main tcp lib (default is 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)
#define TFTP_ALLOW_BUG		// Work-around certain TFTP server upload bug

// 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

#memmap xmem
#use dcrtcp.lib


/*
 * Print some of the DHCP or BOOTP parameters received.
 */
static void print_results(void)
{
	auto long tz;
	auto word i;
	
	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);
	for (i = 0; i < *_last_nameserver; i++)
		printf("  DNS server #%u = %08lX\n", i+1, def_nameservers[i]);
	if (_smtpsrv)
		printf("  SMTP server = %08lX\n", _smtpsrv);
	for (i = 0; i < _last_cookie; i++)
		printf("  Cookie server #%u = %08lX\n", i+1, _cookie[i]);
	ip_print_ifs();
	router_printall();
}

void 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()...
	ifconfig(IF_DEFAULT,			// (DHCP only works on default interface)
		IFS_DHCP_TIMEOUT, 6,		// Specify timeout in seconds
		IFS_DHCP_FALLBACK, 1,	// Allow use of fallbacks to static configuration
		IFS_ICMP_CONFIG, 1,		// Also allow use of directed ping to configure (only if DHCP times out).
		IFS_END);

	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();
      	ifconfig(IF_DEFAULT, IFS_DHCP, 0, IFS_END);
      	printf("Network relinquished; re-acquiring in 3 secs...\n");
      	ip_print_ifs();
      	st = SEC_TIMER + 3;
      	while (SEC_TIMER < st);
      	//if (!(status = dhcp_acquire())) {
      	if (!(status = ifconfig(IF_DEFAULT, IFS_DHCP, 1, IFS_END))) {
      		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);
      }
   }
}
