/** @file
  The implementation of EFI IPv4 Configuration II Protocol.

  Copyright (c) 2015 - 2017, Intel Corporation. All rights reserved.<BR>
  (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP<BR>

  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php.

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "Ip4Impl.h"

LIST_ENTRY  mIp4Config2InstanceList = {&mIp4Config2InstanceList, &mIp4Config2InstanceList};

/**
  The event process routine when the DHCPv4 service binding protocol is installed
  in the system.

  @param[in]     Event         Not used.
  @param[in]     Context       Pointer to the IP4 config2 instance data.

**/
VOID
EFIAPI
Ip4Config2OnDhcp4SbInstalled (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  );

/**
  Destroy the Dhcp4 child in IP4_CONFIG2_INSTANCE and release the resources.

  @param[in, out] Instance    The buffer of IP4 config2 instance to be freed.

  @retval EFI_SUCCESS         The child was successfully destroyed.
  @retval Others              Failed to destroy the child.

**/
EFI_STATUS
Ip4Config2DestroyDhcp4 (
  IN OUT IP4_CONFIG2_INSTANCE  *Instance
  )
{
  IP4_SERVICE                 *IpSb;
  EFI_STATUS                  Status;
  EFI_DHCP4_PROTOCOL          *Dhcp4;

  Dhcp4 = Instance->Dhcp4;
  ASSERT (Dhcp4 != NULL);

  Dhcp4->Stop (Dhcp4);
  Dhcp4->Configure (Dhcp4, NULL);
  Instance->Dhcp4 = NULL;

  IpSb = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);

  //
  // Close DHCPv4 protocol and destroy the child.
  //
  Status = gBS->CloseProtocol (
                  Instance->Dhcp4Handle,
                  &gEfiDhcp4ProtocolGuid,
                  IpSb->Image,
                  IpSb->Controller
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = NetLibDestroyServiceChild (
             IpSb->Controller,
             IpSb->Image,
             &gEfiDhcp4ServiceBindingProtocolGuid,
             Instance->Dhcp4Handle
             );

  Instance->Dhcp4Handle = NULL;

  return Status;  
}

/**
  Update the current policy to NewPolicy. During the transition
  period, the default router list
  and address list in all interfaces will be released.

  @param[in]  IpSb               The IP4 service binding instance.
  @param[in]  NewPolicy          The new policy to be updated to.

**/
VOID
Ip4Config2OnPolicyChanged (
  IN IP4_SERVICE            *IpSb,
  IN EFI_IP4_CONFIG2_POLICY NewPolicy
  )
{
  IP4_INTERFACE   *IpIf;
  IP4_ROUTE_TABLE *RouteTable;

  //
  // Currently there are only two policies: static and dhcp. Regardless of
  // what transition is going on, i.e., static -> dhcp and dhcp ->
  // static, we have to free default router table and all addresses.
  //

  if (IpSb->DefaultInterface != NULL) {
    if (IpSb->DefaultRouteTable != NULL) {
      Ip4FreeRouteTable (IpSb->DefaultRouteTable);
      IpSb->DefaultRouteTable = NULL;    
    }

    Ip4CancelReceive (IpSb->DefaultInterface);

    Ip4FreeInterface (IpSb->DefaultInterface, NULL);
    IpSb->DefaultInterface = NULL;
  }

  Ip4CleanAssembleTable (&IpSb->Assemble);

  //
  // Create new default interface and route table.
  //    
  IpIf = Ip4CreateInterface (IpSb->Mnp, IpSb->Controller, IpSb->Image);
  if (IpIf == NULL) {
    return ;
  }

  RouteTable = Ip4CreateRouteTable ();
  if (RouteTable == NULL) {
    Ip4FreeInterface (IpIf, NULL);
    return ;
  }
  
  IpSb->DefaultInterface  = IpIf;
  InsertHeadList (&IpSb->Interfaces, &IpIf->Link);
  IpSb->DefaultRouteTable = RouteTable;
  Ip4ReceiveFrame (IpIf, NULL, Ip4AccpetFrame, IpSb);

  if (IpSb->State == IP4_SERVICE_CONFIGED || IpSb->State == IP4_SERVICE_STARTED) {
    IpSb->State = IP4_SERVICE_UNSTARTED;
  }

  //
  // Start the dhcp configuration.
  //
  if (NewPolicy == Ip4Config2PolicyDhcp) {
    Ip4StartAutoConfig (&IpSb->Ip4Config2Instance);
  }

}

/**
  Signal the registered event. It is the callback routine for NetMapIterate.

  @param[in]  Map    Points to the list of registered event.
  @param[in]  Item   The registered event.
  @param[in]  Arg    Not used.

  @retval EFI_SUCCESS           The event was signaled successfully.
**/
EFI_STATUS
EFIAPI
Ip4Config2SignalEvent (
  IN NET_MAP                *Map,
  IN NET_MAP_ITEM           *Item,
  IN VOID                   *Arg
  )
{
  gBS->SignalEvent ((EFI_EVENT) Item->Key);

  return EFI_SUCCESS;
}

