/**HEADER********************************************************************
* 
* Copyright (c) 2008 Freescale Semiconductor;
* All Rights Reserved
*
* Copyright (c) 2004-2008 Embedded Access Inc.;
* All Rights Reserved
*
* Copyright (c) 1989-2008 ARC International;
* All Rights Reserved
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
* IN NO EVENT SHALL FREESCALE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
* THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************
*
* $FileName: dhcpclnt.c$
* $Version : 3.0.9.0$
* $Date    : Jun-23-2009$
*
* Comments:
*
*   This file contains the implementation of the DHCP client
*   for the TCP/IP protocol suite.
*   For more information, refer to:
*   RFC 2131 (DHCP)
*   RPC 2132 (DHCP Options).
*   Limitations:  Although DHCP can be used over any medium, this
*   implementation will work only over an Ethernet
*   link layer.
*   Implementation Note:
*   The DHCP Client packet is set up in such a way that it may be re-used
*   for any message the DCHP Client needs to send.  Only DHCP_MAGIC is
*   required for a DHCPDISCOVER message, the other options may or may not
*   be present if the application did not specify them upon initialization.
*   If the option is passed to the DHCP Client, it will be placed in the
*   packet in the following order.  The Server and Client IP pads will be
*   used even if no other options are specified.
*   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*   |                       DHCP_MAGIC (4)                          |
*   +---------------------------------------------------------------+
*   |                                                               |
*   //                 ClIENT ID (variable, 6 - 255)               //
*   |                                                               |
*   +-----------------------------------------------+---------------+
*   |                  OVERLOAD (3)                 |
*   +-----------------------------------------------+--------------------------+
*   |                  SERVER IP PAD (6)  (zero, unless used)                  |
*   +--------------------------------------------------------------------------+
*   |                  CLIENT IP PAD (6)  (zero, unless used)                  |
*   +-------------------+------------------------------------------------------+
*   | BUFFER END PAD (1)|
*   +-------------------+-------------------------------------------+
*   |                                                               |
*   //                  PARAMETER LIST (variable, 3 - 255)         //
*   |                                                               |
*   +---------------------------------------------------------------+
*   |                   REQUESTED LEASE TIME (4)                    |
*   +---------------------------------------------------------------+
*   |                                                               |
*   //                  CLASS ID (variable, 6 -255)                //
*   |                                                               |
*   +---------------------------------------------------------------+
*   |                   MAX MESSAGE SIZE (4)                        |
*   +---------------------------------------------------------------+
*   |                                                               |
*   //                  SITE SPECIFIC OPTIONS (variable)           //
*   |                                                               |
*   +--------------------+------------------------------------------+
*   | BUFFER END PAD (1) |
*   +--------------------------------------------------------------------------+
*   |                   REQUESTED CLIENT IP PAD (6)                            |
*   +--------------------+-----------------------------------------------------+
*   | BUFFER END PAD (1) |
*   +--------------------+
*   Pointers are kept to modify the packet, depending on the current state.
*   Please refer to RFC 2131, pg 38, to see what options are allowed in
*   each situation.  As an example, if sending a DHCPDECLINE message, only the
*   Server IP is allowed.  Therefore, the Server IP pad section is filled using
*   the DHCPCLNT_modify_options() function and the first buffer end pad is
*   filled with 0xFF. Only this section of the buffer will be sent to the
*   Server.  When a state change occurs, these pads will be zeroed out and
*   the packet will be set up for the correct message type.
*   There are three timed events which may or may not be active depending on
*   the current state. If we are initializing for the first time, only the
*   RESEND event will be active. Once we have a lease and are in a bound state,
*   the RESEND event is cancelled and the RENEW and TERMINATE events are set.
*   If we are in a RESENDING state, all three events will be set until we are
*   re-bound. If we are in a REBINDING state, only the RESEND and TERMINATE
*   events will be active.  If we received a DHCPNAK message when renewing or
*   rebinding, we will try to get a new lease starting from the INITIALIZE
*   state. If the DHCP server does not wish to renew the current lease,
*   then only the TERMINATE event will be set for the remainder of the lease. SMR
*
*END************************************************************************/

#include <string.h>
#include <rtcs.h>
#include "rtcs_prv.h"
#include "tcpip.h"
#include "ip_prv.h"
#include "dhcp_prv.h"
#include "dhcpuprv.h"
#include "arp.h"

extern UCB_STRUCT_PTR   BOOT_port;


uint_32 DHCPCLNT_probe_address
   (
      _ip_address       ipaddress,
      pointer           interface
   );

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_init
* Returned Values : void
* Comments        :
*     Initialize the DHCP client. This function should only be called by the
*     user's application
*
*END*-----------------------------------------------------------------*/

