/** @file
  The Hii functions for WiFi Connection Manager.

  Copyright (c) 2019 - 2022, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "WifiConnectionMgrDxe.h"

CHAR16  mVendorStorageName[] = L"WIFI_MANAGER_IFR_NVDATA";

HII_VENDOR_DEVICE_PATH  mWifiMgrDxeHiiVendorDevicePath = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_VENDOR_DP,
      {
        (UINT8)(sizeof (VENDOR_DEVICE_PATH)),
        (UINT8)((sizeof (VENDOR_DEVICE_PATH)) >> 8)
      }
    },
    WIFI_CONNECTION_MANAGER_CONFIG_GUID
  },
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    {
      (UINT8)(END_DEVICE_PATH_LENGTH),
      (UINT8)((END_DEVICE_PATH_LENGTH) >> 8)
    }
  }
};

//
// HII Config Access Protocol instance
//
GLOBAL_REMOVE_IF_UNREFERENCED
EFI_HII_CONFIG_ACCESS_PROTOCOL  gWifiMgrDxeHiiConfigAccess = {
  WifiMgrDxeHiiConfigAccessExtractConfig,
  WifiMgrDxeHiiConfigAccessRouteConfig,
  WifiMgrDxeHiiConfigAccessCallback
};

CHAR16  *mSecurityType[] = {
  L"OPEN           ",
  L"WPA-Enterprise ",
  L"WPA2-Enterprise",
  L"WPA-Personal   ",
  L"WPA2-Personal  ",
  L"WEP            ",
  L"WPA3-Personal  ",
  L"WPA3-Enterprise",
  L"UnKnown        "
};

CHAR16  *mSignalStrengthBar[] = {
  L"[-----]",
  L"[*----]",
  L"[**---]",
  L"[***--]",
  L"[****-]",
  L"[*****]"
};

#define  RSSI_TO_SIGNAL_STRENGTH_BAR(Rssi)  mSignalStrengthBar[((Rssi + 19)/20)]

#define  NET_LIST_FOR_EACH_FROM_NODE(Entry, Node, ListHead) \
  for(Entry = Node->ForwardLink; Entry != (ListHead); Entry = Entry->ForwardLink)

extern EFI_GUID  gWifiConfigFormSetGuid;

/**
  Create Hii Extend Label OpCode as the start opcode and end opcode.
  The caller is responsible for freeing the OpCode with HiiFreeOpCodeHandle().

  @param[in]  StartLabelNumber   The number of start label.
  @param[out] StartOpCodeHandle  Points to the start opcode handle.
  @param[out] EndOpCodeHandle    Points to the end opcode handle.

  @retval EFI_OUT_OF_RESOURCES   Do not have sufficient resource to finish this
                                 operation.
  @retval EFI_INVALID_PARAMETER  Any input parameter is invalid.
  @retval EFI_SUCCESS            The operation is completed successfully.
  @retval Other Errors           Returned errors when updating the HII form.

**/
EFI_STATUS
WifiMgrCreateOpCode (
  IN  UINT16  StartLabelNumber,
  OUT VOID    **StartOpCodeHandle,
  OUT VOID    **EndOpCodeHandle
  )
{
  EFI_STATUS          Status;
  EFI_IFR_GUID_LABEL  *InternalStartLabel;
  EFI_IFR_GUID_LABEL  *InternalEndLabel;

  if ((StartOpCodeHandle == NULL) || (EndOpCodeHandle == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  Status             = EFI_OUT_OF_RESOURCES;
  *StartOpCodeHandle = NULL;
  *EndOpCodeHandle   = NULL;

  //
  // Initialize the container for dynamic opcodes.
  //
  *StartOpCodeHandle = HiiAllocateOpCodeHandle ();
  if (*StartOpCodeHandle == NULL) {
    goto Exit;
  }

  *EndOpCodeHandle = HiiAllocateOpCodeHandle ();
  if (*EndOpCodeHandle == NULL) {
    goto Exit;
  }

  //
  // Create Hii Extend Label OpCode as the start opcode.
  //
  InternalStartLabel = (EFI_IFR_GUID_LABEL *)HiiCreateGuidOpCode (
                                               *StartOpCodeHandle,
                                               &gEfiIfrTianoGuid,
                                               NULL,
                                               sizeof (EFI_IFR_GUID_LABEL)
                                               );
  if (InternalStartLabel == NULL) {
    goto Exit;
  }

  InternalStartLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
  InternalStartLabel->Number       = StartLabelNumber;

  //
  // Create Hii Extend Label OpCode as the end opcode.
  //
  InternalEndLabel = (EFI_IFR_GUID_LABEL *)HiiCreateGuidOpCode (
                                             *EndOpCodeHandle,
                                             &gEfiIfrTianoGuid,
                                             NULL,
                                             sizeof (EFI_IFR_GUID_LABEL)
                                             );
  if (InternalEndLabel == NULL) {
    goto Exit;
  }

  InternalEndLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
  InternalEndLabel->Number       = LABEL_END;

  return EFI_SUCCESS;

Exit:

  if (*StartOpCodeHandle != NULL) {
    HiiFreeOpCodeHandle (*StartOpCodeHandle);
  }

  if (*EndOpCodeHandle != NULL) {
    HiiFreeOpCodeHandle (*EndOpCodeHandle);
  }

  return Status;
}

/**
  Display the Nic list contains all available Nics.

  @param[in]  Private            The pointer to the global private data structure.

  @retval EFI_INVALID_PARAMETER  Any input parameter is invalid.
  @retval EFI_SUCCESS            The operation is completed successfully.

**/
EFI_STATUS
WifiMgrShowNicList (
  IN  WIFI_MGR_PRIVATE_DATA  *Private
  )
{
  EFI_STATUS            Status;
  CHAR16                MacString[WIFI_MGR_MAX_MAC_STRING_LEN];
  CHAR16                PortString[WIFI_STR_MAX_SIZE];
  EFI_STRING_ID         PortTitleToken;
  EFI_STRING_ID         PortTitleHelpToken;
  WIFI_MGR_DEVICE_DATA  *Nic;
  LIST_ENTRY            *Entry;
  VOID                  *StartOpCodeHandle;
  VOID                  *EndOpCodeHandle;

  if (Private == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Status = WifiMgrCreateOpCode (
             LABEL_MAC_ENTRY,
             &StartOpCodeHandle,
             &EndOpCodeHandle
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  NET_LIST_FOR_EACH (Entry, &Private->NicList) {
    Nic = NET_LIST_USER_STRUCT_S (Entry, WIFI_MGR_DEVICE_DATA, Link, WIFI_MGR_DEVICE_DATA_SIGNATURE);
    WifiMgrMacAddrToStr (&Nic->MacAddress, sizeof (MacString), MacString);
    UnicodeSPrint (PortString, sizeof (PortString), L"MAC %s", MacString);
    PortTitleToken = HiiSetString (
                       Private->RegisteredHandle,
                       0,
                       PortString,
                       NULL
                       );
    if (PortTitleToken == 0) {
      Status = EFI_INVALID_PARAMETER;
      goto Exit;
    }

    UnicodeSPrint (PortString, sizeof (PortString), L"MAC Address");
    PortTitleHelpToken = HiiSetString (
                           Private->RegisteredHandle,
                           0,
                           PortString,
                           NULL
                           );
    if (PortTitleHelpToken == 0) {
      Status = EFI_INVALID_PARAMETER;
      goto Exit;
    }

    HiiCreateGotoOpCode (
      StartOpCodeHandle,
      FORMID_WIFI_MAINPAGE,
      PortTitleToken,
      PortTitleHelpToken,
      EFI_IFR_FLAG_CALLBACK,
      (UINT16)(KEY_MAC_ENTRY_BASE + Nic->NicIndex)
      );
  }

  Status = HiiUpdateForm (
             Private->RegisteredHandle,       // HII handle
             &gWifiConfigFormSetGuid,         // Formset GUID
             FORMID_MAC_SELECTION,            // Form ID
             StartOpCodeHandle,               // Label for where to insert opcodes
             EndOpCodeHandle                  // Replace data
             );

Exit:

  HiiFreeOpCodeHandle (StartOpCodeHandle);
  HiiFreeOpCodeHandle (EndOpCodeHandle);
  return Status;
}

/**
  Retreive the unicode string of the AKM Suite list of a profile.
  The caller is responsible for freeing the string with FreePool().

  @param[in]  Profile           The network profile contains a AKM suite list.

  @return the unicode string of AKM suite list or "None".

**/
CHAR16 *
WifiMgrGetStrAKMList (
  IN  WIFI_MGR_NETWORK_PROFILE  *Profile
  )
{
  UINT8   Index;
  UINT16  AKMSuiteCount;
  CHAR16  *AKMListDisplay;
  UINTN   Length;

  AKMListDisplay = NULL;
  if ((Profile == NULL) || (Profile->Network.AKMSuite == NULL)) {
    goto Exit;
  }

  AKMSuiteCount = Profile->Network.AKMSuite->AKMSuiteCount;
  if (AKMSuiteCount != 0) {
    //
    // Current AKM Suite is between 1-18
    //
    AKMListDisplay = (CHAR16 *)AllocateZeroPool (sizeof (CHAR16) * (AKMSuiteCount * 3 + 1));
    Length         = 0;
    if (AKMListDisplay != NULL) {
      for (Index = 0; Index < AKMSuiteCount; Index++) {
        //
        // The size of buffer should be 4 CHAR16 for Null-terminated Unicode string.
        //
        UnicodeSPrint (
          AKMListDisplay + Length,
          sizeof (CHAR16) * 4,
          L"%d ",
          Profile->Network.AKMSuite->AKMSuiteList[Index].SuiteType
          );
        Length = StrLen (AKMListDisplay + Length) + Length;
        if (Index == AKMSuiteCount - 1) {
          *(AKMListDisplay + (Length - 1)) = L'\0';
        }
      }
    }
  }

Exit:

  if (AKMListDisplay == NULL) {
    AKMListDisplay = AllocateCopyPool (sizeof (L"None"), L"None");
  }

  return AKMListDisplay;
}

/**
  Retreive the unicode string of the Cipher Suite list of a profile.
  The caller is responsible for freeing the string with FreePool().

  @param[in]  Profile           The network profile contains a Cipher suite list.

  @return the unicode string of Cipher suite list or "None".

**/
CHAR16 *
WifiMgrGetStrCipherList (
  IN  WIFI_MGR_NETWORK_PROFILE  *Profile
  )
{
  UINT8   Index;
  UINT16  CipherSuiteCount;
  CHAR16  *CipherListDisplay;

  CipherListDisplay = NULL;
  if ((Profile == NULL) || (Profile->Network.CipherSuite == NULL)) {
    goto Exit;
  }

  CipherSuiteCount = Profile->Network.CipherSuite->CipherSuiteCount;
  if (CipherSuiteCount != 0) {
    //
    // Current Cipher Suite is between 1-9
    //
    CipherListDisplay = (CHAR16 *)AllocateZeroPool (sizeof (CHAR16) * (CipherSuiteCount * 2 + 1));
    if (CipherListDisplay != NULL) {
      for (Index = 0; Index < CipherSuiteCount; Index++) {
        //
        // The size of buffer should be 3 CHAR16 for Null-terminated Unicode string.
        // The first char is the Cipher Suite number, the second char is ' ', the third char is '\0'.
        //
        UnicodeSPrint (
          CipherListDisplay + (Index * 2),
          sizeof (CHAR16) * 3,
          L"%d ",
          Profile->Network.CipherSuite->CipherSuiteList[Index].SuiteType
          );
        if (Index == CipherSuiteCount - 1) {
          *(CipherListDisplay + (Index * 2 + 1)) = L'\0';
        }
      }
    }
  }

Exit:

  if (CipherListDisplay == NULL) {
    CipherListDisplay = AllocateCopyPool (sizeof (L"None"), L"None");
  }

  return CipherListDisplay;
}

/**
  Refresh the network list display of the current Nic.

  @param[in]   Private           The pointer to the global private data structure.
  @param[out]  IfrNvData         The IFR NV data.

  @retval EFI_SUCCESS            The operation is completed successfully.
  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory.
  @retval Other Errors           Returned errors when creating Opcodes or updating the
                                 Hii form.

**/
EFI_STATUS
WifiMgrRefreshNetworkList (
  IN    WIFI_MGR_PRIVATE_DATA    *Private,
  OUT   WIFI_MANAGER_IFR_NVDATA  *IfrNvData
  )
{
  EFI_STATUS                Status;
  EFI_TPL                   OldTpl;
  EFI_STRING_ID             PortPromptToken;
  EFI_STRING_ID             PortTextToken;
  EFI_STRING_ID             PortHelpToken;
  WIFI_MGR_NETWORK_PROFILE  *Profile;
  LIST_ENTRY                *Entry;
  VOID                      *StartOpCodeHandle;
  VOID                      *EndOpCodeHandle;
  CHAR16                    *AKMListDisplay;
  CHAR16                    *CipherListDisplay;
  CHAR16                    PortString[WIFI_STR_MAX_SIZE];
  UINTN                     PortStringSize;
  WIFI_MGR_NETWORK_PROFILE  *ConnectedProfile;

  if (Private->CurrentNic == NULL) {
    return EFI_SUCCESS;
  }

  Status = WifiMgrCreateOpCode (
             LABEL_NETWORK_LIST_ENTRY,
             &StartOpCodeHandle,
             &EndOpCodeHandle
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  OldTpl            = gBS->RaiseTPL (TPL_CALLBACK);
  PortStringSize    = sizeof (PortString);
  ConnectedProfile  = NULL;
  AKMListDisplay    = NULL;
  CipherListDisplay = NULL;

  if (Private->CurrentNic->ConnectState == WifiMgrConnectedToAp) {
    //
    // Display the current connected network.
    // Find the current operate network under connected status.
    //
    if ((Private->CurrentNic->CurrentOperateNetwork != NULL) &&
        Private->CurrentNic->CurrentOperateNetwork->IsAvailable)
    {
      Profile = Private->CurrentNic->CurrentOperateNetwork;

      AKMListDisplay = WifiMgrGetStrAKMList (Profile);
      if (AKMListDisplay == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }

      CipherListDisplay = WifiMgrGetStrCipherList (Profile);
      if (CipherListDisplay == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }

      UnicodeSPrint (PortString, PortStringSize, L"%s (Connected)", Profile->SSId);
      PortPromptToken = HiiSetString (Private->RegisteredHandle, 0, PortString, NULL);

      if (Profile->SecurityType == SECURITY_TYPE_NONE) {
        PortHelpToken = 0;
      } else {
        UnicodeSPrint (PortString, PortStringSize, L"AKMSuite: %s CipherSuite: %s", AKMListDisplay, CipherListDisplay);
        PortHelpToken = HiiSetString (Private->RegisteredHandle, 0, PortString, NULL);
      }

      FreePool (AKMListDisplay);
      FreePool (CipherListDisplay);
      AKMListDisplay    = NULL;
      CipherListDisplay = NULL;

      HiiCreateGotoOpCode (
        StartOpCodeHandle,
        FORMID_CONNECT_NETWORK,
        PortPromptToken,
        PortHelpToken,
        EFI_IFR_FLAG_CALLBACK,
        (UINT16)(KEY_AVAILABLE_NETWORK_ENTRY_BASE + Profile->ProfileIndex)
        );

      UnicodeSPrint (
        PortString,
        PortStringSize,
        L"%s       %s %s",
        (Profile->SecurityType != SECURITY_TYPE_NONE ? L"Secured" : L"Open   "),
        mSecurityType[Profile->SecurityType],
        RSSI_TO_SIGNAL_STRENGTH_BAR (Profile->NetworkQuality)
        );
      PortTextToken = HiiSetString (Private->RegisteredHandle, 0, PortString, NULL);

      HiiCreateTextOpCode (
        StartOpCodeHandle,
        PortTextToken,
        0,
        0
        );

      ConnectedProfile = Profile;
    } else {
      Private->CurrentNic->HasDisconnectPendingNetwork = TRUE;
    }
  }

  //
  // Display all supported available networks.
  //
  NET_LIST_FOR_EACH (Entry, &Private->CurrentNic->ProfileList) {
    Profile = NET_LIST_USER_STRUCT_S (
                Entry,
                WIFI_MGR_NETWORK_PROFILE,
                Link,
                WIFI_MGR_PROFILE_SIGNATURE
                );
    if (ConnectedProfile == Profile) {
      continue;
    }

    if (Profile->IsAvailable && Profile->CipherSuiteSupported) {
      AKMListDisplay = WifiMgrGetStrAKMList (Profile);
      if (AKMListDisplay == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }

      CipherListDisplay = WifiMgrGetStrCipherList (Profile);
      if (CipherListDisplay == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }

      PortPromptToken = HiiSetString (Private->RegisteredHandle, 0, Profile->SSId, NULL);
      if (PortPromptToken == 0) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }

      if (Profile->SecurityType == SECURITY_TYPE_NONE) {
        PortHelpToken = 0;
      } else {
        UnicodeSPrint (
          PortString,
          PortStringSize,
          L"AKMSuite: %s CipherSuite: %s",
          AKMListDisplay,
          CipherListDisplay
          );
        PortHelpToken = HiiSetString (Private->RegisteredHandle, 0, PortString, NULL);
        if (PortHelpToken == 0) {
          Status = EFI_OUT_OF_RESOURCES;
          goto Exit;
        }
      }

      FreePool (AKMListDisplay);
      FreePool (CipherListDisplay);
      AKMListDisplay    = NULL;
      CipherListDisplay = NULL;

      HiiCreateGotoOpCode (
        StartOpCodeHandle,
        FORMID_CONNECT_NETWORK,
        PortPromptToken,
        PortHelpToken,
        EFI_IFR_FLAG_CALLBACK,
        (UINT16)(KEY_AVAILABLE_NETWORK_ENTRY_BASE + Profile->ProfileIndex)
        );

      UnicodeSPrint (
        PortString,
        PortStringSize,
        L"%s       %s %s",
        (Profile->SecurityType != SECURITY_TYPE_NONE ? L"Secured" : L"Open   "),
        mSecurityType[Profile->SecurityType],
        RSSI_TO_SIGNAL_STRENGTH_BAR (Profile->NetworkQuality)
        );
      PortTextToken = HiiSetString (Private->RegisteredHandle, 0, PortString, NULL);
      if (PortTextToken == 0) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }

      HiiCreateTextOpCode (
        StartOpCodeHandle,
        PortTextToken,
        0,
        0
        );
    }
  }

  //
  // Display all Unsupported available networks.
  //
  NET_LIST_FOR_EACH (Entry, &Private->CurrentNic->ProfileList) {
    Profile = NET_LIST_USER_STRUCT_S (
                Entry,
                WIFI_MGR_NETWORK_PROFILE,
                Link,
                WIFI_MGR_PROFILE_SIGNATURE
                );
    if (ConnectedProfile == Profile) {
      continue;
    }

    if (Profile->IsAvailable && !Profile->CipherSuiteSupported) {
      AKMListDisplay = WifiMgrGetStrAKMList (Profile);
      if (AKMListDisplay == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }

      CipherListDisplay = WifiMgrGetStrCipherList (Profile);
      if (CipherListDisplay == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }

      PortPromptToken = HiiSetString (Private->RegisteredHandle, 0, Profile->SSId, NULL);

      if (Profile->AKMSuiteSupported) {
        UnicodeSPrint (
          PortString,
          PortStringSize,
          L"AKMSuite: %s CipherSuite(UnSupported): %s",
          AKMListDisplay,
          CipherListDisplay
          );
      } else {
        UnicodeSPrint (
          PortString,
          PortStringSize,
          L"AKMSuite(UnSupported): %s CipherSuite(UnSupported): %s",
          AKMListDisplay,
          CipherListDisplay
          );
      }

      FreePool (AKMListDisplay);
      FreePool (CipherListDisplay);
      AKMListDisplay    = NULL;
      CipherListDisplay = NULL;

      PortHelpToken = HiiSetString (Private->RegisteredHandle, 0, PortString, NULL);

      HiiCreateGotoOpCode (
        StartOpCodeHandle,
        FORMID_CONNECT_NETWORK,
        PortPromptToken,
        PortHelpToken,
        EFI_IFR_FLAG_CALLBACK,
        (UINT16)(KEY_AVAILABLE_NETWORK_ENTRY_BASE + Profile->ProfileIndex)
        );

      UnicodeSPrint (
        PortString,
        PortStringSize,
        L"%s       %s %s",
        L"UnSupported",
        mSecurityType[Profile->SecurityType],
        RSSI_TO_SIGNAL_STRENGTH_BAR (Profile->NetworkQuality)
        );
      PortTextToken = HiiSetString (Private->RegisteredHandle, 0, PortString, NULL);

      HiiCreateTextOpCode (
        StartOpCodeHandle,
        PortTextToken,
        0,
        0
        );
    }
  }

  Status = HiiUpdateForm (
             Private->RegisteredHandle,       // HII handle
             &gWifiConfigFormSetGuid,         // Formset GUID
             FORMID_NETWORK_LIST,             // Form ID
             StartOpCodeHandle,               // Label for where to insert opcodes
             EndOpCodeHandle                  // Replace data
             );

Exit:

  gBS->RestoreTPL (OldTpl);

  if (AKMListDisplay != NULL) {
    FreePool (AKMListDisplay);
  }

  if (CipherListDisplay != NULL) {
    FreePool (CipherListDisplay);
  }

  HiiFreeOpCodeHandle (StartOpCodeHandle);
  HiiFreeOpCodeHandle (EndOpCodeHandle);

  DEBUG ((DEBUG_INFO, "[WiFi Connection Manager] Network List is Refreshed!\n"));
  return Status;
}

/**
  Refresh the hidden network list configured by user.

  @param[in]   Private           The pointer to the global private data structure.

  @retval EFI_SUCCESS            The operation is completed successfully.
  @retval Other Errors           Returned errors when creating Opcodes or updating the
                                 Hii form.
**/
EFI_STATUS
WifiMgrRefreshHiddenList (
  IN    WIFI_MGR_PRIVATE_DATA  *Private
  )
{
  EFI_STATUS                Status;
  EFI_TPL                   OldTpl;
  UINTN                     Index;
  EFI_STRING_ID             StringId;
  VOID                      *StartOpCodeHandle;
  VOID                      *EndOpCodeHandle;
  WIFI_HIDDEN_NETWORK_DATA  *HiddenNetwork;
  LIST_ENTRY                *Entry;

  if (Private == NULL) {
    return EFI_SUCCESS;
  }

  Status = WifiMgrCreateOpCode (
             LABEL_HIDDEN_NETWORK_ENTRY,
             &StartOpCodeHandle,
             &EndOpCodeHandle
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
  Index  = 0;

  NET_LIST_FOR_EACH (Entry, &Private->HiddenNetworkList) {
    HiddenNetwork = NET_LIST_USER_STRUCT_S (
                      Entry,
                      WIFI_HIDDEN_NETWORK_DATA,
                      Link,
                      WIFI_MGR_HIDDEN_NETWORK_SIGNATURE
                      );
    StringId = HiiSetString (Private->RegisteredHandle, 0, HiddenNetwork->SSId, NULL);

    HiiCreateCheckBoxOpCode (
      StartOpCodeHandle,
      (EFI_QUESTION_ID)(KEY_HIDDEN_NETWORK_ENTRY_BASE + Index),
      MANAGER_VARSTORE_ID,
      (UINT16)(HIDDEN_NETWORK_LIST_VAR_OFFSET + Index),
      StringId,
      0,
      0,
      0,
      NULL
      );
    Index++;
  }

  Status = HiiUpdateForm (
             Private->RegisteredHandle,       // HII handle
             &gWifiConfigFormSetGuid,         // Formset GUID
             FORMID_HIDDEN_NETWORK_LIST,      // Form ID
             StartOpCodeHandle,               // Label for where to insert opcodes
             EndOpCodeHandle                  // Replace data
             );

  gBS->RestoreTPL (OldTpl);
  HiiFreeOpCodeHandle (StartOpCodeHandle);
  HiiFreeOpCodeHandle (EndOpCodeHandle);
  return Status;
}

/**
  Callback function for user to select a Nic.

  @param[in]  Private            The pointer to the global private data structure.
  @param[in]  KeyValue           The key value received from HII input.

  @retval EFI_NOT_FOUND          The corresponding Nic is not found.
  @retval EFI_SUCCESS            The operation is completed successfully.

**/
EFI_STATUS
WifiMgrSelectNic (
  IN     WIFI_MGR_PRIVATE_DATA  *Private,
  IN     EFI_QUESTION_ID        KeyValue
  )
{
  WIFI_MGR_DEVICE_DATA  *Nic;
  UINT32                NicIndex;
  CHAR16                MacString[WIFI_MGR_MAX_MAC_STRING_LEN];

  NicIndex = KeyValue - KEY_MAC_ENTRY_BASE;
  Nic      = WifiMgrGetNicByIndex (Private, NicIndex);
  if (Nic == NULL) {
    return EFI_NOT_FOUND;
  }

  Private->CurrentNic = Nic;

  WifiMgrMacAddrToStr (&Nic->MacAddress, sizeof (MacString), MacString);
  HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_MAC_ADDRESS), MacString, NULL);
  return EFI_SUCCESS;
}

/**
  Restore the NV data to be default.

  @param[in]  Private             The pointer to the global private data structure.
  @param[out] IfrNvData           The IFR NV data.

**/
VOID
WifiMgrCleanUserInput (
  IN  WIFI_MGR_PRIVATE_DATA  *Private
  )
{
  Private->SecurityType        = SECURITY_TYPE_NONE;
  Private->EapAuthMethod       = EAP_AUTH_METHOD_TTLS;
  Private->EapSecondAuthMethod = EAP_SEAUTH_METHOD_MSCHAPV2;
  Private->FileType            = FileTypeMax;
}

/**
  UI handle function when user select a network to connect.

  @param[in]  Private             The pointer to the global private data structure.
  @param[in]  ProfileIndex        The profile index user selected to connect.

  @retval EFI_INVALID_PARAMETER   Nic is null.
  @retval EFI_NOT_FOUND           Profile could not be found.
  @retval EFI_SUCCESS             The operation is completed successfully.

**/
EFI_STATUS
WifiMgrUserSelectProfileToConnect (
  IN     WIFI_MGR_PRIVATE_DATA  *Private,
  IN     UINT32                 ProfileIndex
  )
{
  WIFI_MGR_NETWORK_PROFILE  *Profile;
  WIFI_MGR_DEVICE_DATA      *Nic;

  Nic = Private->CurrentNic;
  if (Nic == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Initialize the connection page
  //
  WifiMgrCleanUserInput (Private);

  Profile = WifiMgrGetProfileByProfileIndex (ProfileIndex, &Nic->ProfileList);
  if (Profile == NULL) {
    return EFI_NOT_FOUND;
  }

  Private->CurrentNic->UserSelectedProfile = Profile;

  return EFI_SUCCESS;
}

/**
  Record password from a HII input string.

  @param[in]  Private             The pointer to the global private data structure.
  @param[in]  StringId            The QuestionId received from HII input.
  @param[in]  StringBuffer        The unicode string buffer to store password.
  @param[in]  StringBufferLen     The len of unicode string buffer.

  @retval EFI_INVALID_PARAMETER   Any input parameter is invalid.
  @retval EFI_NOT_FOUND           The password string is not found or invalid.
  @retval EFI_SUCCESS             The operation is completed successfully.

**/
EFI_STATUS
WifiMgrRecordPassword (
  IN   WIFI_MGR_PRIVATE_DATA  *Private,
  IN   EFI_STRING_ID          StringId,
  IN   CHAR16                 *StringBuffer,
  IN   UINTN                  StringBufferLen
  )
{
  CHAR16  *Password;

  if ((StringId == 0) || (StringBuffer == NULL) || (StringBufferLen <= 0)) {
    return EFI_INVALID_PARAMETER;
  }

  Password = HiiGetString (Private->RegisteredHandle, StringId, NULL);
  if (Password == NULL) {
    return EFI_NOT_FOUND;
  }

  if (StrLen (Password) > StringBufferLen) {
    FreePool (Password);
    return EFI_NOT_FOUND;
  }

  StrnCpyS (StringBuffer, StringBufferLen, Password, StrLen (Password));
  ZeroMem (Password, (StrLen (Password) + 1) * sizeof (CHAR16));
  FreePool (Password);

  //
  // Clean password in string package
  //
  HiiSetString (Private->RegisteredHandle, StringId, L"", NULL);
  return EFI_SUCCESS;
}

/**
  Update connection message on connect configuration page, and trigger related form refresh.

  @param[in]   Nic                        The related Nic for updating message.
  @param[in]   ConnectStateChanged        The tag to tell if the connection state has been changed, only
                                          when the connection changes from "Connected" or "Disconnecting"
                                          to "Disconnected", or from "Disconnected" or "Connecting" to
                                          "Connected", this tag can be set as TRUE.
  @param[in]   ConnectStatusMessage       The message to show on connected status bar, if NULL, will
                                          use default message.

**/
VOID
WifiMgrUpdateConnectMessage (
  IN  WIFI_MGR_DEVICE_DATA  *Nic,
  IN  BOOLEAN               ConnectStateChanged,
  IN  EFI_STRING            ConnectStatusMessage
  )
{
  CHAR16                 ConnectStatusStr[WIFI_STR_MAX_SIZE];
  WIFI_MGR_PRIVATE_DATA  *Private;

  Private = Nic->Private;
  if ((Private == NULL) || (Private->CurrentNic != Nic)) {
    return;
  }

  //
  // Update Connection Status Bar
  //
  if (ConnectStatusMessage != NULL) {
    HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECT_STATUS), ConnectStatusMessage, NULL);
  } else {
    if (Nic->ConnectState == WifiMgrConnectedToAp) {
      UnicodeSPrint (
        ConnectStatusStr,
        sizeof (ConnectStatusStr),
        L"Connected to %s",
        Nic->CurrentOperateNetwork->SSId
        );
      HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECT_STATUS), ConnectStatusStr, NULL);
    } else if (Nic->ConnectState == WifiMgrDisconnected) {
      HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECT_STATUS), L"Disconnected", NULL);
    } else if (Nic->ConnectState == WifiMgrConnectingToAp) {
      UnicodeSPrint (
        ConnectStatusStr,
        sizeof (ConnectStatusStr),
        L"Connecting to %s ...",
        Nic->CurrentOperateNetwork->SSId
        );
      HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECT_STATUS), ConnectStatusStr, NULL);
    } else if (Nic->ConnectState == WifiMgrDisconnectingToAp) {
      UnicodeSPrint (
        ConnectStatusStr,
        sizeof (ConnectStatusStr),
        L"Disconnecting from %s ...",
        Nic->CurrentOperateNetwork->SSId
        );
      HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECT_STATUS), ConnectStatusStr, NULL);
    } else {
      return;
    }
  }

  //
  // Update Connect Button
  //
  if ((Nic->ConnectState == WifiMgrConnectedToAp) && (Nic->UserSelectedProfile == Nic->CurrentOperateNetwork)) {
    HiiSetString (
      Private->RegisteredHandle,
      STRING_TOKEN (STR_CONNECT_NOW),
      L"Disconnect from this Network",
      NULL
      );
  } else {
    HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECT_NOW), L"Connect to this Network", NULL);
  }

  gBS->SignalEvent (Private->ConnectFormRefreshEvent);

  //
  // Update Main Page and Network List
  //
  if (ConnectStateChanged) {
    if (Nic->ConnectState == WifiMgrConnectedToAp) {
      HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECTION_INFO), L"Connected to", NULL);
      HiiSetString (
        Private->RegisteredHandle,
        STRING_TOKEN (STR_CONNECTED_SSID),
        Nic->CurrentOperateNetwork->SSId,
        NULL
        );
    } else {
      HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECTION_INFO), L"Disconnected", NULL);
      HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_CONNECTED_SSID), L"", NULL);
    }

    gBS->SignalEvent (Private->NetworkListRefreshEvent);
    gBS->SignalEvent (Private->MainPageRefreshEvent);
  }
}