/**
  Read the configuration data from variable storage according to the VarName and
  gEfiIp4Config2ProtocolGuid. It checks the integrity of variable data. If the
  data is corrupted, it clears the variable data to ZERO. Othewise, it outputs the
  configuration data to IP4_CONFIG2_INSTANCE.

  @param[in]      VarName       The pointer to the variable name
  @param[in, out] Instance      The pointer to the IP4 config2 instance data.

  @retval EFI_NOT_FOUND         The variable can not be found or already corrupted.
  @retval EFI_OUT_OF_RESOURCES  Fail to allocate resource to complete the operation.
  @retval EFI_SUCCESS           The configuration data was retrieved successfully.

**/
EFI_STATUS
Ip4Config2ReadConfigData (
  IN     CHAR16               *VarName,
  IN OUT IP4_CONFIG2_INSTANCE *Instance
  )
{
  EFI_STATUS              Status;
  UINTN                   VarSize;
  IP4_CONFIG2_VARIABLE    *Variable;
  IP4_CONFIG2_DATA_ITEM   *DataItem;
  UINTN                   Index;
  IP4_CONFIG2_DATA_RECORD DataRecord;
  CHAR8                   *Data;

  //
  // Try to read the configuration variable.
  //
  VarSize = 0;
  Status  = gRT->GetVariable (
                   VarName,
                   &gEfiIp4Config2ProtocolGuid,
                   NULL,
                   &VarSize,
                   NULL
                   );

  if (Status == EFI_BUFFER_TOO_SMALL) {
    //
    // Allocate buffer and read the config variable.
    //
    Variable = AllocatePool (VarSize);
    if (Variable == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    Status = gRT->GetVariable (
                    VarName,
                    &gEfiIp4Config2ProtocolGuid,
                    NULL,
                    &VarSize,
                    Variable
                    );
    if (EFI_ERROR (Status) || (UINT16) (~NetblockChecksum ((UINT8 *) Variable, (UINT32) VarSize)) != 0) {
      //
      // GetVariable still error or the variable is corrupted.
      // Fall back to the default value.
      //
      FreePool (Variable);

      //
      // Remove the problematic variable and return EFI_NOT_FOUND, a new
      // variable will be set again.
      //
      gRT->SetVariable (
             VarName,
             &gEfiIp4Config2ProtocolGuid,
             IP4_CONFIG2_VARIABLE_ATTRIBUTE,
             0,
             NULL
             );

      return EFI_NOT_FOUND;
    }

 
    for (Index = 0; Index < Variable->DataRecordCount; Index++) {

      CopyMem (&DataRecord, &Variable->DataRecord[Index], sizeof (DataRecord));

      DataItem = &Instance->DataItem[DataRecord.DataType];
      if (DATA_ATTRIB_SET (DataItem->Attribute, DATA_ATTRIB_SIZE_FIXED) &&
          (DataItem->DataSize != DataRecord.DataSize)
          ) {
        //
        // Perhaps a corrupted data record...
        //
        continue;
      }

      if (!DATA_ATTRIB_SET (DataItem->Attribute, DATA_ATTRIB_SIZE_FIXED)) {
        //
        // This data item has variable length data.
        //
        DataItem->Data.Ptr = AllocatePool (DataRecord.DataSize);
        if (DataItem->Data.Ptr == NULL) {
          //
          // no memory resource
          //
          continue;
        }
      }

      Data = (CHAR8 *) Variable + DataRecord.Offset;
      CopyMem (DataItem->Data.Ptr, Data, DataRecord.DataSize);

      DataItem->DataSize = DataRecord.DataSize;
      DataItem->Status   = EFI_SUCCESS;
    }

    FreePool (Variable);
    return EFI_SUCCESS;
  }

  return Status;
}

/**
  Write the configuration data from IP4_CONFIG2_INSTANCE to variable storage.

  @param[in]      VarName       The pointer to the variable name.
  @param[in]      Instance      The pointer to the IP4 config2 instance data.

  @retval EFI_OUT_OF_RESOURCES  Fail to allocate resource to complete the operation.
  @retval EFI_SUCCESS           The configuration data is written successfully.

**/
EFI_STATUS
Ip4Config2WriteConfigData (
  IN CHAR16               *VarName,
  IN IP4_CONFIG2_INSTANCE *Instance
  )
{
  UINTN                   Index;
  UINTN                   VarSize;
  IP4_CONFIG2_DATA_ITEM   *DataItem;
  IP4_CONFIG2_VARIABLE    *Variable;
  IP4_CONFIG2_DATA_RECORD *DataRecord;
  CHAR8                   *Heap;
  EFI_STATUS              Status;

  VarSize = sizeof (IP4_CONFIG2_VARIABLE) - sizeof (IP4_CONFIG2_DATA_RECORD);

  for (Index = 0; Index < Ip4Config2DataTypeMaximum; Index++) {

    DataItem = &Instance->DataItem[Index];
    if (!DATA_ATTRIB_SET (DataItem->Attribute, DATA_ATTRIB_VOLATILE) && !EFI_ERROR (DataItem->Status)) {

      VarSize += sizeof (IP4_CONFIG2_DATA_RECORD) + DataItem->DataSize;
    }
  }

  Variable = AllocatePool (VarSize);
  if (Variable == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Heap                      = (CHAR8 *) Variable + VarSize;
  Variable->DataRecordCount = 0;

  for (Index = 0; Index < Ip4Config2DataTypeMaximum; Index++) {

    DataItem = &Instance->DataItem[Index];
    if (!DATA_ATTRIB_SET (DataItem->Attribute, DATA_ATTRIB_VOLATILE) && !EFI_ERROR (DataItem->Status)) {

      Heap -= DataItem->DataSize;
      CopyMem (Heap, DataItem->Data.Ptr, DataItem->DataSize);

      DataRecord           = &Variable->DataRecord[Variable->DataRecordCount];
      DataRecord->DataType = (EFI_IP4_CONFIG2_DATA_TYPE) Index;
      DataRecord->DataSize = (UINT32) DataItem->DataSize;
      DataRecord->Offset   = (UINT16) (Heap - (CHAR8 *) Variable);

      Variable->DataRecordCount++;
    }
  }

  Variable->Checksum = 0;
  Variable->Checksum = (UINT16) ~NetblockChecksum ((UINT8 *) Variable, (UINT32) VarSize);

  Status = gRT->SetVariable (
                  VarName,
                  &gEfiIp4Config2ProtocolGuid,
                  IP4_CONFIG2_VARIABLE_ATTRIBUTE,
                  VarSize,
                  Variable
                  );

  FreePool (Variable);

  return Status;
}


/**
  Build a EFI_IP4_ROUTE_TABLE to be returned to the caller of GetModeData. 
  The EFI_IP4_ROUTE_TABLE is clumsy to use in the internal operation of the 
  IP4 driver.

  @param[in]   IpSb        The IP4 service binding instance.
  @param[out]  Table       The built IP4 route table.

  @retval EFI_SUCCESS           The route table is successfully build
  @retval EFI_NOT_FOUND         Failed to allocate the memory for the rotue table.

**/
EFI_STATUS
Ip4Config2BuildDefaultRouteTable (
  IN  IP4_SERVICE               *IpSb,
  OUT EFI_IP4_ROUTE_TABLE       *Table
  )
{
  LIST_ENTRY                *Entry; 
  IP4_ROUTE_ENTRY           *RtEntry;
  UINT32                    Count;
  INT32                     Index;

  if (IpSb->DefaultRouteTable == NULL) {
    return EFI_NOT_FOUND;
  }

  Count = IpSb->DefaultRouteTable->TotalNum;

  if (Count == 0) {
    return EFI_NOT_FOUND;
  }

  //
  // Copy the route entry to EFI route table. Keep the order of
  // route entry copied from most specific to default route. That
  // is, interlevel the route entry from the instance's route area
  // and those from the default route table's route area.
  //
  Count = 0;

  for (Index = IP4_MASK_MAX; Index >= 0; Index--) {

    NET_LIST_FOR_EACH (Entry, &(IpSb->DefaultRouteTable->RouteArea[Index])) {
      RtEntry = NET_LIST_USER_STRUCT (Entry, IP4_ROUTE_ENTRY, Link);

      EFI_IP4 (Table[Count].SubnetAddress)  = HTONL (RtEntry->Dest & RtEntry->Netmask);
      EFI_IP4 (Table[Count].SubnetMask)     = HTONL (RtEntry->Netmask);
      EFI_IP4 (Table[Count].GatewayAddress) = HTONL (RtEntry->NextHop);

      Count++;
    }

  }

  return EFI_SUCCESS;
}

/**
  The event process routine when the DHCPv4 service binding protocol is installed
  in the system.

  @param[in]     Event         Not used.
  @param[in]     Context       The pointer to the IP4 config2 instance data.

**/
VOID
EFIAPI
Ip4Config2OnDhcp4SbInstalled (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  IP4_CONFIG2_INSTANCE  *Instance;

  Instance = (IP4_CONFIG2_INSTANCE *) Context;

  if ((Instance->Dhcp4Handle != NULL) || (Instance->Policy != Ip4Config2PolicyDhcp)) {
    //
    // The DHCP4 child is already created or the policy is no longer DHCP.
    //
    return ;
  }

  Ip4StartAutoConfig (Instance);
}

/**
  Set the station address and subnetmask for the default interface.

  @param[in]  IpSb               The pointer to the IP4 service binding instance.
  @param[in]  StationAddress     Ip address to be set.
  @param[in]  SubnetMask         Subnet to be set.

  @retval EFI_SUCCESS   Set default address successful.     
  @retval Others        Some errors occur in setting.     

**/
EFI_STATUS
Ip4Config2SetDefaultAddr (
  IN IP4_SERVICE            *IpSb,
  IN IP4_ADDR               StationAddress,
  IN IP4_ADDR               SubnetMask
  )
{
  EFI_STATUS                Status;
  IP4_INTERFACE             *IpIf;
  IP4_PROTOCOL              *Ip4Instance;
  EFI_ARP_PROTOCOL          *Arp;
  LIST_ENTRY                *Entry;
  IP4_ADDR                  Subnet;
  IP4_ROUTE_TABLE           *RouteTable;

  IpIf = IpSb->DefaultInterface;
  ASSERT (IpIf != NULL);

  if ((IpIf->Ip == StationAddress) && (IpIf->SubnetMask == SubnetMask)) {
    IpSb->State = IP4_SERVICE_CONFIGED;
    return EFI_SUCCESS;
  }

  if (IpSb->Reconfig) {
    //
    // The default address is changed, free the previous interface first.
    //
    if (IpSb->DefaultRouteTable != NULL) {
      Ip4FreeRouteTable (IpSb->DefaultRouteTable);
      IpSb->DefaultRouteTable = NULL;    
    }

    Ip4CancelReceive (IpSb->DefaultInterface);
    Ip4FreeInterface (IpSb->DefaultInterface, NULL);
    IpSb->DefaultInterface = NULL;
    //
    // Create new default interface and route table.
    //    
    IpIf = Ip4CreateInterface (IpSb->Mnp, IpSb->Controller, IpSb->Image);
    if (IpIf == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    RouteTable = Ip4CreateRouteTable ();
    if (RouteTable == NULL) {
      Ip4FreeInterface (IpIf, NULL);
      return EFI_OUT_OF_RESOURCES;
    }
    
    IpSb->DefaultInterface  = IpIf;
    InsertHeadList (&IpSb->Interfaces, &IpIf->Link);
    IpSb->DefaultRouteTable = RouteTable;
    Ip4ReceiveFrame (IpIf, NULL, Ip4AccpetFrame, IpSb);
  }

  if (IpSb->State == IP4_SERVICE_CONFIGED) {
    IpSb->State = IP4_SERVICE_UNSTARTED;
  }

  Status = Ip4SetAddress (IpIf, StationAddress, SubnetMask);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (IpIf->Arp != NULL) {
    //   
    // A non-NULL IpIf->Arp here means a new ARP child is created when setting default address, 
    // but some IP children may have referenced the default interface before it is configured,
    // these IP instances also consume this ARP protocol so they need to open it BY_CHILD_CONTROLLER.
    //
    Arp = NULL;
    NET_LIST_FOR_EACH (Entry, &IpIf->IpInstances) {
      Ip4Instance = NET_LIST_USER_STRUCT_S (Entry, IP4_PROTOCOL, AddrLink, IP4_PROTOCOL_SIGNATURE);
      Status = gBS->OpenProtocol (
                      IpIf->ArpHandle,
                      &gEfiArpProtocolGuid,
                      (VOID **) &Arp,
                      gIp4DriverBinding.DriverBindingHandle,
                      Ip4Instance->Handle,
                      EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                      );
      if (EFI_ERROR (Status)) {
        return Status;
      }
    }
  }

  Ip4AddRoute (
    IpSb->DefaultRouteTable,
    StationAddress,
    SubnetMask,
    IP4_ALLZERO_ADDRESS
    );

  //
  // Add a route for the connected network.
  //
  Subnet = StationAddress & SubnetMask;

  Ip4AddRoute (
    IpSb->DefaultRouteTable,
    Subnet,
    SubnetMask,
    IP4_ALLZERO_ADDRESS
    );

  IpSb->State = IP4_SERVICE_CONFIGED;
  IpSb->Reconfig = FALSE;
  
  return EFI_SUCCESS;
}

/**
  Set the station address, subnetmask and gateway address for the default interface.

  @param[in]  Instance         The pointer to the IP4 config2 instance data.  
  @param[in]  StationAddress   Ip address to be set.
  @param[in]  SubnetMask       Subnet to be set.
  @param[in]  GatewayAddress   Gateway to be set.

  @retval EFI_SUCCESS     Set default If successful.    
  @retval Others          Errors occur as indicated.  

**/
EFI_STATUS
Ip4Config2SetDefaultIf (
  IN IP4_CONFIG2_INSTANCE   *Instance,
  IN IP4_ADDR               StationAddress,
  IN IP4_ADDR               SubnetMask,
  IN IP4_ADDR               GatewayAddress
  )
{
  EFI_STATUS                Status;
  IP4_SERVICE               *IpSb;

  IpSb = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);

  //
  // Check whether the StationAddress/SubnetMask pair is valid.
  //
  if (!Ip4StationAddressValid (StationAddress, SubnetMask)) {
    return EFI_INVALID_PARAMETER;
  }

  Status = Ip4Config2SetDefaultAddr (IpSb, StationAddress, SubnetMask);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Create a route if there is a default router.
  //
  if (GatewayAddress != IP4_ALLZERO_ADDRESS) {
    Ip4AddRoute (
      IpSb->DefaultRouteTable,
      IP4_ALLZERO_ADDRESS,
      IP4_ALLZERO_ADDRESS,
      GatewayAddress
      );        
  }

  return EFI_SUCCESS;
}


/**
  Release all the DHCP related resources.

  @param  Instance              The IP4 config2 instance.

  @return None

**/
VOID
Ip4Config2CleanDhcp4 (
  IN IP4_CONFIG2_INSTANCE   *Instance
  )
{
  IP4_SERVICE               *IpSb;

  IpSb = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);

  if (Instance->Dhcp4 != NULL) {
    Instance->Dhcp4->Stop (Instance->Dhcp4);

    gBS->CloseProtocol (
          Instance->Dhcp4Handle,
          &gEfiDhcp4ProtocolGuid,
          IpSb->Image,
          IpSb->Controller
          );

    Instance->Dhcp4 = NULL;
  }

  if (Instance->Dhcp4Handle != NULL) {
    NetLibDestroyServiceChild (
      IpSb->Controller,
      IpSb->Image,
      &gEfiDhcp4ServiceBindingProtocolGuid,
      Instance->Dhcp4Handle
      );

    Instance->Dhcp4Handle = NULL;
  }

  if (Instance->Dhcp4Event != NULL) {
    gBS->CloseEvent (Instance->Dhcp4Event);
    Instance->Dhcp4Event = NULL;
  }
}

/**
  This worker function sets the DNS server list for the EFI IPv4 network
  stack running on the communication device that this EFI_IP4_CONFIG2_PROTOCOL
  manages. The DNS server addresses must be unicast IPv4 addresses. 

  @param[in]     Instance        The pointer to the IP4 config2 instance data.
  @param[in]     DataSize        The size of the buffer pointed to by Data in bytes.
  @param[in]     Data            The data buffer to set, points to an array of
                                 EFI_IPv4_ADDRESS instances.

  @retval EFI_BAD_BUFFER_SIZE    The DataSize does not match the size of the type.
  @retval EFI_INVALID_PARAMETER  One or more fields in Data is invalid.
  @retval EFI_OUT_OF_RESOURCES   Failed to allocate resources to complete the operation.
  @retval EFI_ABORTED            The DNS server addresses to be set equal the current
                                 configuration.
  @retval EFI_SUCCESS            The specified configuration data for the EFI IPv4
                                 network stack was set.

**/
EFI_STATUS
Ip4Config2SetDnsServerWorker (
  IN IP4_CONFIG2_INSTANCE    *Instance,
  IN UINTN                   DataSize,
  IN VOID                    *Data
  )
{
  UINTN                 OldIndex;
  UINTN                 NewIndex;
  EFI_IPv4_ADDRESS      *OldDns;
  EFI_IPv4_ADDRESS      *NewDns;
  UINTN                 OldDnsCount;
  UINTN                 NewDnsCount;
  IP4_CONFIG2_DATA_ITEM *Item;
  BOOLEAN               OneAdded;
  VOID                  *Tmp;
  IP4_ADDR              DnsAddress;

  if ((DataSize % sizeof (EFI_IPv4_ADDRESS) != 0) || (DataSize == 0)) {
    return EFI_BAD_BUFFER_SIZE;
  }

  Item        = &Instance->DataItem[Ip4Config2DataTypeDnsServer];
  NewDns      = (EFI_IPv4_ADDRESS *) Data;
  OldDns      = Item->Data.DnsServers;
  NewDnsCount = DataSize / sizeof (EFI_IPv4_ADDRESS);  
  OldDnsCount = Item->DataSize / sizeof (EFI_IPv4_ADDRESS);
  OneAdded    = FALSE;

  if (NewDnsCount != OldDnsCount) {
    Tmp = AllocatePool (DataSize);
    if (Tmp == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  } else {
    Tmp = NULL;
  }

  for (NewIndex = 0; NewIndex < NewDnsCount; NewIndex++) {
    CopyMem (&DnsAddress, NewDns + NewIndex, sizeof (IP4_ADDR));
    if (IP4_IS_UNSPECIFIED (NTOHL (DnsAddress)) || IP4_IS_LOCAL_BROADCAST (NTOHL (DnsAddress))) {
      //
      // The dns server address must be unicast.
      //
      if (Tmp != NULL) {
        FreePool (Tmp);
      }
      return EFI_INVALID_PARAMETER;
    }

    if (OneAdded) {
      //
      // If any address in the new setting is not in the old settings, skip the
      // comparision below.
      //
      continue;
    }

    for (OldIndex = 0; OldIndex < OldDnsCount; OldIndex++) {
      if (EFI_IP4_EQUAL (NewDns + NewIndex, OldDns + OldIndex)) {
        //
        // If found break out.
        //
        break;
      }
    }

    if (OldIndex == OldDnsCount) {
      OneAdded = TRUE;
    }
  }

  if (!OneAdded && (DataSize == Item->DataSize)) {
    //
    // No new item is added and the size is the same.
    //
    Item->Status = EFI_SUCCESS;
    return EFI_ABORTED;
  } else {
    if (Tmp != NULL) {
      if (Item->Data.Ptr != NULL) {
        FreePool (Item->Data.Ptr);
      }      
      Item->Data.Ptr = Tmp;
    }

    CopyMem (Item->Data.Ptr, Data, DataSize);
    Item->DataSize = DataSize;
    Item->Status   = EFI_SUCCESS;
    return EFI_SUCCESS;
  }
}



/**
  Callback function when DHCP process finished. It will save the
  retrieved IP configure parameter from DHCP to the NVRam.

  @param  Event                  The callback event
  @param  Context                Opaque context to the callback

  @return None

**/
VOID
EFIAPI
Ip4Config2OnDhcp4Complete (
  IN EFI_EVENT              Event,
  IN VOID                   *Context
  )
{
  IP4_CONFIG2_INSTANCE      *Instance;
  EFI_DHCP4_MODE_DATA       Dhcp4Mode;
  EFI_STATUS                Status;
  IP4_ADDR                  StationAddress;
  IP4_ADDR                  SubnetMask;
  IP4_ADDR                  GatewayAddress;
  UINT32                    Index;
  UINT32                    OptionCount;
  EFI_DHCP4_PACKET_OPTION   **OptionList;

  Instance = (IP4_CONFIG2_INSTANCE *) Context;
  ASSERT (Instance->Dhcp4 != NULL);

  //
  // Get the DHCP retrieved parameters
  //
  Status = Instance->Dhcp4->GetModeData (Instance->Dhcp4, &Dhcp4Mode);

  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  if (Dhcp4Mode.State == Dhcp4Bound) {
    StationAddress = EFI_NTOHL (Dhcp4Mode.ClientAddress);
    SubnetMask = EFI_NTOHL (Dhcp4Mode.SubnetMask);
    GatewayAddress = EFI_NTOHL (Dhcp4Mode.RouterAddress);

    Status = Ip4Config2SetDefaultIf (Instance, StationAddress, SubnetMask, GatewayAddress);
    if (EFI_ERROR (Status)) {
      goto Exit;
    }
  
    //
    // Parse the ACK to get required DNS server information.
    //
    OptionCount = 0;
    OptionList  = NULL;

    Status      = Instance->Dhcp4->Parse (Instance->Dhcp4, Dhcp4Mode.ReplyPacket, &OptionCount, OptionList);
    if (Status != EFI_BUFFER_TOO_SMALL) {
      goto Exit;
    }

    OptionList = AllocateZeroPool (OptionCount * sizeof (EFI_DHCP4_PACKET_OPTION *));
    if (OptionList == NULL) {
      goto Exit;
    }

    Status = Instance->Dhcp4->Parse (Instance->Dhcp4, Dhcp4Mode.ReplyPacket, &OptionCount, OptionList);
    if (EFI_ERROR (Status)) {
      FreePool (OptionList);
      goto Exit;
    }

    for (Index = 0; Index < OptionCount; Index++) {
      //
      // Look for DNS Server opcode (6).
      //
      if (OptionList[Index]->OpCode == DHCP4_TAG_DNS_SERVER) {
        if (((OptionList[Index]->Length & 0x3) != 0) || (OptionList[Index]->Length == 0)) {
          break;
        }

        Ip4Config2SetDnsServerWorker (Instance, OptionList[Index]->Length, &OptionList[Index]->Data[0]);
        break;
      }
    }

    FreePool (OptionList);

    Instance->DhcpSuccess = TRUE;
  }

Exit:
  Ip4Config2CleanDhcp4 (Instance);
  DispatchDpc ();
}


/**
  Start the DHCP configuration for this IP service instance.
  It will locates the EFI_IP4_CONFIG2_PROTOCOL, then start the
  DHCP configuration.

  @param[in]  Instance           The IP4 config2 instance to configure

  @retval EFI_SUCCESS            The auto configuration is successfully started
  @retval Others                 Failed to start auto configuration.

**/
EFI_STATUS
Ip4StartAutoConfig (
  IN IP4_CONFIG2_INSTANCE   *Instance
  )
{
  IP4_SERVICE                    *IpSb;
  EFI_DHCP4_PROTOCOL             *Dhcp4;
  EFI_DHCP4_MODE_DATA            Dhcp4Mode;
  EFI_DHCP4_PACKET_OPTION        *OptionList[1];
  IP4_CONFIG2_DHCP4_OPTION       ParaList;
  EFI_STATUS                     Status;
 

  IpSb = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);

  if (IpSb->State > IP4_SERVICE_UNSTARTED) {
    return EFI_SUCCESS;
  }

  //
  // A host must not invoke DHCP configuration if it is already
  // participating in the DHCP configuraiton process.
  //
  if (Instance->Dhcp4Handle != NULL) {
    return EFI_SUCCESS;
  }

  Status = NetLibCreateServiceChild (
             IpSb->Controller,
             IpSb->Image,
             &gEfiDhcp4ServiceBindingProtocolGuid,
             &Instance->Dhcp4Handle
             );

  if (Status == EFI_UNSUPPORTED) {
    //
    // No DHCPv4 Service Binding protocol, register a notify.
    //
    if (Instance->Dhcp4SbNotifyEvent == NULL) {
      Instance->Dhcp4SbNotifyEvent = EfiCreateProtocolNotifyEvent (
                                       &gEfiDhcp4ServiceBindingProtocolGuid,
                                       TPL_CALLBACK,
                                       Ip4Config2OnDhcp4SbInstalled,
                                       (VOID *) Instance,
                                       &Instance->Registration
                                       );
    }
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Instance->Dhcp4SbNotifyEvent != NULL) {
    gBS->CloseEvent (Instance->Dhcp4SbNotifyEvent);
  }

  Status = gBS->OpenProtocol (
                  Instance->Dhcp4Handle,
                  &gEfiDhcp4ProtocolGuid,
                  (VOID **) &Instance->Dhcp4,
                  IpSb->Image,
                  IpSb->Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  ASSERT_EFI_ERROR (Status);


  //
  // Check the current DHCP status, if the DHCP process has
  // already finished, return now.
  //
  Dhcp4  = Instance->Dhcp4;
  Status = Dhcp4->GetModeData (Dhcp4, &Dhcp4Mode);

  if (Dhcp4Mode.State == Dhcp4Bound) {
    Ip4Config2OnDhcp4Complete (NULL, Instance);
    return EFI_SUCCESS;

  }

  //
  // Try to start the DHCP process. Use most of the current
  // DHCP configuration to avoid problems if some DHCP client
  // yields the control of this DHCP service to us.
  //
  ParaList.Head.OpCode             = DHCP4_TAG_PARA_LIST;
  ParaList.Head.Length             = 3;
  ParaList.Head.Data[0]            = DHCP4_TAG_NETMASK;
  ParaList.Route                   = DHCP4_TAG_ROUTER;
  ParaList.Dns                     = DHCP4_TAG_DNS_SERVER;
  OptionList[0]                    = &ParaList.Head;
  Dhcp4Mode.ConfigData.OptionCount = 1;
  Dhcp4Mode.ConfigData.OptionList  = OptionList;

  Status = Dhcp4->Configure (Dhcp4, &Dhcp4Mode.ConfigData);

  if (EFI_ERROR (Status)) {
    return Status;
  }
  
  //
  // Start the DHCP process
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  Ip4Config2OnDhcp4Complete,
                  Instance,
                  &Instance->Dhcp4Event
                  );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = Dhcp4->Start (Dhcp4, Instance->Dhcp4Event);

  if (EFI_ERROR (Status)) {
    return Status;
  }
 
  IpSb->State     = IP4_SERVICE_STARTED;
  DispatchDpc ();
  return EFI_SUCCESS;

}



/**
  The work function is to get the interface information of the communication 
  device this IP4_CONFIG2_INSTANCE manages.

  @param[in]      Instance Pointer to the IP4 config2 instance data.
  @param[in, out] DataSize On input, in bytes, the size of Data. On output, in
                           bytes, the size of buffer required to store the specified
                           configuration data.
  @param[in]      Data     The data buffer in which the configuration data is returned.
                           Ignored if DataSize is ZERO.

  @retval EFI_BUFFER_TOO_SMALL The size of Data is too small for the specified
                               configuration data, and the required size is
                               returned in DataSize.
  @retval EFI_SUCCESS          The specified configuration data was obtained.

**/
EFI_STATUS
Ip4Config2GetIfInfo (
  IN IP4_CONFIG2_INSTANCE *Instance,
  IN OUT UINTN            *DataSize,
  IN VOID                 *Data      OPTIONAL
  )
{
  IP4_SERVICE                    *IpSb;
  UINTN                          Length;
  IP4_CONFIG2_DATA_ITEM          *Item;
  EFI_IP4_CONFIG2_INTERFACE_INFO *IfInfo;
  IP4_ADDR                       Address;

  IpSb   = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);
  Length = sizeof (EFI_IP4_CONFIG2_INTERFACE_INFO);

  if (IpSb->DefaultRouteTable != NULL) {
    Length += IpSb->DefaultRouteTable->TotalNum * sizeof (EFI_IP4_ROUTE_TABLE);
  }
  
  if (*DataSize < Length) {
    *DataSize = Length;
    return EFI_BUFFER_TOO_SMALL;
  }

  //
  // Copy the fixed size part of the interface info.
  //
  Item = &Instance->DataItem[Ip4Config2DataTypeInterfaceInfo];
  IfInfo = (EFI_IP4_CONFIG2_INTERFACE_INFO *) Data;
  CopyMem (IfInfo, Item->Data.Ptr, sizeof (EFI_IP4_CONFIG2_INTERFACE_INFO));

  //
  // Update the address info.
  //
  if (IpSb->DefaultInterface != NULL) {
    Address = HTONL (IpSb->DefaultInterface->Ip);
    CopyMem (&IfInfo->StationAddress, &Address, sizeof (EFI_IPv4_ADDRESS));
    Address = HTONL (IpSb->DefaultInterface->SubnetMask);
    CopyMem (&IfInfo->SubnetMask, &Address, sizeof (EFI_IPv4_ADDRESS));
  }

  if (IpSb->DefaultRouteTable != NULL) {
    IfInfo->RouteTableSize = IpSb->DefaultRouteTable->TotalNum;
    IfInfo->RouteTable   = (EFI_IP4_ROUTE_TABLE *) ((UINT8 *) Data + sizeof (EFI_IP4_CONFIG2_INTERFACE_INFO));

    Ip4Config2BuildDefaultRouteTable (IpSb, IfInfo->RouteTable);  
  }
  
  return EFI_SUCCESS;
}

