/** @file
  The Mac Connection2 Protocol adapter 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"

EFI_EAP_TYPE  mEapAuthMethod[] = {
  EFI_EAP_TYPE_TTLS,
  EFI_EAP_TYPE_PEAP,
  EFI_EAP_TYPE_EAPTLS
};

EFI_EAP_TYPE  mEapSecondAuthMethod[] = {
  EFI_EAP_TYPE_MSCHAPV2
};

UINT8  mWifiConnectionCount = 0;

/**
  The callback function for scan operation. This function updates networks
  according to the latest scan result, and trigger UI refresh.

  ASSERT when errors occur in config token.

  @param[in]  Event                 The GetNetworks token receive event.
  @param[in]  Context               The context of the GetNetworks token.

**/
VOID
EFIAPI
WifiMgrOnScanFinished (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  EFI_STATUS                     Status;
  WIFI_MGR_MAC_CONFIG_TOKEN      *ConfigToken;
  WIFI_MGR_DEVICE_DATA           *Nic;
  WIFI_MGR_NETWORK_PROFILE       *Profile;
  EFI_80211_NETWORK              *Network;
  UINTN                          DataSize;
  EFI_80211_NETWORK_DESCRIPTION  *NetworkDescription;
  EFI_80211_GET_NETWORKS_RESULT  *Result;
  LIST_ENTRY                     *Entry;
  UINT8                          SecurityType;
  BOOLEAN                        AKMSuiteSupported;
  BOOLEAN                        CipherSuiteSupported;
  CHAR8                          *AsciiSSId;
  UINTN                          Index;

  ASSERT (Context != NULL);

  ConfigToken = (WIFI_MGR_MAC_CONFIG_TOKEN *)Context;
  ASSERT (ConfigToken->Nic != NULL);
  ASSERT (ConfigToken->Type == TokenTypeGetNetworksToken);

  //
  // It is the GetNetworks token, set scan state to "ScanFinished"
  //
  ConfigToken->Nic->ScanState = WifiMgrScanFinished;

  ASSERT (ConfigToken->Token.GetNetworksToken != NULL);
  Result = ConfigToken->Token.GetNetworksToken->Result;
  Nic    = ConfigToken->Nic;

  //
  // Clean previous result, and update network list according to the scan result
  //
  Nic->AvailableCount = 0;

  NET_LIST_FOR_EACH (Entry, &Nic->ProfileList) {
    Profile = NET_LIST_USER_STRUCT_S (
                Entry,
                WIFI_MGR_NETWORK_PROFILE,
                Link,
                WIFI_MGR_PROFILE_SIGNATURE
                );
    Profile->IsAvailable = FALSE;
  }

  if (Result == NULL) {
    gBS->SignalEvent (Nic->Private->NetworkListRefreshEvent);
    WifiMgrFreeToken (ConfigToken);
    return;
  }

  for (Index = 0; Index < Result->NumOfNetworkDesc; Index++) {
    NetworkDescription = Result->NetworkDesc + Index;
    if (NetworkDescription == NULL) {
      continue;
    }

    Network = &NetworkDescription->Network;
    if ((Network == NULL) || (Network->SSId.SSIdLen == 0)) {
      continue;
    }

    Status = WifiMgrCheckRSN (
               Network->AKMSuite,
               Network->CipherSuite,
               Nic,
               &SecurityType,
               &AKMSuiteSupported,
               &CipherSuiteSupported
               );
    if (EFI_ERROR (Status)) {
      SecurityType         = SECURITY_TYPE_UNKNOWN;
      AKMSuiteSupported    = FALSE;
      CipherSuiteSupported = FALSE;
    }

    AsciiSSId = (CHAR8 *)AllocateZeroPool (sizeof (CHAR8) * (Network->SSId.SSIdLen + 1));
    if (AsciiSSId == NULL) {
      continue;
    }

    CopyMem (AsciiSSId, (CHAR8 *)Network->SSId.SSId, sizeof (CHAR8) * Network->SSId.SSIdLen);
    *(AsciiSSId + Network->SSId.SSIdLen) = '\0';

    Profile = WifiMgrGetProfileByAsciiSSId (AsciiSSId, SecurityType, &Nic->ProfileList);
    if (Profile == NULL) {
      if (Nic->MaxProfileIndex >= NETWORK_LIST_COUNT_MAX) {
        FreePool (AsciiSSId);
        continue;
      }

      //
      // Create a new profile
      //
      Profile = AllocateZeroPool (sizeof (WIFI_MGR_NETWORK_PROFILE));
      if (Profile == NULL) {
        FreePool (AsciiSSId);
        continue;
      }

      Profile->Signature    = WIFI_MGR_PROFILE_SIGNATURE;
      Profile->NicIndex     = Nic->NicIndex;
      Profile->ProfileIndex = Nic->MaxProfileIndex + 1;
      AsciiStrToUnicodeStrS (AsciiSSId, Profile->SSId, SSID_STORAGE_SIZE);
      InsertTailList (&Nic->ProfileList, &Profile->Link);
      Nic->MaxProfileIndex++;
    }

    FreePool (AsciiSSId);

    //
    // May receive duplicate networks in scan results, check if it has already
    // been processed.
    //
    if (!Profile->IsAvailable) {
      Profile->IsAvailable          = TRUE;
      Profile->SecurityType         = SecurityType;
      Profile->AKMSuiteSupported    = AKMSuiteSupported;
      Profile->CipherSuiteSupported = CipherSuiteSupported;
      Profile->NetworkQuality       = NetworkDescription->NetworkQuality;
      Nic->AvailableCount++;

      //
      // Copy BSSType and SSId
      //
      CopyMem (&Profile->Network, Network, sizeof (EFI_80211_NETWORK));

      //
      // Copy AKMSuite list
      //
      if (Network->AKMSuite != NULL) {
        if (Network->AKMSuite->AKMSuiteCount == 0) {
          DataSize = sizeof (EFI_80211_AKM_SUITE_SELECTOR);
        } else {
          DataSize = sizeof (EFI_80211_AKM_SUITE_SELECTOR) + sizeof (EFI_80211_SUITE_SELECTOR)
                     * (Network->AKMSuite->AKMSuiteCount - 1);
        }

        Profile->Network.AKMSuite = (EFI_80211_AKM_SUITE_SELECTOR *)AllocateZeroPool (DataSize);
        if (Profile->Network.AKMSuite == NULL) {
          continue;
        }

        CopyMem (Profile->Network.AKMSuite, Network->AKMSuite, DataSize);
      }

      //
      // Copy CipherSuite list
      //
      if (Network->CipherSuite != NULL) {
        if (Network->CipherSuite->CipherSuiteCount == 0) {
          DataSize = sizeof (EFI_80211_CIPHER_SUITE_SELECTOR);
        } else {
          DataSize = sizeof (EFI_80211_CIPHER_SUITE_SELECTOR) + sizeof (EFI_80211_SUITE_SELECTOR)
                     * (Network->CipherSuite->CipherSuiteCount - 1);
        }

        Profile->Network.CipherSuite = (EFI_80211_CIPHER_SUITE_SELECTOR *)AllocateZeroPool (DataSize);
        if (Profile->Network.CipherSuite == NULL) {
          continue;
        }

        CopyMem (Profile->Network.CipherSuite, Network->CipherSuite, DataSize);
      }
    } else {
      //
      // A duplicate network, update signal quality
      //
      if (Profile->NetworkQuality < NetworkDescription->NetworkQuality) {
        Profile->NetworkQuality = NetworkDescription->NetworkQuality;
      }

      continue;
    }
  }

  gBS->SignalEvent (Nic->Private->NetworkListRefreshEvent);

  //
  // The current connected network should always be available until disconnection
  // happens in Wifi FW layer, even when it is not in this time's scan result.
  //
  if ((Nic->ConnectState == WifiMgrConnectedToAp) && (Nic->CurrentOperateNetwork != NULL)) {
    if (!Nic->CurrentOperateNetwork->IsAvailable) {
      Nic->CurrentOperateNetwork->IsAvailable = TRUE;
      Nic->AvailableCount++;
    }
  }

  WifiMgrFreeToken (ConfigToken);
}