void DHCPCLNT_init
   (
      TCPIP_PARM_IF_DHCP _PTR_ parms
         /* [IN/OUT] This is the DHCP Client structure from the application */
   )
{ /* Body */
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr;
   uint_32                buff_len;
   uint_32                error;

   buff_len = parms->OPT_BUFF_SIZE + sizeof(DHCP_HEADER) + 23;

   dhcp_ptr = RTCS_mem_alloc_zero(sizeof(DHCP_CLNT_STRUCT) + buff_len);
#if RTCSCFG_CHECK_MEMORY_ALLOCATION_ERRORS
   if ( dhcp_ptr == NULL ) {
      RTCSCMD_complete(parms, RTCSERR_OUT_OF_MEMORY);
      return;
   } /* Endif */
#endif
   _mem_set_type(dhcp_ptr, MEM_TYPE_DHCP_CLNT_STRUCT);

   error = DHCPCLNT_start(parms, dhcp_ptr);

#if RTCSCFG_CHECK_ERRORS
   if ( error ) {
      /* Self Destruct, can't do anything if the port isn't open */
      _mem_free( dhcp_ptr );
      RTCSCMD_complete(parms, error);
      return;
   } /* Endif */
#endif

   DHCPCLNT_open(dhcp_ptr);

   RTCSCMD_complete(parms, error);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_reinit
* Returned Values : void
* Comments        :
*     Initialize the DHCP client without the initial DHCP negotiation.
*
*END*-----------------------------------------------------------------*/

void DHCPCLNT_reinit
   (
      TCPIP_PARM_IF_DHCP _PTR_ parms
         /* [IN/OUT] This is the DHCP Client structure from the application */
   )
{ /* Body */
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr;
   uint_32                buff_len;
   uint_32                error;

   buff_len = parms->OPT_BUFF_SIZE + sizeof(DHCP_HEADER) + 23;

   dhcp_ptr = RTCS_mem_alloc_zero(sizeof(DHCP_CLNT_STRUCT) + buff_len);
#if RTCSCFG_CHECK_MEMORY_ALLOCATION_ERRORS
   if ( dhcp_ptr == NULL ) {
      RTCSCMD_complete(parms, RTCSERR_OUT_OF_MEMORY);
      return;
   } /* Endif */
#endif
   _mem_set_type(dhcp_ptr, MEM_TYPE_DHCP_CLNT_STRUCT);

   error = DHCPCLNT_start(parms, dhcp_ptr);

#if RTCSCFG_CHECK_ERRORS
   if ( error ) {
      /* Self Destruct, can't do anything if the port isn't open */
      _mem_free( dhcp_ptr );
      RTCSCMD_complete(parms, error);
      return;
   } /* Endif */
#endif

   dhcp_ptr->DATA.ServerIp        = parms->SERVER_IP_ADDR;
   dhcp_ptr->DATA.ClientIPaddress = parms->CLNT_IP_ADDR;
   dhcp_ptr->DATA.LEASE           = parms->LEASE;
   dhcp_ptr->DATA.Netmask         = parms->CLNT_IP_MASK;

   dhcp_ptr->PARMS_BIND.address = parms->CLNT_IP_ADDR;
   dhcp_ptr->PARMS_BIND.locmask = INADDR_BROADCAST;
   dhcp_ptr->PARMS_BIND.netmask = parms->CLNT_IP_MASK;
   dhcp_ptr->PARMS_BIND.probe   = FALSE;

   RTCSCMD_internal(dhcp_ptr->PARMS_BIND, IPIF_bind);

   dhcp_ptr->BOUND = TRUE;
   dhcp_ptr->STATE = DHCP_BOUND;

   dhcp_ptr->NEW_RENEW_TIME = 0;
   dhcp_ptr->NEW_REBIND_TIME = 0;
   DHCPCLNT_set_timed_events(dhcp_ptr);
   TCPIP_Event_add(&dhcp_ptr->RENEW);
   TCPIP_Event_add(&dhcp_ptr->TERMINATE);

   RTCSCMD_complete(parms, RTCS_OK);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_start
* Returned Values : error code
* Comments        :
*     Initialize the DHCP client state structure.
*
*END*-----------------------------------------------------------------*/

uint_32 DHCPCLNT_start
   (
      TCPIP_PARM_IF_DHCP _PTR_ parms,
         /* [IN/OUT] This is the DHCP Client structure from the application */
      DHCP_CLNT_STRUCT _PTR_ dhcp_ptr
         /* [IN/OUT] This is the DHCP Client control structure */
   )
{ /* Body */
   uint_32     buff_len;

   /*
   ** 23: 4 = Magic Cookie, 3 = Msg Type, 6 = ServerID, 6 = Requested IP,
   ** 4 = Various Ends
   */
   buff_len = parms->OPT_BUFF_SIZE + sizeof(DHCP_HEADER) + 23;

   if ( parms->FLAGS & DHCP_SEND_INFORM_MESSAGE ) {
      dhcp_ptr->STATE = DHCP_INFORMED;
      dhcp_ptr->DATA.ClientIPaddress = parms->CLNT_IP_ADDR;
      if ( parms->SERVER_IP_ADDR == INADDR_ANY ) {
         dhcp_ptr->DATA.ServerIp = INADDR_BROADCAST;
      } else  {
         dhcp_ptr->DATA.ServerIp = parms->SERVER_IP_ADDR;
      } /* Endif */
      dhcp_ptr->BOUND = TRUE;
   } else  {
      dhcp_ptr->STATE = DHCP_INITIALIZE;
   }/* Endif */
   dhcp_ptr->HANDLE             = (IP_IF _PTR_)parms->HANDLE;
   dhcp_ptr->PARMS_BIND.ihandle = parms->HANDLE;
   dhcp_ptr->HANDLE->BOOTFN     = NULL;
   dhcp_ptr->HANDLE->BOOT       = dhcp_ptr;
   dhcp_ptr->CHOICE_FUNC        = parms->OPTIONS->CHOICE_FUNC;
   dhcp_ptr->BIND_FUNC          = parms->OPTIONS->BIND_FUNC;
   dhcp_ptr->UNBIND_FUNC        = parms->OPTIONS->UNBIND_FUNC;
   dhcp_ptr->FAILURE_FUNC       = parms->OPTIONS->FAILURE_FUNC;
   dhcp_ptr->NAK_FUNC           = parms->OPTIONS->NAK_FUNC;
   dhcp_ptr->FLAGS              = parms->FLAGS;
   dhcp_ptr->PACKET             = (uchar _PTR_)(dhcp_ptr + 1);
   dhcp_ptr->TOTAL_PACKET_SIZE  = buff_len;
   dhcp_ptr->RESEND.EVENT       = DHCPCLNT_send;
   dhcp_ptr->RESEND.PRIVATE     = dhcp_ptr;
   dhcp_ptr->RENEW.EVENT        = DHCPCLNT_renew_lease;
   dhcp_ptr->RENEW.PRIVATE      = dhcp_ptr;
   dhcp_ptr->TERMINATE.EVENT    = DHCPCLNT_terminate_lease;
   dhcp_ptr->TERMINATE.PRIVATE  = dhcp_ptr;
   dhcp_ptr->INIT_TIMEOUT       = parms->TIMEOUT * 1000;

   DHCPCLNT_fill_header( dhcp_ptr );
   DHCPCLNT_fill_options( dhcp_ptr, parms );

   return BOOT_open(&DHCPCLNT_service);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_fill_header
* Returned Values : void
* Comments        :
*     Sets up the DHCP Client header structure.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_fill_header
   (
   DHCP_CLNT_STRUCT  _PTR_ dhcp_ptr
   /* [IN/OUT] This is the DHCP Client control structure */
   )
{ /* Body */
   DHCP_HEADER _PTR_      header_ptr;
   uint_32                xid;

   header_ptr = (DHCP_HEADER _PTR_)dhcp_ptr->PACKET;

   /* Build the DHCP HEADER  */
   htonc(header_ptr->OP,    DHCPOP_BOOTREQUEST);
   htonc(header_ptr->HTYPE, dhcp_ptr->HANDLE->DEV_TYPE);
   htonc(header_ptr->HLEN,  dhcp_ptr->HANDLE->DEV_ADDRLEN);
   htonc(header_ptr->HOPS,  0);
   /* Pick a random transaction ID */
   xid = RTCS_rand();
   htonl(header_ptr->XID, xid);

   /* htons(header_ptr->FLAGS, DHCPFLAG_BROADCAST); */
   htons(header_ptr->FLAGS, _DHCP_broadcast ? DHCPFLAG_BROADCAST : 0);
   
   if ( dhcp_ptr->STATE == DHCP_INFORMED ) {
      htonl(header_ptr->CIADDR, dhcp_ptr->DATA.ClientIPaddress);
   } else {
      htonl(header_ptr->CIADDR, INADDR_ANY);
   } /* Endif */
   htonl(header_ptr->YIADDR, INADDR_ANY);
   htonl(header_ptr->SIADDR, INADDR_ANY);
   htonl(header_ptr->GIADDR, INADDR_ANY);

   _mem_zero(header_ptr->CHADDR, sizeof(header_ptr->CHADDR));
   _mem_copy(dhcp_ptr->HANDLE->DEV_ADDR, header_ptr->CHADDR, dhcp_ptr->HANDLE->DEV_ADDRLEN);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_fill_options
* Returned Values : void
* Comments        :
*     Checks and fills in the DHCP Client options in the buffer. This
*     function should only be called once upon initialization. The DHCP_FILL
*     macro is defined in DHCP_PRV.H.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_fill_options
   (
   DHCP_CLNT_STRUCT  _PTR_ dhcp_ptr,
   /* [IN/OUT] This is the DHCP Client control structure */
   TCPIP_PARM_IF_DHCP _PTR_ parms
   /* [IN/OUT] This is the DHCP Client structure from the application */
   )
{ /* Body */
   uchar _PTR_ outp;
   uchar _PTR_ temp_ptr;
   uchar _PTR_ opt_ptr;
   uint_32     len;
   uint_32     i;
   uint_32     new_total_length;
   uchar       addr_buffer[6];
   uchar _PTR_ buffer_end   = parms->OPT_BUFF
                              + parms->OPT_BUFF_SIZE - 1;

   outp = dhcp_ptr->PACKET + sizeof(DHCP_HEADER);
   htonl(outp, DHCP_MAGIC);            outp += 4;
   htonc(outp, DHCPOPT_MSGTYPE);       outp++;
   htonc(outp, 1);                     outp++;
   dhcp_ptr->MSGTYPE_PTR = outp;
   if ( dhcp_ptr->STATE == DHCP_INFORMED ) {
      htonc(outp, DHCPTYPE_DHCPINFORM);
   } else  {
      htonc(outp, DHCPTYPE_DHCPDISCOVER);
   } /* Endif */
   outp++;
   /*
   ** Only the following options are allowed for a DHCP Client message.
   ** Client ID, Use 'file'/'sname' fields, Parameter request list,
   ** IP address lease time, Class Id, Max message size, Requested IP addr.
   ** DO NOT change the order of insertion into the buffer, as other
   ** functions will depend on the order given here.
   ** See pg. 33 of RFC 1541 for more details.
   */
   /* Minimum size allowed is 2 (added 2 for option and length fields) */
   DHCP_FILL(CLIENTID, (len >= 4))
   /*
   ** Allows 'file'/'sname' fields to be used in replies from DHCP server
   ** Since we also use these fields for BOOTP, we do not allow this
   ** functionality at this time.  However, if we are to offer it in the future,
   ** other functions depend on us filling in the overload option here.
   ** Leave the commented code here for potential future use. SMR
   */
   /* Minimum size allowed is 1 (added 2 for option and length fields) */
   /* DHCP_FILL(OVERLOAD, (len == 3)) */

   dhcp_ptr->SERVERID_PTR = outp;
   /* This is to pad for insertion of ServerID and Requested IP later */
   outp += 13;

   /* Minimum size allowed is 1 (added 2 for option and length fields) */
   DHCP_FILL(PARAMLIST, (len >= 3))
   if ( dhcp_ptr->STATE != DHCP_INFORMED ) {
      /* Size must be 4 (added 2 for option and length fields) */
      DHCP_FILL(LEASE, (len == 6))
   }/* Endif */
   /* Minimum size allowed is 1 (added 2 for option and length fields) */
   DHCP_FILL(CLASSID, (len >= 3))

   opt_ptr = DHCP_find_option(parms->OPT_BUFF,
                              parms->OPT_BUFF_SIZE,
                              DHCPOPT_MSGSIZE);
   if (opt_ptr) {
      /* 576 octets is an imposed minimum, RFC 2131 */
      if ( ntohs(opt_ptr + 2) < DHCP_MIN_MESSAGE_SIZE ) {
         htonc(outp, DHCPOPT_MSGSIZE);       outp++;
         htonc(outp, 2);                     outp++;
         htons(outp, DHCP_MIN_MESSAGE_SIZE); outp += 2;
      } else  {
         _mem_copy(opt_ptr, outp, 4);
         outp += 4;
      } /* Endif */
      for (i = 0; i < 4; i++) {
         htonc(opt_ptr, 0);
         opt_ptr++;
      } /* Endfor */
   } /* Endif */

   /*
   ** Have to parse out the ADDRESS option, as it goes after the site specific
   ** options in the packet.
   */
   opt_ptr = DHCP_find_option(parms->OPT_BUFF,
                              parms->OPT_BUFF_SIZE,
                              DHCPOPT_ADDRESS);
   if ( opt_ptr ) {
      for (i = 0; i < 6; i++) {
         addr_buffer[i] = ntohc(opt_ptr);
         htonc(opt_ptr, 0);
         opt_ptr++;
      } /* Endfor */
   }/* Endif */

   /*
   ** Remove all the remaining options and place in the site specific section.
   ** Break out of the while loop once all the options are parsed.
   */

   temp_ptr = parms->OPT_BUFF;
   while ( temp_ptr ) {
      temp_ptr = parms->OPT_BUFF;
      while ((ntohc(temp_ptr) == 0) && (temp_ptr < buffer_end)) {
         temp_ptr++;
      } /* Endwhile */
      if ( ntohc(temp_ptr) != 0 ) {
         new_total_length = buffer_end - temp_ptr + 1;
         len = ntohc(temp_ptr + 1) + 2;
         if ( len <= new_total_length ) {
            _mem_copy(temp_ptr, outp, len);
            outp += len;
         } else  {
            len = new_total_length;
         }/* Endif */
         for ( i = 0; i < len; i++) {
            htonc(temp_ptr, 0);
            temp_ptr++;
         } /* Endfor */
      } else  {
         temp_ptr = NULL;
      }/* Endif */
   } /* Endwhile */

   dhcp_ptr->REQUEST_OPT_PTR = outp;
   /* This pad is for DHCP REQUEST messages so we won't overwrite the addr */
   outp++;

   if (opt_ptr && (dhcp_ptr->STATE != DHCP_INFORMED)) {
      for ( i = 0; i < 6; i++ ) {
         htonc(outp, addr_buffer[i]);
         outp++;
      } /* Endfor */
   } /* Endif */

   /* Add the DHCPEND flag last */
   htonc(outp, DHCPOPT_END);
   outp++;
   dhcp_ptr->CURRENT_PACKET_SIZE = outp - dhcp_ptr->PACKET;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_open
* Returned Values : void
* Comments        : This function is used to open the DHCP Client Port
*                   and set up the RESEND event.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_open
   (
   DHCP_CLNT_STRUCT  _PTR_ dhcp_ptr
   /* [IN/OUT] This is the DHCP Client control structure */
   )
{ /* Body */
   DHCP_HEADER _PTR_   header_ptr = (DHCP_HEADER _PTR_)dhcp_ptr->PACKET;

   htons(header_ptr->SECS, 0);
   /* Set initial timeout */
   dhcp_ptr->RETRY_TIMEOUT = DHCP_TIMEOUT_MIN;
   dhcp_ptr->SECS = 0;
   /* Start the retransmission timer to start sending immediately */
   dhcp_ptr->RESEND.TIME    = 0;
   TCPIP_Event_add( &dhcp_ptr->RESEND );

   if (dhcp_ptr->INIT_TIMEOUT) {
      /* Set a cancellation event in case of failure to bind. */
      dhcp_ptr->TERMINATE.EVENT    = DHCPCLNT_bind_attempt_timeout;
      dhcp_ptr->TERMINATE.TIME     = dhcp_ptr->INIT_TIMEOUT;
      TCPIP_Event_add(&dhcp_ptr->TERMINATE);
   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_modify_options
* Returned Values : void
* Comments        :
*     Modifies the DHCP Client buffer for packet send, depending on the
*     current context. Allowed options change depending on the packet type.
*     NOTE: State MUST be checked BEFORE calling, this function is not used
*     in an initial or terminated state
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_modify_options
   (
   DHCP_CLNT_STRUCT  _PTR_ dhcp_ptr
   /* [IN/OUT] This is the DHCP Client control structure */
   )
{  /* Body */
   DHCP_HEADER _PTR_ header_ptr;
   uchar       _PTR_ outp;
   uint_32           i;

   header_ptr = (DHCP_HEADER _PTR_)dhcp_ptr->PACKET;
   outp       = dhcp_ptr->SERVERID_PTR;

   for ( i = 0 ; i < 13 ; i++ ) {
         htonc(outp, 0); outp++;
      } /* Endfor */
   outp -= 13;
   htonc(dhcp_ptr->REQUEST_OPT_PTR, 0);

   if ( dhcp_ptr->BOUND && dhcp_ptr->STATE != DHCP_REQUESTING ) {
      htonl(header_ptr->CIADDR, dhcp_ptr->DATA.ClientIPaddress);
   } else  {
      htonl(header_ptr->CIADDR, INADDR_ANY);
   }/* Endif */

   if ( dhcp_ptr->STATE == DHCP_INITIALIZE ) {
      htonc(dhcp_ptr->MSGTYPE_PTR, DHCPTYPE_DHCPDISCOVER);
      dhcp_ptr->CURRENT_PACKET_SIZE = dhcp_ptr->TOTAL_PACKET_SIZE;
   } else  {
      htonc(dhcp_ptr->MSGTYPE_PTR, DHCPTYPE_DHCPREQUEST);
      if ( dhcp_ptr->STATE == DHCP_REQUESTING ||
           dhcp_ptr->STATE == DHCP_DECLINING  ||
           dhcp_ptr->STATE == DHCP_RELEASING ) {
         htonc(outp, DHCPOPT_SERVERID); outp++;
         htonc(outp, 4); outp++;
         if ( dhcp_ptr->STATE == DHCP_RELEASING ) {
            htonc(dhcp_ptr->MSGTYPE_PTR, DHCPTYPE_DHCPRELEASE);
            htonl(outp, dhcp_ptr->DATA.ServerIp); outp += 4;
         } else  {
            htonl(outp, dhcp_ptr->NEW_DATA.ServerIp); outp += 4;
            htonc(outp, DHCPOPT_ADDRESS); outp++;
            htonc(outp, 4); outp++;
            htonl(outp, dhcp_ptr->NEW_DATA.ClientIPaddress); outp += 4;
            if (dhcp_ptr->STATE == DHCP_DECLINING ) {
               htonl(header_ptr->CIADDR, INADDR_ANY);
               htonc(dhcp_ptr->MSGTYPE_PTR, DHCPTYPE_DHCPDECLINE);
            } else  {
               outp = dhcp_ptr->REQUEST_OPT_PTR;
            }/* Endif */
         } /* Endif */
      } else  {
         /* We're Renewing or Rebinding */
         outp = dhcp_ptr->REQUEST_OPT_PTR;
      }/* Endif */
      htonc(outp, DHCPOPT_END); outp++;
      dhcp_ptr->CURRENT_PACKET_SIZE = outp - dhcp_ptr->PACKET;
   }/* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_send
* Returned Value  : boolean
* Comments        :
*     Called by the Timer.  Send a DHCP packet.
*
*END*-----------------------------------------------------------------*/

boolean DHCPCLNT_send
   (
   TCPIP_EVENT_PTR   event
   /* [IN/OUT] the resend event */
   )
{ /* Body */
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr = event->PRIVATE;
   DHCP_HEADER     _PTR_ header_ptr;
   RTCSPCB_PTR           pcb_ptr;
   _ip_address           destination_address;
   uint_32               retry_time;

   if ( dhcp_ptr->S_EVENT_TIME_LEFT ) {
      DHCPCLNT_TIME_OVERFLOW_CHECK(retry_time, dhcp_ptr->S_EVENT_TIME_LEFT)
      dhcp_ptr->RESEND.TIME = retry_time;
      return( TRUE );
   }/* Endif */

   switch ( dhcp_ptr->STATE ) {

      case DHCP_REQUESTING:
      case DHCP_INFORMED:
         /* Must retry 4 times before starting over, total is under 60 secs */
         if ( dhcp_ptr->RETRY_TIMEOUT != DHCP_TIMEOUT_MAX ) {
            /*Set the event to trigger for the next retransmission (+/- 1 sec)*/
            retry_time = dhcp_ptr->RETRY_TIMEOUT +
                         (RTCS_rand() & 0x7FF) - 0x400;
            if ( dhcp_ptr->RENEW_TIME &&
                 dhcp_ptr->RENEW_TIME < dhcp_ptr->REBIND_TIME )
            {
               dhcp_ptr->RENEW_TIME += (retry_time / 1000);
            } else  {
               /* We were rebinding, increment rebind time */
               dhcp_ptr->REBIND_TIME += (retry_time / 1000);
            }/* Endif */
            dhcp_ptr->RESEND.TIME = retry_time;
            /* Double the timeout */
            dhcp_ptr->RETRY_TIMEOUT <<= 1;
            if (dhcp_ptr->RETRY_TIMEOUT > DHCP_TIMEOUT_MAX) {
               dhcp_ptr->RETRY_TIMEOUT = DHCP_TIMEOUT_MAX;
            } /* Endif */
            dhcp_ptr->STATS.ST_TX_REQUEST++;
            break;
         } else  {
            if ( dhcp_ptr->STATE == DHCP_INFORMED ) {
               /*
               ** There was no response to our request for parameters, inform
               ** the application to use defaults and self destruct
               */
               if ( dhcp_ptr->BIND_FUNC != NULL ) {
                  dhcp_ptr->BIND_FUNC(NULL, 0,
                                      (_rtcs_if_handle)dhcp_ptr->HANDLE);
               } /* Endif */
               BOOT_close();
               dhcp_ptr->HANDLE->BOOT = NULL;
               /* Free the DHCP Structure */
               _mem_free(dhcp_ptr);
               return( FALSE );
            } else  {
               /* We were initializing from an unbound state before */
               dhcp_ptr->STATE = DHCP_INITIALIZE;
               dhcp_ptr->RETRY_TIMEOUT = DHCP_TIMEOUT_MIN;
               DHCPCLNT_modify_options( dhcp_ptr );
            } /* Endif */
         }/* Endif */

      case DHCP_INITIALIZE:
         /* Set the event to trigger for the next retransmission (+/- 1 sec) */
         retry_time = dhcp_ptr->RETRY_TIMEOUT + (RTCS_rand() & 0x7FF) - 0x400;

         dhcp_ptr->RESEND.TIME = retry_time;
         /* Double the timeout */
         dhcp_ptr->RETRY_TIMEOUT <<= 1;
         if (dhcp_ptr->RETRY_TIMEOUT > DHCP_TIMEOUT_MAX) {
            dhcp_ptr->RETRY_TIMEOUT = DHCP_TIMEOUT_MAX;
         } /* Endif */
         dhcp_ptr->STATS.ST_TX_DISCOVER++;
         break;

      case DHCP_RENEWING:
         retry_time = (dhcp_ptr->REBIND_TIME - dhcp_ptr->RENEW_TIME)/2;
         if ( retry_time < 60 ) {
            /* A 60 second minimum is imposed */
            retry_time = 60;
         }/* Endif */
         dhcp_ptr->RENEW_TIME += retry_time;

         dhcp_ptr->S_EVENT_TIME_LEFT = retry_time;
         DHCPCLNT_TIME_OVERFLOW_CHECK(retry_time, dhcp_ptr->S_EVENT_TIME_LEFT)

         dhcp_ptr->RESEND.TIME = retry_time;
         dhcp_ptr->STATS.ST_TX_REQUEST++;
         break;

      case DHCP_REBINDING:
         retry_time = (dhcp_ptr->DATA.LEASE - dhcp_ptr->REBIND_TIME)/2;
         if ( retry_time < 60 ) {
            /* A 60 second minimum is imposed */
            retry_time = 60;
         }/* Endif */
         if ((dhcp_ptr->REBIND_TIME + retry_time) < dhcp_ptr->DATA.LEASE) {
            dhcp_ptr->REBIND_TIME += retry_time;
         }/* Endif */

         dhcp_ptr->S_EVENT_TIME_LEFT = retry_time;
         DHCPCLNT_TIME_OVERFLOW_CHECK(retry_time, dhcp_ptr->S_EVENT_TIME_LEFT)

         dhcp_ptr->RESEND.TIME = retry_time;
         dhcp_ptr->STATS.ST_TX_REQUEST++;

   } /* Endswitch */

   /* Allocate a PCB */
   pcb_ptr = RTCSPCB_alloc_send();
   if (pcb_ptr == NULL) {
      return TRUE;
   } /* Endif */

   //RTCSLOG_PCB_ALLOC(pcb_ptr);

   /* The only field that changes in DHCP packets is 'secs' */
   header_ptr = (DHCP_HEADER _PTR_)dhcp_ptr->PACKET;
   htons(header_ptr->SECS, dhcp_ptr->SECS);
   dhcp_ptr->SECS += (retry_time / 1000);

   /* Put the DHCP packet in the PCB */
   RTCSLOG_PCB_WRITE(pcb_ptr, RTCS_LOGCTRL_PORT(IPPORT_BOOTPC), 0);
   RTCSPCB_append_fragment(pcb_ptr, dhcp_ptr->CURRENT_PACKET_SIZE, dhcp_ptr->PACKET);

   if ( dhcp_ptr->BOUND ) {
      /* We already have an IP address available, we're renewing or rebinding */
      if ( dhcp_ptr->STATE == DHCP_RENEWING ||
           dhcp_ptr->STATE == DHCP_INFORMED )
      {
         /* Unicast the message to the server */
         destination_address = dhcp_ptr->DATA.ServerIp;
      } else {
         /* Broadcast the request */
         destination_address = INADDR_BROADCAST;
      } /* Endif */

      UDP_send_internal(BOOT_port,
                        dhcp_ptr->DATA.ClientIPaddress,
                        destination_address,
                        DHCP_SERVER_PORT,
                        pcb_ptr,
                        0);
   } else {
      /* Send the datagram */
      BOOT_send(pcb_ptr, dhcp_ptr->HANDLE);
   } /* Endif */

   return TRUE;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_send_one_shot
* Returned Values : void
* Comments        : Sends a single DHCP packet without resend.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_send_one_shot
   (
    DHCP_CLNT_STRUCT  _PTR_ dhcp_ptr
   /* [IN/OUT] This is the DHCP Client control structure */
   )
{ /* Body */
   RTCSPCB_PTR       pcb_ptr;

   /* Allocate a PCB */
   pcb_ptr = RTCSPCB_alloc_send();
   if (pcb_ptr == NULL) {
      return;
   } /* Endif */

   //RTCSLOG_PCB_ALLOC(pcb_ptr);

   /* Put the DHCP packet in the PCB */
   RTCSLOG_PCB_WRITE(pcb_ptr, RTCS_LOGCTRL_PORT(IPPORT_BOOTPC), 0);
   RTCSPCB_append_fragment(pcb_ptr, dhcp_ptr->CURRENT_PACKET_SIZE, dhcp_ptr->PACKET);

   if ( dhcp_ptr->BOUND ) {
      /* We already have an IP address available */
      UDP_send_internal(BOOT_port,
                        dhcp_ptr->NEW_DATA.ClientIPaddress,
                        dhcp_ptr->NEW_DATA.ServerIp,
                        DHCP_SERVER_PORT,
                        pcb_ptr,
                        0);
   } else {
      /* Send the datagram */
      BOOT_send(pcb_ptr, dhcp_ptr->HANDLE);
   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_rebind_lease
* Returned Values : boolean
* Comments        :
*     Attempts to renew the current lease unicasting to the server.
*
*END*-----------------------------------------------------------------*/

static boolean DHCPCLNT_rebind_lease
   (
   TCPIP_EVENT_PTR   event
   /* [IN/OUT] the renew event */
   )
{ /* Body */
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr = event->PRIVATE;
   uint_32                rebind_time;
   boolean                reset_event = FALSE;

   if ( dhcp_ptr->R_EVENTS_TIME_LEFT ) {
      DHCPCLNT_TIME_OVERFLOW_CHECK(rebind_time, dhcp_ptr->R_EVENTS_TIME_LEFT)
      dhcp_ptr->RENEW.TIME = rebind_time;
      reset_event = TRUE;
   } else {
      TCPIP_Event_cancel(&dhcp_ptr->RESEND);
      /*
      ** Need to make sure that the renew time is now greater than the rebind
      ** for use by DHCPCLNT_send to know the previous state if requests fail.
      */
      dhcp_ptr->RENEW_TIME = dhcp_ptr->DATA.LEASE;
      dhcp_ptr->STATE = DHCP_REBINDING;
      DHCPCLNT_modify_options( dhcp_ptr );
      DHCPCLNT_open( dhcp_ptr );
   } /* Endif */

   return( reset_event );
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_renew_lease
* Returned Values : boolean
* Comments        :
*     Attempts to renew the current lease unicasting to the server.
*
*END*-----------------------------------------------------------------*/

static boolean DHCPCLNT_renew_lease
   (
   TCPIP_EVENT_PTR   event
   /* [IN/OUT] the renew event */
   )
{ /* Body */
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr = event->PRIVATE;
   uint_32                renew_time;

   if (!dhcp_ptr->R_EVENTS_TIME_LEFT ) {
      dhcp_ptr->STATE = DHCP_RENEWING;
      DHCPCLNT_modify_options( dhcp_ptr );

      dhcp_ptr->INIT_TIMEOUT = 0;

      DHCPCLNT_open( dhcp_ptr );

      /* Set up for DHCP Rebinding */
      dhcp_ptr->R_EVENTS_TIME_LEFT = dhcp_ptr->REBIND_TIME;
      dhcp_ptr->RENEW.EVENT = DHCPCLNT_rebind_lease;
   } /* Endif */

   DHCPCLNT_TIME_OVERFLOW_CHECK(renew_time, dhcp_ptr->R_EVENTS_TIME_LEFT);
   dhcp_ptr->RENEW_TIME = 0;

   dhcp_ptr->RENEW.TIME = renew_time;

   return( TRUE );
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_terminate_lease
* Returned Values : boolean
* Comments        :
*     If a lease expires, or a DHCPNAK message is received, this function
*     will broadcast another DHCPDISCOVER message.
*
*END*-----------------------------------------------------------------*/

static boolean DHCPCLNT_terminate_lease
   (
   TCPIP_EVENT_PTR   event
   /* [IN/OUT] the termination event */
   )
{  /* Body */
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr = event->PRIVATE;
   uint_32                terminate_time;
   boolean                retry = FALSE;
   boolean                reset_event = FALSE;

   if ( dhcp_ptr->TERMINATE_TIME ) {
      DHCPCLNT_TIME_OVERFLOW_CHECK(terminate_time, dhcp_ptr->TERMINATE_TIME)
      dhcp_ptr->TERMINATE.TIME = terminate_time;
      reset_event = TRUE;
   } else {
      if (dhcp_ptr->BOUND) {
         RTCSCMD_internal( dhcp_ptr->PARMS_BIND, IPIF_unbind );
         if ( dhcp_ptr->UNBIND_FUNC != NULL ) {
            retry = dhcp_ptr->UNBIND_FUNC((_rtcs_if_handle)dhcp_ptr->HANDLE);
         } /* Endif */
      } else {
         if ( dhcp_ptr->FAILURE_FUNC != NULL ) {
            dhcp_ptr->FAILURE_FUNC((_rtcs_if_handle)dhcp_ptr->HANDLE);
         } /* Endif */   
      } /* Endif */

      TCPIP_Event_cancel(&dhcp_ptr->RENEW);
      TCPIP_Event_cancel(&dhcp_ptr->RESEND);

      if ( retry ) {
         dhcp_ptr->REBIND_TIME = 0;
         dhcp_ptr->BOUND = FALSE;
         dhcp_ptr->STATE = DHCP_INITIALIZE;
         DHCPCLNT_modify_options( dhcp_ptr );
         DHCPCLNT_open( dhcp_ptr );
      } else  {
         BOOT_close();
         dhcp_ptr->HANDLE->BOOT = NULL;
         /* Free the DHCP Structure */
         _mem_free(dhcp_ptr);
      }/* Endif */
   } /* Endif */

   return( reset_event );
}  /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_bind_attempt_timeout
* Returned Values : boolean
* Comments        :
*     This function will timeout a lease attempt if the binding is not
*     successful.
*
*END*-----------------------------------------------------------------*/

static boolean DHCPCLNT_bind_attempt_timeout
   (
   TCPIP_EVENT_PTR   event
   /* [IN/OUT] the termination event */
   )
{  /* Body */
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr = event->PRIVATE;
   boolean                retry = FALSE;

   if ( dhcp_ptr->FAILURE_FUNC != NULL ) {
      retry = dhcp_ptr->FAILURE_FUNC((_rtcs_if_handle)dhcp_ptr->HANDLE);
   } /* Endif */

   TCPIP_Event_cancel(&dhcp_ptr->RESEND);

   if ( retry ) {
      dhcp_ptr->REBIND_TIME = 0;
      dhcp_ptr->BOUND = FALSE;
      dhcp_ptr->STATE = DHCP_INITIALIZE;
      DHCPCLNT_modify_options( dhcp_ptr );
      DHCPCLNT_open( dhcp_ptr );
   } else  {
      BOOT_close();
      dhcp_ptr->HANDLE->BOOT = NULL;
      /* Free the DHCP Structure */
      _mem_free(dhcp_ptr);
   }/* Endif */

   return FALSE;
}  /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_decline
* Returned Values : void
* Comments        :
*     Declines a lease, sends a message to the DCHP Server.
*     Add error code message later.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_decline
   (
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr,
   /* [IN/OUT] This is the DHCP Client control structure */
   uint_32     error
   /* [IN] The reason for rejection */
   )
{  /* Body */
    uint_32     old_state = dhcp_ptr->STATE;

    dhcp_ptr->STATE = DHCP_DECLINING;
    DHCPCLNT_modify_options( dhcp_ptr );

    DHCPCLNT_send_one_shot( dhcp_ptr );
    dhcp_ptr->STATS.ST_TX_DECLINE++;
    dhcp_ptr->STATE = old_state;
    DHCPCLNT_modify_options( dhcp_ptr );

}  /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_release_internal
* Returned Values : void
* Comments        :
*
*END*-----------------------------------------------------------------*/

void DHCPCLNT_release_internal
   (
      TCPIP_PARM_IF_DHCP_RELEASE _PTR_ parms
         /* [IN/OUT] This is the DHCP Client structure from the application */
   )
{  /* Body */
   IP_IF_PTR              if_ptr = (IP_IF_PTR)parms->HANDLE;
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr;

   if ( if_ptr->BOOT != NULL ) {
      dhcp_ptr = (DHCP_CLNT_STRUCT _PTR_)if_ptr->BOOT;

      TCPIP_Event_cancel(&dhcp_ptr->RENEW);
      TCPIP_Event_cancel(&dhcp_ptr->RESEND);
      TCPIP_Event_cancel(&dhcp_ptr->TERMINATE);

      dhcp_ptr->STATE = DHCP_RELEASING;
      DHCPCLNT_modify_options( dhcp_ptr );

      DHCPCLNT_send_one_shot( dhcp_ptr );
      if ( dhcp_ptr->BOUND ) {
         RTCSCMD_internal( dhcp_ptr->PARMS_BIND, IPIF_unbind );
      }/* Endif */
      BOOT_close();
      /* Free the DHCP Structure */
      _mem_free(dhcp_ptr);
      if_ptr->BOOT = NULL;
   }/* Endif */

   RTCSCMD_complete(parms, RTCS_OK);
}  /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_parse_offer
* Returned Values : void
* Comments        : We only look for the options that affect us directly.
*                   It is assumed that the application will parse any
*                   site specific options.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_parse_offer
   (
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr,
   /* [IN/OUT] This is the DHCP Client control structure */
   RTCSPCB_PTR pcb_ptr
   /*[IN] The offer we're accepting */
   )
{ /* Body */
   DHCP_HEADER _PTR_ header_ptr;
   uchar       _PTR_ opt;
   uint_32           len;
   uint_32           optlen;
   uchar             optval;

   dhcp_ptr->NEW_RENEW_TIME = 0;
   dhcp_ptr->NEW_REBIND_TIME = 0;
   /* Make sure we have a server ID */
   header_ptr = (DHCP_HEADER _PTR_)RTCSPCB_DATA(pcb_ptr);

   /* Get our IP address, and pick the default netmask */
   dhcp_ptr->NEW_PARMS_BIND.address = ntohl(header_ptr->YIADDR);
   dhcp_ptr->NEW_DATA.ClientIPaddress = dhcp_ptr->NEW_PARMS_BIND.address;
   dhcp_ptr->NEW_PARMS_BIND.locmask = 0xFFFFFFFFL;
   dhcp_ptr->NEW_PARMS_BIND.netmask =
      IN_DEFAULT_NET(dhcp_ptr->NEW_PARMS_BIND.address);

   /* Parse the options field */

   opt = (uchar _PTR_)header_ptr + sizeof(DHCP_HEADER) + 4;
   len = RTCSPCB_SIZE(pcb_ptr) - (sizeof(DHCP_HEADER) + 4);

#define DHCP_NEXTOPT    opt += optlen; \
                        break

   while (len) {

      /* Get the next option code */
      optval = ntohc(opt);
      opt++;
      len--;

      /* Interpret the pad and end options */
      if (optval == DHCPOPT_END) break;
      if (optval == DHCPOPT_PAD) continue;

      /* All other codes have a length byte */
      if (len == 0) break;
      optlen = ntohc(opt);
      opt++;
      len--;
      if (len < optlen) break;
      len -= optlen;

      switch (optval) {

      case DHCPOPT_LEASE:
         if (optlen != 4) {DHCP_NEXTOPT;}
         dhcp_ptr->NEW_DATA.LEASE = ntohl(opt);
         opt += 4;
         break;

      case DHCPOPT_MASK:
         if (optlen != 4) {DHCP_NEXTOPT;}
         dhcp_ptr->NEW_PARMS_BIND.netmask = ntohl(opt);
         dhcp_ptr->NEW_DATA.Netmask = dhcp_ptr->NEW_PARMS_BIND.netmask;
         opt += 4;
         break;

      case DHCPOPT_SERVERNAME:

         _mem_copy(opt, dhcp_ptr->NEW_DATA.SNAME, optlen);
         opt+=optlen;
         break;

      case DHCPOPT_FILENAME:

         _mem_copy(opt, dhcp_ptr->NEW_DATA.BOOTFILE, optlen);
         opt+=optlen;
         break;

      case DHCPOPT_RENEWALL:
         if ( optlen != DHCPSIZE_LEASE ) {
            DHCP_NEXTOPT;
         } /* Endif */
         dhcp_ptr->NEW_RENEW_TIME = ntohl(opt);
         opt += DHCPSIZE_LEASE;
         break;

      case DHCPOPT_REBINDING:
         if ( optlen != DHCPSIZE_LEASE ) {
            DHCP_NEXTOPT;
         } /* Endif */
         dhcp_ptr->NEW_REBIND_TIME = ntohl(opt);
         opt += DHCPSIZE_LEASE;
         break;

      default:
         DHCP_NEXTOPT;
      } /* Endswitch */

   } /* Endwhile */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_copy_binding
* Returned Values : void
* Comments        :
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_copy_binding
   (
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr
   /* [IN/OUT] This is the DHCP Client control structure */
   )
{  /* Body */
   dhcp_ptr->PARMS_BIND.address = dhcp_ptr->NEW_PARMS_BIND.address;
   dhcp_ptr->PARMS_BIND.locmask = dhcp_ptr->NEW_PARMS_BIND.locmask;
   dhcp_ptr->PARMS_BIND.netmask = dhcp_ptr->NEW_PARMS_BIND.netmask;
   dhcp_ptr->DATA.ServerIp = dhcp_ptr->NEW_DATA.ServerIp;
   dhcp_ptr->DATA.ClientIPaddress = dhcp_ptr->NEW_DATA.ClientIPaddress;
   dhcp_ptr->DATA.LEASE = dhcp_ptr->NEW_DATA.LEASE;
   dhcp_ptr->DATA.Netmask = dhcp_ptr->NEW_DATA.Netmask;
   dhcp_ptr->DATA.SADDR   = dhcp_ptr->NEW_DATA.SADDR;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_verify_packet
* Returned Values : uchar
* Comments        :
*     Verifies the validity of an incoming DHCP packet.
*
*END*-----------------------------------------------------------------*/

static uchar DHCPCLNT_verify_packet
 (
 DHCP_CLNT_STRUCT _PTR_ dhcp_ptr,
 RTCSPCB_PTR            pcb_ptr,
 uchar            _PTR_ bootreply,
 uint_32          _PTR_ packet_size,
 _ip_address      _PTR_ temp_addr,
 _ip_address      _PTR_ current_ip,
 uint_32          _PTR_ error
 )

{ /* Body */
   DHCP_HEADER      _PTR_ send_header_ptr;
   DHCP_HEADER      _PTR_ header_ptr;
   uchar            _PTR_ opt;
   uint_32                len;
   uchar            _PTR_ packet_option;

   /* Make sure the datagram is large enough for the magic cookie */
   if (RTCSPCB_SIZE(pcb_ptr) < sizeof(DHCP_HEADER) + 4) {
      *error = RTCSERR_DHCPCLNT_PACKET_SIZE_ERROR;
      return 0;
   } /* Endif */

   send_header_ptr = (DHCP_HEADER _PTR_)dhcp_ptr->PACKET;
   header_ptr = (DHCP_HEADER _PTR_)bootreply;
   /* Make sure the XID matches */
   if (ntohl(header_ptr->XID) != ntohl(send_header_ptr->XID)) {
      *error = RTCSERR_DHCPCLNT_XID_MISMATCH;
      return 0;
   } /* Endif */

   /* Parse the options field to find the message type */
   opt = bootreply + sizeof(DHCP_HEADER);
   *packet_size = RTCSPCB_SIZE(pcb_ptr);
   len = *packet_size - sizeof(DHCP_HEADER);
   if (ntohl(opt) != DHCP_MAGIC) {
      *error = RTCSERR_DHCP_PACKET_ERROR;
      return 0;
   } /* Endif */
   opt += 4;
   len -= 4;
   /* These are to avoid problems in DHCPCLNT_modify_options if we decline */
   *temp_addr = dhcp_ptr->NEW_DATA.ServerIp;
   dhcp_ptr->NEW_DATA.ServerIp = IP_source(pcb_ptr);
   dhcp_ptr->NEW_DATA.ClientIPaddress = ntohl(header_ptr->YIADDR);

   packet_option = DHCP_find_option(opt, len, DHCPOPT_SERVERID);
   if ( !packet_option || ntohc(packet_option + 1) != 4 ) {
      /* Server id option is required in all DHCP responses */
      DHCPCLNT_decline( dhcp_ptr, RTCSERR_DHCP_SERVER_OPTION_MISSING );
      dhcp_ptr->NEW_DATA.ServerIp = *temp_addr;
      *error = RTCSERR_DHCPCLNT_ERROR_DECLINED;
      return 0;
   } else  {
      packet_option += 2;
      *current_ip = ntohl(packet_option);
   } /* Endif */

   packet_option = DHCP_find_option(opt, len, DHCPOPT_MSGTYPE);
   if ( !packet_option || ntohc(packet_option + 1) != 1 ) {
      /* Message type option is required in all DHCP packets */
      DHCPCLNT_decline( dhcp_ptr, RTCSERR_DHCP_MESSAGE_OPTION_MISSING);
      dhcp_ptr->NEW_DATA.ServerIp = *temp_addr;
      *error = RTCSERR_DHCPCLNT_ERROR_DECLINED;
      return 0;
   } /* Endif */
   packet_option += 2;
   *error = RTCS_OK;
   return(ntohc(packet_option));
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_service_offer
* Returned Values : void
* Comments        :
*     Services an incoming DHCP offer packet.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_service_offer
 (
 DHCP_CLNT_STRUCT _PTR_ dhcp_ptr,
 RTCSPCB_PTR            pcb_ptr,
 uchar            _PTR_ bootreply,
 uint_32                packet_size,
 _ip_address            temp_addr,
 _ip_address            current_ip
  )

{  /* Body */

   dhcp_ptr->STATS.ST_RX_OFFER++;
   if ( dhcp_ptr->STATE == DHCP_INITIALIZE ) {
      if ( dhcp_ptr->CHOICE_FUNC != NULL ) {
         if (dhcp_ptr->CHOICE_FUNC(bootreply, packet_size) < 0) {
            /* It's been rejected */
            dhcp_ptr->NEW_DATA.ServerIp = temp_addr;
            return;
         }/* Endif */
      }/* Endif */
      
      /* start the probe - this needs to complete before we service the dhACK */
      if (dhcp_ptr->FLAGS & DHCP_SEND_PROBE) {
         DHCPCLNT_probe_address( dhcp_ptr->NEW_DATA.ClientIPaddress,  dhcp_ptr->HANDLE);
      }
      /* We're accepting this offer */
      TCPIP_Event_cancel(&dhcp_ptr->RESEND);
      dhcp_ptr->NEW_DATA.ServerIp = current_ip;
      DHCPCLNT_parse_offer( dhcp_ptr, pcb_ptr );
      dhcp_ptr->STATE = DHCP_REQUESTING;
      DHCPCLNT_modify_options( dhcp_ptr );
      dhcp_ptr->RETRY_TIMEOUT = DHCP_TIMEOUT_MIN;
      
      /* 
      ** put a bit of delay in before we send the REQEST. This is to give the
      ** probe (started above) a chance to compete.
      */
      dhcp_ptr->RESEND.TIME = 50;  /* 50 miliseconds */
      TCPIP_Event_add( &dhcp_ptr->RESEND );
   }/* Endif */

} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_set_timed_events
* Returned Values : void
* Comments        :
*     Sets the renewall, rebinding, and termination times.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_set_timed_events
(
DHCP_CLNT_STRUCT _PTR_ dhcp_ptr
)


{  /* Body */
   uint_32                random;
   uint_32                renewall_range;
   uint_32                renewall_time;
   uint_32                terminate_time;

   /* Set or reset the renewall event */
   if ( dhcp_ptr->NEW_RENEW_TIME ) {
      dhcp_ptr->RENEW_TIME = dhcp_ptr->NEW_RENEW_TIME;
   } else {
      /*
      ** The renewall time wasn't specified by the DHCP server,
      ** we need to set it ourselves.
      */
      DHCP_SET_RENEWALL_RANGE(dhcp_ptr->DATA.LEASE, renewall_range);
      random = RTCS_rand() % renewall_range;
      dhcp_ptr->RENEW_TIME =
         DHCP_RENEWALL_BIAS( dhcp_ptr->DATA.LEASE ) + random;
   } /* Endif */

   /* Set or reset rebinding event */
   if ( dhcp_ptr->NEW_REBIND_TIME ) {
      dhcp_ptr->REBIND_TIME = dhcp_ptr->NEW_REBIND_TIME
         - dhcp_ptr->RENEW_TIME;
   } else {
      /*
      ** The rebinding time wasn't specified by the DHCP server,
      ** we need to set it ourselves.
      */
      DHCP_SET_RENEWALL_RANGE(dhcp_ptr->DATA.LEASE, renewall_range);
      random = RTCS_rand() % renewall_range;
      dhcp_ptr->REBIND_TIME =
         (DHCP_REBINDING_BIAS(dhcp_ptr->DATA.LEASE) + random)
         - dhcp_ptr->RENEW_TIME;
   }/* Endif */

   dhcp_ptr->R_EVENTS_TIME_LEFT = dhcp_ptr->RENEW_TIME;
   DHCPCLNT_TIME_OVERFLOW_CHECK(renewall_time, dhcp_ptr->R_EVENTS_TIME_LEFT)
   dhcp_ptr->RENEW.TIME = renewall_time;
   dhcp_ptr->RENEW.EVENT = DHCPCLNT_renew_lease;
   dhcp_ptr->TERMINATE_TIME = dhcp_ptr->DATA.LEASE;

   DHCPCLNT_TIME_OVERFLOW_CHECK(terminate_time, dhcp_ptr->TERMINATE_TIME)
   dhcp_ptr->TERMINATE.TIME = terminate_time;
}  /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_service_ack
* Returned Values : void
* Comments        :
*     Services an incoming DHCP ack packet.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_service_ack
(
DHCP_CLNT_STRUCT _PTR_ dhcp_ptr,
RTCSPCB_PTR            pcb_ptr,
uchar            _PTR_ bootreply,
uint_32                packet_size,
IP_IF_PTR              if_ptr
)

{  /* Body */
   DHCP_HEADER      _PTR_ header_ptr;
   boolean                renew_again = TRUE;

   dhcp_ptr->STATS.ST_RX_ACK++;
   if ( dhcp_ptr->STATE == DHCP_INFORMED ) {
      /* Give the application the info and self destruct */
      TCPIP_Event_cancel(&dhcp_ptr->RESEND);
      if ( dhcp_ptr->BIND_FUNC != NULL ) {
         dhcp_ptr->BIND_FUNC(bootreply, packet_size, (_rtcs_if_handle)if_ptr);
      } /* Endif */
      BOOT_close();
      dhcp_ptr->HANDLE->BOOT = NULL;
      /* Free the DHCP Structure */
      _mem_free(dhcp_ptr);
      return;
   } /* Endif */

   header_ptr = (DHCP_HEADER _PTR_)bootreply;
   /* OK, we've got a valid lease */
   dhcp_ptr->DATA.SADDR = ntohl(header_ptr->SIADDR);
   dhcp_ptr->NEW_DATA.ClientIPaddress = ntohl(header_ptr->YIADDR);

   TCPIP_Event_cancel(&dhcp_ptr->TERMINATE);
   DHCPCLNT_parse_offer( dhcp_ptr, pcb_ptr );
   if ( dhcp_ptr->BOUND ) {
      if (dhcp_ptr->NEW_DATA.LEASE < dhcp_ptr->DATA.LEASE) {
         /* Our request was refused, wait for termination to bind again */
         renew_again = FALSE;
      }/* Endif */
      dhcp_ptr->DATA.LEASE = dhcp_ptr->NEW_DATA.LEASE;
      /* Cancel renewall and lease expiry events */
      TCPIP_Event_cancel(&dhcp_ptr->RENEW);
   } else {
      /* Bind the received IP address to this interface */
      DHCPCLNT_copy_binding( dhcp_ptr );
      RTCSCMD_internal( dhcp_ptr->PARMS_BIND, IPIF_bind );
      dhcp_ptr->TERMINATE.EVENT = DHCPCLNT_terminate_lease;
   } /* Endif */

   if ( dhcp_ptr->DATA.LEASE != DHCP_LEASE_INFINITE ) {
      DHCPCLNT_set_timed_events(dhcp_ptr);
   } /* Endif */

   TCPIP_Event_cancel(&dhcp_ptr->RESEND);
   dhcp_ptr->BOUND = TRUE;
   if ( DHCPCLNT_verify_address( dhcp_ptr )) {
      /* Address is in use by another host, decline and restart */
      DHCPCLNT_decline( dhcp_ptr, RTCSERR_DHCP_ADDR_IN_USE);
      RTCSCMD_internal( dhcp_ptr->PARMS_BIND, IPIF_unbind );
      dhcp_ptr->BOUND = FALSE;
      dhcp_ptr->RENEW_TIME = 0;
      dhcp_ptr->STATE = DHCP_INITIALIZE;
      DHCPCLNT_modify_options( dhcp_ptr );
      DHCPCLNT_open( dhcp_ptr );
   } else  {
      dhcp_ptr->STATE = DHCP_BOUND;
      if ( dhcp_ptr->BIND_FUNC != NULL ) {
         dhcp_ptr->BIND_FUNC(bootreply, packet_size,
            (_rtcs_if_handle)if_ptr);
      }/* Endif */

      if (dhcp_ptr->DATA.LEASE == DHCP_LEASE_INFINITE) {
         if(!(dhcp_ptr->FLAGS & DHCP_MAINTAIN_STATE_ON_INFINITE_LEASE)) {
            BOOT_close();
            dhcp_ptr->HANDLE->BOOT = NULL;
            /* Free the DHCP Structure */
            _mem_free(dhcp_ptr);
         } /* Endif */
      } else  {
         if (renew_again) {
            TCPIP_Event_add(&dhcp_ptr->RENEW);
         }/* Endif */
         TCPIP_Event_add(&dhcp_ptr->TERMINATE);
      } /* Endif */
   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_service_nak
* Returned Values : void
* Comments        :
*     Services an incoming DHCP NAK packet.
*
*END*-----------------------------------------------------------------*/

static void DHCPCLNT_service_nak
(
DHCP_CLNT_STRUCT _PTR_ dhcp_ptr,
uchar            _PTR_ bootreply,
uint_32                packet_size
)
{ /* Body */
   boolean retry = FALSE;

   /* Our request was refused. Start request over */
   dhcp_ptr->STATS.ST_RX_NAK++;

   if ( dhcp_ptr->NAK_FUNC != NULL )
   {
      retry = dhcp_ptr->NAK_FUNC(bootreply, packet_size, (_rtcs_if_handle)dhcp_ptr->HANDLE);
   } /* Endif */

   if ( retry ) {
      dhcp_ptr->STATE = DHCP_REQUESTING;
      DHCPCLNT_modify_options( dhcp_ptr ); 
   } else {
      dhcp_ptr->RENEW_TIME = 0;
      dhcp_ptr->TERMINATE_TIME = 0;

      TCPIP_Event_cancel(&dhcp_ptr->TERMINATE);
      DHCPCLNT_terminate_lease(&dhcp_ptr->TERMINATE);
   } /* Endif */

} /* Endbody */

/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_service
* Returned Values : void
* Comments        :
*     Services an incoming DHCP packet.
*
*END*-----------------------------------------------------------------*/

void DHCPCLNT_service
   (
      RTCSPCB_PTR    pcb_ptr,     /* [IN] DHCP packet */
      UCB_STRUCT_PTR ucb_ptr      /* [IN]     target UCB       */
   )
{ /* Body */
   IP_IF_PTR              if_ptr   = (IP_IF_PTR)pcb_ptr->IFSRC;
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr = (DHCP_CLNT_STRUCT _PTR_)if_ptr->BOOT;
   uchar            _PTR_ bootreply;
   uchar                  msgtype;
   uint_32                packet_size;
   uint_32                error;
   _ip_address            current_ip;
   _ip_address            temp_addr;

   RTCSLOG_PCB_READ(pcb_ptr, RTCS_LOGCTRL_PORT(IPPORT_BOOTPC), 0);

   if ((dhcp_ptr != NULL) && (dhcp_ptr->STATE != DHCP_BOUND)) {
      bootreply = RTCSPCB_DATA(pcb_ptr);
      msgtype = DHCPCLNT_verify_packet(dhcp_ptr, pcb_ptr, bootreply,
         &packet_size, &temp_addr, &current_ip, &error);
      if (error) {
         RTCSLOG_PCB_FREE(pcb_ptr, error);
         RTCSPCB_free(pcb_ptr);
         return;
      } /* Endif */
      if ( msgtype == DHCPTYPE_DHCPOFFER ) {
         DHCPCLNT_service_offer(dhcp_ptr, pcb_ptr, bootreply, packet_size,
            temp_addr, current_ip);
      } else {
         /* It's an ACK or a NAK message */
         if ( dhcp_ptr->STATE != DHCP_INITIALIZE ) {
            if ( msgtype == DHCPTYPE_DHCPACK ) {
               DHCPCLNT_service_ack(dhcp_ptr, pcb_ptr, bootreply, packet_size,
                  if_ptr);
            } else if (msgtype == DHCPTYPE_DHCPNAK ) {
               DHCPCLNT_service_nak(dhcp_ptr, bootreply, packet_size);
            } /* Endif */
         } /* Endif */
      } /* Endif */
   } /* Endif */

   RTCSLOG_PCB_FREE(pcb_ptr, RTCS_OK);
   RTCSPCB_free(pcb_ptr);
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_verify_address
* Returned Values : boolean, TRUE if the address is in use by another host,
*                   FALSE otherwise
* Comments        :
*
*END*-----------------------------------------------------------------*/

static boolean DHCPCLNT_verify_address
   (
   DHCP_CLNT_STRUCT _PTR_ dhcp_ptr
   /* [IN] This is the DHCP Client control structure */
   )
{  /* Body */
   _ip_address 		new_ip = dhcp_ptr->DATA.ClientIPaddress;
   IP_IF_PTR		if_ptr = dhcp_ptr->HANDLE;
   
   if (! dhcp_ptr->FLAGS & DHCP_SEND_PROBE) {
      /* we are configured to not probe, so don't try to verify */
      return FALSE;
   } 
   
   /* note: an arp probe was sent out during DHCPCLNT_service_offer.
      we expect that if an arp response was comming, it arrived by
      now.
    */
    
   return ARP_is_complete (if_ptr, new_ip);
}  /* Endbody */



/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : DHCPCLNT_probe_address
* Returned Values : RTCS_OK meanse that probe was sent out the interface. 
* Parameters      :
*
*     _ip_address       probe_addr    [IN] IP address to probe the network for
*	  pointer     :     src_interface [IN] interface we will send the probe from
* 
* Comments        :
*     Send out an arp reuest to ipaddress from interface. An arp reply will
*     be noted by the arp system on this interface and may be referenced later.
*
*END*-----------------------------------------------------------------*/

uint_32 DHCPCLNT_probe_address
   (
      _ip_address       probe_addr,
      pointer           src_interface
   )
{ /* Body */

	return( ARP_request(src_interface, probe_addr, probe_addr ));

} /* Endbody */

/* EOF */
