
/*
 *
 *     Module Name: blDhcp.c
 *	       Version: 1.00
 *   Original Date: 07/19/02	
 *	        Author: Charles Gordon
 *	      Language: Ansi C
 * Compile Options:
 * Compile defines:
 *	     Libraries:
 *    Link Options:
 *
 *    Entry Points:
 *
 *  Copyright (c) Digi International Inc., All Rights Reserved
 *  
 *  This software contains proprietary and confidential information of Digi
 *  International Inc.  By accepting transfer of this copy, Recipient agrees
 *  to retain this software in confidence, to prevent disclosure to others,
 *  and to make no use of this software other than that for which it was
 *  delivered.  This is an unpublished copyrighted work of Digi International
 *  Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 *  prohibited.
 *  
 *  Restricted Rights Legend
 * 
 *  Use, duplication, or disclosure by the Government is subject to
 *  restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 *  Technical Data and Computer Software clause at DFARS 252.227-7031 or
 *  subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 *  Restricted Rights at 48 CFR 52.227-19, as applicable.
 * 
 *  Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 * Description.
 * =======================================================================
 *  This file contains the routines that implement the DHCP support in
 *  the boot loader.
 *
 *
 *
 * Edit Date/Ver   Edit Description
 * ==============  =======================================================
 *  4/03/03  CHG    Changed generateXid() so that the XID is more closely
 *                  based on the unit's Ethernet MAC address.  This will
 *                  minimize the chances of a collision between two
 *                  units.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <Npttypes.h>
#include <bele.h>
#include <dhcp.h>
#include <packet.h>
#include <armeth.h>
#include <udp.h>
#include <timer.h>
#include <bootldr.h>
#include <blerror.h>
#include <bsp.h>
#include <gdb.h>


/*
 * UDP port numbers for BOOTP/DHCP protocol.
 */
#define BOOTP_CLIENT_PORT   68
#define BOOTP_SERVER_PORT   67


/*
 * IP local broadcast address.
 */
#define LOCAL_BROADCAST     0xffffffff


/*
 * This structure defines the format of DHCP packets.  See section 2 of 
 * RFC 2131.
 */
typedef struct
{
    BYTE op;                    /* BOOTP op code either BOOTREQUEST or BOOTREPLY */
    BYTE htype;                 /* MAC address type, set to 1 for Ethernet*/
    BYTE hlen;                  /* MAC address length, set to 6 for Ethernet*/
    BYTE hops;                  /* used by BOOTP relay agents, client sets to 0*/
    WORD32 xid;                 /* transaction ID, set to random number by client*/
    WORD16 secs;                /* set by client to seconds since client started DHCP*/
    WORD16 flags;               /* high bit set if server must broadcast response to us*/
    WORD32 ciaddr;              /* what the client think's its IP address is*/
    WORD32 yiaddr;              /* what the server says the client's IP address should be*/
    WORD32 siaddr;              /* address of server to use for next step in boot process*/
    WORD32 giaddr;              /* address of relay agent used to communicate with server*/
    BYTE chaddr[16];            /* client's MAC address*/
    BYTE sname[64];             /* name of server we want to use*/
    BYTE file[128];             /* name of boot file, client sets to give server hint, server fills in with actual*/
    WORD32 magicCookie;         /* should be set to 0x63825363 */
} dhcpHeaderType;


/*
 * Values for op code field.
 */
#define BOOTREQUEST         1
#define BOOTREPLY           2

/*
 * Value for htype field that specifies Ethernet.
 */
#define HTYPE_VALUE         1

/*
 * Value for hlen field that specifies 6 byte Ethernet address.
 */
#define HLEN_VALUE          6

/*
 * Value for magic cookie.
 */
#ifdef _LITTLE_ENDIAN
#define MAGIC_COOKIE_VALUE  0x63538263
#else
#define MAGIC_COOKIE_VALUE  0x63825363
#endif


/*
 * This structure puts all the DHCP client global variables into one variable.
 */
typedef struct
{
    int result;                 /* BL_SUCCESS or BL_FAILURE*/
    int retriesRemaining;       /* number of retries before we give up*/
    WORD32 timeout;             /* current timeout value in ticks*/
    PACKET *packet;             /* pointer to current request packet*/
    int lastRequest;            /* set to DHCP message type of last transmitted message*/
    blDhcpInfoType *info;       /* caller's data buffer*/
    BOOLEAN receivedNack;       /* set if we have received a NACK to a DHCPREQUEST message*/
    WORD32 xid;                 /* transaction ID, set to random number by client*/
    WORD32 startTime;           /* set to time we started the negotiation*/
    WORD32 serverIp;            /* set to IP of expected server after DHCPOFFER*/
    int udpSendFailed;          /* number of times we couldn't send a packet*/
    int allocFailed;            /* number of times we couldn't allocate a packet*/
} dhcpInfoType;