/**
  Convert the driver configuration data into the IFR data.

  @param[in]   Private            The pointer to the global private data structure.
  @param[out]  IfrNvData          The IFR NV data.

  @retval EFI_SUCCESS             The operation is completed successfully.

**/
EFI_STATUS
WifiMgrConvertConfigDataToIfrNvData (
  IN   WIFI_MGR_PRIVATE_DATA    *Private,
  OUT  WIFI_MANAGER_IFR_NVDATA  *IfrNvData
  )
{
  //
  // Private shouldn't be NULL here, assert if Private is NULL.
  //
  ASSERT (Private != NULL);

  if (Private->CurrentNic != NULL) {
    IfrNvData->ProfileCount = Private->CurrentNic->AvailableCount;
  } else {
    IfrNvData->ProfileCount = 0;
  }

  return EFI_SUCCESS;
}

/**
  Convert the IFR data into the driver configuration data.

  @param[in]       Private             The pointer to the global private data structure.
  @param[in, out]  IfrNvData           The IFR NV data.

  @retval EFI_SUCCESS                  The operation is completed successfully.

**/
EFI_STATUS
WifiMgrConvertIfrNvDataToConfigData (
  IN     WIFI_MGR_PRIVATE_DATA    *Private,
  IN OUT WIFI_MANAGER_IFR_NVDATA  *IfrNvData
  )
{
  return EFI_SUCCESS;
}

