/*****************************************************************************
* Copyright (c) 2005 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
*
*****************************************************************************/
#include "boardParams.h"
#include "iam.hh"
#include "starttcp.h"
#include "bsp.h"

static const char *iam_method_string[] = {"STATIC", "DHCP", "AUTOIP", \
                                          "USER","","AUTO-CONFIGURED"};
static const char *iam_ip_version_string[] = {"IPv4", "IPv4", "IPv4", \
                                              "IPv4","","IPv6"};

/*ad*
 * This list comprises IPv4 acquisition methods IAM attempts to use to acquire an IPv4
 * address; however, the methods actually used depend on NVRAM settings. This list 
 * is passed into @link naIamStart in function @link customizeIamStart.
 * The order of the list determines the priority assigned to the methods with the above 
 * definition  making static the highest priority, followed by DHCPv4 and Auto IP. 
 * Priority determines how IAM treats acquired addresses. For example, the above priority
 * scheme ensures that static addresses will always be configured even when a previously
 * acquired DHCPv4 or Auto IP address has been configured on an interface. In this case,
 * the lower priority address is removed from the interface and replaced with the newly
 * acquired static address. Consequently, when a static address is configured first on an
 * interface and a lower priority address is obtained after the fact, the lower priority
 * address is ignored. The default method callbacks do not use any context arguments. 
 * A user defined method callback can be included in this list in the desired priority
 * slot along with the NA_IAM_METHOD_USER identifier. The last
 * element of the list has to be set to NA_IAM_NULL_METHOD_CB to mark its end.
 *
 * @external
 * @include "iam.hh"
 * @category IAM:callbacks
 * @since 7.0
 *
*ad*/   
static NaIamMethodInfo_t method_list[] = \
            {{NA_IAM_DEFAULT_STATIC_METHOD_CB, NULL, NA_IAM_METHOD_STATIC},
             {NA_IAM_DEFAULT_DHCP_METHOD_CB,   NULL, NA_IAM_METHOD_DHCP},
             {NA_IAM_DEFAULT_AUTOIP_METHOD_CB, NULL, NA_IAM_METHOD_AUTOIP},
             {NA_IAM_NULL_METHOD_CB,           NULL, NA_IAM_METHOD_NONE}};  
                

/* Number of interfaces we have to configure
 * before starting the application layer. */
#if BSP_WANT_ETHERNET && BSP_WANT_WIRELESS
#define NUM_INTERFACES 2
/* We assume that one is enabled, otherwise we will
 * never get this far in code execution. */
#else
#define NUM_INTERFACES 1
#endif

/*
 * This structure is used to determine when all interfaces have been
 * configured. This is needed in order to prevent the application
 * layer from starting before the interfaces are minimally configured.
 * Minimal configuration means one configured IP address per interface,
 * two, one v4 and one v6, addresses if running a dual stack.
 */ 
static struct {
    void *interface;    /*Interface handle*/
    int  num_addrs;     /*number of v4 addresses configured*/
#ifdef IPV6_ENABLED
    int  num_v6_addrs;  /*number of v6 addresses configured */
#endif
} addrs_cfg[NUM_INTERFACES];

/* Return index of interface, used to access addrs_cfg array. */ 
static int addr_cfg_get_if_index(void * interface)
{
    static int init = 0;
    int i = 0;

    if (!init)
    {
        memset(addrs_cfg, 0, sizeof addrs_cfg);
        init = 1;
    }        

    /* First, attempt to find existing interface. */
    for (i = 0; i < NUM_INTERFACES; i++)
    {
        if (addrs_cfg[i].interface == interface)
        {
            goto exit;
        }
    }

    /* If we didn't find an interface, add it to
     * next available spot. */
    for (i = 0; i < NUM_INTERFACES; i++)
    {
        if (addrs_cfg[i].interface == NULL)
        {
            addrs_cfg[i].interface = interface;
            goto exit;
        }
    }
exit:
    return i;
}    

/* Add ip address to interface. */
static void addr_cfg_acquired(void * interface, int family)
{
    int i;

    i = addr_cfg_get_if_index(interface);

#if IPV6_ENABLED
    if (family == AF_INET6)
        addrs_cfg[i].num_v6_addrs++;
    else
#endif
        addrs_cfg[i].num_addrs++;
}