/*
 * Possible values for state field.
 */
#define WAIT_FOR_OFFER      0
#define WAIT_FOR_ACK        1


static dhcpInfoType dhcpInfo;
static volatile BOOLEAN negotiationInProgress = FALSE;

static void dhcpRetransmit(WORD32 notUsed);








/*
 *
 *  Function: void shutdown (int ccode)
 *
 *  Description:
 *
 *      This routine is called to shutdown the DHCP negotiation process
 *      either because we have succeeded, or because an error was
 *      detected.
 *
 *  Parameters:
 *
 *      ccode       contains the completion code for the negotiation
 *
 *  Return Values:
 *
 *      none
 *
 */

static void shutdown (int ccode)

{
    dhcpInfo.result = ccode;
    negotiationInProgress = FALSE;
    TimerStop(dhcpRetransmit, 0);
}




/*
 *
 *  Function: BYTE *getClientId(void)
 *
 *  Description:
 *
 *      This routine returns the ID for this client.  The ID is our Ethernet
 *      address prefixed with the byte 1.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *      client ID in statically allocated buffer
 *
 */

static BYTE *getClientId(void)
{
    static BYTE id[CLIENT_ID_LENGTH];
    static BOOLEAN initialized = FALSE;

    if (!initialized)
    {
        id[0] = 1;
        memcpy (&id[1], EthernetMyMac(), 6);
        initialized = TRUE;
    }

    return id;
}






/*
 *
 *  Function: WORD32 generateXid(void)
 *
 *  Description:
 *
 *      This routine generates an XID.  An XID is suppose to be a random number that
 *      is used to identify packets from a particular session.  We don't have a random
 *      number generator.  We generate a value by munging together some Ethernet 
 *      statistics with the system clock value.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *      XID to use for this session
 *
 */

static WORD32 generateXid(void)
{
    WORD32 result = 7; /* 7 is just a number picked out of a hat */
    BYTE *clientId = getClientId();

/*
 * Try to create the lower byte of the ID so that it has a chance of 
 * of being different each time this routine is run.
  */
    result *= (estats.rx_receive | 1);
    result *= (estats.rx_broadcast | 1);
    result *= (estats.tx_sent | 1);
    result *= (TimerGetTime() | 1);
    result *= (estats.rx_receive  | 1);
    result &= 0xff;
    
/*
 * Set the upper 3 bytes to be the unit's MAC address.  Each unit 
 * should have a unique MAC address.
 */
    result |= (clientId[4] << 24);
    result |= (clientId[5] << 16);
    result |= (clientId[6] << 8);
    
    return result;
}






/*
 *
 *  Function: void dhcpRetransmit(WORD32 notUsed)
 *
 *  Description:
 *
 *      Send out the packet in dhcpInfo->packet.  When we are called,
 *      this packet will already be setup for us.  We just copy it into
 *      a new buffer and send it.
 *
 *      Setup automatic retransmission if we have not run out of retries.
 *      Terminate the task if we have run out of retries.
 *
 *  Parameters:
 *
 *      notUsed     available to pass context information when called
 *                  from the timer ISR, but not used by this function
 *
 *  Return Values:
 *
 *      none
 *
 */

static void dhcpRetransmit(WORD32 notUsed)
{

    if (dhcpInfo.retriesRemaining)
    {
        PACKET *packet = PacketAllocate();

    	if (packet)
        {
        	memcpy(packet->frame, dhcpInfo.packet->frame, dhcpInfo.packet->length);
        	packet->length = dhcpInfo.packet->length;
        
        	if (!UdpSend(packet, LOCAL_BROADCAST, BOOTP_CLIENT_PORT, BOOTP_SERVER_PORT))
        	{
        	    dhcpInfo.udpSendFailed++;
        	    PacketFree(packet);
        	}
        }
        else
    	{
    	    dhcpInfo.allocFailed++;
    	}

        TimerSchedule (dhcpInfo.timeout, dhcpRetransmit, 0);
        dhcpInfo.timeout *= 2;
        dhcpInfo.retriesRemaining--;
    }
    else
    {
        shutdown(BL_DHCP_RETRANSMIT_ERROR);
    }
}








