/** @file
  Shell application for VLAN configuration.

  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>

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

**/

#include <Uefi.h>

#include <Protocol/VlanConfig.h>

#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/ShellLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/HiiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiHiiServicesLib.h>
#include <Library/NetLib.h>

//
// String token ID of VConfig command help message text.
//
GLOBAL_REMOVE_IF_UNREFERENCED EFI_STRING_ID  mStringVConfigHelpTokenId = STRING_TOKEN (STR_VCONFIG_HELP);

#define INVALID_NIC_INDEX  0xffff
#define INVALID_VLAN_ID    0xffff

//
// This is the generated String package data for all .UNI files.
// This data array is ready to be used as input of HiiAddPackages() to
// create a packagelist (which contains Form packages, String packages, etc).
//
extern UINT8  VConfigStrings[];

EFI_HANDLE      mImageHandle = NULL;
EFI_HII_HANDLE  mHiiHandle   = NULL;

SHELL_PARAM_ITEM  mParamList[] = {
  {
    L"-l",
    TypeValue
  },
  {
    L"-a",
    TypeMaxValue
  },
  {
    L"-d",
    TypeValue
  },
  {
    NULL,
    TypeMax
  }
};

/**
  Locate the network interface handle buffer.

  @param[out]  NumberOfHandles Pointer to the number of handles.
  @param[out]  HandleBuffer    Pointer to the buffer to store the returned handles.

**/
VOID
LocateNicHandleBuffer (
  OUT UINTN       *NumberOfHandles,
  OUT EFI_HANDLE  **HandleBuffer
  )
{
  EFI_STATUS  Status;

  *NumberOfHandles = 0;
  *HandleBuffer    = NULL;

  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiVlanConfigProtocolGuid,
                  NULL,
                  NumberOfHandles,
                  HandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_LOCATE_FAIL), mHiiHandle, Status);
  }
}

/**
  Extract the decimal index from the network interface name.

  @param[in]  Name           Name of the network interface.

  @retval INVALID_NIC_INDEX  Failed to extract the network interface index.
  @return others             The network interface index.

**/
UINTN
NicNameToIndex (
  IN CHAR16  *Name
  )
{
  CHAR16  *Str;

  Str = Name + 3;
  if ((StrnCmp (Name, L"eth", 3) != 0) || (*Str == 0)) {
    return INVALID_NIC_INDEX;
  }

  while (*Str != 0) {
    if ((*Str < L'0') || (*Str > L'9')) {
      return INVALID_NIC_INDEX;
    }

    Str++;
  }

  return (UINT16)StrDecimalToUintn (Name + 3);
}

/**
  Find network interface device handle by its name.

  @param[in]  Name           Name of the network interface.

  @retval NULL               Cannot find the network interface.
  @return others             Handle of the network interface.

**/
EFI_HANDLE
NicNameToHandle (
  IN CHAR16  *Name
  )
{
  UINTN       NumberOfHandles;
  EFI_HANDLE  *HandleBuffer;
  UINTN       Index;
  EFI_HANDLE  Handle;

  //
  // Find all NIC handles.
  //
  LocateNicHandleBuffer (&NumberOfHandles, &HandleBuffer);
  if (NumberOfHandles == 0) {
    return NULL;
  }

  Index = NicNameToIndex (Name);
  if (Index >= NumberOfHandles) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_INVALID_IF), mHiiHandle, Name);
    Handle = NULL;
  } else {
    Handle = HandleBuffer[Index];
  }

  FreePool (HandleBuffer);
  return Handle;
}

/**
  Open VlanConfig protocol from a handle.

  @param[in]  Handle         The handle to open the VlanConfig protocol.

  @return The VlanConfig protocol interface.

**/
EFI_VLAN_CONFIG_PROTOCOL *
OpenVlanConfigProtocol (
  IN EFI_HANDLE  Handle
  )
{
  EFI_VLAN_CONFIG_PROTOCOL  *VlanConfig;

  VlanConfig = NULL;
  gBS->OpenProtocol (
         Handle,
         &gEfiVlanConfigProtocolGuid,
         (VOID **)&VlanConfig,
         mImageHandle,
         Handle,
         EFI_OPEN_PROTOCOL_GET_PROTOCOL
         );

  return VlanConfig;
}