/* Remove ip address from interface. */ 
static void addr_cfg_lost(void * interface, int family)
{
    int i;
     
    i = addr_cfg_get_if_index(interface);

#if IPV6_ENABLED
    if (family == AF_INET6)
        addrs_cfg[i].num_v6_addrs--;
    else
#endif
        addrs_cfg[i].num_addrs--;
}

/*Return 1 if interface is configured, 0 otherwise. */
static int addr_cfg_is_configured(void *interface)
{
    int result = 1;
    int interfaceIndex = addr_cfg_get_if_index(interface);
    
    result &= addrs_cfg[interfaceIndex].num_addrs ? 1 : 0;
#if IPV6_ENABLED
    result &= addrs_cfg[interfaceIndex].num_v6_addrs ? 1 : 0;
#endif
    
    return result; 
}


/*ad*
 * This function is the default user callback invoked by IAM for address
 * events. It may be called from the TCP/IP stack's timer task context when
 * address conflict detection is enabled or an asynchronous event such as
 * a DHCP lease expiration is received, hence the processing time spent in this
 * function should be kept to an accepted minimum.
 *
 * The default implementation of this function accepts all acquired addresses
 * and does not configure any IPv4 addresses found to be in initial conflict by returning
 * NA_IAM_STATUS_ERROR. In addition, this function accepts the first DHCP offer received
 * by the DHCP client and does not stop ongoing conflict detection when either
 * an IPv4 or IPv6 address is found to be in conflict after being configured on an interface.
 * The customization of this function is left up to the user. 
 *
 * IAM calls this function for the following events:
 *
 * <list type="unordered">
 *	 <item>@link NA_IAM_ADDR_CONFIGURED - IPv4 and IPv6. The interface/multihome has been configured with an acquired
 *	                                      address. For IPv6, the address passed Duplicate Address Detection(DAD) and 
 *                                        has been configured on the interface.</item>
 *	 <item>@link NA_IAM_ADDR_LOST -  The interface/multihome has lost its configured IP address. For IPv4, the 
 *                                   address is removed when the callback returns. For IPv6, this
 *                                   event is called when a stateless auto-configured address has been
 *                                   invalidated and removed.</item>
 *	 <item>@link NA_IAM_ADDR_CONFLICT - The configured IPv4/IPv6 address has been found to be in conflict with 
 *	                                 another node on the network. Returning NA_IAM_STATUS_ERROR disables 
 *                                   ongoing Address Conflict Detection(ACD) for the IPv4 address. Auto IP always
 *                                   enables ongoing ACD.</item>
 *   <item>@link NA_IAM_ADDR_INITIAL_CONFLICT - The acquired IPv4/IPv6 address has been found to be in conflict with 
 *	                                 another node on the network. An acquired IP address is one that has
 *									 not been used to configure an interface/multihome. For IPv4, this event can
 *									 only be generated if initial ACD has been enabled for an acquisition method 
 *									 If NA_IAM_STATUS_SUCCESS is returned for IPv4
 *                                   this address is used to configure the interface in spite of being in conflict.
 *                                   Returning NA_IAM_STATUS_ERROR ignores the IPv4 address and does not configure it.
 *                                   For IPv6, this address cannot be used to configure the interface and this 
 *                                   event is informational only. DAD is always enabled for IPv6 and if the
 *                                   auto-configured address in conflict is a link-local scope address, IAM generates
 *                                   a random interface ID to form a new IPv6 link-local address as part of its
 *                                   auto-recovery mechanism, which ensures that a valid link-local address is 
 *                                   always generated. Due to the implementation of the DHCPv4 client, initial ACD
 *                                   is always performed for DHCP and an address in conflict is never allowed to
 *                                   be configured, hence this event will never be reported for DHCPv4.
 *                                   Auto IP also always performs initial ACD and like DHCP Auto IP will never report
 *                                   this event; however, unlike DHCP, Auto IP generates random addresses to
 *                                   recover from initial conflicts, similarly to the auto-configured IPv6 link-local
 *                                   auto-recovery mechanism described above.   
 *                                   </item>
 *   <item>@link NA_IAM_ADDR_OFFER - The DHCPv4 client has received an offer that can be accepted by returning 
 *                                   NA_IAM_STATUS_SUCCESS or rejected by returning NA_IAM_STATUS_ERROR.</item>
 *   <item>@link NA_IAM_ADDR_LINK_DOWN - The network interface has gone down.  This may be caused by a network
 *                                       switch failure, by a user pulling the Ethernet cable, or by a problem
 *                                       on the wireless access point.  It is possible that the IP address assigned
 *                                       to the interface will no longer be valid when the interface comes back
 *                                       up.  Therefore the address acquisition process will be restarted when the
 *                                       interface comes back up.  If the gateway assigned to this interface was
 *                                       the default gateway, then the default gateway will be switched to the gateway
 *                                       on the next highest priority interface.  See 
 *                                       @link Interface_Config:naIfConfigCreateInterface for details. </item>
 *          
 * </list>     
 *
 * @external
 * @include "iam.hh"
 * @category IAM:callbacks 
 * @since 7.0
 *
 * @param ifname        nul-terminated interface name string in the "ethx:x" format.    
 * @param method        IAM aquisition method.
 * @param event         IAM acquisition event.
 * @param acquiredCfg   Configuration acquired by IAM.
 * @param userData      User context data pointer passed into @link naIamStart. 
 *
 * @return NA_IAM_STATUS_SUCCESS    Success.
 * @return NA_IAM_STATUS_ERROR      Error.     
*ad*/
NaIamStatus_t customizeIamEventCallback(const char * ifname, int method, int event, NaIamAcquiredCfg_t * acquiredCfg, void * userData)
{

    NaIamStatus_t status = NA_IAM_STATUS_SUCCESS; /* Assume success. */
    static int configured = 0;
    static int callbackInstalled = 0;
    char addrString[MAX_IP_ADDR_STR_LEN];
    char af;
    char *ip_addr;
    void * interface;
    unsigned multihome;

    naIfConfigGetInterfaceHandle(ifname, &interface, &multihome);
        
    (void)userData;

    switch(event)
    {
        case NA_IAM_ADDR_CONFIGURED: /* Acquired IP address. */
        {                
#ifdef IPV6_ENABLED
            /* Only stateless auto-configured IPv6 addresses are
             * supported at this time. */
            if (method == NA_IAM_METHOD_IPV6_AUTOCFG)   
            {
                /* Register the IPv6 address. */
                addr_cfg_acquired(interface, AF_INET6);
                af = AF_INET6, 
                ip_addr = (char *)&acquiredCfg->ipAddress.addr.ipv6.sin6_addr;
            }
            else 
#endif
            {
                /* Register the IPv4 address. */
                addr_cfg_acquired(interface, AF_INET);
                af = AF_INET; 
                ip_addr = (char *)&acquiredCfg->IPV4_ADDR(ipAddress);
            }

            /* Convert address to ASCII. */
            inet_ntop(af, ip_addr, addrString, MAX_IP_ADDR_STR_LEN);
 
            printf("IAM:Got %s %s address %s on interface %s\r\n",
                         iam_method_string[method], iam_ip_version_string[method], 
                         addrString, ifname);

            if (!configured) /* Do only once. */
            {
                /* Determine if interface is configured.*/
                if (addr_cfg_is_configured(interface))
                {
                    /* Let the ROOT task know that we have configured all required
                     * ip addresses on an interface, which allows network 
                     * applications to come up. We require both v4 and v6
                     * addresses if running in dual stack mode. */
                    NASetIPConfigureState(naIpStateConfigured);
                    configured = 1;
                }
            }      
            /*
             * This is a little questionable.  We allow the system to
             * come up with a statically assigned IP address, even if the Ethernet 
             * cable is disconnected.  That's what the if statment above does.
             * Then, we install the Ethernet link callback in the code below.  If
             * the cable is disconnected, then the callback will disable the 
             * interface immediately.  The effect is to let the system come up with
             * a statically allocated IP address even if the cable is pulled, but 
             * then disable the network interface, which makes sure the correct
             * default gateway is setup if we also have a wireless interface.
             */
            if (strncmp(ifname, BP_ETH_INTERFACE, strlen(BP_ETH_INTERFACE)) == 0)
            {
                if ((addr_cfg_is_configured(interface)) && (callbackInstalled == 0))
                {
                    iamInstallCallback(interface); 
                    callbackInstalled = 1;
                }
            }
        }
        break;

        case NA_IAM_ADDR_LOST: /* Lost IP address. */
        {
#ifdef IPV6_ENABLED
            if (method == NA_IAM_METHOD_IPV6_AUTOCFG)   
            {
                /* Register lost IPv6 address. */
                addr_cfg_lost(interface, AF_INET6); 
                af = AF_INET6, 
                ip_addr = (char *)&acquiredCfg->ipAddress.addr.ipv6.sin6_addr;
            }
            else
#endif
            {
                /* Register lost IPv6 address. */
                addr_cfg_lost(interface, AF_INET);
                af = AF_INET; 
                ip_addr = (char *)&acquiredCfg->IPV4_ADDR(ipAddress);
            }
            
            /* Convert address to ASCII. */
            inet_ntop(af, ip_addr, addrString, MAX_IP_ADDR_STR_LEN);

            printf("IAM - lost %s %s address %s on interface %s\n",
                       iam_method_string[method], iam_ip_version_string[method], addrString, ifname);
        }            
        break;

        /* This event is informational only for IPv6 as all auto-configured 
         * addresses are never configured if found to be in initial conflict.
         * In the case of an auto-configured link-local scope IPv6 address
         * found to be in initial conflict, a random EUI64 Interface ID is
         * generated by IAM to form a new link-local address for the interface
         * after this function returns. */ 
        case NA_IAM_ADDR_INITIAL_CONFLICT:/* Initial IP address conflict.*/
        {
#ifdef IPV6_ENABLED
            if (method == NA_IAM_METHOD_IPV6_AUTOCFG)   
            {
                af = AF_INET6, 
                ip_addr = (char *)&acquiredCfg->ipAddress.addr.ipv6.sin6_addr;
            }
            else
#endif
            {
                af = AF_INET; 
                ip_addr = (char *)&acquiredCfg->IPV4_ADDR(ipAddress);
            }

            inet_ntop(af, ip_addr, addrString, MAX_IP_ADDR_STR_LEN);

            printf("IAM - %s %s address %s on interface %s is in initial conflict!\n",
                                    iam_method_string[method], iam_ip_version_string[method], addrString, ifname);

            /* If the IPv6 auto-configured link-local scope address is in 
             * conflict, a random EUI64 interface ID will be generated to configure
             * a new link local address. Other IPv6 auto-configured addresses are
             * are never configured when they're in initial conflict.*/
#ifdef IPV6_ENABLED 
            if (method == NA_IAM_METHOD_IPV6_AUTOCFG &&
                IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)ip_addr))
            {
                printf("      A random EUI64 Interface ID will be generated.\n");  
            }
            else
#endif
            if (af == AF_INET)
            {
                printf("      This address will not be configured. \n");  
                /* By returning an error, the address in conflict is not 
                 * configured on the interface. */
                status = NA_IAM_STATUS_ERROR;
            }
            else
            {
                printf("      This auto-configured address will not be used. \n");
            }     
        }            
        break;

        case NA_IAM_ADDR_CONFLICT:/* Ongoing IP address conflict.*/
        {
#ifdef IPV6_ENABLED
            if (method == NA_IAM_METHOD_IPV6_AUTOCFG)   
            {
                af = AF_INET6, 
                ip_addr = (char *)&acquiredCfg->ipAddress.addr.ipv6.sin6_addr;
            }
            else
#endif
            {
                af = AF_INET; 
                ip_addr = (char *)&acquiredCfg->IPV4_ADDR(ipAddress);
            }

            inet_ntop(af, ip_addr, addrString, MAX_IP_ADDR_STR_LEN);

            printf("IAM - %s %s address %s on interface %s is in conflict!\n",
                            iam_method_string[method], iam_ip_version_string[method], 
                            addrString, ifname);
        }            
        break;

        case NA_IAM_ADDR_OFFER: /* DHCP offer*/
        {
            /* We always Accept the first DHCP offer. This is where an offer
             * may be parsed and inspected. Returning NA_IAM_STATUS_ERROR
             * rejects the offer. */
        }
        break;
        
        case NA_IAM_ADDR_LINK_DOWN: /* Someone pulled the Ethernet cable */
        {                           
            /*
             * The safest thing to do is to tell the stack the interface has been disconnected.
             * Unfortunately, this has a nasty side effect:  We lose the IP configuration for that
             * interface and have to restart the IP address acquisition process from scratch when 
             * the link comes up again.  Other options are to do nothing, or to just switch the 
             * default router.
             */
            return NA_IAM_DISCONNECT_INTERFACE;
        }
 
    }

    return status;
}

      
/*ad*
 * This function is called from the ROOT task to start IAM on network interfaces
 * enabled in NVRAM. Default acquisition methods, Static, DHCP, and Auto IP are started
 * for each interface in this order if they are enabled in NVRAM. The order in which
 * these methods are started determines their priority, hence Static always has the
 * highest priority, followed by DHCP and AutoIP. This means that when a Static address
 * is obtained for an interface/multihome pair after a DHCP or an Auto IP address has been
 * used to configure the interface, the configured address will be unconfigured and the Static
 * address will be used to configure it instead. 
 *
 * This function calls @link naIamStart with the @link customizeIamEventCallback callback function
 * as the "app_cb" parameter argument. @link customizeIamEventCallback is the default application callback used
 * with IAM and it is called by IAM for events such as address acquisition or address lost
 * events. See the description of this function for its default implementation details,
 * which can be modified as needed. 
 *
 * The "user_data" parameter of @link naIamStart is given a NULL argument since the default
 * implementation of @link customizeIamEventCallback does not use this context data pointer passed into
 * this function in the "userData" parameter. The "flag" and "buffersPerFrameCount" parameters of
 * @link naIamStart are passed in arguments retrieved by calling @link naIfConfigGetInterfaceParams.
 * @link naIamStart should always be called with configuration parameters returned by
 * @link naIfConfigGetInterfaceParams as they are platform specific and should not be modified by the 
 * application.
 *
 * When running in dual stack mode, @link naIamStart begins the acquisition of a stateless
 * auto-configured link-local IPv6 address for each physical interface automatically; however,
 * at least one acquisition method for IPv4 must be started as well event if it is disabled in 
 * NVRAM. See @link naIamStart for a detailed description of its usage.             
 *
 * @external
 * @include "iam.hh"
 * @category IAM:iam_params 
 * @since 7.0
 *
 * @return 0       Success.
 * @return -1      Error.     
*ad*/      
int customizeIamStart(void) 
{
    NaIamParamsInfo_t * iam_info;
    int result = 0, j = 0;
    NaIfInterfaceConfigureParams_t cfg_params;

    iam_info = (NaIamParamsInfo_t *)malloc(sizeof *iam_info);
    if (iam_info == NULL)
    {
        printf("customizeIamStart: malloc() failed!\n");
        result = -1;
        goto exit;
    }

    /* Read the whole IAM NVRAM structure */
    if (customizeIamGetConfig(iam_info) == BP_FAILURE)
    {
        printf("customizeIamStart: customizeIamGetConfig() failed!\n");
        result = -1;
        goto exit;
    }

    for (j = 0; j < NA_IAM_INTERFACE_MAX; j++)
    {
    #if !BSP_WANT_ETHERNET            
        if (strncmp(iam_info->iamParams[j].ifname, BP_ETH_INTERFACE, strlen(BP_ETH_INTERFACE)) == 0)  
            iam_info->iamParams[j].isEnabled = FALSE;
    #endif
    #if !BSP_WANT_WIRELESS
        if (strncmp(iam_info->iamParams[j].ifname, BP_WLN_INTERFACE, strlen(BP_WLN_INTERFACE)) == 0)  
            iam_info->iamParams[j].isEnabled = FALSE;
    #endif

        /* Only start IAM on an interface that has been enabled for IAM. */ 
        if (iam_info->iamParams[j].isEnabled)
        {
            /* Retrieve interface specific parameters. */
            if (naIfConfigGetInterfaceParams(iam_info->iamParams[j].ifname,
                                             &cfg_params) != NA_SUCCESS)
            {
                printf("customizeIamStart: naIfConfigGetInterfaceParams failed!\n");
                result = -1;
                goto exit;
            }        
                
            if (naIamStart(iam_info->iamParams[j].ifname,
                           method_list,
                           cfg_params.if_config_flags,
                           cfg_params.buffers_per_frame,  
                           NULL,
                           customizeIamEventCallback) != NA_IAM_STATUS_SUCCESS)
            {
                printf("customizeIamStart: naIamStart() failed for interface %s\n",
                                                     iam_info->iamParams[j].ifname);
                result = -1;
                goto exit;
            }
        }
    }

exit:
    if (iam_info)
    {
        free(iam_info);
    }

    return result;
}