/*
 *
 *  Function: BYTE *addOption(BYTE *buffer, BYTE option, BYTE length, BYTE *data)
 *
 *  Description:
 *
 *      Add a DHCP option into a buffer.  An option consists of an option ID followed
 *      by a length byte, followed by length bytes of data.
 *
 *      Two options, PAD and END, are special cases.  These options have no length
 *      byte or data.
 *
 *  Parameters:
 *
 *      buffer          pointer to buffer to write option into
 *      option          option ID
 *      length          length of data portion of option
 *      data            pointer to data to write
 *
 *  Return Values:
 *
 *      pointer to place to put next option
 *
 */

static BYTE *addOption(BYTE *buffer, BYTE option, BYTE length, BYTE *data)
{
    int optionDataByte;

    *buffer = option;
    buffer++;

    if ((option != PAD) && (option != END))
    {
        *buffer = length;
        buffer++;
        for (optionDataByte = 0; optionDataByte < length; optionDataByte++)
        {
            *buffer = *data;
            buffer++;
            data++;
        }
    }

    return buffer;              /* return pointer to place to put next option*/
}





/*
 *
 *  Function: int sendDhcpRequest(BYTE messageType)
 *
 *  Description:
 *
 *      Send a DHCPDISCOVER or DHCPREQUEST message.  The messageType is passed
 *      to us as a parameter.  Other than the message type, which is embedded in
 *      the message as an option, DHCPDISCOVER and DHCPREQUEST messages are
 *      identical.
 *
 *      The following information is encoded into the message.
 *
 *          op          set to BOOTREQUEST
 *          htype       set to 1 to indicate an Ethernet network
 *          hlen        set to 6 to indicate a 6 byte Ethernet address
 *          hops        set to 0
 *          xid         set to the (psuedo) random value in dhcpInfo.xid
 *          secs        set to current time - dhcpInfo.startTime (which was set by the caller)
 *          ciaddr      set to 0
 *          yiaddr      set to 0
 *          siaddr      set to 0
 *          giaddr      set to 0
 *          chaddr      set to our Ethernet MAC address
 *          sname       filled with 0
 *          file        filled with 0
 *          magicCookie set to 0x63825363 to indicate a DHCP message
 *          options     buffer filled with options listed below:
 *
 *      The following options are set.
 *
 *          MAX_DHCP_MESSAGE_SIZE       set to 1000 which should be large enough for what we are doing
 *          REQUESTED_IP_ADDRESS        set to dhcpInfo->info.ipAddress if dhcpInfo->info.ipAddress is not 0
 *                                      this is set by the caller of blGetDhcpInfo().
 *          DHCP_MESSAGE_TYPE           set to value of messageType parameter
 *          SERVER_IDENTIFIER           set to ID of server that sent DHCPOFFER message, or zero if we are
 *                                      sending a DHCPDISCOVER or doing a DHCPREQUEST with a previously 
 *                                      used IP address
 *          PARAM_LIST                  if we want TFTP information then we request SUBNET_MASK, GATEWAY, 
 *                                      VENDOR_INFO, BOOT_FILENAME, ROOTPATH, and BOOT_FILE_SIZE.  If we only 
 *                                      want an IP address then we request SUBNET_MASK, GATEWAY, and VENDOR_INFO.
 *          VENDOR_CLASS_ID             set to the contents of dhcpInfo->info.vendorClass if the caller 
 *                                      provided this value.
 *          CLIENT_ID                   set to 1 followed by our Ethernet MAC address.
 *          VENDOR_INFO                 set to the contents of dhcpInfo->info.vendorInfo if the caller provided
 *                                      this value.
 *          END                         signifies end of option list.
 *
 *  Parameters:
 *
 *      messageType     either DHCPDISCOVER or DHCPREQUEST
 *
 *  Return Values:
 *
 *      none
 *
 */