/**
  Close VlanConfig protocol of a handle.

  @param[in]  Handle         The handle to close the VlanConfig protocol.

**/
VOID
CloseVlanConfigProtocol (
  IN EFI_HANDLE  Handle
  )
{
  gBS->CloseProtocol (
         Handle,
         &gEfiVlanConfigProtocolGuid,
         mImageHandle,
         Handle
         );
}

/**
  Display VLAN configuration of a network interface.

  @param[in]  Handle         Handle of the network interface.
  @param[in]  NicIndex       Index of the network interface.

**/
VOID
ShowNicVlanInfo (
  IN EFI_HANDLE  Handle,
  IN UINTN       NicIndex
  )
{
  CHAR16                    *MacStr;
  EFI_STATUS                Status;
  UINTN                     Index;
  EFI_VLAN_CONFIG_PROTOCOL  *VlanConfig;
  UINT16                    NumberOfVlan;
  EFI_VLAN_FIND_DATA        *VlanData;

  VlanConfig = OpenVlanConfigProtocol (Handle);
  if (VlanConfig == NULL) {
    return;
  }

  MacStr = NULL;
  Status = NetLibGetMacString (Handle, mImageHandle, &MacStr);
  if (EFI_ERROR (Status)) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_MAC_FAIL), mHiiHandle, Status);
    goto Exit;
  }

  ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_ETH_MAC), mHiiHandle, NicIndex, MacStr);

  Status = VlanConfig->Find (VlanConfig, NULL, &NumberOfVlan, &VlanData);
  if (EFI_ERROR (Status)) {
    if (Status == EFI_NOT_FOUND) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_NO_VLAN), mHiiHandle);
    } else {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_FIND_FAIL), mHiiHandle, Status);
    }

    goto Exit;
  }

  for (Index = 0; Index < NumberOfVlan; Index++) {
    ShellPrintHiiEx (
      -1,
      -1,
      NULL,
      STRING_TOKEN (STR_VCONFIG_VLAN_DISPLAY),
      mHiiHandle,
      VlanData[Index].VlanId,
      VlanData[Index].Priority
      );
  }

  FreePool (VlanData);

Exit:
  CloseVlanConfigProtocol (Handle);

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

/**
  Display the VLAN configuration of all, or a specified network interface.

  @param[in]  Name           Name of the network interface. If NULL, the VLAN
                             configuration of all network will be displayed.

**/
VOID
DisplayVlan (
  IN CHAR16  *Name OPTIONAL
  )
{
  UINTN       NumberOfHandles;
  EFI_HANDLE  *HandleBuffer;
  UINTN       Index;
  EFI_HANDLE  NicHandle;

  if (Name != NULL) {
    //
    // Display specified NIC
    //
    NicHandle = NicNameToHandle (Name);
    if (NicHandle == NULL) {
      return;
    }

    ShowNicVlanInfo (NicHandle, 0);
    return;
  }

  //
  // Find all NIC handles
  //
  LocateNicHandleBuffer (&NumberOfHandles, &HandleBuffer);
  if (NumberOfHandles == 0) {
    return;
  }

  for (Index = 0; Index < NumberOfHandles; Index++) {
    ShowNicVlanInfo (HandleBuffer[Index], Index);
  }

  FreePool (HandleBuffer);
}

/**
  Convert a NULL-terminated unicode decimal VLAN ID string to VLAN ID.

  @param[in]  String       Pointer to VLAN ID string from user input.

  @retval Value translated from String, or INVALID_VLAN_ID is string is invalid.

**/
UINT16
StrToVlanId (
  IN CHAR16  *String
  )
{
  CHAR16  *Str;

  if (String == NULL) {
    return INVALID_VLAN_ID;
  }

  Str = String;
  while ((*Str >= '0') && (*Str <= '9')) {
    Str++;
  }

  if (*Str != 0) {
    return INVALID_VLAN_ID;
  }

  return (UINT16)StrDecimalToUintn (String);
}