/*ad*
 * This function retrieves IPv4/6 addresses and other relevant configuration parameters
 * assigned to the network interface specified by ifname. Any nonexistent
 * addresses are zeroed out by this function. For IPv6, there may be 
 * more than one auto-configured address on a single interface and a link-local
 * address must always exist in order for that IPv6 interface to be configured. The
 * link-local address is always the first element of the "autoCfgInfo.cfg" list and will
 * always have a prefix length of 64. 
 *
 * Although IAM stores auto-configured IPv6 addresses at multihome indexes equal to or
 * greater than NA_MAX_HOMES_PER_IF(4), this function does not use this convention to
 * retrieve these addresses. In other words, calling this function with "ifname" argument
 * "eth0:3"(fourth multihome index) will not work. Instead, this function only accepts
 * interface names with multihome indexes smaller than NA_MAX_HOMES_PER_IF; however, each
 * of these multihome indexes returns the auto-configured IPv6 addresses configured and 
 * global to the physical interface.     
 *
 * @note In order to find out, if the interface home has a configured IPv4 address, 
 *       it's necessary to check that the IPv4 address value is not 0 in addition to 
 *       checking the return code of the customizeIamGetIfAddrInfo:
 *
 *  if (customizeIamGetIfAddrInfo("eth0", &addrInfo) == NA_IAM_STATUS_SUCCESS &&
 *      addrInfo.v4Info.IPV4_ADDR(ipAddress) != 0)
 *
 * @external
 * @include "iam.hh"
 * @category IAM:iam_params 
 * @since 7.0
 *
 * @param ifname        nul-terminated interface name string in the "ethx:x" format.
 * @param addrInfo      Pointer to a caller allocated interface address information
 *                      structure filled in by this function.
 *
 * @return NA_IAM_STATUS_SUCCESS             Success.
 * @return NA_IAM_STATUS_IF_NOT_CONFIGURED   Interface does not have a configured ip address.
 * @return NA_IAM_STATUS_ERROR               Invalid argument(s).     
*ad*/
NaIamStatus_t customizeIamGetIfAddrInfo(const char * ifname, NaIamIfAddrInfo_t * addrInfo)
{
    void *interfaceHandle = NULL;
    NaIamStatus_t status = NA_IAM_STATUS_SUCCESS;
#ifdef IPV6_ENABLED
    unsigned i;
#endif
    unsigned ip_configured = 0;
    unsigned home_index;

    if ((ifname == NULL) || (addrInfo == NULL))
    {
        status = NA_IAM_STATUS_ERROR;
        goto exit;
    }

    if (naIfConfigGetInterfaceHandle(ifname, &interfaceHandle, &home_index) != NA_SUCCESS)
    {
        printf("customizeIamGetIfAddrInfo: naIfConfigGetInterfaceHandle failed!\n");
        status = NA_IAM_STATUS_ERROR;
        goto exit;
    }
    
    memset(addrInfo, 0, sizeof *addrInfo);

    /* No useful return values. */
    tfGetIfMtu(interfaceHandle, &(addrInfo->mtu));       

    if (naIamGetCurrentMethod(ifname, &(addrInfo->method))
                                          != NA_IAM_STATUS_SUCCESS)
    {
        addrInfo->method = NA_IAM_METHOD_NONE;
    }
    else
    {
        ip_configured = 1;                          
        /* First get the IPv4 address if one exists. No useful return values. */
        tfNgGetIpAddress(interfaceHandle, &(addrInfo->v4Info.ipAddress), AF_INET, home_index);
        tfGetNetMask(interfaceHandle, &(addrInfo->v4Info.subnetMask),
                     (unsigned char)home_index);

        if (tfGetDefaultGateway(&(addrInfo->v4Info.defaultGateway.addr.ipv4.sin_addr.s_addr))
                                == TM_ENOERROR)
        {
            addrInfo->v4Info.defaultGateway.ss_family = AF_INET;
        }
    }
    
    /* These could be v4 or 6 adrresses. */
    tfNgDnsGetServer(&(addrInfo->primaryDnsServer), TM_DNS_PRI_SERVER);
    tfNgDnsGetServer(&(addrInfo->secondaryDnsServer), TM_DNS_SEC_SERVER);           

#ifdef IPV6_ENABLED

    /* Get IPv6 information if it exists. There is a possibility of not even having 
     * a link local address due to duplicate address detection. No need to check return values. */
    tfNgGetIpAddress(interfaceHandle, &(addrInfo->v6Info.ipAddress), AF_INET6, home_index);

    tfNgGetPrefixLen(interfaceHandle, &(addrInfo->v6Info.prefixLen), AF_INET6, home_index);
    tf6GetDefaultGatewayTunnel(&(addrInfo->v6Info.gatewayTunnel));

    /* There is a total of (2 * NA_MAX_HOMES_PER_IF), or 8, v6 addresses per interface
     * with the first NA_MAX_HOMES_PER_IF being manually configured
     * and the latter NA_MAX_HOMES_PER_IF auto-configured. We copy the latter NA_MAX_HOMES_PER_IF
     * here. If they don't exist, we just return zero'd out members.*/
    for (i = 0; i < NA_MAX_HOMES_PER_IF; i++)
    {   
        tfNgGetIpAddress(interfaceHandle, &(addrInfo->v6Info.autoCfgInfo.addr[i].ipAddress),
                         AF_INET6, i + NA_MAX_HOMES_PER_IF);

        tfNgGetPrefixLen(interfaceHandle, &(addrInfo->v6Info.autoCfgInfo.addr[i].prefixLen),
                         AF_INET6, i + NA_MAX_HOMES_PER_IF);
    }

    /* We must always have a link local address in order for a v6 interface to be considered 
     * configured. A non-zero prefix lenght is enough to verify this since it will always 
     * be 64 if configured. */ 
    if (addrInfo->v6Info.autoCfgInfo.addr[0].prefixLen != 0)
    {
        ip_configured = 1;
    }

#endif

    /* Must have at least one configured address to succeed. */
    if (ip_configured == 0)
    {
        status = NA_IAM_STATUS_IF_NOT_CONFIGURED;
    }

exit: 
    return status;   
}