static void sendDhcpRequest(BYTE messageType)
{
    PACKET *packet = dhcpInfo.packet;
    static dhcpHeaderType header;
    BYTE *nextOption;
    WORD16 maxMessageSize = 1000;          /* big enough for anything we will do*/
    BYTE paramList[] = {SUBNET_MASK, GATEWAY, VENDOR_INFO};
    BYTE paramListWithTftp[] = {SUBNET_MASK, GATEWAY, VENDOR_INFO, BOOT_FILENAME, ROOTPATH, BOOT_FILE_SIZE};

    /*
     * Fill in the DHCP header values.  Header is setup as a static variable and then copied into
     * the packet buffer later because it will be missaligned when it is in the packet buffer.
     */
    memset(&header, 0, sizeof(header));
    header.op = BOOTREQUEST;
    header.htype = HTYPE_VALUE;
    header.hlen = HLEN_VALUE;
    header.xid = dhcpInfo.xid;
    header.secs = ((TimerGetTime() - dhcpInfo.startTime) / TIMER_TICKS_PER_SEC) + 1;
    memcpy (header.chaddr, EthernetMyMac(), HLEN_VALUE);
    memcpy (header.file, dhcpInfo.info->imageFilename, sizeof(header.file));
    header.magicCookie = MAGIC_COOKIE_VALUE;
    memcpy (&packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE], &header, sizeof(header));

    /*
     * Write option fields.
     */
    nextOption = (BYTE *) &packet->frame[UDP_HEADER_OFFSET+UDP_HEADER_SIZE + sizeof(header)];

    if (dhcpInfo.info->vendorInfoLength)
    {
        nextOption = addOption(nextOption, VENDOR_INFO, dhcpInfo.info->vendorInfoLength, dhcpInfo.info->vendorInfo);
    }

    /*
     * Write the DHCP specific options.  Some BOOTP servers will ignore DHCP options, others will not.
     * So we include a way of suppressing DHCP options for those BOOTP servers that have a problem 
     * with them.
     */
    if (dhcpInfo.info->useDhcpOptions)
    {
        nextOption = addOption(nextOption, MAX_DHCP_MESSAGE_SIZE, MAX_DHCP_MESSAGE_SIZE_LENGTH, (BYTE *) &maxMessageSize);
        if (dhcpInfo.info->ipAddress)
        {
            nextOption = addOption(nextOption, REQUESTED_IP_ADDRESS, REQUESTED_IP_ADDRESS_LENGTH, (BYTE *) &dhcpInfo.info->ipAddress);
        }
        nextOption = addOption(nextOption, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_LENGTH, &messageType);
        if (dhcpInfo.serverIp != 0)
        {
            nextOption = addOption(nextOption, SERVER_IDENTIFIER, SERVER_IDENTIFIER_LENGTH, (BYTE *) &dhcpInfo.serverIp);
        }
        if (dhcpInfo.info->vendorClassLength)
        {
            nextOption = addOption(nextOption, VENDOR_CLASS_ID, dhcpInfo.info->vendorClassLength, dhcpInfo.info->vendorClass);
        }
        nextOption = addOption(nextOption, CLIENT_ID, CLIENT_ID_LENGTH, getClientId());
    
        if (dhcpInfo.info->wantTftpInfo)
        {
            nextOption = addOption(nextOption, PARAM_LIST, sizeof(paramListWithTftp), paramListWithTftp);
        }
        else
        {
            nextOption = addOption(nextOption, PARAM_LIST, sizeof(paramList), paramList);
        }
    }

    nextOption = addOption(nextOption, END, 0, NULL);

    packet->length = (WORD32) (nextOption - (BYTE *) dhcpInfo.packet->frame);

    /*
     * Okay.  The message is complete now.  We use the dhcpRetransmit() routine to send the
     * message out the first time so that we don't have duplicate code.  The timeout for the
     * first message is 4 seconds.  The retransmit routine doubles it each on each retransmission.
     * The number of retries is set to 5.  However, since the first transmit counts as a retry,
     * we actually only retry 4 times.
     */
    dhcpInfo.timeout = 4 * TIMER_TICKS_PER_SEC;
    #if (BSP_BOOTLOADER_RECOVERY_METHOD == BSP_BOOTLOADER_TRY_ALL)
    dhcpInfo.retriesRemaining = 2;
    #else
    dhcpInfo.retriesRemaining = 5;
    #endif
    dhcpInfo.lastRequest = messageType;

    dhcpRetransmit(0);
}










/*
 *
 *  Function: BYTE *findOption(BYTE *buffer, BYTE option)
 *
 *  Description:
 *
 *      Scan a buffer for a DHCP option.  Return a pointer to
 *      the length byte of the option if we find it.
 *      DHCP options consist of an option code, a length byte,
 *      and then <length> data bytes.  We scan through the options
 *      one at a time until we either find the option we want,
 *      or reach the end of the buffer.
 *
 *      Two options are special.  The option value of 0 is used
 *      as a spacer and should be ignored.  The option value of 255
 *      indicates the end of the option buffer.  Neither of these
 *      options has a length byte or any data bytes.
 *
 *  Parameters:
 *
 *      buffer      pointer to buffer that contains DHCP options
 *      option      value of option we are looking for
 *
 *  Return Values:
 *
 *      NULL        did not find the option
 *      !NULL       pointer to length byte of option
 *
 */