/**
  Start scan operation, and send out a token to collect available networks.

  @param[in]  Nic                 Pointer to the device data of the selected NIC.

  @retval EFI_SUCCESS             The operation is completed.
  @retval EFI_ALREADY_STARTED     A former scan operation is already ongoing.
  @retval EFI_INVALID_PARAMETER   One or more parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES    Failed to allocate memory.
  @retval Other Errors            Return errors when getting networks from low layer.

**/
EFI_STATUS
WifiMgrStartScan (
  IN      WIFI_MGR_DEVICE_DATA  *Nic
  )
{
  EFI_STATUS                    Status;
  EFI_TPL                       OldTpl;
  WIFI_MGR_MAC_CONFIG_TOKEN     *ConfigToken;
  EFI_80211_GET_NETWORKS_TOKEN  *GetNetworksToken;
  UINT32                        HiddenSSIdIndex;
  UINT32                        HiddenSSIdCount;
  EFI_80211_SSID                *HiddenSSIdList;
  WIFI_HIDDEN_NETWORK_DATA      *HiddenNetwork;
  LIST_ENTRY                    *Entry;

  if ((Nic == NULL) || (Nic->Wmp == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (Nic->ScanState == WifiMgrScanning) {
    return EFI_ALREADY_STARTED;
  }

  Nic->ScanState  = WifiMgrScanning;
  OldTpl          = gBS->RaiseTPL (TPL_CALLBACK);
  Status          = EFI_SUCCESS;
  HiddenSSIdList  = NULL;
  HiddenSSIdCount = Nic->Private->HiddenNetworkCount;
  HiddenSSIdIndex = 0;

  //
  // create a new get network token
  //
  ConfigToken = AllocateZeroPool (sizeof (WIFI_MGR_MAC_CONFIG_TOKEN));
  if (ConfigToken == NULL) {
    gBS->RestoreTPL (OldTpl);
    return EFI_OUT_OF_RESOURCES;
  }

  ConfigToken->Type                   = TokenTypeGetNetworksToken;
  ConfigToken->Nic                    = Nic;
  ConfigToken->Token.GetNetworksToken = AllocateZeroPool (sizeof (EFI_80211_GET_NETWORKS_TOKEN));
  if (ConfigToken->Token.GetNetworksToken == NULL) {
    WifiMgrFreeToken (ConfigToken);
    gBS->RestoreTPL (OldTpl);
    return EFI_OUT_OF_RESOURCES;
  }

  GetNetworksToken = ConfigToken->Token.GetNetworksToken;

  //
  // There are some hidden networks to scan, add them into scan list
  //
  if (HiddenSSIdCount > 0) {
    HiddenSSIdList = AllocateZeroPool (HiddenSSIdCount * sizeof (EFI_80211_SSID));
    if (HiddenSSIdList == NULL) {
      WifiMgrFreeToken (ConfigToken);
      gBS->RestoreTPL (OldTpl);
      return EFI_OUT_OF_RESOURCES;
    }

    HiddenSSIdIndex = 0;
    NET_LIST_FOR_EACH (Entry, &Nic->Private->HiddenNetworkList) {
      HiddenNetwork = NET_LIST_USER_STRUCT_S (
                        Entry,
                        WIFI_HIDDEN_NETWORK_DATA,
                        Link,
                        WIFI_MGR_HIDDEN_NETWORK_SIGNATURE
                        );
      HiddenSSIdList[HiddenSSIdIndex].SSIdLen = (UINT8)StrLen (HiddenNetwork->SSId);
      UnicodeStrToAsciiStrS (
        HiddenNetwork->SSId,
        (CHAR8 *)HiddenSSIdList[HiddenSSIdIndex].SSId,
        SSID_STORAGE_SIZE
        );
      HiddenSSIdIndex++;
    }
    GetNetworksToken->Data = AllocateZeroPool (
                               sizeof (EFI_80211_GET_NETWORKS_DATA) +
                               (HiddenSSIdCount - 1) * sizeof (EFI_80211_SSID)
                               );
    if (GetNetworksToken->Data == NULL) {
      FreePool (HiddenSSIdList);
      WifiMgrFreeToken (ConfigToken);
      gBS->RestoreTPL (OldTpl);
      return EFI_OUT_OF_RESOURCES;
    }

    GetNetworksToken->Data->NumOfSSID = HiddenSSIdCount;
    CopyMem (GetNetworksToken->Data->SSIDList, HiddenSSIdList, HiddenSSIdCount * sizeof (EFI_80211_SSID));
    FreePool (HiddenSSIdList);
  } else {
    GetNetworksToken->Data = AllocateZeroPool (sizeof (EFI_80211_GET_NETWORKS_DATA));
    if (GetNetworksToken->Data == NULL) {
      WifiMgrFreeToken (ConfigToken);
      gBS->RestoreTPL (OldTpl);
      return EFI_OUT_OF_RESOURCES;
    }

    GetNetworksToken->Data->NumOfSSID = 0;
  }

  //
  // Create a handle when scan process ends
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  WifiMgrOnScanFinished,
                  ConfigToken,
                  &GetNetworksToken->Event
                  );
  if (EFI_ERROR (Status)) {
    WifiMgrFreeToken (ConfigToken);
    gBS->RestoreTPL (OldTpl);
    return Status;
  }

  //
  // Start scan ...
  //
  Status = Nic->Wmp->GetNetworks (Nic->Wmp, GetNetworksToken);
  if (EFI_ERROR (Status)) {
    Nic->ScanState = WifiMgrScanFinished;
    WifiMgrFreeToken (ConfigToken);
    gBS->RestoreTPL (OldTpl);
    return Status;
  }

  gBS->RestoreTPL (OldTpl);
  return EFI_SUCCESS;
}

/**
  Configure password to supplicant before connecting to a secured network.

  @param[in]  Nic                 Pointer to the device data of the selected NIC.
  @param[in]  Profile             The target network to be connected.

  @retval EFI_SUCCESS             The operation is completed.
  @retval EFI_INVALID_PARAMETER   One or more parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES    Failed to allocate memory.
  @retval EFI_NOT_FOUND           No valid password is found to configure.
  @retval Other Errors            Returned errors when setting data to supplicant.

**/
EFI_STATUS
WifiMgrConfigPassword (
  IN    WIFI_MGR_DEVICE_DATA      *Nic,
  IN    WIFI_MGR_NETWORK_PROFILE  *Profile
  )
{
  EFI_STATUS               Status;
  EFI_SUPPLICANT_PROTOCOL  *Supplicant;
  EFI_80211_SSID           SSId;
  UINT8                    *AsciiPassword;

  if ((Nic == NULL) || (Nic->Supplicant == NULL) || (Profile == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  Supplicant = Nic->Supplicant;
  //
  // Set SSId to supplicant
  //
  SSId.SSIdLen = Profile->Network.SSId.SSIdLen;
  CopyMem (SSId.SSId, Profile->Network.SSId.SSId, sizeof (Profile->Network.SSId.SSId));
  Status = Supplicant->SetData (
                         Supplicant,
                         EfiSupplicant80211TargetSSIDName,
                         (VOID *)&SSId,
                         sizeof (EFI_80211_SSID)
                         );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Set password to supplicant
  //
  if (StrLen (Profile->Password) < PASSWORD_MIN_LEN) {
    return EFI_NOT_FOUND;
  }

  if (StrLen (Profile->Password) >= PASSWORD_STORAGE_SIZE) {
    ASSERT (EFI_INVALID_PARAMETER);
    return EFI_INVALID_PARAMETER;
  }

  AsciiPassword = AllocateZeroPool ((StrLen (Profile->Password) + 1) * sizeof (CHAR8));
  if (AsciiPassword == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = UnicodeStrToAsciiStrS (Profile->Password, (CHAR8 *)AsciiPassword, (StrLen (Profile->Password) + 1));
  if (!EFI_ERROR (Status)) {
    Status = Supplicant->SetData (
                           Supplicant,
                           EfiSupplicant80211PskPassword,
                           AsciiPassword,
                           (StrLen (Profile->Password) + 1) * sizeof (CHAR8)
                           );
  }

  ZeroMem (AsciiPassword, AsciiStrLen ((CHAR8 *)AsciiPassword) + 1);
  FreePool (AsciiPassword);

  return Status;
}

/**
  Conduct EAP configuration to supplicant before connecting to a EAP network.
  Current WiFi Connection Manager only supports three kinds of EAP networks:
  1). EAP-TLS (Two-Way Authentication is required in our implementation)
  2). EAP-TTLS/MSCHAPv2 (One-Way Authentication is required in our implementation)
  3). PEAPv0/MSCHAPv2 (One-Way Authentication is required in our implementation)

  @param[in]  Nic                 Pointer to the device data of the selected NIC.
  @param[in]  Profile             The target network to be connected.

  @retval EFI_SUCCESS             The operation is completed.
  @retval EFI_INVALID_PARAMETER   One or more parameters are invalid.
  @retval EFI_UNSUPPORTED         The expected EAP method is not supported.
  @retval EFI_OUT_OF_RESOURCES    Failed to allocate memory.
  @retval Other Errors            Returned errors when setting data to supplicant.

**/
EFI_STATUS
WifiMgrConfigEap (
  IN    WIFI_MGR_DEVICE_DATA      *Nic,
  IN    WIFI_MGR_NETWORK_PROFILE  *Profile
  )
{
  EFI_STATUS                        Status;
  EDKII_WIFI_PROFILE_SYNC_PROTOCOL  *WiFiProfileSyncProtocol;
  EFI_EAP_CONFIGURATION_PROTOCOL    *EapConfig;
  EFI_EAP_TYPE                      EapAuthMethod;
  EFI_EAP_TYPE                      EapSecondAuthMethod;
  EFI_EAP_TYPE                      *AuthMethodList;
  CHAR8                             *Identity;
  UINTN                             IdentitySize;
  CHAR16                            *Password;
  UINTN                             PasswordSize;
  UINTN                             EncryptPasswordLen;
  CHAR8                             *AsciiEncryptPassword;
  UINTN                             AuthMethodListSize;
  UINTN                             Index;

  if ((Nic == NULL) || (Nic->EapConfig == NULL) || (Profile == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  EapConfig = Nic->EapConfig;

  if (Profile->EapAuthMethod >= EAP_AUTH_METHOD_MAX) {
    return EFI_INVALID_PARAMETER;
  }

  EapAuthMethod = mEapAuthMethod[Profile->EapAuthMethod];

  if (EapAuthMethod != EFI_EAP_TYPE_EAPTLS) {
    if (Profile->EapSecondAuthMethod >= EAP_SEAUTH_METHOD_MAX) {
      return EFI_INVALID_PARAMETER;
    }

    EapSecondAuthMethod = mEapSecondAuthMethod[Profile->EapSecondAuthMethod];
  }

  //
  // The first time to get Supported Auth Method list, return the size.
  //
  AuthMethodListSize = 0;
  AuthMethodList     = NULL;
  Status             = EapConfig->GetData (
                                    EapConfig,
                                    EFI_EAP_TYPE_ATTRIBUTE,
                                    EfiEapConfigEapSupportedAuthMethod,
                                    (VOID *)AuthMethodList,
                                    &AuthMethodListSize
                                    );
  if (Status == EFI_SUCCESS) {
    //
    // No Supported Eap Auth Method
    //
    return EFI_UNSUPPORTED;
  } else if (Status != EFI_BUFFER_TOO_SMALL) {
    return Status;
  }

  //
  // The second time to get Supported Auth Method list, return the list.
  // In current design, only EAPTLS, TTLS and PEAP are supported
  //
  AuthMethodList = (EFI_EAP_TYPE *)AllocateZeroPool (AuthMethodListSize);
  if (AuthMethodList == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = EapConfig->GetData (
                        EapConfig,
                        EFI_EAP_TYPE_ATTRIBUTE,
                        EfiEapConfigEapSupportedAuthMethod,
                        (VOID *)AuthMethodList,
                        &AuthMethodListSize
                        );
  if (EFI_ERROR (Status)) {
    FreePool (AuthMethodList);
    return Status;
  }

  //
  // Check if EapAuthMethod is in supported Auth Method list, if found, skip the loop.
  //
  for (Index = 0; Index < AuthMethodListSize / sizeof (EFI_EAP_TYPE); Index++) {
    if (EapAuthMethod == AuthMethodList[Index]) {
      break;
    }
  }

  if (Index == AuthMethodListSize / sizeof (EFI_EAP_TYPE)) {
    FreePool (AuthMethodList);
    return EFI_UNSUPPORTED;
  }

  FreePool (AuthMethodList);

  //
  // Set Identity to Eap peer, Mandatory field for PEAP and TTLS
  //
  if (StrLen (Profile->EapIdentity) > 0) {
    IdentitySize = sizeof (CHAR8) * (StrLen (Profile->EapIdentity) + 1);
    Identity     = AllocateZeroPool (IdentitySize);
    if (Identity == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    Status = gBS->LocateProtocol (&gEdkiiWiFiProfileSyncProtocolGuid, NULL, (VOID **)&WiFiProfileSyncProtocol);
    if (!EFI_ERROR (Status)) {
      CopyMem (Identity, &Profile->EapIdentity, IdentitySize);
    } else {
      UnicodeStrToAsciiStrS (Profile->EapIdentity, Identity, IdentitySize);
    }

    Status = EapConfig->SetData (
                          EapConfig,
                          EFI_EAP_TYPE_IDENTITY,
                          EfiEapConfigIdentityString,
                          (VOID *)Identity,
                          IdentitySize - 1
                          );
    if (EFI_ERROR (Status)) {
      FreePool (Identity);
      return Status;
    }

    FreePool (Identity);
  } else {
    if (EapAuthMethod != EFI_EAP_TYPE_EAPTLS) {
      return EFI_INVALID_PARAMETER;
    }
  }

  //
  // Set Auth Method to Eap peer, Mandatory field
  //
  Status = EapConfig->SetData (
                        EapConfig,
                        EFI_EAP_TYPE_ATTRIBUTE,
                        EfiEapConfigEapAuthMethod,
                        (VOID *)&EapAuthMethod,
                        sizeof (EapAuthMethod)
                        );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if ((EapAuthMethod == EFI_EAP_TYPE_TTLS) || (EapAuthMethod == EFI_EAP_TYPE_PEAP)) {
    Status = EapConfig->SetData (
                          EapConfig,
                          EapAuthMethod,
                          EfiEapConfigEap2ndAuthMethod,
                          (VOID *)&EapSecondAuthMethod,
                          sizeof (EapSecondAuthMethod)
                          );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Set Password to Eap peer
    //
    if (StrLen (Profile->EapPassword) < PASSWORD_MIN_LEN) {
      DEBUG ((DEBUG_ERROR, "[WiFi Connection Manager] Error: No Eap Password for Network: %s.\n", Profile->SSId));
      return EFI_INVALID_PARAMETER;
    }

    PasswordSize = sizeof (CHAR16) * (StrLen (Profile->EapPassword) + 1);
    Password     = AllocateZeroPool (PasswordSize);
    if (Password == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    StrCpyS (Password, PasswordSize, Profile->EapPassword);
    Status = EapConfig->SetData (
                          EapConfig,
                          EFI_EAP_TYPE_MSCHAPV2,
                          EfiEapConfigEapMSChapV2Password,
                          (VOID *)Password,
                          PasswordSize
                          );
    ZeroMem (Password, PasswordSize);
    FreePool (Password);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // If CA cert is required, set it to Eap peer
    //
    if (Profile->CACertData != NULL) {
      Status = EapConfig->SetData (
                            EapConfig,
                            EapAuthMethod,
                            EfiEapConfigEapTlsCACert,
                            Profile->CACertData,
                            Profile->CACertSize
                            );
      if (EFI_ERROR (Status)) {
        return Status;
      }
    } else {
      return EFI_INVALID_PARAMETER;
    }
  } else if (EapAuthMethod == EFI_EAP_TYPE_EAPTLS) {
    //
    // Set CA cert to Eap peer
    //
    if (Profile->CACertData == NULL) {
      return EFI_INVALID_PARAMETER;
    }

    Status = EapConfig->SetData (
                          EapConfig,
                          EFI_EAP_TYPE_EAPTLS,
                          EfiEapConfigEapTlsCACert,
                          Profile->CACertData,
                          Profile->CACertSize
                          );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Set Client cert to Eap peer
    //
    if (Profile->ClientCertData == NULL) {
      return EFI_INVALID_PARAMETER;
    }

    Status = EapConfig->SetData (
                          EapConfig,
                          EFI_EAP_TYPE_EAPTLS,
                          EfiEapConfigEapTlsClientCert,
                          Profile->ClientCertData,
                          Profile->ClientCertSize
                          );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Set Private key to Eap peer
    //
    if (Profile->PrivateKeyData == NULL) {
      DEBUG ((DEBUG_ERROR, "[WiFi Connection Manager]  Error: No Private Key for Network: %s.\n", Profile->SSId));
      return EFI_INVALID_PARAMETER;
    }

    Status = EapConfig->SetData (
                          EapConfig,
                          EFI_EAP_TYPE_EAPTLS,
                          EfiEapConfigEapTlsClientPrivateKeyFile,
                          Profile->PrivateKeyData,
                          Profile->PrivateKeyDataSize
                          );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    if (StrLen (Profile->PrivateKeyPassword) > 0) {
      EncryptPasswordLen   = StrLen (Profile->PrivateKeyPassword);
      AsciiEncryptPassword = AllocateZeroPool (EncryptPasswordLen + 1);
      if (AsciiEncryptPassword == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }

      UnicodeStrToAsciiStrS (Profile->PrivateKeyPassword, AsciiEncryptPassword, EncryptPasswordLen + 1);
      Status = EapConfig->SetData (
                            EapConfig,
                            EFI_EAP_TYPE_EAPTLS,
                            EfiEapConfigEapTlsClientPrivateKeyFilePassword,
                            (VOID *)AsciiEncryptPassword,
                            EncryptPasswordLen + 1
                            );
      if (EFI_ERROR (Status)) {
        ZeroMem (AsciiEncryptPassword, EncryptPasswordLen + 1);
        FreePool (AsciiEncryptPassword);
        return Status;
      }

      ZeroMem (AsciiEncryptPassword, EncryptPasswordLen + 1);
      FreePool (AsciiEncryptPassword);
    }
  } else {
    return EFI_INVALID_PARAMETER;
  }

  return EFI_SUCCESS;
}

/**
  Get current link state from low layer.

  @param[in]   Nic                Pointer to the device data of the selected NIC.
  @param[out]  LinkState          The pointer to buffer to retrieve link state.

  @retval EFI_SUCCESS             The operation is completed.
  @retval EFI_INVALID_PARAMETER   One or more parameters are invalid.
  @retval EFI_UNSUPPORTED         Adapter information protocol is not supported.
  @retval Other Errors            Returned errors when retrieving link state from low layer.

**/
EFI_STATUS
WifiMgrGetLinkState (
  IN   WIFI_MGR_DEVICE_DATA          *Nic,
  OUT  EFI_ADAPTER_INFO_MEDIA_STATE  *LinkState
  )
{
  EFI_STATUS                        Status;
  EFI_TPL                           OldTpl;
  UINTN                             DataSize;
  EFI_ADAPTER_INFO_MEDIA_STATE      *UndiState;
  EFI_ADAPTER_INFORMATION_PROTOCOL  *Aip;

  if ((Nic == NULL) || (LinkState == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
  Status = gBS->OpenProtocol (
                  Nic->ControllerHandle,
                  &gEfiAdapterInformationProtocolGuid,
                  (VOID **)&Aip,
                  Nic->DriverHandle,
                  Nic->ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    gBS->RestoreTPL (OldTpl);
    return EFI_UNSUPPORTED;
  }

  Status = Aip->GetInformation (
                  Aip,
                  &gEfiAdapterInfoMediaStateGuid,
                  (VOID **)&UndiState,
                  &DataSize
                  );
  if (EFI_ERROR (Status)) {
    gBS->RestoreTPL (OldTpl);
    return Status;
  }

  gBS->RestoreTPL (OldTpl);

  CopyMem (LinkState, UndiState, sizeof (EFI_ADAPTER_INFO_MEDIA_STATE));
  FreePool (UndiState);
  return EFI_SUCCESS;
}

/**
  Prepare configuration work before connecting to the target network.
  For WPA2 Personal networks, password should be checked; and for EAP networks, parameters
  are different for different networks.

  @param[in]  Nic                 Pointer to the device data of the selected NIC.
  @param[in]  Profile             The target network to be connected.

  @retval EFI_SUCCESS             The operation is completed.
  @retval EFI_UNSUPPORTED         This network is not supported.
  @retval EFI_INVALID_PARAMETER   One or more parameters are invalid.

**/
EFI_STATUS
WifiMgrPrepareConnection (
  IN    WIFI_MGR_DEVICE_DATA      *Nic,
  IN    WIFI_MGR_NETWORK_PROFILE  *Profile
  )
{
  EFI_STATUS  Status;
  UINT8       SecurityType;
  BOOLEAN     AKMSuiteSupported;
  BOOLEAN     CipherSuiteSupported;

  if ((Profile == NULL) || (Nic == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  Status = WifiMgrCheckRSN (
             Profile->Network.AKMSuite,
             Profile->Network.CipherSuite,
             Nic,
             &SecurityType,
             &AKMSuiteSupported,
             &CipherSuiteSupported
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (AKMSuiteSupported && CipherSuiteSupported) {
    switch (SecurityType) {
      case SECURITY_TYPE_WPA2_PERSONAL:
      case SECURITY_TYPE_WPA3_PERSONAL:

        Status = WifiMgrConfigPassword (Nic, Profile);
        if (EFI_ERROR (Status)) {
          if (Status == EFI_NOT_FOUND) {
            if (Nic->OneTimeConnectRequest) {
              WifiMgrUpdateConnectMessage (Nic, FALSE, L"Connect Failed: Invalid Password!");
            }
          }

          return Status;
        }

        break;

      case SECURITY_TYPE_WPA2_ENTERPRISE:
      case SECURITY_TYPE_WPA3_ENTERPRISE:

        Status = WifiMgrConfigEap (Nic, Profile);
        if (EFI_ERROR (Status)) {
          if (Status == EFI_INVALID_PARAMETER) {
            if (Nic->OneTimeConnectRequest) {
              WifiMgrUpdateConnectMessage (Nic, FALSE, L"Connect Failed: Invalid Configuration!");
            }
          }

          return Status;
        }

        break;

      case SECURITY_TYPE_NONE:
        break;

      default:
        return EFI_UNSUPPORTED;
    }
  } else {
    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

/**
  Will reset NiC data, get profile from profile sync driver, and send for
  another connection attempt.This function should not be called more than
  3 times.

  @param[in]  WiFiProfileSyncProtocol  The target network profile to connect.

  @retval EFI_SUCCESS                  The operation is completed.
  @retval other                        Operation failure.

**/
EFI_STATUS
ConnectionRetry (
  IN   EDKII_WIFI_PROFILE_SYNC_PROTOCOL  *WiFiProfileSyncProtocol
  )
{
  EFI_STATUS                               Status;
  WIFI_MGR_DEVICE_DATA                     *Nic;
  EFI_WIRELESS_MAC_CONNECTION_II_PROTOCOL  *Wmp;
  EFI_SUPPLICANT_PROTOCOL                  *Supplicant;
  EFI_EAP_CONFIGURATION_PROTOCOL           *EapConfig;

  Nic = NULL;

  Status = gBS->LocateProtocol (
                  &gEfiWiFi2ProtocolGuid,
                  NULL,
                  (VOID **)&Wmp
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->LocateProtocol (
                  &gEfiSupplicantProtocolGuid,
                  NULL,
                  (VOID **)&Supplicant
                  );
  if (EFI_ERROR (Status)) {
    Supplicant = NULL;
  }

  Status = gBS->LocateProtocol (
                  &gEfiEapConfigurationProtocolGuid,
                  NULL,
                  (VOID **)&EapConfig
                  );
  if (EFI_ERROR (Status)) {
    EapConfig = NULL;
  }

  //
  // Initialize Nic device data
  //
  Nic = AllocateZeroPool (sizeof (WIFI_MGR_DEVICE_DATA));
  if (Nic == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    return Status;
  }

  Nic->Signature           = WIFI_MGR_DEVICE_DATA_SIGNATURE;
  Nic->Private             = mPrivate;
  Nic->Wmp                 = Wmp;
  Nic->Supplicant          = Supplicant;
  Nic->EapConfig           = EapConfig;
  Nic->UserSelectedProfile = NULL;
  Nic->OneTimeScanRequest  = FALSE;

  if (Nic->Supplicant != NULL) {
    Status = WifiMgrGetSupportedSuites (Nic);
  }

  if (!EFI_ERROR (Status)) {
    InitializeListHead (&Nic->ProfileList);

    Nic->ConnectPendingNetwork = (WIFI_MGR_NETWORK_PROFILE *)AllocateZeroPool (sizeof (WIFI_MGR_NETWORK_PROFILE));
    if (Nic->ConnectPendingNetwork == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      DEBUG ((DEBUG_ERROR, "[WiFi Connection Manager] Failed to allocate memory for ConnectPendingNetwork\n"));
      goto ERROR;
    }

    Status = WiFiProfileSyncProtocol->GetProfile (Nic->ConnectPendingNetwork, Nic->MacAddress);
    if (!EFI_ERROR (Status) && (Nic->ConnectPendingNetwork != NULL)) {
      Status = WifiMgrConnectToNetwork (Nic, Nic->ConnectPendingNetwork);
      if (!EFI_ERROR (Status)) {
        return Status;
      }
    } else {
      DEBUG ((DEBUG_ERROR, "[WiFi Connection Manager] Failed to get WiFi profile with status %r\n", Status));
    }
  } else {
    DEBUG ((DEBUG_ERROR, "[WiFi Connection Manager] Failed to get Supported suites with status %r\n", Status));
  }

  if (Nic->ConnectPendingNetwork != NULL) {
    if (Nic->ConnectPendingNetwork->Network.AKMSuite != NULL) {
      FreePool (Nic->ConnectPendingNetwork->Network.AKMSuite);
    }

    if (Nic->ConnectPendingNetwork->Network.CipherSuite != NULL) {
      FreePool (Nic->ConnectPendingNetwork->Network.CipherSuite);
    }

    FreePool (Nic->ConnectPendingNetwork);
  }

ERROR:
  if (Nic->Supplicant != NULL) {
    if (Nic->SupportedSuites.SupportedAKMSuites != NULL) {
      FreePool (Nic->SupportedSuites.SupportedAKMSuites);
    }

    if (Nic->SupportedSuites.SupportedSwCipherSuites != NULL) {
      FreePool (Nic->SupportedSuites.SupportedSwCipherSuites);
    }

    if (Nic->SupportedSuites.SupportedHwCipherSuites != NULL) {
      FreePool (Nic->SupportedSuites.SupportedHwCipherSuites);
    }
  }

  FreePool (Nic);

  return Status;
}

/**
  The callback function for connect operation.

  ASSERT when errors occur in config token.

  @param[in]  Event                 The Connect token receive event.
  @param[in]  Context               The context of the connect token.

**/
VOID
EFIAPI
WifiMgrOnConnectFinished (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  EFI_STATUS                        Status;
  WIFI_MGR_MAC_CONFIG_TOKEN         *ConfigToken;
  WIFI_MGR_NETWORK_PROFILE          *ConnectedProfile;
  UINT8                             SecurityType;
  UINT8                             SSIdLen;
  CHAR8                             *AsciiSSId;
  EDKII_WIFI_PROFILE_SYNC_PROTOCOL  *WiFiProfileSyncProtocol;

  ASSERT (Context != NULL);

  ConnectedProfile = NULL;
  ConfigToken      = (WIFI_MGR_MAC_CONFIG_TOKEN *)Context;
  ASSERT (ConfigToken->Nic != NULL);

  ConfigToken->Nic->ConnectState = WifiMgrDisconnected;
  ASSERT (ConfigToken->Type == TokenTypeConnectNetworkToken);

  ASSERT (ConfigToken->Token.ConnectNetworkToken != NULL);

  Status = gBS->LocateProtocol (&gEdkiiWiFiProfileSyncProtocolGuid, NULL, (VOID **)&WiFiProfileSyncProtocol);
  if (!EFI_ERROR (Status)) {
    WiFiProfileSyncProtocol->SetConnectState (ConfigToken->Token.ConnectNetworkToken->ResultCode);
    if ((mWifiConnectionCount < MAX_WIFI_CONNETION_ATTEMPTS) &&
        (ConfigToken->Token.ConnectNetworkToken->ResultCode != ConnectSuccess))
    {
      mWifiConnectionCount++;
      gBS->CloseEvent (Event);
      Status = ConnectionRetry (WiFiProfileSyncProtocol);
      if (!EFI_ERROR (Status)) {
        return;
      }

      WiFiProfileSyncProtocol->SetConnectState (Status);
    }
  }

  if (ConfigToken->Token.ConnectNetworkToken->Status != EFI_SUCCESS) {
    if (ConfigToken->Nic->OneTimeConnectRequest) {
      //
      // Only update message for user triggered connection
      //
      if (ConfigToken->Token.ConnectNetworkToken->Status == EFI_ACCESS_DENIED) {
        WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, L"Connect Failed: Permission Denied!");
      } else {
        WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, L"Connect Failed!");
      }

      ConfigToken->Nic->OneTimeConnectRequest = FALSE;
    }

    ConfigToken->Nic->CurrentOperateNetwork = NULL;
    return;
  }

  if (ConfigToken->Token.ConnectNetworkToken->ResultCode != ConnectSuccess) {
    if (ConfigToken->Nic->OneTimeConnectRequest) {
      if (ConfigToken->Token.ConnectNetworkToken->ResultCode == ConnectFailedReasonUnspecified) {
        WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, L"Connect Failed: Wrong Password or Unexpected Error!");
      } else {
        WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, L"Connect Failed!");
      }
    }

    goto Exit;
  }

  if ((ConfigToken->Token.ConnectNetworkToken->Data == NULL) ||
      (ConfigToken->Token.ConnectNetworkToken->Data->Network == NULL))
  {
    //
    // An unexpected error occurs, tell low layer to perform a disconnect
    //
    ConfigToken->Nic->HasDisconnectPendingNetwork = TRUE;
    WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, NULL);
    goto Exit;
  }

  //
  // A correct connect token received, terminate the connection process
  //
  Status = WifiMgrCheckRSN (
             ConfigToken->Token.ConnectNetworkToken->Data->Network->AKMSuite,
             ConfigToken->Token.ConnectNetworkToken->Data->Network->CipherSuite,
             ConfigToken->Nic,
             &SecurityType,
             NULL,
             NULL
             );
  if (EFI_ERROR (Status)) {
    SecurityType = SECURITY_TYPE_UNKNOWN;
  }

  SSIdLen   = ConfigToken->Token.ConnectNetworkToken->Data->Network->SSId.SSIdLen;
  AsciiSSId = (CHAR8 *)AllocateZeroPool (sizeof (CHAR8) * (SSIdLen + 1));
  if (AsciiSSId == NULL) {
    ConfigToken->Nic->HasDisconnectPendingNetwork = TRUE;
    WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, NULL);
    goto Exit;
  }

  CopyMem (AsciiSSId, ConfigToken->Token.ConnectNetworkToken->Data->Network->SSId.SSId, SSIdLen);
  *(AsciiSSId + SSIdLen) = '\0';

  ConnectedProfile = WifiMgrGetProfileByAsciiSSId (AsciiSSId, SecurityType, &ConfigToken->Nic->ProfileList);
  FreePool (AsciiSSId);
  if (ConnectedProfile == NULL) {
    ConfigToken->Nic->HasDisconnectPendingNetwork = TRUE;
    WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, NULL);
    goto Exit;
  }

  ConfigToken->Nic->ConnectState = WifiMgrConnectedToAp;
  WifiMgrUpdateConnectMessage (ConfigToken->Nic, TRUE, NULL);

Exit:

  if (ConfigToken->Nic->ConnectState == WifiMgrDisconnected) {
    ConfigToken->Nic->CurrentOperateNetwork = NULL;
  }

  ConfigToken->Nic->OneTimeConnectRequest = FALSE;
  WifiMgrFreeToken (ConfigToken);
}

/**
  Start connect operation, and send out a token to connect to a target network.

  @param[in]  Nic                 Pointer to the device data of the selected NIC.
  @param[in]  Profile             The target network to be connected.

  @retval EFI_SUCCESS             The operation is completed.
  @retval EFI_ALREADY_STARTED     Already in "connected" state, need to perform a disconnect
                                  operation first.
  @retval EFI_INVALID_PARAMETER   One or more parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES    Failed to allocate memory.
  @retval Other Errors            Return errors when connecting network on low layer.

**/
EFI_STATUS
WifiMgrConnectToNetwork (
  IN    WIFI_MGR_DEVICE_DATA      *Nic,
  IN    WIFI_MGR_NETWORK_PROFILE  *Profile
  )
{
  EFI_STATUS                       Status;
  EFI_TPL                          OldTpl;
  EFI_ADAPTER_INFO_MEDIA_STATE     LinkState;
  WIFI_MGR_MAC_CONFIG_TOKEN        *ConfigToken;
  EFI_80211_CONNECT_NETWORK_TOKEN  *ConnectToken;

  if ((Nic == NULL) || (Nic->Wmp == NULL) || (Profile == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  Status = WifiMgrGetLinkState (Nic, &LinkState);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (LinkState.MediaState == EFI_SUCCESS) {
    return EFI_ALREADY_STARTED;
  }

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
  Status = WifiMgrPrepareConnection (Nic, Profile);
  if (EFI_ERROR (Status)) {
    gBS->RestoreTPL (OldTpl);
    return Status;
  }

  //
  // Create a new connect token
  //
  ConfigToken = AllocateZeroPool (sizeof (WIFI_MGR_MAC_CONFIG_TOKEN));
  if (ConfigToken == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  ConfigToken->Type                      = TokenTypeConnectNetworkToken;
  ConfigToken->Nic                       = Nic;
  ConfigToken->Token.ConnectNetworkToken = AllocateZeroPool (sizeof (EFI_80211_CONNECT_NETWORK_TOKEN));
  if (ConfigToken->Token.ConnectNetworkToken == NULL) {
    goto Exit;
  }

  ConnectToken       = ConfigToken->Token.ConnectNetworkToken;
  ConnectToken->Data = AllocateZeroPool (sizeof (EFI_80211_CONNECT_NETWORK_DATA));
  if (ConnectToken->Data == NULL) {
    goto Exit;
  }

  ConnectToken->Data->Network = AllocateZeroPool (sizeof (EFI_80211_NETWORK));
  if (ConnectToken->Data->Network == NULL) {
    goto Exit;
  }

  CopyMem (ConnectToken->Data->Network, &Profile->Network, sizeof (EFI_80211_NETWORK));

  //
  // Add event handle and start to connect
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  WifiMgrOnConnectFinished,
                  ConfigToken,
                  &ConnectToken->Event
                  );
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Nic->ConnectState          = WifiMgrConnectingToAp;
  Nic->CurrentOperateNetwork = Profile;
  WifiMgrUpdateConnectMessage (Nic, FALSE, NULL);

  //
  // Start Connecting ...
  //
  Status = Nic->Wmp->ConnectNetwork (Nic->Wmp, ConnectToken);

  //
  // Erase secrets after connection is triggered
  //
  WifiMgrCleanProfileSecrets (Profile);

  if (EFI_ERROR (Status)) {
    if (Status == EFI_ALREADY_STARTED) {
      Nic->ConnectState = WifiMgrConnectedToAp;
      WifiMgrUpdateConnectMessage (Nic, TRUE, NULL);
    } else {
      Nic->ConnectState          = WifiMgrDisconnected;
      Nic->CurrentOperateNetwork = NULL;

      if (Nic->OneTimeConnectRequest) {
        if (Status == EFI_NOT_FOUND) {
          WifiMgrUpdateConnectMessage (Nic, FALSE, L"Connect Failed: Not Available!");
        } else {
          WifiMgrUpdateConnectMessage (Nic, FALSE, L"Connect Failed: Unexpected Error!");
        }
      }
    }

    goto Exit;
  }

Exit:

  if (EFI_ERROR (Status)) {
    WifiMgrFreeToken (ConfigToken);
  }

  gBS->RestoreTPL (OldTpl);

  DEBUG ((DEBUG_INFO, "[WiFi Connection Manager] WifiMgrConnectToNetwork: %r\n", Status));
  return Status;
}

/**
  The callback function for disconnect operation.

  ASSERT when errors occur in config token.

  @param[in]  Event                 The Disconnect token receive event.
  @param[in]  Context               The context of the Disconnect token.

**/
VOID
EFIAPI
WifiMgrOnDisconnectFinished (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  WIFI_MGR_MAC_CONFIG_TOKEN  *ConfigToken;

  ASSERT (Context != NULL);

  ConfigToken = (WIFI_MGR_MAC_CONFIG_TOKEN *)Context;
  ASSERT (ConfigToken->Nic != NULL);
  ASSERT (ConfigToken->Type == TokenTypeDisconnectNetworkToken);

  ASSERT (ConfigToken->Token.DisconnectNetworkToken != NULL);
  if (ConfigToken->Token.DisconnectNetworkToken->Status != EFI_SUCCESS) {
    ConfigToken->Nic->ConnectState = WifiMgrConnectedToAp;
    WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, NULL);
    ConfigToken->Nic->OneTimeDisconnectRequest = FALSE;
    goto Exit;
  }

  ConfigToken->Nic->ConnectState          = WifiMgrDisconnected;
  ConfigToken->Nic->CurrentOperateNetwork = NULL;
  WifiMgrUpdateConnectMessage (ConfigToken->Nic, TRUE, NULL);
  ConfigToken->Nic->OneTimeDisconnectRequest = FALSE;

  //
  // Disconnected network may not be in network list now, trigger a scan again!
  //
  ConfigToken->Nic->OneTimeScanRequest = TRUE;

Exit:
  WifiMgrFreeToken (ConfigToken);
  return;
}

/**
  Start disconnect operation, and send out a token to disconnect from current connected
  network.

  @param[in]  Nic                 Pointer to the device data of the selected NIC.

  @retval EFI_SUCCESS             The operation is completed.
  @retval EFI_OUT_OF_RESOURCES    Failed to allocate memory.
  @retval EFI_INVALID_PARAMETER   One or more parameters are invalid.
  @retval Other Errors            Return errors when disconnecting a network on low layer.

**/
EFI_STATUS
WifiMgrDisconnectToNetwork (
  IN    WIFI_MGR_DEVICE_DATA  *Nic
  )
{
  EFI_STATUS                          Status;
  EFI_TPL                             OldTpl;
  WIFI_MGR_MAC_CONFIG_TOKEN           *ConfigToken;
  EFI_80211_DISCONNECT_NETWORK_TOKEN  *DisconnectToken;

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

  OldTpl      = gBS->RaiseTPL (TPL_CALLBACK);
  Status      = EFI_SUCCESS;
  ConfigToken = AllocateZeroPool (sizeof (WIFI_MGR_MAC_CONFIG_TOKEN));
  if (ConfigToken == NULL) {
    gBS->RestoreTPL (OldTpl);
    return EFI_OUT_OF_RESOURCES;
  }

  ConfigToken->Type                         = TokenTypeDisconnectNetworkToken;
  ConfigToken->Nic                          = Nic;
  ConfigToken->Token.DisconnectNetworkToken = AllocateZeroPool (sizeof (EFI_80211_DISCONNECT_NETWORK_TOKEN));
  if (ConfigToken->Token.DisconnectNetworkToken == NULL) {
    WifiMgrFreeToken (ConfigToken);
    gBS->RestoreTPL (OldTpl);
    return EFI_OUT_OF_RESOURCES;
  }

  DisconnectToken = ConfigToken->Token.DisconnectNetworkToken;

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  WifiMgrOnDisconnectFinished,
                  ConfigToken,
                  &DisconnectToken->Event
                  );
  if (EFI_ERROR (Status)) {
    WifiMgrFreeToken (ConfigToken);
    gBS->RestoreTPL (OldTpl);
    return Status;
  }

  Nic->ConnectState = WifiMgrDisconnectingToAp;
  WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, NULL);

  Status = Nic->Wmp->DisconnectNetwork (Nic->Wmp, DisconnectToken);
  if (EFI_ERROR (Status)) {
    if (Status == EFI_NOT_FOUND) {
      Nic->ConnectState          = WifiMgrDisconnected;
      Nic->CurrentOperateNetwork = NULL;

      //
      // This network is not in network list now, trigger a scan again!
      //
      Nic->OneTimeScanRequest = TRUE;

      //
      // State has been changed from Connected to Disconnected
      //
      WifiMgrUpdateConnectMessage (ConfigToken->Nic, TRUE, NULL);
      Status = EFI_SUCCESS;
    } else {
      if (Nic->OneTimeDisconnectRequest) {
        WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, L"Disconnect Failed: Unexpected Error!");
      }

      Nic->ConnectState = WifiMgrConnectedToAp;
      WifiMgrUpdateConnectMessage (ConfigToken->Nic, FALSE, NULL);
    }

    WifiMgrFreeToken (ConfigToken);
  }

  gBS->RestoreTPL (OldTpl);
  return Status;
}

/**
  The state machine of the connection manager, periodically check the state and
  perform a corresponding operation.

  @param[in]  Event                   The timer event to be triggered.
  @param[in]  Context                 The context of the Nic device data.

**/
VOID
EFIAPI
WifiMgrOnTimerTick (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  WIFI_MGR_DEVICE_DATA          *Nic;
  EFI_STATUS                    Status;
  EFI_ADAPTER_INFO_MEDIA_STATE  LinkState;
  WIFI_MGR_NETWORK_PROFILE      *Profile;

  if (Context == NULL) {
    return;
  }

  Nic = (WIFI_MGR_DEVICE_DATA *)Context;
  NET_CHECK_SIGNATURE (Nic, WIFI_MGR_DEVICE_DATA_SIGNATURE);

  Status = WifiMgrGetLinkState (Nic, &LinkState);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "[WiFi Connection Manager] Error: Failed to get link state!\n"));
    return;
  }

  if (Nic->LastLinkState.MediaState != LinkState.MediaState) {
    if ((Nic->LastLinkState.MediaState == EFI_SUCCESS) && (LinkState.MediaState == EFI_NO_MEDIA)) {
      Nic->HasDisconnectPendingNetwork = TRUE;
    }

    Nic->LastLinkState.MediaState = LinkState.MediaState;
  }

  Nic->ScanTickTime++;
  if (((Nic->ScanTickTime > WIFI_SCAN_FREQUENCY) || Nic->OneTimeScanRequest) &&
      (Nic->ScanState == WifiMgrScanFinished))
  {
    Nic->OneTimeScanRequest = FALSE;
    Nic->ScanTickTime       = 0;

    DEBUG ((DEBUG_INFO, "[WiFi Connection Manager] Scan is triggered.\n"));
    WifiMgrStartScan (Nic);
  }

  if ((Nic->AvailableCount > 0) && (Nic->ScanState == WifiMgrScanFinished)) {
    switch (Nic->ConnectState) {
      case WifiMgrDisconnected:

        if (Nic->HasDisconnectPendingNetwork) {
          Nic->HasDisconnectPendingNetwork = FALSE;
        }

        if (Nic->ConnectPendingNetwork != NULL) {
          Profile                    = Nic->ConnectPendingNetwork;
          Status                     = WifiMgrConnectToNetwork (Nic, Profile);
          Nic->ConnectPendingNetwork = NULL;
          if (EFI_ERROR (Status)) {
            //
            // Some error happened, don't wait for a return connect token!
            //
            Nic->OneTimeConnectRequest = FALSE;
          }
        }

        break;

      case WifiMgrConnectingToAp:
        break;

      case WifiMgrDisconnectingToAp:
        break;

      case WifiMgrConnectedToAp:

        if ((Nic->ConnectPendingNetwork != NULL) || Nic->HasDisconnectPendingNetwork) {
          Status = WifiMgrDisconnectToNetwork (Nic);
          if (EFI_ERROR (Status)) {
            //
            // Some error happened, don't wait for a return disconnect token!
            //
            Nic->OneTimeDisconnectRequest = FALSE;
          }
        }

        break;

      default:
        break;
    }
  }
}