/*ad*
 * This function is called by IAM every time IAM settings such as the static IP address
 * or DHCP options are required in order to carry out an operation. This 
 * function may be modified to retrieve setting from a location other than NVRAM. Currently,
 * settings retireved for IAM are read from NVRAM. 
 *
 * The default acquisition methods call this function when they receive the 
 * NA_IAM_COMMAND_INITIALIZE and NA_IAM_COMMAND_ACQUIRE commands and when the
 * TCP/IP stack notifies them of asynchronous events such as ARP probing expiration,
 * initial address conflicts, or address lease expiration as can occur in DHCP.     
 *
 * @external
 * @category IAM:callbacks
 * @since 7.0
 *
 * @param ifname      nul-terminated interface name in the "ethx:x" format.
 * @param method      Acquisition method for which IAM settings are requested.
 * @param cfg         Pointer to the settings structure filled in by this function.   
 *
 * @return NA_IAM_STATUS_SUCCESS    Success
 * @return NA_IAM_STATUS_ERROR      Unable to retrieve IAM settings.
*ad*/
NaIamStatus_t customizeIamGetSettingsCallback(const char * ifname, int method, NaIamCfg_t * cfg)
{ 
    NaIamStatus_t status = NA_IAM_STATUS_SUCCESS;

    NA_ASSERT((ifname != NULL) && (cfg != NULL)); 
   
    if ((ifname == NULL) || (cfg == NULL))
    {
        status = NA_IAM_STATUS_ERROR;
        goto done;
    }     
    
    /* Retrieve IAM NVRAM settings for IAM.*/
    switch(method)
    {
        case NA_IAM_METHOD_STATIC:
        {
            NaIamStaticParams_t staticParams;

            if (customizeIamGetStaticConfig(ifname, &staticParams) != BP_SUCCESS)
            {
                status = NA_IAM_STATUS_ERROR; 
                goto done;
            }

            cfg->cfg.staticCfg.isEnabled    = staticParams.isEnabled;
            cfg->cfg.staticCfg.ipAddress    = staticParams.ipAddress;
            cfg->cfg.staticCfg.subnetMask   = staticParams.subnetMask;
            cfg->cfg.staticCfg.gateway      = staticParams.gateway;
            cfg->cfg.staticCfg.primaryDns   = staticParams.primaryDns;
            cfg->cfg.staticCfg.secondaryDns = staticParams.secondaryDns;
            cfg->cfg.staticCfg.initialAcd   = BSP_ENABLE_ADDR_CONFLICT_DETECTION;
            cfg->cfg.staticCfg.ongoingAcd   = BSP_ENABLE_ONGOING_ADDR_CONFLICT_DETECTION;          
        }
        break;

        case NA_IAM_METHOD_DHCP:
        {
            NaIamDhcpParams_t dhcpParams;

            if (customizeIamGetDhcpConfig(ifname, &dhcpParams) != BP_SUCCESS)
            {
                status = NA_IAM_STATUS_ERROR; 
                goto done;
            }

            cfg->cfg.dhcpCfg.isEnabled  = dhcpParams.isEnabled;
            memcpy(cfg->cfg.dhcpCfg.optionsBuffer,
                   dhcpParams.optionsBuffer,
                   dhcpParams.optionsLength);
            cfg->cfg.dhcpCfg.optionsLength = dhcpParams.optionsLength;
            cfg->cfg.dhcpCfg.ongoingAcd = 
                           BSP_ENABLE_ONGOING_ADDR_CONFLICT_DETECTION;

                    
        }
        break;

        case NA_IAM_METHOD_AUTOIP:
        {
            NaIamAutoipParams_t autoipParams;

            if (customizeIamGetAutoipConfig(ifname, &autoipParams) != BP_SUCCESS)
            {
                status = NA_IAM_STATUS_ERROR; 
                goto done;
            }
            
            cfg->cfg.autoipCfg.isEnabled = autoipParams.isEnabled; 
             
        }
        break; 

        default:
            NA_ASSERT(0);
            status =  NA_IAM_STATUS_ERROR;
            goto done;        
    }

    /* These are common to all acquisition methods. */
    cfg->probeInterval         = BSP_DEFAULT_ETH_PROBE_INTERVAL_MS;
    cfg->numProbes             = BSP_DEFAULT_ETH_NUM_ADDR_PROBES;
    cfg->numAnnouncements      = BSP_DEFAULT_ETH_NUM_ADDR_ANNOUNCEMENTS;  
    cfg->announcementsInterval = BSP_DEFAULT_ETH_ANNOUNCE_INTERVAL_MS; 

done:
    return status;
}

/*
 * Private function used by the IAM library to install IAM's Ethernet
 * link monitoring callback.  We cannot implement this code inside of
 * the IAM library because it depends on the BSP_WANT_ETHERNET #define,
 * which is only valid when the BSP is compiled.
 */
void iamInstallEthernetLinkCallback(NaLinkStatusCallbackFn_t callbackFn)
{
    (void) callbackFn;
#if BSP_WANT_ETHERNET
    naEthRegisterLinkStatusCallback(callbackFn);
#endif
}