static BYTE *findOption(BYTE *buffer, BYTE option)
{
    BYTE *result = NULL;
    int optionLength;

    while ((*buffer != END) && (result == NULL))
    {
        if (*buffer == PAD)                     /* skip over spaces*/
        {
            buffer++;
        }
        else if (*buffer == option)             /* if we found it*/
        {
            result = ++buffer;                  /* set result to point to length byte*/
        }
        else                                    /* if some other option */
        {
            ++buffer;
            optionLength = *buffer;             /* get length of option*/
            ++buffer;                           /* point to first data byte*/
            while (optionLength)
            {                                   /* and skip over the data bytes*/
                ++buffer;
                optionLength--;
            }
        }
    }

    return result;
}





/*
 *
 *  Function: BYTE *getOption(dhcpHeaderType *header, BYTE option, int *length)
 *
 *  Description:
 *
 *      This function scans a DHCP response for an option value.  A
 *      pointer to the start of the option is returned.
 *
 *  Parameters:
 *
 *      header      pointer to DHCP packet header
 *      option      value of desired option
 *      length      to be set to length of option
 *
 *  Return Values:
 *
 *      NULL        option was not found
 *      !NULL       pointer to start of option just past length byte
 *
 */

static BYTE *getOption(dhcpHeaderType *header, BYTE option, int *length)
{
    BYTE *optionField = ((BYTE *) header) + sizeof(dhcpHeaderType);
    BYTE *overload = findOption(optionField, OPTION_OVERLOAD);
    BYTE *desiredOption = findOption(optionField, option);
    BYTE *result = NULL;

    /*
     * At this point, we have scanned the normal option field for the option.
     * If we found it, then desiredOption points to the length byte.  If not,
     * then we need to look in the file and sname fields of the header if 
     * those have been overloaded.  If they are overloaded, then the server
     * will indicate so with an OPTION_OVERLOAD option in the normal option
     * field and we have already set overload to point to it if it's there.
     */

    *length = 0;
    if ((desiredOption == NULL) && (overload != NULL))
    {
        overload += 1;
        if (*overload & FILE_FIELD_OVERLOADED)
        {
            desiredOption = findOption(header->file, option);
        }
        if ((*overload & SNAME_FIELD_OVERLOADED) && (desiredOption == NULL))
        {
            desiredOption = findOption(header->sname, option);
        }
    }

    /*
     * If we found the option, then desiredOption points to the length
     * byte.  Load *length and return a pointer to the start of data.
     */
    if (desiredOption != NULL)
    {
        *length = *desiredOption;
        result = desiredOption + 1;
    }

    return result;
}










/*
 *
 *  Function: int dhcpMessageType (dhcpHeaderType *header)
 *
 *  Description:
 *
 *      Returns the DHCP message type of a message.  This is done by reading
 *      the DHCP_MESSAGE_TYPE option.
 *
 *  Parameters:
 *
 *      header      pointer to DHCP packet header
 *
 *  Return Values:
 *
 *      UNKNOWN     unknown message type, probably not DHCP
 *      DHCPOFFER   
 *      DHCPACK
 *      DHCPNACK
 *
 */

static int dhcpMessageType (dhcpHeaderType *header)
{
    int result = UNKNOWN;
    int length;
    BYTE *messageType = getOption (header, DHCP_MESSAGE_TYPE, &length);

    if (messageType)
    {
        result = *messageType;
    }

    return result;
}













/*
 *
 *  Function: int copyOption (dhcpHeaderType *header, int option, void *buffer, int maxLength)
 *
 *  Description:
 *
 *      Copy an option value into a buffer.
 *
 *  Parameters:
 *
 *      header          pointer to DHCP message header
 *      option          option to copy
 *      buffer          buffer to copy it to
 *      maxLength       size of buffer in bytes
 *
 *  Return Values:
 *
 *      0       option not found
 *      !0      number of bytes copied into buffer
 *
 */

static int copyOption (dhcpHeaderType *header, int option, void *buffer, int maxLength)
{
    int length, result = 0;
    BYTE *source = getOption (header, option, &length);

    if (buffer)
    {
        if (length > maxLength)
        {
            length = maxLength;
        }
        memcpy (buffer, source, length);
        result = length;
    }

    return result;
}











/*
 *
 *  Function: WORD32 defaultSubnetMask(WORD32 ipAddress)
 *
 *  Description:
 *
 *      Calculate the default subnet mask based upon the class of
 *      our IP address.
 *
 *  Parameters:
 *
 *      ipAddress       our IP address as a WORD32
 *
 *  Return Values:
 *
 *      0xff000000      class A subnet
 *      0xffff0000      class B subnet
 *      0xffffff00      class C subnet
 *
 */