/**
  This function allows the caller to request the current
  configuration for one or more named elements. The resulting
  string is in <ConfigAltResp> format. Any and all alternative
  configuration strings shall also be appended to the end of the
  current configuration string. If they are, they must appear
  after the current configuration. They must contain the same
  routing (GUID, NAME, PATH) as the current configuration string.
  They must have an additional description indicating the type of
  alternative configuration the string represents,
  "ALTCFG=<StringToken>". That <StringToken> (when
  converted from Hex UNICODE to binary) is a reference to a
  string in the associated string pack.

  @param This       Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.

  @param Request    A null-terminated Unicode string in
                    <ConfigRequest> format. Note that this
                    includes the routing information as well as
                    the configurable name / value pairs. It is
                    invalid for this string to be in
                    <MultiConfigRequest> format.
                    If a NULL is passed in for the Request field,
                    all of the settings being abstracted by this function
                    will be returned in the Results field.  In addition,
                    if a ConfigHdr is passed in with no request elements,
                    all of the settings being abstracted for that particular
                    ConfigHdr reference will be returned in the Results Field.

  @param Progress   On return, points to a character in the
                    Request string. Points to the string's null
                    terminator if request was successful. Points
                    to the most recent "&" before the first
                    failing name / value pair (or the beginning
                    of the string if the failure is in the first
                    name / value pair) if the request was not
                    successful.

  @param Results    A null-terminated Unicode string in
                    <MultiConfigAltResp> format which has all values
                    filled in for the names in the Request string.
                    String to be allocated by the called function.

  @retval EFI_SUCCESS             The Results string is filled with the
                                  values corresponding to all requested
                                  names.

  @retval EFI_OUT_OF_RESOURCES    Not enough memory to store the
                                  parts of the results that must be
                                  stored awaiting possible future
                                  protocols.

  @retval EFI_NOT_FOUND           Routing data doesn't match any
                                  known driver. Progress set to the
                                  first character in the routing header.
                                  Note: There is no requirement that the
                                  driver validate the routing data. It
                                  must skip the <ConfigHdr> in order to
                                  process the names.

  @retval EFI_INVALID_PARAMETER   Illegal syntax. Progress set
                                  to most recent "&" before the
                                  error or the beginning of the
                                  string.

  @retval EFI_INVALID_PARAMETER   Unknown name. Progress points
                                  to the & before the name in
                                  question.

**/
EFI_STATUS
EFIAPI
WifiMgrDxeHiiConfigAccessExtractConfig (
  IN CONST  EFI_HII_CONFIG_ACCESS_PROTOCOL  *This,
  IN CONST  EFI_STRING                      Request,
  OUT       EFI_STRING                      *Progress,
  OUT       EFI_STRING                      *Results
  )
{
  WIFI_MGR_PRIVATE_DATA    *Private;
  WIFI_MANAGER_IFR_NVDATA  *IfrNvData;
  EFI_STRING               ConfigRequestHdr;
  EFI_STRING               ConfigRequest;
  UINTN                    Size;
  BOOLEAN                  AllocatedRequest;
  UINTN                    BufferSize;
  EFI_STATUS               Status;

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

  *Progress = Request;
  if ((Request != NULL) &&
      !HiiIsConfigHdrMatch (Request, &gWifiConfigFormSetGuid, mVendorStorageName))
  {
    return EFI_NOT_FOUND;
  }

  ConfigRequestHdr = NULL;
  ConfigRequest    = NULL;
  AllocatedRequest = FALSE;
  Size             = 0;

  Private = WIFI_MGR_PRIVATE_DATA_FROM_CONFIG_ACCESS (This);

  BufferSize = sizeof (WIFI_MANAGER_IFR_NVDATA);
  IfrNvData  = AllocateZeroPool (BufferSize);
  if (IfrNvData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  WifiMgrConvertConfigDataToIfrNvData (Private, IfrNvData);

  ConfigRequest = Request;
  if ((Request == NULL) || (StrStr (Request, L"OFFSET") == NULL)) {
    //
    // Request has no request element, construct full request string.
    // Allocate and fill a buffer large enough to hold the <ConfigHdr> template
    // followed by "&OFFSET=0&WIDTH=WWWWWWWWWWWWWWWW" followed by a Null-terminator.
    //
    ConfigRequestHdr = HiiConstructConfigHdr (
                         &gWifiConfigFormSetGuid,
                         mVendorStorageName,
                         Private->DriverHandle
                         );
    if (ConfigRequestHdr == NULL) {
      FreePool (IfrNvData);
      return EFI_OUT_OF_RESOURCES;
    }

    Size          = (StrLen (ConfigRequestHdr) + 32 + 1) * sizeof (CHAR16);
    ConfigRequest = AllocateZeroPool (Size);
    if (ConfigRequest == NULL) {
      FreePool (IfrNvData);
      FreePool (ConfigRequestHdr);
      return EFI_OUT_OF_RESOURCES;
    }

    AllocatedRequest = TRUE;
    UnicodeSPrint (
      ConfigRequest,
      Size,
      L"%s&OFFSET=0&WIDTH=%016LX",
      ConfigRequestHdr,
      (UINT64)BufferSize
      );
    FreePool (ConfigRequestHdr);
  }

  //
  // Convert buffer data to <ConfigResp> by helper function BlockToConfig()
  //
  Status = gHiiConfigRouting->BlockToConfig (
                                gHiiConfigRouting,
                                ConfigRequest,
                                (UINT8 *)IfrNvData,
                                BufferSize,
                                Results,
                                Progress
                                );

  FreePool (IfrNvData);
  //
  // Free the allocated config request string.
  //
  if (AllocatedRequest) {
    FreePool (ConfigRequest);
    ConfigRequest = NULL;
  }

  //
  // Set Progress string to the original request string.
  //
  if (Request == NULL) {
    *Progress = NULL;
  } else if (StrStr (Request, L"OFFSET") == NULL) {
    *Progress = Request + StrLen (Request);
  }

  return Status;
}

/**
  This function applies changes in a driver's configuration.
  Input is a Configuration, which has the routing data for this
  driver followed by name / value configuration pairs. The driver
  must apply those pairs to its configurable storage. If the
  driver's configuration is stored in a linear block of data
  and the driver's name / value pairs are in <BlockConfig>
  format, it may use the ConfigToBlock helper function (above) to
  simplify the job.

  @param This           Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.

  @param Configuration  A null-terminated Unicode string in
                        <ConfigString> format.

  @param Progress       A pointer to a string filled in with the
                        offset of the most recent '&' before the
                        first failing name / value pair (or the
                        beginn ing of the string if the failure
                        is in the first name / value pair) or
                        the terminating NULL if all was
                        successful.

  @retval EFI_SUCCESS             The results have been distributed or are
                                  awaiting distribution.

  @retval EFI_OUT_OF_RESOURCES    Not enough memory to store the
                                  parts of the results that must be
                                  stored awaiting possible future
                                  protocols.

  @retval EFI_INVALID_PARAMETERS  Passing in a NULL for the
                                  Results parameter would result
                                  in this type of error.

  @retval EFI_NOT_FOUND           Target for the specified routing data
                                  was not found

**/
EFI_STATUS
EFIAPI
WifiMgrDxeHiiConfigAccessRouteConfig (
  IN CONST  EFI_HII_CONFIG_ACCESS_PROTOCOL  *This,
  IN CONST  EFI_STRING                      Configuration,
  OUT       EFI_STRING                      *Progress
  )
{
  EFI_STATUS               Status;
  UINTN                    BufferSize;
  WIFI_MGR_PRIVATE_DATA    *Private;
  WIFI_MANAGER_IFR_NVDATA  *IfrNvData;

  if ((Configuration == NULL) || (Progress == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  IfrNvData  = NULL;
  *Progress  = Configuration;
  BufferSize = sizeof (WIFI_MANAGER_IFR_NVDATA);
  Private    = WIFI_MGR_PRIVATE_DATA_FROM_CONFIG_ACCESS (This);

  if (!HiiIsConfigHdrMatch (Configuration, &gWifiConfigFormSetGuid, mVendorStorageName)) {
    return EFI_NOT_FOUND;
  }

  IfrNvData = AllocateZeroPool (BufferSize);
  if (IfrNvData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  WifiMgrConvertConfigDataToIfrNvData (Private, IfrNvData);

  Status = gHiiConfigRouting->ConfigToBlock (
                                gHiiConfigRouting,
                                Configuration,
                                (UINT8 *)IfrNvData,
                                &BufferSize,
                                Progress
                                );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = WifiMgrConvertIfrNvDataToConfigData (Private, IfrNvData);
  ZeroMem (IfrNvData, sizeof (WIFI_MANAGER_IFR_NVDATA));
  FreePool (IfrNvData);

  return Status;
}

/**
  This function is called to provide results data to the driver.
  This data consists of a unique key that is used to identify
  which data is either being passed back or being asked for.

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Action                 Specifies the type of action taken by the browser.
  @param  QuestionId             A unique value which is sent to the original
                                 exporting driver so that it can identify the type
                                 of data to expect. The format of the data tends to
                                 vary based on the opcode that generated the callback.
  @param  Type                   The type of value for the question.
  @param  Value                  A pointer to the data being sent to the original
                                 exporting driver.
  @param  ActionRequest          On return, points to the action requested by the
                                 callback function.

  @retval EFI_SUCCESS            The callback successfully handled the action.
  @retval EFI_OUT_OF_RESOURCES   Not enough storage is available to hold the
                                 variable and its data.
  @retval EFI_UNSUPPORTED        The specified Action is not supported by the
                                 callback.

**/
EFI_STATUS
EFIAPI
WifiMgrDxeHiiConfigAccessCallback (
  IN     CONST EFI_HII_CONFIG_ACCESS_PROTOCOL  *This,
  IN     EFI_BROWSER_ACTION                    Action,
  IN     EFI_QUESTION_ID                       QuestionId,
  IN     UINT8                                 Type,
  IN OUT EFI_IFR_TYPE_VALUE                    *Value,
  OUT    EFI_BROWSER_ACTION_REQUEST            *ActionRequest
  )
{
  EFI_STATUS                Status;
  EFI_INPUT_KEY             Key;
  UINTN                     BufferSize;
  WIFI_MGR_PRIVATE_DATA     *Private;
  WIFI_MANAGER_IFR_NVDATA   *IfrNvData;
  EFI_DEVICE_PATH_PROTOCOL  *FilePath;
  WIFI_MGR_NETWORK_PROFILE  *Profile;
  WIFI_MGR_NETWORK_PROFILE  *ProfileToConnect;
  WIFI_HIDDEN_NETWORK_DATA  *HiddenNetwork;
  UINTN                     TempDataSize;
  VOID                      *TempData;
  LIST_ENTRY                *Entry;
  UINT32                    Index;
  UINT32                    RemoveCount;
  CHAR16                    *TempPassword;
  CHAR16                    *ErrorMessage;

  if ((Action != EFI_BROWSER_ACTION_FORM_OPEN) &&
      (Action != EFI_BROWSER_ACTION_FORM_CLOSE) &&
      (Action != EFI_BROWSER_ACTION_CHANGING) &&
      (Action != EFI_BROWSER_ACTION_CHANGED) &&
      (Action != EFI_BROWSER_ACTION_RETRIEVE))
  {
    return EFI_UNSUPPORTED;
  }

  if ((Value == NULL) || (ActionRequest == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  Status  = EFI_SUCCESS;
  Private = WIFI_MGR_PRIVATE_DATA_FROM_CONFIG_ACCESS (This);
  if (Private->CurrentNic == NULL) {
    return EFI_UNSUPPORTED;
  }

  //
  // Retrieve uncommitted data from Browser
  //
  BufferSize = sizeof (WIFI_MANAGER_IFR_NVDATA);
  IfrNvData  = AllocateZeroPool (BufferSize);
  if (IfrNvData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  if (Action != EFI_BROWSER_ACTION_FORM_OPEN) {
    HiiGetBrowserData (&gWifiConfigFormSetGuid, mVendorStorageName, BufferSize, (UINT8 *)IfrNvData);
  }

  if (Action == EFI_BROWSER_ACTION_FORM_OPEN) {
    switch (QuestionId) {
      case KEY_MAC_LIST:

        Status = WifiMgrShowNicList (Private);
        break;

      case KEY_REFRESH_NETWORK_LIST:

        if (Private->CurrentNic->UserSelectedProfile != NULL) {
          Profile = Private->CurrentNic->UserSelectedProfile;

          //
          // Erase secrets since user has left Connection Page
          // Connection Page may direct to Network List Page or Eap Configuration Page,
          // secrets only need to be erased when head to Network List Page
          //
          WifiMgrCleanProfileSecrets (Profile);

          Private->CurrentNic->UserSelectedProfile = NULL;
        }

        break;

      case KEY_ENROLLED_CERT_NAME:

        if (Private->CurrentNic->UserSelectedProfile == NULL) {
          break;
        }

        Profile = Private->CurrentNic->UserSelectedProfile;

        //
        // Enter the key enrollment page
        // For TTLS and PEAP, only CA cert needs to be cared
        //
        if (Private->FileType == FileTypeCACert) {
          if (Profile->CACertData != NULL) {
            HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_EAP_ENROLLED_CERT_NAME), Profile->CACertName, NULL);
          } else {
            HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_EAP_ENROLLED_CERT_NAME), L"", NULL);
          }
        } else if (Private->FileType == FileTypeClientCert) {
          if (Profile->ClientCertData != NULL) {
            HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_EAP_ENROLLED_CERT_NAME), Profile->ClientCertName, NULL);
          } else {
            HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_EAP_ENROLLED_CERT_NAME), L"", NULL);
          }
        }

        break;

      case KEY_ENROLLED_PRIVATE_KEY_NAME:

        if (Private->CurrentNic->UserSelectedProfile == NULL) {
          break;
        }

        Profile = Private->CurrentNic->UserSelectedProfile;

        if (Profile->PrivateKeyData != NULL) {
          HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_EAP_ENROLLED_PRIVATE_KEY_NAME), Profile->PrivateKeyName, NULL);
        } else {
          HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_EAP_ENROLLED_PRIVATE_KEY_NAME), L"", NULL);
        }

        break;

      default:
        break;
    }
  } else if (Action == EFI_BROWSER_ACTION_FORM_CLOSE) {
    switch (QuestionId) {
      case KEY_EAP_ENROLL_CERT_FROM_FILE:
      case KEY_EAP_ENROLL_PRIVATE_KEY_FROM_FILE:
      case KEY_REFRESH_NETWORK_LIST:

        if (Private->CurrentNic->UserSelectedProfile == NULL) {
          break;
        }

        Profile = Private->CurrentNic->UserSelectedProfile;

        //
        // Enter the network connection configuration page
        // Recovery from restored data
        //
        if (HiiSetString (Private->RegisteredHandle, STRING_TOKEN (STR_SSID), Profile->SSId, NULL) == 0) {
          return EFI_OUT_OF_RESOURCES;
        }

        IfrNvData->SecurityType = Profile->SecurityType;
        if (HiiSetString (
              Private->RegisteredHandle,
              STRING_TOKEN (STR_SECURITY_TYPE),
              mSecurityType[IfrNvData->SecurityType],
              NULL
              ) == 0)
        {
          return EFI_OUT_OF_RESOURCES;
        }

        if (  (IfrNvData->SecurityType == SECURITY_TYPE_WPA2_ENTERPRISE)
           || (IfrNvData->SecurityType == SECURITY_TYPE_WPA3_ENTERPRISE))
        {
          IfrNvData->EapAuthMethod       = Profile->EapAuthMethod;
          IfrNvData->EapSecondAuthMethod = Profile->EapSecondAuthMethod;
          StrCpyS (IfrNvData->EapIdentity, EAP_IDENTITY_SIZE, Profile->EapIdentity);
        }

        break;

      case KEY_CONNECT_ACTION:

        if (Private->CurrentNic->UserSelectedProfile == NULL) {
          break;
        }

        Profile = Private->CurrentNic->UserSelectedProfile;

        //
        // Restore User Config Data for Page recovery
        //
        if ((IfrNvData->SecurityType == SECURITY_TYPE_WPA2_ENTERPRISE) ||
            (IfrNvData->SecurityType == SECURITY_TYPE_WPA3_ENTERPRISE))
        {
          Profile->EapAuthMethod       = IfrNvData->EapAuthMethod;
          Profile->EapSecondAuthMethod = IfrNvData->EapSecondAuthMethod;
          StrCpyS (Profile->EapIdentity, EAP_IDENTITY_SIZE, IfrNvData->EapIdentity);
        }

        break;

      default:
        break;
    }
  } else if (Action == EFI_BROWSER_ACTION_CHANGING) {
    switch (QuestionId) {
      case KEY_NETWORK_LIST:

        //
        // User triggered a scan process.
        //
        Private->CurrentNic->OneTimeScanRequest = TRUE;
        Status                                  = WifiMgrStartScan (Private->CurrentNic);
        if (EFI_ERROR (Status)) {
          DEBUG ((DEBUG_WARN, "[WiFi Connection Manager] Error: Failed in start scan for network list with status %r", Status));
        }

        break;

      case KEY_PASSWORD_CONNECT_NETWORK:
      case KEY_EAP_PASSWORD_CONNECT_NETWORK:
      case KEY_PRIVATE_KEY_PASSWORD:

        if (Private->CurrentNic->UserSelectedProfile == NULL) {
          break;
        }

        Profile = Private->CurrentNic->UserSelectedProfile;

        if (QuestionId == KEY_PASSWORD_CONNECT_NETWORK) {
          TempPassword = Profile->Password;
        } else if (QuestionId == KEY_EAP_PASSWORD_CONNECT_NETWORK) {
          TempPassword = Profile->EapPassword;
        } else {
          TempPassword = Profile->PrivateKeyPassword;
        }

        Status = WifiMgrRecordPassword (Private, Value->string, TempPassword, PASSWORD_STORAGE_SIZE);
        if (EFI_ERROR (Status)) {
          DEBUG ((DEBUG_ERROR, "[WiFi Connection Manager] Error: Failed to input password!"));
          break;
        }

        //
        // This password is not a new created password, so no need to confirm.
        //
        Status = EFI_NOT_FOUND;
        break;

      case KEY_CONNECT_ACTION:

        ErrorMessage     = NULL;
        ProfileToConnect = NULL;

        if (Private->CurrentNic->UserSelectedProfile == NULL) {
          break;
        }

        Profile = Private->CurrentNic->UserSelectedProfile;

        if ((Private->CurrentNic->ConnectState == WifiMgrDisconnected) ||
            (Profile != Private->CurrentNic->CurrentOperateNetwork))
        {
          //
          // When this network is not currently connected, pend it to connect.
          //
          if (Profile->AKMSuiteSupported && Profile->CipherSuiteSupported) {
            if ((Profile->SecurityType == SECURITY_TYPE_NONE) ||
                (Profile->SecurityType == SECURITY_TYPE_WPA2_PERSONAL) ||
                (Profile->SecurityType == SECURITY_TYPE_WPA3_PERSONAL))
            {
              //
              // For Open network, connect directly.
              //
              ProfileToConnect = Profile;
            } else if ((Profile->SecurityType == SECURITY_TYPE_WPA2_ENTERPRISE) ||
                       (Profile->SecurityType == SECURITY_TYPE_WPA3_ENTERPRISE))
            {
              //
              // For WPA/WPA2-Enterprise network, conduct eap configuration first.
              // Only EAP-TLS, TTLS and PEAP is supported now!
              //
              Profile->EapAuthMethod = IfrNvData->EapAuthMethod;
              StrCpyS (Profile->EapIdentity, EAP_IDENTITY_SIZE, IfrNvData->EapIdentity);

              if ((IfrNvData->EapAuthMethod == EAP_AUTH_METHOD_TTLS) || (IfrNvData->EapAuthMethod == EAP_AUTH_METHOD_PEAP)) {
                Profile->EapSecondAuthMethod = IfrNvData->EapSecondAuthMethod;
                ProfileToConnect             = Profile;
              } else if (IfrNvData->EapAuthMethod == EAP_AUTH_METHOD_TLS) {
                ProfileToConnect = Profile;
              } else {
                ErrorMessage = L"ERROR: Only EAP-TLS, TTLS or PEAP is supported now!";
              }
            } else {
              ErrorMessage = L"ERROR: Can't connect to this network!";
            }
          } else {
            ErrorMessage = L"ERROR: This network is not supported!";
          }

          if (ErrorMessage != NULL) {
            CreatePopUp (
              EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
              &Key,
              ErrorMessage,
              NULL
              );
          }

          if (ProfileToConnect != NULL) {
            Private->CurrentNic->OneTimeConnectRequest = TRUE;
            Private->CurrentNic->ConnectPendingNetwork = ProfileToConnect;
          }
        } else if (Private->CurrentNic->ConnectState == WifiMgrConnectedToAp) {
          //
          // This network is currently connected, just disconnect from it.
          //
          Private->CurrentNic->OneTimeDisconnectRequest    = TRUE;
          Private->CurrentNic->HasDisconnectPendingNetwork = TRUE;
        }

        Status = gBS->SetTimer (Private->CurrentNic->TickTimer, TimerPeriodic, EFI_TIMER_PERIOD_MILLISECONDS (500));
        if (EFI_ERROR (Status)) {
          DEBUG ((DEBUG_WARN, "[WiFi Connection Manager] Error: Failed to set timer for connect action!"));
          gBS->SetTimer (Private->CurrentNic->TickTimer, TimerCancel, 0);
        }

        break;

      case KEY_ENROLL_CA_CERT_CONNECT_NETWORK:

        Private->FileType = FileTypeCACert;
        break;

      case KEY_ENROLL_CLIENT_CERT_CONNECT_NETWORK:

        Private->FileType = FileTypeClientCert;
        break;

      case KEY_EAP_ENROLL_PRIVATE_KEY_FROM_FILE:

        FilePath = NULL;
        ChooseFile (NULL, NULL, NULL, &FilePath);

        if (FilePath != NULL) {
          UpdatePrivateKeyFromFile (Private, FilePath);
          FreePool (FilePath);
        }

        break;

      case KEY_EAP_ENROLL_CERT_FROM_FILE:

        //
        // User will select a cert file from File Explore
        //
        FilePath = NULL;
        ChooseFile (NULL, NULL, NULL, &FilePath);

        if (FilePath != NULL) {
          UpdateCAFromFile (Private, FilePath);
          FreePool (FilePath);
        }

        break;

      case KEY_SAVE_PRIVATE_KEY_TO_MEM:

        if ((Private->FileContext != NULL) && (Private->FileContext->FHandle != NULL) &&
            (Private->CurrentNic->UserSelectedProfile != NULL))
        {
          //
          // Read Private Key file to Buffer
          //
          Profile = Private->CurrentNic->UserSelectedProfile;
          if (Profile->PrivateKeyData != NULL) {
            ZeroMem (Profile->PrivateKeyData, Profile->PrivateKeyDataSize);
            FreePool (Profile->PrivateKeyData);
            Profile->PrivateKeyData = NULL;
          }

          Status = WifiMgrReadFileToBuffer (
                     Private->FileContext,
                     &TempData,
                     &TempDataSize
                     );
          if (EFI_ERROR (Status)) {
            CreatePopUp (
              EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
              &Key,
              L"ERROR: Can't read this private key file!",
              NULL
              );
          } else {
            ASSERT (Private->FileContext->FileName != NULL);

            Profile->PrivateKeyData     = TempData;
            Profile->PrivateKeyDataSize = TempDataSize;
            StrCpyS (Profile->PrivateKeyName, WIFI_FILENAME_STR_MAX_SIZE, Private->FileContext->FileName);

            DEBUG ((
              DEBUG_INFO,
              "[WiFi Connection Manager] Private Key: %s has been enrolled! Size: %d\n",
              Profile->PrivateKeyName,
              Profile->PrivateKeyDataSize
              ));
          }
        }

        break;

      case KEY_SAVE_CERT_TO_MEM:

        if ((Private->FileContext != NULL) && (Private->FileContext->FHandle != NULL) &&
            (Private->CurrentNic->UserSelectedProfile != NULL))
        {
          //
          // Read Cert file to Buffer
          //
          Profile = Private->CurrentNic->UserSelectedProfile;

          if (Private->FileType == FileTypeCACert) {
            if (Profile->CACertData != NULL) {
              ZeroMem (Profile->CACertData, Profile->CACertSize);
              FreePool (Profile->CACertData);
              Profile->CACertData = NULL;
            }
          } else if (Private->FileType == FileTypeClientCert) {
            if (Profile->ClientCertData != NULL) {
              ZeroMem (Profile->ClientCertData, Profile->ClientCertSize);
              FreePool (Profile->ClientCertData);
              Profile->ClientCertData = NULL;
            }
          } else {
            break;
          }

          Status = WifiMgrReadFileToBuffer (
                     Private->FileContext,
                     &TempData,
                     &TempDataSize
                     );
          if (EFI_ERROR (Status)) {
            CreatePopUp (
              EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
              &Key,
              L"ERROR: Can't read this certificate file!",
              NULL
              );
          } else {
            ASSERT (Private->FileContext->FileName != NULL);
            if (Private->FileType == FileTypeCACert) {
              Profile->CACertData = TempData;
              Profile->CACertSize = TempDataSize;
              StrCpyS (Profile->CACertName, WIFI_FILENAME_STR_MAX_SIZE, Private->FileContext->FileName);
              DEBUG ((
                DEBUG_INFO,
                "[WiFi Connection Manager] CA Cert: %s has been enrolled! Size: %d\n",
                Profile->CACertName,
                Profile->CACertSize
                ));
            } else {
              Profile->ClientCertData = TempData;
              Profile->ClientCertSize = TempDataSize;
              StrCpyS (Profile->ClientCertName, WIFI_FILENAME_STR_MAX_SIZE, Private->FileContext->FileName);
              DEBUG ((
                DEBUG_INFO,
                "[WiFi Connection Manager] Client Cert: %s has been enrolled! Size: %d\n",
                Profile->ClientCertName,
                Profile->ClientCertSize
                ));
            }
          }
        }

        break;

      case KEY_ADD_HIDDEN_NETWORK:

        //
        // Add a Hidden Network
        //
        if ((StrLen (IfrNvData->SSId) < SSID_MIN_LEN) ||
            (Private->HiddenNetworkCount >= HIDDEN_NETWORK_LIST_COUNT_MAX))
        {
          Status = EFI_ABORTED;
          break;
        } else {
          //
          // Check if this SSId is already in Hidden Network List
          //
          NET_LIST_FOR_EACH (Entry, &Private->HiddenNetworkList) {
            HiddenNetwork = NET_LIST_USER_STRUCT_S (
                              Entry,
                              WIFI_HIDDEN_NETWORK_DATA,
                              Link,
                              WIFI_MGR_HIDDEN_NETWORK_SIGNATURE
                              );
            if (StrCmp (HiddenNetwork->SSId, IfrNvData->SSId) == 0) {
              Status = EFI_ABORTED;
              break;
            }
          }
        }

        HiddenNetwork = (WIFI_HIDDEN_NETWORK_DATA *)AllocateZeroPool (sizeof (WIFI_HIDDEN_NETWORK_DATA));
        if (HiddenNetwork == NULL) {
          Status = EFI_OUT_OF_RESOURCES;
          break;
        }

        HiddenNetwork->Signature = WIFI_MGR_HIDDEN_NETWORK_SIGNATURE;
        StrCpyS (HiddenNetwork->SSId, SSID_STORAGE_SIZE, IfrNvData->SSId);

        InsertTailList (&Private->HiddenNetworkList, &HiddenNetwork->Link);
        Private->HiddenNetworkCount++;

        WifiMgrRefreshHiddenList (Private);
        break;

      case KEY_REMOVE_HIDDEN_NETWORK:

        //
        // Remove Hidden Networks
        //
        Entry       = GetFirstNode (&Private->HiddenNetworkList);
        RemoveCount = 0;
        for (Index = 0; Index < Private->HiddenNetworkCount; Index++) {
          if (IfrNvData->HiddenNetworkList[Index] != 0) {
            HiddenNetwork = NET_LIST_USER_STRUCT_S (Entry, WIFI_HIDDEN_NETWORK_DATA, Link, WIFI_MGR_HIDDEN_NETWORK_SIGNATURE);
            Entry         = RemoveEntryList (Entry);
            RemoveCount++;

            FreePool (HiddenNetwork);
          } else {
            Entry = GetNextNode (&Private->HiddenNetworkList, Entry);
          }
        }

        Private->HiddenNetworkCount -= RemoveCount;
        WifiMgrRefreshHiddenList (Private);
        break;

      default:

        if ((QuestionId >= KEY_MAC_ENTRY_BASE) && (QuestionId < KEY_MAC_ENTRY_BASE + Private->NicCount)) {
          //
          // User selects a wireless NIC.
          //
          Status = WifiMgrSelectNic (Private, QuestionId);
          if (EFI_ERROR (Status)) {
            CreatePopUp (
              EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
              &Key,
              L"ERROR: Fail to operate the wireless NIC!",
              NULL
              );
          }
        } else if (Private->CurrentNic != NULL) {
          if ((QuestionId >= KEY_AVAILABLE_NETWORK_ENTRY_BASE) &&
              (QuestionId <= KEY_AVAILABLE_NETWORK_ENTRY_BASE + Private->CurrentNic->MaxProfileIndex))
          {
            Status = WifiMgrUserSelectProfileToConnect (Private, QuestionId - KEY_AVAILABLE_NETWORK_ENTRY_BASE);
            if (!EFI_ERROR (Status)) {
              WifiMgrUpdateConnectMessage (Private->CurrentNic, FALSE, NULL);
            }
          }

          if (EFI_ERROR (Status)) {
            CreatePopUp (
              EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
              &Key,
              L"ERROR: Fail to operate this profile!",
              NULL
              );
          }
        }

        Status = gBS->SetTimer (Private->CurrentNic->TickTimer, TimerPeriodic, EFI_TIMER_PERIOD_MILLISECONDS (500));
        if (EFI_ERROR (Status)) {
          DEBUG ((DEBUG_WARN, "[WiFi Connection Manager] Error: Failed to set timer for connect action!"));
          gBS->SetTimer (Private->CurrentNic->TickTimer, TimerCancel, 0);
        }

        break;
    }
  } else if (Action == EFI_BROWSER_ACTION_CHANGED) {
    switch (QuestionId) {
      case KEY_SAVE_CERT_TO_MEM:
      case KEY_SAVE_PRIVATE_KEY_TO_MEM:

        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_SUBMIT_EXIT;
        break;

      case KEY_NO_SAVE_CERT_TO_MEM:
      case KEY_NO_SAVE_PRIVATE_KEY_TO_MEM:

        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_DISCARD_EXIT;
        break;

      default:

        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        break;
    }
  } else if (Action == EFI_BROWSER_ACTION_RETRIEVE) {
    switch (QuestionId) {
      case KEY_REFRESH_NETWORK_LIST:

        WifiMgrRefreshNetworkList (Private, IfrNvData);
        break;

      default:
        break;
    }
  }

  if (!EFI_ERROR (Status) && (Action != EFI_BROWSER_ACTION_FORM_OPEN)) {
    //
    // Pass changed uncommitted data back to Form Browser.
    //
    BufferSize = sizeof (WIFI_MANAGER_IFR_NVDATA);
    HiiSetBrowserData (&gWifiConfigFormSetGuid, mVendorStorageName, BufferSize, (UINT8 *)IfrNvData, NULL);
  }

  ZeroMem (IfrNvData, sizeof (WIFI_MANAGER_IFR_NVDATA));
  FreePool (IfrNvData);
  return Status;
}