/**
  The work function is to set the general configuration policy for the EFI IPv4 network 
  stack that is running on the communication device managed by this IP4_CONFIG2_INSTANCE.
  The policy will affect other configuration settings.

  @param[in]     Instance Pointer to the IP4 config2 instance data.
  @param[in]     DataSize Size of the buffer pointed to by Data in bytes.
  @param[in]     Data     The data buffer to set.

  @retval EFI_INVALID_PARAMETER The to be set policy is invalid.
  @retval EFI_BAD_BUFFER_SIZE   The DataSize does not match the size of the type.
  @retval EFI_ABORTED           The new policy equals the current policy.
  @retval EFI_SUCCESS           The specified configuration data for the EFI IPv6
                                network stack was set.

**/
EFI_STATUS
Ip4Config2SetPolicy (
  IN IP4_CONFIG2_INSTANCE *Instance,
  IN UINTN                DataSize,
  IN VOID                 *Data
  )
{
  EFI_IP4_CONFIG2_POLICY NewPolicy;
  IP4_CONFIG2_DATA_ITEM  *DataItem;
  IP4_SERVICE            *IpSb;

  if (DataSize != sizeof (EFI_IP4_CONFIG2_POLICY)) {
    return EFI_BAD_BUFFER_SIZE;
  }

  NewPolicy = *((EFI_IP4_CONFIG2_POLICY *) Data);

  if (NewPolicy >= Ip4Config2PolicyMax) {
    return EFI_INVALID_PARAMETER;
  }

  if (NewPolicy == Instance->Policy) {
    if (NewPolicy != Ip4Config2PolicyDhcp || Instance->DhcpSuccess) {
      return EFI_ABORTED;
    }
  } else {
    //
    // The policy is changed. Clean the ManualAddress, Gateway and DnsServers, 
    // shrink the variable data size, and fire up all the related events.
    //
    DataItem           = &Instance->DataItem[Ip4Config2DataTypeManualAddress];
    if (DataItem->Data.Ptr != NULL) {
      FreePool (DataItem->Data.Ptr);
    }
    DataItem->Data.Ptr = NULL;
    DataItem->DataSize = 0;
    DataItem->Status   = EFI_NOT_FOUND;
    NetMapIterate (&DataItem->EventMap, Ip4Config2SignalEvent, NULL);

    DataItem           = &Instance->DataItem[Ip4Config2DataTypeGateway];
    if (DataItem->Data.Ptr != NULL) {
      FreePool (DataItem->Data.Ptr);
    }
    DataItem->Data.Ptr = NULL;
    DataItem->DataSize = 0;
    DataItem->Status   = EFI_NOT_FOUND;
    NetMapIterate (&DataItem->EventMap, Ip4Config2SignalEvent, NULL);

    DataItem           = &Instance->DataItem[Ip4Config2DataTypeDnsServer];
    if (DataItem->Data.Ptr != NULL) {
      FreePool (DataItem->Data.Ptr);
    }
    DataItem->Data.Ptr = NULL;
    DataItem->DataSize = 0;
    DataItem->Status   = EFI_NOT_FOUND;
    NetMapIterate (&DataItem->EventMap, Ip4Config2SignalEvent, NULL);
    
    if (NewPolicy == Ip4Config2PolicyDhcp) {
      SET_DATA_ATTRIB (DataItem->Attribute, DATA_ATTRIB_VOLATILE);
    } else {
      //
      // The policy is changed from dhcp to static. Stop the DHCPv4 process
      // and destroy the DHCPv4 child.
      //
      if (Instance->Dhcp4Handle != NULL) {
        Ip4Config2DestroyDhcp4 (Instance);
      }
      
      //
      // Close the event.
      //
      if (Instance->Dhcp4Event != NULL) {
        gBS->CloseEvent (Instance->Dhcp4Event);
        Instance->Dhcp4Event = NULL;
      }
    }
  }

  IpSb = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);
  Ip4Config2OnPolicyChanged (IpSb, NewPolicy);

  Instance->Policy = NewPolicy;

  return EFI_SUCCESS;
}