/**
  Add a VLAN device.

  @param[in]  ParamStr       Parameter string from user input.

**/
VOID
AddVlan (
  IN CHAR16  *ParamStr
  )
{
  CHAR16                    *Name;
  CHAR16                    *VlanIdStr;
  CHAR16                    *PriorityStr;
  CHAR16                    *StrPtr;
  BOOLEAN                   IsSpace;
  UINTN                     VlanId;
  UINTN                     Priority;
  EFI_HANDLE                Handle;
  EFI_HANDLE                VlanHandle;
  EFI_VLAN_CONFIG_PROTOCOL  *VlanConfig;
  EFI_STATUS                Status;

  VlanConfig = NULL;
  Priority   = 0;

  if (ParamStr == NULL) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_NO_IF), mHiiHandle);
    return;
  }

  StrPtr = AllocateCopyPool (StrSize (ParamStr), ParamStr);
  if (StrPtr == NULL) {
    return;
  }

  Name        = StrPtr;
  VlanIdStr   = NULL;
  PriorityStr = NULL;
  IsSpace     = FALSE;
  while (*StrPtr != 0) {
    if (*StrPtr == L' ') {
      *StrPtr = 0;
      IsSpace = TRUE;
    } else {
      if (IsSpace) {
        //
        // Start of a parameter.
        //
        if (VlanIdStr == NULL) {
          //
          // 2nd parameter is VLAN ID.
          //
          VlanIdStr = StrPtr;
        } else if (PriorityStr == NULL) {
          //
          // 3rd parameter is Priority.
          //
          PriorityStr = StrPtr;
        } else {
          //
          // Ignore else parameters.
          //
          break;
        }
      }

      IsSpace = FALSE;
    }

    StrPtr++;
  }

  Handle = NicNameToHandle (Name);
  if (Handle == NULL) {
    goto Exit;
  }

  VlanConfig = OpenVlanConfigProtocol (Handle);
  if (VlanConfig == NULL) {
    goto Exit;
  }

  //
  // Check VLAN ID.
  //
  if ((VlanIdStr == NULL) || (*VlanIdStr == 0)) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_NO_VID), mHiiHandle);
    goto Exit;
  }

  VlanId = StrToVlanId (VlanIdStr);
  if (VlanId > 4094) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_INVALID_VID), mHiiHandle, VlanIdStr);
    goto Exit;
  }

  //
  // Check Priority.
  //
  if ((PriorityStr != NULL) && (*PriorityStr != 0)) {
    Priority = StrDecimalToUintn (PriorityStr);
    if (Priority > 7) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_INVALID_PRIORITY), mHiiHandle, PriorityStr);
      goto Exit;
    }
  }

  //
  // Set VLAN
  //
  Status = VlanConfig->Set (VlanConfig, (UINT16)VlanId, (UINT8)Priority);
  if (EFI_ERROR (Status)) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_SET_FAIL), mHiiHandle, Status);
    goto Exit;
  }

  //
  // Connect the VLAN device.
  //
  VlanHandle = NetLibGetVlanHandle (Handle, (UINT16)VlanId);
  if (VlanHandle != NULL) {
    gBS->ConnectController (VlanHandle, NULL, NULL, TRUE);
  }

  ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_SET_SUCCESS), mHiiHandle);

Exit:
  if (VlanConfig != NULL) {
    CloseVlanConfigProtocol (Handle);
  }

  FreePool (Name);
}