#define CLASS_A_SUBNET_TYPE_MASK    0x80000000
#define CLASS_A_SUBNET_TYPE_ID      0x0
#define CLASS_A_SUBNET_MASK         0xff000000

#define CLASS_B_SUBNET_TYPE_MASK    0xc0000000
#define CLASS_B_SUBNET_TYPE_ID      0x80000000
#define CLASS_B_SUBNET_MASK         0xffff0000

#define CLASS_C_SUBNET_TYPE_MASK    0xe0000000
#define CLASS_C_SUBNET_TYPE_ID      0xc0000000
#define CLASS_C_SUBNET_MASK         0xffffff00

static WORD32 defaultSubnetMask(WORD32 ipAddress)
{
    WORD32 result = 0;

    if ((CLASS_A_SUBNET_TYPE_MASK & ipAddress) == CLASS_A_SUBNET_TYPE_ID)
    {
        result = CLASS_A_SUBNET_MASK;
    }
    else if ((CLASS_B_SUBNET_TYPE_MASK & ipAddress) == CLASS_B_SUBNET_TYPE_ID)
    {
        result = CLASS_B_SUBNET_MASK;
    }
    else if ((CLASS_C_SUBNET_TYPE_MASK & ipAddress) == CLASS_C_SUBNET_TYPE_ID)
    {
        result = CLASS_C_SUBNET_MASK;
    }

    return result;
}





/*
 *
 *  Function: void saveInfo(dhcpHeaderType *header)
 *
 *  Description:
 *
 *      This function rifles through a DHCP or BOOTP reply message and
 *      saves all the relevent information into the data buffer provided
 *      by the caller of blDhcpGetInfo().  We save the following 
 *      information.
 *
 *          our IP address          from siaddr field
 *          subnet mask             from SUBNET_MASK option or calculated based on IP address
 *          gateway address         from GATEWAY option or from giaddr field
 *          TFTP server address     from siaddr field
 *          boot file size          from BOOT_FILE_SIZE option
 *          boot filename           from BOOT_FILENAME option or file field
 *          vendor info             from VENDOR_INFO option
 *
 *  Parameters:
 *
 *      header      pointer to header of BOOTP/DHCP reply
 *
 *  Return Values:
 *
 *      none
 *
 */

static void saveInfo(dhcpHeaderType *header)
{
    BOOLEAN isDhcp = (dhcpMessageType(header) != UNKNOWN);
    blDhcpInfoType *info = dhcpInfo.info;
    int length;
    BYTE *overload = getOption (header, OPTION_OVERLOAD, &length);
    BOOLEAN fileFieldOverloaded = FALSE;

    if (overload != NULL)
    {
        fileFieldOverloaded = ((*overload & FILE_FIELD_OVERLOADED) != 0);
    }
                                                        /* get the IP address*/
    info->ipAddress = header->yiaddr;
                                                        /* get gateway address*/
    info->gateway = 0;                                  /* assume no gateway*/
    copyOption (header, GATEWAY, &info->gateway, sizeof(info->gateway));
                                                        /* get subnet mask, generate default one first*/
    info->subnetMask = defaultSubnetMask(info->ipAddress);
    copyOption (header, SUBNET_MASK, &info->subnetMask, sizeof(info->subnetMask));
                                                        /* get IP address of next (TFTP) server*/
    if (header->siaddr)                                 /* it is suppose to be loaded in siaddr*/
    {
        info->serverIp = header->siaddr;
    }
    else                                                /* but Microsoft DHCP sometimes doesn't set this field*/
    {
        info->serverIp = dhcpInfo.serverIp;             /* in this case just use the IP address of the DHCP server*/
    }

    info->imageSize = 0;                                /* get boot file size, default is zero*/
    copyOption (header, BOOT_FILE_SIZE, &info->imageSize, sizeof(info->imageSize));
                                                        /* get boot file name*/
    memset(info->imageFilename, 0, sizeof(info->imageFilename));
    if (copyOption (header, BOOT_FILENAME, info->imageFilename, sizeof(info->imageFilename) - 1) == 0)
    {                                                   /* if it wasn't passed to us as an option*/
        if (!fileFieldOverloaded)                       /* but the file field was not over loaded*/
        {                                               /* then it's in the file field*/
            memcpy (info->imageFilename, header->file, sizeof(info->imageFilename) - 1);
        }
    }
                                                        /* get vendor info*/
    memset(info->vendorInfo, 0, sizeof(info->vendorInfo));
    info->vendorInfoLength = copyOption (header, VENDOR_INFO, info->vendorInfo, sizeof(info->vendorInfo));
}