/**
  Initialize the WiFi configuration form.

  @param[in]  Private             The pointer to the global private data structure.

  @retval EFI_SUCCESS             The configuration form is initialized.
  @retval EFI_OUT_OF_RESOURCES    Failed to allocate memory.
  @retval EFI_INVALID_PARAMETER   Any input parameter is invalid.
  @retval Other Erros             Returned Errors when installing protocols.

**/
EFI_STATUS
WifiMgrDxeConfigFormInit (
  WIFI_MGR_PRIVATE_DATA  *Private
  )
{
  EFI_STATUS  Status;

  if (Private == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Private->ConfigAccess.ExtractConfig = WifiMgrDxeHiiConfigAccessExtractConfig;
  Private->ConfigAccess.RouteConfig   = WifiMgrDxeHiiConfigAccessRouteConfig;
  Private->ConfigAccess.Callback      = WifiMgrDxeHiiConfigAccessCallback;

  //
  // Install Device Path Protocol and Config Access protocol to driver handle.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Private->DriverHandle,
                  &gEfiDevicePathProtocolGuid,
                  &mWifiMgrDxeHiiVendorDevicePath,
                  &gEfiHiiConfigAccessProtocolGuid,
                  &Private->ConfigAccess,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Publish our HII data.
  //
  Private->RegisteredHandle = HiiAddPackages (
                                &gWifiConfigFormSetGuid,
                                Private->DriverHandle,
                                WifiConnectionManagerDxeStrings,
                                WifiConnectionManagerDxeBin,
                                NULL
                                );
  if (Private->RegisteredHandle == NULL) {
    gBS->UninstallMultipleProtocolInterfaces (
           Private->DriverHandle,
           &gEfiDevicePathProtocolGuid,
           &mWifiMgrDxeHiiVendorDevicePath,
           &gEfiHiiConfigAccessProtocolGuid,
           &Private->ConfigAccess,
           NULL
           );
    return EFI_OUT_OF_RESOURCES;
  }

  Private->FileContext = AllocateZeroPool (sizeof (WIFI_MGR_FILE_CONTEXT));
  if (Private->FileContext == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  Unload the WiFi configuration form.

  @param[in]  Private             The pointer to the global private data structure.

  @retval EFI_SUCCESS             The configuration form is unloaded successfully.
  @retval EFI_INVALID_PARAMETER   Any input parameter is invalid.
  @retval Other Errors            Returned Erros when uninstalling protocols.

**/
EFI_STATUS
WifiMgrDxeConfigFormUnload (
  WIFI_MGR_PRIVATE_DATA  *Private
  )
{
  EFI_STATUS  Status;

  if (Private == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (Private->FileContext != NULL) {
    if (Private->FileContext->FHandle != NULL) {
      Private->FileContext->FHandle->Close (Private->FileContext->FHandle);
    }

    if (Private->FileContext->FileName != NULL) {
      FreePool (Private->FileContext->FileName);
    }

    FreePool (Private->FileContext);
  }

  HiiRemovePackages (Private->RegisteredHandle);

  Status = gBS->UninstallMultipleProtocolInterfaces (
                  Private->DriverHandle,
                  &gEfiDevicePathProtocolGuid,
                  &mWifiMgrDxeHiiVendorDevicePath,
                  &gEfiHiiConfigAccessProtocolGuid,
                  &Private->ConfigAccess,
                  NULL
                  );

  return Status;
}