/**
  Remove a VLAN device.

  @param[in]  ParamStr       Parameter string from user input.

**/
VOID
DeleteVlan (
  IN CHAR16  *ParamStr
  )
{
  CHAR16                    *Name;
  CHAR16                    *VlanIdStr;
  CHAR16                    *StrPtr;
  UINTN                     VlanId;
  EFI_HANDLE                Handle;
  EFI_VLAN_CONFIG_PROTOCOL  *VlanConfig;
  EFI_STATUS                Status;
  UINT16                    NumberOfVlan;
  EFI_VLAN_FIND_DATA        *VlanData;

  VlanConfig = NULL;

  if (ParamStr == NULL) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_NO_IF), mHiiHandle);
    return;
  }

  StrPtr = AllocateCopyPool (StrSize (ParamStr), ParamStr);
  if (StrPtr == NULL) {
    return;
  }

  Name      = StrPtr;
  VlanIdStr = NULL;
  while (*StrPtr != 0) {
    if (*StrPtr == L'.') {
      *StrPtr   = 0;
      VlanIdStr = StrPtr + 1;
      break;
    }

    StrPtr++;
  }

  Handle = NicNameToHandle (Name);
  if (Handle == NULL) {
    goto Exit;
  }

  VlanConfig = OpenVlanConfigProtocol (Handle);
  if (VlanConfig == NULL) {
    goto Exit;
  }

  //
  // Check VLAN ID
  //
  if ((VlanIdStr == NULL) || (*VlanIdStr == 0)) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_NO_VID), mHiiHandle);
    goto Exit;
  }

  VlanId = StrToVlanId (VlanIdStr);
  if (VlanId > 4094) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_INVALID_VID), mHiiHandle, VlanIdStr);
    goto Exit;
  }

  //
  // Delete VLAN.
  //
  Status = VlanConfig->Remove (VlanConfig, (UINT16)VlanId);
  if (EFI_ERROR (Status)) {
    if (Status == EFI_NOT_FOUND) {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_NOT_FOUND), mHiiHandle);
    } else {
      ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_REMOVE_FAIL), mHiiHandle, Status);
    }

    goto Exit;
  }

  //
  // Check whether this is the last VLAN to remove.
  //
  Status = VlanConfig->Find (VlanConfig, NULL, &NumberOfVlan, &VlanData);
  if (EFI_ERROR (Status)) {
    //
    // This is the last VLAN to remove, try to connect the controller handle.
    //
    gBS->ConnectController (Handle, NULL, NULL, TRUE);
  } else {
    FreePool (VlanData);
  }

  ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_REMOVE_SUCCESS), mHiiHandle);

Exit:
  if (VlanConfig != NULL) {
    CloseVlanConfigProtocol (Handle);
  }

  FreePool (Name);
}

/**
  The actual entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point executed successfully.
  @retval other             Some error occur when executing this entry point.

**/
EFI_STATUS
EFIAPI
VlanConfigMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  LIST_ENTRY    *List;
  CONST CHAR16  *Str;
  EFI_STATUS    Status;

  mImageHandle = ImageHandle;

  //
  // Publish HII package list to HII Database.
  //
  Status = gHiiDatabase->NewPackageList (
                           gHiiDatabase,
                           &gModuleHiiPackageList->Header,
                           NULL,
                           &mHiiHandle
                           );
  if (EFI_ERROR (Status)) {
    return Status;
  }

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

  List = NULL;
  ShellCommandLineParseEx (mParamList, &List, NULL, FALSE, FALSE);
  if (List == NULL) {
    ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_NO_ARG), mHiiHandle);
    goto Exit;
  }

  if (ShellCommandLineGetFlag (List, L"-l")) {
    Str = ShellCommandLineGetValue (List, L"-l");
    DisplayVlan ((CHAR16 *)Str);
    goto Exit;
  }

  if (ShellCommandLineGetFlag (List, L"-a")) {
    Str = ShellCommandLineGetValue (List, L"-a");
    AddVlan ((CHAR16 *)Str);
    goto Exit;
  }

  if (ShellCommandLineGetFlag (List, L"-d")) {
    Str = ShellCommandLineGetValue (List, L"-d");
    DeleteVlan ((CHAR16 *)Str);
    goto Exit;
  }

  //
  // No valid argument till now.
  //
  ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_VCONFIG_NO_ARG), mHiiHandle);

Exit:
  if (List != NULL) {
    ShellCommandLineFreeVarList (List);
  }

  //
  // Remove our string package from HII database.
  //
  HiiRemovePackages (mHiiHandle);

  return EFI_SUCCESS;
}