/*
 *
 *  Function: BOOLEAN hasTftpInfo(dhcpHeaderType *header)
 *
 *  Description:
 *
 *      Determine if a response packet contains TFTP information.  Do this
 *      by looking for the boot filename.
 *
 *  Parameters:
 *
 *      header      pointer to DHCP response header
 *
 *  Return Values:
 *
 *      TRUE        has TFTP download information
 *      FALSE       does not have TFTP information
 *
 */

static BOOLEAN hasTftpInfo(dhcpHeaderType *header)
{
    int length;
    BYTE *overload = getOption(header, OPTION_OVERLOAD, &length);
/*
 * First check for DHCP BOOT_FILENAME option.
 */
    BYTE *bootFilename = getOption(header, BOOT_FILENAME, &length);
    BOOLEAN result = (bootFilename != NULL);

/*
 * If boot filename not passed as DHCP option, see if it was 
 * passed in the file field of the header.  Make sure the
 * field was not overloaded.
 */
    if (bootFilename == NULL)
    {
        if ((overload == NULL) || ((*overload & FILE_FIELD_OVERLOADED) == 0))
        {
            result = (header->file[0] != 0);
        }
    }

    return result;
}






/*
 *
 *  Function: void dhcpReceive (PACKET *packet, WORD32 headerOffset)
 *
 *  Description:
 *
 *      This routine is called when we receive a UDP packet. The packet
 *      should be either a DHCPOFFER packet sent in response to a
 *      DHCPDISCOVER packet from us, or a DHCPACK packet sent in response
 *      to a DHCPREQUEST packet from us.
 *
 *      A DHCPOFFER packet is received halfway through the negotiation
 *      process.  Basically, we look at the offer and accept it if we
 *      like it.  If we want TFTP information, then we only accept an
 *      offer that includes it.  If all we want is an IP address, then
 *      we take the first offer that has one.
 *
 *      A DHCPACK packet is received at the end of the negotiation 
 *      process.  It is possible to receive DHCPACK packets from more 
 *      than one server if we are trying to reuse an IP address that
 *      we remembered.  We accept the first DHCPACK that we like.  If
 *      we want TFTP information, then we accept the first one that
 *      has it.  If all we want is an IP address, then we take the first
 *      one with an IP address.  If we get one we like, we copy the
 *      information in it into the caller's buffer and shutdown.
 *
 *  Parameters:
 *
 *      packet          pointer to packet just received
 *      headerOffset    start of data portion of packet
 *
 *  Return Values:
 *
 *      none
 *
 */