/**
  The work function is to set the station addresses manually for the EFI IPv4 
  network stack. It is only configurable when the policy is Ip4Config2PolicyStatic.

  @param[in]     Instance Pointer to the IP4 config2 instance data.
  @param[in]     DataSize Size of the buffer pointed to by Data in bytes.
  @param[in]     Data     The data buffer to set.

  @retval EFI_BAD_BUFFER_SIZE   The DataSize does not match the size of the type.
  @retval EFI_WRITE_PROTECTED   The specified configuration data cannot be set
                                under the current policy.
  @retval EFI_INVALID_PARAMETER One or more fields in Data is invalid.
  @retval EFI_OUT_OF_RESOURCES  Fail to allocate resource to complete the operation.
  @retval EFI_NOT_READY         An asynchrous process is invoked to set the specified
                                configuration data, and the process is not finished.
  @retval EFI_ABORTED           The manual addresses to be set equal current
                                configuration.
  @retval EFI_SUCCESS           The specified configuration data for the EFI IPv6
                                network stack was set.

**/
EFI_STATUS
Ip4Config2SetMaunualAddress (
  IN IP4_CONFIG2_INSTANCE *Instance,
  IN UINTN                DataSize,
  IN VOID                 *Data
  )
{
  EFI_IP4_CONFIG2_MANUAL_ADDRESS NewAddress;
  IP4_CONFIG2_DATA_ITEM          *DataItem;
  EFI_STATUS                     Status;
  IP4_ADDR                       StationAddress;
  IP4_ADDR                       SubnetMask;
  VOID                           *Ptr;
  IP4_SERVICE                    *IpSb;

  IpSb = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);

  ASSERT (Instance->DataItem[Ip4Config2DataTypeManualAddress].Status != EFI_NOT_READY);

  if (((DataSize % sizeof (EFI_IP4_CONFIG2_MANUAL_ADDRESS)) != 0) || (DataSize == 0)) {
    return EFI_BAD_BUFFER_SIZE;
  }

  if (Instance->Policy != Ip4Config2PolicyStatic) {
    return EFI_WRITE_PROTECTED;
  }

  NewAddress = *((EFI_IP4_CONFIG2_MANUAL_ADDRESS *) Data);

  StationAddress = EFI_NTOHL (NewAddress.Address);
  SubnetMask = EFI_NTOHL (NewAddress.SubnetMask);

  //
  // Check whether the StationAddress/SubnetMask pair is valid.
  //
  if (!Ip4StationAddressValid (StationAddress, SubnetMask)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Store the new data, and init the DataItem status to EFI_NOT_READY because
  // we may have an asynchronous configuration process.
  //
  Ptr = AllocateCopyPool (DataSize, Data);
  if (Ptr == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  DataItem = &Instance->DataItem[Ip4Config2DataTypeManualAddress];
  if (DataItem->Data.Ptr != NULL) {
    FreePool (DataItem->Data.Ptr);
  }
  
  DataItem->Data.Ptr = Ptr;
  DataItem->DataSize = DataSize;
  DataItem->Status   = EFI_NOT_READY;

  IpSb->Reconfig = TRUE;
  Status = Ip4Config2SetDefaultAddr (IpSb, StationAddress, SubnetMask);

  DataItem->Status = Status; 

  if (EFI_ERROR (DataItem->Status) && DataItem->Status != EFI_NOT_READY) {
    if (Ptr != NULL) {
      FreePool (Ptr);
    }
    DataItem->Data.Ptr = NULL; 
  }

  return Status;
}

/**
  The work function is to set the gateway addresses manually for the EFI IPv4 
  network stack that is running on the communication device that this EFI IPv4 
  Configuration Protocol manages. It is not configurable when the policy is
  Ip4Config2PolicyDhcp. The gateway addresses must be unicast IPv4 addresses.

  @param[in]     Instance The pointer to the IP4 config2 instance data.
  @param[in]     DataSize The size of the buffer pointed to by Data in bytes.
  @param[in]     Data     The data buffer to set. This points to an array of
                          EFI_IPv6_ADDRESS instances.

  @retval EFI_BAD_BUFFER_SIZE   The DataSize does not match the size of the type.
  @retval EFI_WRITE_PROTECTED   The specified configuration data cannot be set
                                under the current policy.
  @retval EFI_INVALID_PARAMETER One or more fields in Data is invalid.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate resource to complete the operation.
  @retval EFI_ABORTED           The manual gateway addresses to be set equal the
                                current configuration.
  @retval EFI_SUCCESS           The specified configuration data for the EFI IPv6
                                network stack was set.

**/
EFI_STATUS
Ip4Config2SetGateway (
  IN IP4_CONFIG2_INSTANCE *Instance,
  IN UINTN                DataSize,
  IN VOID                 *Data
  )
{
  IP4_SERVICE                    *IpSb;
  IP4_CONFIG2_DATA_ITEM          *DataItem;
  IP4_ADDR                       Gateway;

  UINTN                 Index1;
  UINTN                 Index2;
  EFI_IPv4_ADDRESS      *OldGateway;
  EFI_IPv4_ADDRESS      *NewGateway;
  UINTN                 OldGatewayCount;
  UINTN                 NewGatewayCount;
  BOOLEAN               OneRemoved;
  BOOLEAN               OneAdded;
  VOID                  *Tmp;

  if ((DataSize % sizeof (EFI_IPv4_ADDRESS) != 0) || (DataSize == 0)) {
    return EFI_BAD_BUFFER_SIZE;
  }

  if (Instance->Policy != Ip4Config2PolicyStatic) {
    return EFI_WRITE_PROTECTED;
  }

  IpSb = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);

  NewGateway      = (EFI_IPv4_ADDRESS *) Data;
  NewGatewayCount = DataSize / sizeof (EFI_IPv4_ADDRESS);
  for (Index1 = 0; Index1 < NewGatewayCount; Index1++) {
    CopyMem (&Gateway, NewGateway + Index1, sizeof (IP4_ADDR));

    if ((IpSb->DefaultInterface->SubnetMask != 0) && 
        !NetIp4IsUnicast (NTOHL (Gateway), IpSb->DefaultInterface->SubnetMask)) {
      return EFI_INVALID_PARAMETER;
    }

    for (Index2 = Index1 + 1; Index2 < NewGatewayCount; Index2++) {
      if (EFI_IP4_EQUAL (NewGateway + Index1, NewGateway + Index2)) {
        return EFI_INVALID_PARAMETER;
      }
    }
  }
 
  DataItem = &Instance->DataItem[Ip4Config2DataTypeGateway];
  OldGateway      = DataItem->Data.Gateway;
  OldGatewayCount = DataItem->DataSize / sizeof (EFI_IPv4_ADDRESS);
  OneRemoved      = FALSE;
  OneAdded        = FALSE;

  if (NewGatewayCount != OldGatewayCount) {
    Tmp = AllocatePool (DataSize);
    if (Tmp == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  } else {
    Tmp = NULL;
  }

  for (Index1 = 0; Index1 < OldGatewayCount; Index1++) {
    //
    // Remove this route entry.
    //
    CopyMem (&Gateway, OldGateway + Index1, sizeof (IP4_ADDR));
    Ip4DelRoute (
      IpSb->DefaultRouteTable,
      IP4_ALLZERO_ADDRESS,
      IP4_ALLZERO_ADDRESS,
      NTOHL (Gateway)
      );
    OneRemoved = TRUE;

  }

  for (Index1 = 0; Index1 < NewGatewayCount; Index1++) {
    CopyMem (&Gateway, NewGateway + Index1, sizeof (IP4_ADDR));
    Ip4AddRoute (
      IpSb->DefaultRouteTable,
      IP4_ALLZERO_ADDRESS,
      IP4_ALLZERO_ADDRESS,
      NTOHL (Gateway)
      );    

    OneAdded = TRUE;
  }


  if (!OneRemoved && !OneAdded) {
    DataItem->Status = EFI_SUCCESS;
    return EFI_ABORTED;
  } else {

    if (Tmp != NULL) {
      if (DataItem->Data.Ptr != NULL) {
        FreePool (DataItem->Data.Ptr);
      }
      DataItem->Data.Ptr = Tmp;
    }

    CopyMem (DataItem->Data.Ptr, Data, DataSize);
    DataItem->DataSize = DataSize;
    DataItem->Status   = EFI_SUCCESS;
    return EFI_SUCCESS;
  }

}

/**
  The work function is to set the DNS server list for the EFI IPv4 network 
  stack running on the communication device that this EFI_IP4_CONFIG2_PROTOCOL 
  manages. It is not configurable when the policy is Ip4Config2PolicyDhcp. 
  The DNS server addresses must be unicast IPv4 addresses.

  @param[in]     Instance The pointer to the IP4 config2 instance data.
  @param[in]     DataSize The size of the buffer pointed to by Data in bytes.
  @param[in]     Data     The data buffer to set, points to an array of
                          EFI_IPv4_ADDRESS instances.

  @retval EFI_BAD_BUFFER_SIZE   The DataSize does not match the size of the type.
  @retval EFI_WRITE_PROTECTED   The specified configuration data cannot be set
                                under the current policy.
  @retval EFI_INVALID_PARAMETER One or more fields in Data is invalid.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate resources to complete the operation.
  @retval EFI_ABORTED           The DNS server addresses to be set equal the current
                                configuration.
  @retval EFI_SUCCESS           The specified configuration data for the EFI IPv4
                                network stack was set.

**/
EFI_STATUS
Ip4Config2SetDnsServer (
  IN IP4_CONFIG2_INSTANCE *Instance,
  IN UINTN                DataSize,
  IN VOID                 *Data
  )
{
  IP4_CONFIG2_DATA_ITEM *Item;

  Item = NULL;

  if (Instance->Policy != Ip4Config2PolicyStatic) {
    return EFI_WRITE_PROTECTED;
  }

  Item = &Instance->DataItem[Ip4Config2DataTypeDnsServer];

  if (DATA_ATTRIB_SET (Item->Attribute, DATA_ATTRIB_VOLATILE)) {
    REMOVE_DATA_ATTRIB (Item->Attribute, DATA_ATTRIB_VOLATILE);
  }

  return Ip4Config2SetDnsServerWorker (Instance, DataSize, Data);
}

/**
  Generate the operational state of the interface this IP4 config2 instance manages
  and output in EFI_IP4_CONFIG2_INTERFACE_INFO.

  @param[in]      IpSb     The pointer to the IP4 service binding instance.
  @param[out]     IfInfo   The pointer to the IP4 config2 interface information structure.

**/
VOID
Ip4Config2InitIfInfo (
  IN  IP4_SERVICE                    *IpSb,
  OUT EFI_IP4_CONFIG2_INTERFACE_INFO *IfInfo
  )
{
  UnicodeSPrint (
    IfInfo->Name,
    EFI_IP4_CONFIG2_INTERFACE_INFO_NAME_SIZE,
    L"eth%d",
    IpSb->Ip4Config2Instance.IfIndex
  );

  IfInfo->IfType        = IpSb->SnpMode.IfType;
  IfInfo->HwAddressSize = IpSb->SnpMode.HwAddressSize;
  CopyMem (&IfInfo->HwAddress, &IpSb->SnpMode.CurrentAddress, IfInfo->HwAddressSize);
}

/**
  The event handle routine when DHCPv4 process is finished or is updated.

  @param[in]     Event         Not used.
  @param[in]     Context       The pointer to the IP4 configuration instance data.

**/
VOID
EFIAPI
Ip4Config2OnDhcp4Event (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  return ;
}


/**
  Set the configuration for the EFI IPv4 network stack running on the communication
  device this EFI_IP4_CONFIG2_PROTOCOL instance manages.

  This function is used to set the configuration data of type DataType for the EFI
  IPv4 network stack that is running on the communication device that this EFI IPv4
  Configuration Protocol instance manages.

  DataSize is used to calculate the count of structure instances in the Data for
  a DataType in which multiple structure instances are allowed.

  This function is always non-blocking. When setting some type of configuration data,
  an asynchronous process is invoked to check the correctness of the data, such as
  performing Duplicate Address Detection on the manually set local IPv4 addresses.
  EFI_NOT_READY is returned immediately to indicate that such an asynchronous process
  is invoked, and the process is not finished yet. The caller wanting to get the result
  of the asynchronous process is required to call RegisterDataNotify() to register an
  event on the specified configuration data. Once the event is signaled, the caller
  can call GetData() to obtain the configuration data and know the result.
  For other types of configuration data that do not require an asynchronous configuration
  process, the result of the operation is immediately returned.

  @param[in]     This           The pointer to the EFI_IP4_CONFIG2_PROTOCOL instance.
  @param[in]     DataType       The type of data to set.
  @param[in]     DataSize       Size of the buffer pointed to by Data in bytes.
  @param[in]     Data           The data buffer to set. The type of the data buffer is
                                associated with the DataType.

  @retval EFI_SUCCESS           The specified configuration data for the EFI IPv6
                                network stack was set successfully.
  @retval EFI_INVALID_PARAMETER One or more of the following are TRUE:
                                - This is NULL.
                                - Data is NULL.
                                - One or more fields in Data do not match the requirement of the
                                  data type indicated by DataType.
  @retval EFI_WRITE_PROTECTED   The specified configuration data is read-only or the specified
                                configuration data cannot be set under the current policy.
  @retval EFI_ACCESS_DENIED     Another set operation on the specified configuration
                                data is already in process.
  @retval EFI_NOT_READY         An asynchronous process was invoked to set the specified
                                configuration data, and the process is not finished yet.
  @retval EFI_BAD_BUFFER_SIZE   The DataSize does not match the size of the type
                                indicated by DataType.
  @retval EFI_UNSUPPORTED       This DataType is not supported.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected system error or network error occurred.

**/
EFI_STATUS
EFIAPI
EfiIp4Config2SetData (
  IN EFI_IP4_CONFIG2_PROTOCOL   *This,
  IN EFI_IP4_CONFIG2_DATA_TYPE  DataType,
  IN UINTN                      DataSize,
  IN VOID                       *Data
  )
{
  EFI_TPL              OldTpl;
  EFI_STATUS           Status;
  IP4_CONFIG2_INSTANCE *Instance;
  IP4_SERVICE          *IpSb;

  if ((This == NULL) || (Data == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (DataType >= Ip4Config2DataTypeMaximum) {
    return EFI_UNSUPPORTED;
  }

  Instance = IP4_CONFIG2_INSTANCE_FROM_PROTOCOL (This);
  IpSb     = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);
  NET_CHECK_SIGNATURE (IpSb, IP4_SERVICE_SIGNATURE);


  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

  Status = Instance->DataItem[DataType].Status;
  if (Status != EFI_NOT_READY) {

    if (Instance->DataItem[DataType].SetData == NULL) {
      //
      // This type of data is readonly.
      //
      Status = EFI_WRITE_PROTECTED;
    } else {

      Status = Instance->DataItem[DataType].SetData (Instance, DataSize, Data);
      if (!EFI_ERROR (Status)) {
        //
        // Fire up the events registered with this type of data.
        //
        NetMapIterate (&Instance->DataItem[DataType].EventMap, Ip4Config2SignalEvent, NULL);
        Ip4Config2WriteConfigData (IpSb->MacString, Instance);
      } else if (Status == EFI_ABORTED) {
        //
        // The SetData is aborted because the data to set is the same with
        // the one maintained.
        //
        Status = EFI_SUCCESS;
        NetMapIterate (&Instance->DataItem[DataType].EventMap, Ip4Config2SignalEvent, NULL);
      }
    }
  } else {
    //
    // Another asynchornous process is on the way.
    //
    Status = EFI_ACCESS_DENIED;
  }

  gBS->RestoreTPL (OldTpl);

  return Status;
}

/**
  Get the configuration data for the EFI IPv4 network stack running on the communication
  device that this EFI_IP4_CONFIG2_PROTOCOL instance manages.

  This function returns the configuration data of type DataType for the EFI IPv4 network
  stack running on the communication device that this EFI IPv4 Configuration Protocol instance
  manages.

  The caller is responsible for allocating the buffer used to return the specified
  configuration data. The required size will be returned to the caller if the size of
  the buffer is too small.

  EFI_NOT_READY is returned if the specified configuration data is not ready due to an
  asynchronous configuration process already in progress. The caller can call RegisterDataNotify()
  to register an event on the specified configuration data. Once the asynchronous configuration
  process is finished, the event will be signaled, and a subsequent GetData() call will return
  the specified configuration data.

  @param[in]      This           Pointer to the EFI_IP4_CONFIG2_PROTOCOL instance.
  @param[in]      DataType       The type of data to get.
  @param[in, out] DataSize       On input, in bytes, the size of Data. On output, in bytes, the
                                 size of buffer required to store the specified configuration data.
  @param[in]     Data            The data buffer in which the configuration data is returned. The
                                 type of the data buffer is associated with the DataType.
                                 This is an optional parameter that may be NULL.

  @retval EFI_SUCCESS           The specified configuration data was obtained successfully.
  @retval EFI_INVALID_PARAMETER One or more of the followings are TRUE:
                                - This is NULL.
                                - DataSize is NULL.
                                - Data is NULL if *DataSize is not zero.
  @retval EFI_BUFFER_TOO_SMALL  The size of Data is too small for the specified configuration data,
                                and the required size is returned in DataSize.
  @retval EFI_NOT_READY         The specified configuration data is not ready due to an
                                asynchronous configuration process already in progress.
  @retval EFI_NOT_FOUND         The specified configuration data is not found.

**/
EFI_STATUS
EFIAPI
EfiIp4Config2GetData (
  IN EFI_IP4_CONFIG2_PROTOCOL   *This,
  IN EFI_IP4_CONFIG2_DATA_TYPE  DataType,
  IN OUT UINTN                  *DataSize,
  IN VOID                       *Data   OPTIONAL
  )
{
  EFI_TPL               OldTpl;
  EFI_STATUS            Status;
  IP4_CONFIG2_INSTANCE  *Instance;
  IP4_CONFIG2_DATA_ITEM *DataItem;

  if ((This == NULL) || (DataSize == NULL) || ((*DataSize != 0) && (Data == NULL))) {
    return EFI_INVALID_PARAMETER;
  }

  if (DataType >= Ip4Config2DataTypeMaximum) {
    return EFI_NOT_FOUND;
  }

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

  Instance = IP4_CONFIG2_INSTANCE_FROM_PROTOCOL (This);
  DataItem = &Instance->DataItem[DataType];

  Status   = Instance->DataItem[DataType].Status;
  if (!EFI_ERROR (Status)) {

    if (DataItem->GetData != NULL) {

      Status = DataItem->GetData (Instance, DataSize, Data);
    } else if (*DataSize < Instance->DataItem[DataType].DataSize) {
      //
      // Update the buffer length.
      //
      *DataSize = Instance->DataItem[DataType].DataSize;
      Status    = EFI_BUFFER_TOO_SMALL;
    } else {

      *DataSize = Instance->DataItem[DataType].DataSize;
      CopyMem (Data, Instance->DataItem[DataType].Data.Ptr, *DataSize);
    }
  }

  gBS->RestoreTPL (OldTpl);

  return Status;
}

/**
  Register an event that is signaled whenever a configuration process on the specified
  configuration data is done.

  This function registers an event that is to be signaled whenever a configuration
  process on the specified configuration data is performed. An event can be registered
  for a different DataType simultaneously. The caller is responsible for determining
  which type of configuration data causes the signaling of the event in such an event.

  @param[in]     This           Pointer to the EFI_IP4_CONFIG2_PROTOCOL instance.
  @param[in]     DataType       The type of data to unregister the event for.
  @param[in]     Event          The event to register.

  @retval EFI_SUCCESS           The notification event for the specified configuration data is
                                registered.
  @retval EFI_INVALID_PARAMETER This is NULL or Event is NULL.
  @retval EFI_UNSUPPORTED       The configuration data type specified by DataType is not
                                supported.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_ACCESS_DENIED     The Event is already registered for the DataType.

**/
EFI_STATUS
EFIAPI
EfiIp4Config2RegisterDataNotify (
  IN EFI_IP4_CONFIG2_PROTOCOL   *This,
  IN EFI_IP4_CONFIG2_DATA_TYPE  DataType,
  IN EFI_EVENT                  Event
  )
{
  EFI_TPL              OldTpl;
  EFI_STATUS           Status;
  IP4_CONFIG2_INSTANCE *Instance;
  NET_MAP              *EventMap;
  NET_MAP_ITEM         *Item;

  if ((This == NULL) || (Event == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (DataType >= Ip4Config2DataTypeMaximum) {
    return EFI_UNSUPPORTED;
  }

  OldTpl    = gBS->RaiseTPL (TPL_CALLBACK);

  Instance  = IP4_CONFIG2_INSTANCE_FROM_PROTOCOL (This);
  EventMap  = &Instance->DataItem[DataType].EventMap;

  //
  // Check whether this event is already registered for this DataType.
  //
  Item = NetMapFindKey (EventMap, Event);
  if (Item == NULL) {

    Status = NetMapInsertTail (EventMap, Event, NULL);

    if (EFI_ERROR (Status)) {

      Status = EFI_OUT_OF_RESOURCES;
    }

  } else {

    Status = EFI_ACCESS_DENIED;
  }

  gBS->RestoreTPL (OldTpl);

  return Status;
}

/**
  Remove a previously registered event for the specified configuration data.

  @param  This                   The pointer to the EFI_IP4_CONFIG2_PROTOCOL instance.
  @param  DataType               The type of data to remove from the previously
                                 registered event.
  @param  Event                  The event to be unregistered.

  @retval EFI_SUCCESS            The event registered for the specified
                                 configuration data was removed.
  @retval EFI_INVALID_PARAMETER  This is NULL or Event is NULL.
  @retval EFI_NOT_FOUND          The Event has not been registered for the
                                 specified DataType.

**/
EFI_STATUS
EFIAPI
EfiIp4Config2UnregisterDataNotify (
  IN EFI_IP4_CONFIG2_PROTOCOL   *This,
  IN EFI_IP4_CONFIG2_DATA_TYPE  DataType,
  IN EFI_EVENT                  Event
  )
{
  EFI_TPL              OldTpl;
  EFI_STATUS           Status;
  IP4_CONFIG2_INSTANCE *Instance;
  NET_MAP_ITEM         *Item;

  if ((This == NULL) || (Event == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (DataType >= Ip4Config2DataTypeMaximum) {
    return EFI_NOT_FOUND;
  }

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

  Instance = IP4_CONFIG2_INSTANCE_FROM_PROTOCOL (This);

  Item = NetMapFindKey (&Instance->DataItem[DataType].EventMap, Event);
  if (Item != NULL) {

    NetMapRemoveItem (&Instance->DataItem[DataType].EventMap, Item, NULL);
    Status = EFI_SUCCESS;
  } else {

    Status = EFI_NOT_FOUND;
  }

  gBS->RestoreTPL (OldTpl);

  return Status;
}

/**
  Initialize an IP4_CONFIG2_INSTANCE.

  @param[out]    Instance       The buffer of IP4_CONFIG2_INSTANCE to be initialized.

  @retval EFI_OUT_OF_RESOURCES  Failed to allocate resources to complete the operation.
  @retval EFI_SUCCESS           The IP4_CONFIG2_INSTANCE initialized successfully.

**/
EFI_STATUS
Ip4Config2InitInstance (
  OUT IP4_CONFIG2_INSTANCE  *Instance
  )
{
  IP4_SERVICE           *IpSb;
  IP4_CONFIG2_INSTANCE  *TmpInstance;
  LIST_ENTRY            *Entry;
  EFI_STATUS            Status;
  UINTN                 Index;
  UINT16                IfIndex;
  IP4_CONFIG2_DATA_ITEM *DataItem;


  IpSb = IP4_SERVICE_FROM_IP4_CONFIG2_INSTANCE (Instance);

  Instance->Signature = IP4_CONFIG2_INSTANCE_SIGNATURE;


  //
  // Determine the index of this interface.
  //
  IfIndex = 0;
  NET_LIST_FOR_EACH (Entry, &mIp4Config2InstanceList) {
    TmpInstance = NET_LIST_USER_STRUCT_S (Entry, IP4_CONFIG2_INSTANCE, Link, IP4_CONFIG2_INSTANCE_SIGNATURE);

    if (TmpInstance->IfIndex > IfIndex) {
      //
      // There is a sequence hole because some interface is down.
      //
      break;
    }

    IfIndex++;
  }

  Instance->IfIndex = IfIndex;
  NetListInsertBefore (Entry, &Instance->Link);

  for (Index = 0; Index < Ip4Config2DataTypeMaximum; Index++) {
    //
    // Initialize the event map for each data item.
    //
    NetMapInit (&Instance->DataItem[Index].EventMap);
  }

  
  //
  // Initialize each data type: associate storage and set data size for the
  // fixed size data types, hook the SetData function, set the data attribute.
  //
  DataItem           = &Instance->DataItem[Ip4Config2DataTypeInterfaceInfo];
  DataItem->GetData  = Ip4Config2GetIfInfo;
  DataItem->Data.Ptr = &Instance->InterfaceInfo;
  DataItem->DataSize = sizeof (Instance->InterfaceInfo);
  SET_DATA_ATTRIB (DataItem->Attribute, DATA_ATTRIB_SIZE_FIXED | DATA_ATTRIB_VOLATILE);
  Ip4Config2InitIfInfo (IpSb, &Instance->InterfaceInfo);

  DataItem           = &Instance->DataItem[Ip4Config2DataTypePolicy];
  DataItem->SetData  = Ip4Config2SetPolicy;
  DataItem->Data.Ptr = &Instance->Policy;
  DataItem->DataSize = sizeof (Instance->Policy);
  Instance->Policy   = Ip4Config2PolicyStatic;
  SET_DATA_ATTRIB (DataItem->Attribute, DATA_ATTRIB_SIZE_FIXED);

  DataItem           = &Instance->DataItem[Ip4Config2DataTypeManualAddress];
  DataItem->SetData  = Ip4Config2SetMaunualAddress;
  DataItem->Status   = EFI_NOT_FOUND;

  DataItem           = &Instance->DataItem[Ip4Config2DataTypeGateway];
  DataItem->SetData  = Ip4Config2SetGateway;
  DataItem->Status   = EFI_NOT_FOUND;

  DataItem           = &Instance->DataItem[Ip4Config2DataTypeDnsServer];
  DataItem->SetData  = Ip4Config2SetDnsServer;
  DataItem->Status   = EFI_NOT_FOUND;

  //
  // Create the event used for DHCP.
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  Ip4Config2OnDhcp4Event,
                  Instance,
                  &Instance->Dhcp4Event
                  );
  ASSERT_EFI_ERROR (Status);

  Instance->Configured  = TRUE;

  //
  // Try to read the config data from NV variable.
  // If not found, write initialized config data into NV variable 
  // as a default config data.
  //
  Status = Ip4Config2ReadConfigData (IpSb->MacString, Instance);
  if (Status == EFI_NOT_FOUND) {
    Status = Ip4Config2WriteConfigData (IpSb->MacString, Instance);
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }
  
  Instance->Ip4Config2.SetData              = EfiIp4Config2SetData;
  Instance->Ip4Config2.GetData              = EfiIp4Config2GetData;
  Instance->Ip4Config2.RegisterDataNotify   = EfiIp4Config2RegisterDataNotify;
  Instance->Ip4Config2.UnregisterDataNotify = EfiIp4Config2UnregisterDataNotify;

  //
  // Publish the IP4 configuration form
  //
  return Ip4Config2FormInit (Instance);
}


/**
  Release an IP4_CONFIG2_INSTANCE.

  @param[in, out] Instance    The buffer of IP4_CONFIG2_INSTANCE to be freed.

**/
VOID
Ip4Config2CleanInstance (
  IN OUT IP4_CONFIG2_INSTANCE  *Instance
  )
{
  UINTN                 Index;
  IP4_CONFIG2_DATA_ITEM *DataItem;

  if (Instance->DeclineAddress != NULL) {
    FreePool (Instance->DeclineAddress);
  }

  if (!Instance->Configured) {
    return ;
  }

  if (Instance->Dhcp4Handle != NULL) {

    Ip4Config2DestroyDhcp4 (Instance);
  }

  //
  // Close the event.
  //
  if (Instance->Dhcp4Event != NULL) {
    gBS->CloseEvent (Instance->Dhcp4Event);
    Instance->Dhcp4Event = NULL;
  }

  for (Index = 0; Index < Ip4Config2DataTypeMaximum; Index++) {

    DataItem = &Instance->DataItem[Index];

    if (!DATA_ATTRIB_SET (DataItem->Attribute, DATA_ATTRIB_SIZE_FIXED)) {
      if (DataItem->Data.Ptr != NULL) {
        FreePool (DataItem->Data.Ptr);
      }
      DataItem->Data.Ptr = NULL;
      DataItem->DataSize = 0;
    }

    NetMapClean (&Instance->DataItem[Index].EventMap);
  }

  Ip4Config2FormUnload (Instance);

  RemoveEntryList (&Instance->Link);
}

/**
  The event handle for IP4 auto reconfiguration. The original default
  interface and route table will be removed as the default.

  @param[in]  Context                The IP4 service binding instance.

**/
VOID
EFIAPI
Ip4AutoReconfigCallBackDpc (
  IN VOID                   *Context
  )
{
  IP4_SERVICE               *IpSb;

  IpSb      = (IP4_SERVICE *) Context;
  NET_CHECK_SIGNATURE (IpSb, IP4_SERVICE_SIGNATURE);

  if (IpSb->State > IP4_SERVICE_UNSTARTED) {
    IpSb->State = IP4_SERVICE_UNSTARTED;
  }
  
  IpSb->Reconfig = TRUE;

  Ip4StartAutoConfig (&IpSb->Ip4Config2Instance);

  return ;
}


/**
  Request Ip4AutoReconfigCallBackDpc as a DPC at TPL_CALLBACK.

  @param Event     The event that is signalled.
  @param Context   The IP4 service binding instance.

**/
VOID
EFIAPI
Ip4AutoReconfigCallBack (
  IN EFI_EVENT              Event,
  IN VOID                   *Context
  )
{
  //
  // Request Ip4AutoReconfigCallBackDpc as a DPC at TPL_CALLBACK
  //
  QueueDpc (TPL_CALLBACK, Ip4AutoReconfigCallBackDpc, Context);
}