static void dhcpReceive (PACKET *packet, WORD32 headerOffset)
{
    static WORD32 frameBuffer[(PACKET_FRAME_SIZE/sizeof(WORD32)) + 1];
    dhcpHeaderType *header = (dhcpHeaderType *) frameBuffer;
    udp_header_t *udpFrame = (udp_header_t *) &packet->frame[headerOffset];
    BOOLEAN isDhcp;
    int length;
    BYTE *sId;
    WORD32 serverIp;

/*
 * The header will be missaligned in the packet buffer.  So, we copy the DHCP part into another buffer
 * so that it is aligned.  This allows us to access it as a structure.  We copy both the DHCP header,
 * and the option field that immediately follows the header.
 */
    memcpy (frameBuffer, &packet->frame[headerOffset+sizeof(udp_header_t)], FROM_BE16(udpFrame->length) - sizeof(udp_header_t));
    sId = getOption(header, SERVER_IDENTIFIER, &length);
    isDhcp = (dhcpMessageType(header) != UNKNOWN);
    memcpy (&serverIp, sId, sizeof(serverIp));

    if ((negotiationInProgress) && (header->xid == dhcpInfo.xid) && ((dhcpInfo.serverIp == 0) || (dhcpInfo.serverIp == serverIp)))
    {
        if (dhcpInfo.lastRequest == DHCPDISCOVER)           /* if we are expecting a DHCPOFFER*/
        {
            if (dhcpInfo.info->wantTftpInfo)                /* and need the TFTP info*/
            {
                if ((header->yiaddr) && (hasTftpInfo(header)))
                {                                           /* if this offer has TFTP info*/
                    if (isDhcp)                             /* if it is a DHCP message*/
                    {                                       /* validate that it is a DHCPOFFER message*/
                        if (dhcpMessageType(header) == DHCPOFFER)
                        {                                   /* get the ID of the responding server */
                            dhcpInfo.serverIp = serverIp;
                            dhcpInfo.info->ipAddress = header->yiaddr;
                            sendDhcpRequest(DHCPREQUEST);   /* complete the negotiation*/
                        }
                    }
                    else                                    /* else if it is a BOOTP reply*/
                    {                                       /* then we are done negotiating*/
                        saveInfo(header);                   /* save the information*/
                        shutdown(BL_SUCCESS);               /* and terminate*/
                    }
                }
            }
            else                                            /* else if we do not need TFTP info*/
            {
                if (header->yiaddr)                         /* take any offer that has an IP address*/
                {
                    if (isDhcp)                             /* if it is a DHCP message*/
                    {
                        if (dhcpMessageType(header) == DHCPOFFER)
                        {                                   /* get the ID of the responding server */
                            dhcpInfo.serverIp = serverIp;
                            dhcpInfo.info->ipAddress = header->yiaddr;
                            sendDhcpRequest(DHCPREQUEST);   /* complete the negotiation*/
                        }
                    }
                    else                                    /* else if it is a BOOTP reply*/
                    {
                        saveInfo(header);                   /* then we're done, save the info*/
                        shutdown(BL_SUCCESS);               /* and shutdown*/
                    }
                }
            }
        }
        else if (dhcpInfo.lastRequest == DHCPREQUEST)       /* if expecting a DHCPACK*/
        {
            if (dhcpInfo.info->wantTftpInfo)                /* if we need the TFTP inf*/
            {
                if ((header->yiaddr) && (hasTftpInfo(header)))
                {                                           /* then take first ACK that has it*/
                    if (((isDhcp) && (dhcpMessageType(header) == DHCPACK)) || (!isDhcp))
                    {                                       /* ignore it if it isn't a DHCPACK or BOOTP reply*/
                        saveInfo(header);
                        shutdown(BL_SUCCESS);               /* we're done*/
                    }
                }
            }
            else                                            /* if we don't need TFTP stuff*/
            {
                if (header->yiaddr)                         /* take first ACK with IP address*/
                {
                    if (((isDhcp) && (dhcpMessageType(header) == DHCPACK)) || (!isDhcp))
                    {                                       /* ignore it if it isn't a DHCPACK or BOOTP reply*/
                        saveInfo(header);
                        shutdown(BL_SUCCESS);               /* we're done*/
                    }
                }
            }
        }

    /*
     * According to the RFC, we should terminate if we receive an DHCPNACK.  However, Bill Gate's 
     * NT DHCP servers are known to send DHCPNACKs when they aren't suppose to.  So, the best
     * thing to do is to just ignore them.  We will terminate the session when it is successful,
     * or after it has timed out.
     */
        if ((isDhcp) && (dhcpMessageType(header) == DHCPNAK))
        {                                                   /* if a DHCPNACK is received*/
            dhcpInfo.receivedNack = TRUE;                   /* set a flag, but don't shut down*/
        }
    }
}





    


/*
 *
 *  Function: int blDhcpGetInfo (blDhcpInfoType *info, int reuseIp)
 *
 *  Description:
 *
 *      This function used DHCP to get information needed to download a new
 *      application image.
 *
 *  Parameters:
 *
 *      info        pointer to a structure used to pass DHCP parameters
 *
 *  Return Values:
 *
 *      BL_SUCCESS                      DHCP negotiation succeeded
 *      BL_PACKET_ALLOCATE_ERROR        PacketAllocate() returned NULL
 *
 */

int blDhcpGetInfo (blDhcpInfoType *info)
{
    memset(&dhcpInfo, 0, sizeof(dhcpInfo));
    dhcpInfo.packet = PacketAllocate();
    if (dhcpInfo.packet == NULL)
    {
        return BL_PACKET_ALLOCATE_ERROR;
    }

    UdpBindPort(BOOTP_CLIENT_PORT, dhcpReceive);
    dhcpInfo.info = info;
    dhcpInfo.xid = generateXid();
    dhcpInfo.startTime = TimerGetTime();
    negotiationInProgress = TRUE;

    if (info->reuseIp)
    {
        sendDhcpRequest(DHCPREQUEST);
    }
    else
    {
        sendDhcpRequest(DHCPDISCOVER);
    }
    
    while (negotiationInProgress)
    {
    }

    UdpUnbindPort(BOOTP_CLIENT_PORT);
    PacketFree(dhcpInfo.packet);

    return dhcpInfo.result;
}







