/** @file
  PCI/PCIe network interface instace of RedfishPlatformHostInterfaceLib

  Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
  (C) Copyright 2020 Hewlett Packard Enterprise Development LP<BR>
  Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved.<BR>

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

**/

#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Library/RedfishHostInterfaceLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>

#include <Pcd/RestExServiceDevicePath.h>
#include <Guid/GlobalVariable.h>

#define VERBOSE_COLUME_SIZE  (16)

REDFISH_OVER_IP_PROTOCOL_DATA  *mRedfishOverIpProtocolData;
UINT8                          mRedfishProtocolDataSize;

/**
  Get the MAC address of NIC.

  @param[out] MacAddress      Pointer to retrieve MAC address

  @retval   EFI_SUCCESS      MAC address is returned in MacAddress

**/
EFI_STATUS
GetMacAddressInformation (
  OUT EFI_MAC_ADDRESS  *MacAddress
  )
{
  REST_EX_SERVICE_DEVICE_PATH_DATA  *RestExServiceDevicePathData;
  EFI_DEVICE_PATH_PROTOCOL          *RestExServiceDevicePath;
  MAC_ADDR_DEVICE_PATH              *MacAddressDevicePath;

  RestExServiceDevicePathData = NULL;
  RestExServiceDevicePath     = NULL;

  RestExServiceDevicePathData = (REST_EX_SERVICE_DEVICE_PATH_DATA *)PcdGetPtr (PcdRedfishRestExServiceDevicePath);
  if ((RestExServiceDevicePathData == NULL) ||
      (RestExServiceDevicePathData->DevicePathNum == 0) ||
      !IsDevicePathValid (RestExServiceDevicePathData->DevicePath, 0))
  {
    return EFI_NOT_FOUND;
  }

  RestExServiceDevicePath = RestExServiceDevicePathData->DevicePath;
  if (RestExServiceDevicePathData->DevicePathMatchMode != DEVICE_PATH_MATCH_MAC_NODE) {
    return EFI_NOT_FOUND;
  }

  //
  // Find Mac DevicePath Node.
  //
  while (!IsDevicePathEnd (RestExServiceDevicePath) &&
         ((DevicePathType (RestExServiceDevicePath) != MESSAGING_DEVICE_PATH) ||
          (DevicePathSubType (RestExServiceDevicePath) != MSG_MAC_ADDR_DP)))
  {
    RestExServiceDevicePath = NextDevicePathNode (RestExServiceDevicePath);
  }

  if (!IsDevicePathEnd (RestExServiceDevicePath)) {
    MacAddressDevicePath = (MAC_ADDR_DEVICE_PATH *)RestExServiceDevicePath;
    CopyMem ((VOID *)MacAddress, (VOID *)&MacAddressDevicePath->MacAddress, sizeof (EFI_MAC_ADDRESS));
    return EFI_SUCCESS;
  }

  return EFI_NOT_FOUND;
}

/**
  Get platform Redfish host interface device descriptor.

  @param[out] DeviceType        Pointer to retrieve device type.
  @param[out] DeviceDescriptor  Pointer to retrieve REDFISH_INTERFACE_DATA, caller has to free
                                this memory using FreePool().
  @retval EFI_SUCCESS     Device descriptor is returned successfully in DeviceDescriptor.
  @retval EFI_NOT_FOUND   No Redfish host interface descriptor provided on this platform.
  @retval Others          Fail to get device descriptor.
**/
EFI_STATUS
RedfishPlatformHostInterfaceDeviceDescriptor (
  OUT UINT8                   *DeviceType,
  OUT REDFISH_INTERFACE_DATA  **DeviceDescriptor
  )
{
  EFI_STATUS                                  Status;
  EFI_MAC_ADDRESS                             MacAddress;
  REDFISH_INTERFACE_DATA                      *RedfishInterfaceData;
  PCI_OR_PCIE_INTERFACE_DEVICE_DESCRIPTOR_V2  *ThisDeviceDescriptor;

  RedfishInterfaceData = AllocateZeroPool (sizeof (PCI_OR_PCIE_INTERFACE_DEVICE_DESCRIPTOR_V2) + 1);
  if (RedfishInterfaceData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  RedfishInterfaceData->DeviceType = REDFISH_HOST_INTERFACE_DEVICE_TYPE_PCI_PCIE_V2;
  //
  // Fill up device type information.
  //
  ThisDeviceDescriptor         = (PCI_OR_PCIE_INTERFACE_DEVICE_DESCRIPTOR_V2 *)((UINT8 *)RedfishInterfaceData + 1);
  ThisDeviceDescriptor->Length = sizeof (PCI_OR_PCIE_INTERFACE_DEVICE_DESCRIPTOR_V2) + 1;
  Status                       = GetMacAddressInformation (&MacAddress);
  if (EFI_ERROR (Status)) {
    FreePool (RedfishInterfaceData);
    return EFI_NOT_FOUND;
  }

  CopyMem ((VOID *)&ThisDeviceDescriptor->MacAddress, (VOID *)&MacAddress, sizeof (ThisDeviceDescriptor->MacAddress));
  *DeviceType       = REDFISH_HOST_INTERFACE_DEVICE_TYPE_PCI_PCIE_V2;
  *DeviceDescriptor = RedfishInterfaceData;
  return EFI_SUCCESS;
}

/**
  Get platform Redfish host interface protocol data.
  Caller should pass NULL in ProtocolRecord to retrive the first protocol record.
  Then continuously pass previous ProtocolRecord for retrieving the next ProtocolRecord.

  @param[out] ProtocolRecord     Pointer to retrieve the protocol record.
                                 caller has to free the new protocol record returned from
                                 this function using FreePool().
  @param[in] IndexOfProtocolData The index of protocol data.

  @retval EFI_SUCCESS     Protocol records are all returned.
  @retval EFI_NOT_FOUND   No more protocol records.
  @retval Others          Fail to get protocol records.
**/
EFI_STATUS
RedfishPlatformHostInterfaceProtocolData (
  OUT MC_HOST_INTERFACE_PROTOCOL_RECORD  **ProtocolRecord,
  IN UINT8                               IndexOfProtocolData
  )
{
  MC_HOST_INTERFACE_PROTOCOL_RECORD  *ThisProtocolRecord;

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

  if (IndexOfProtocolData == 0) {
    //
    // Return the first Redfish protocol data to caller. We only have
    // one protocol data in this case.
    //
    ThisProtocolRecord                      = (MC_HOST_INTERFACE_PROTOCOL_RECORD *)AllocatePool (mRedfishProtocolDataSize + sizeof (MC_HOST_INTERFACE_PROTOCOL_RECORD) - 1);
    ThisProtocolRecord->ProtocolType        = MCHostInterfaceProtocolTypeRedfishOverIP;
    ThisProtocolRecord->ProtocolTypeDataLen = mRedfishProtocolDataSize;
    CopyMem ((VOID *)&ThisProtocolRecord->ProtocolTypeData, (VOID *)mRedfishOverIpProtocolData, mRedfishProtocolDataSize);
    *ProtocolRecord = ThisProtocolRecord;
    return EFI_SUCCESS;
  }

  return EFI_NOT_FOUND;
}

/**
  Dump IPv4 address.

  @param[in] Ip IPv4 address
**/
VOID
InternalDumpIp4Addr (
  IN EFI_IPv4_ADDRESS  *Ip
  )
{
  UINTN  Index;

  for (Index = 0; Index < 4; Index++) {
    DEBUG ((DEBUG_VERBOSE, "%d", Ip->Addr[Index]));
    if (Index < 3) {
      DEBUG ((DEBUG_VERBOSE, "."));
    }
  }

  DEBUG ((DEBUG_VERBOSE, "\n"));
}

/**
  Dump IPv6 address.

  @param[in] Ip IPv6 address
**/
VOID
InternalDumpIp6Addr (
  IN EFI_IPv6_ADDRESS  *Ip
  )
{
  UINTN  Index;

  for (Index = 0; Index < 16; Index++) {
    if (Ip->Addr[Index] != 0) {
      DEBUG ((DEBUG_VERBOSE, "%x", Ip->Addr[Index]));
    }

    Index++;

    if (Index > 15) {
      return;
    }

    if (((Ip->Addr[Index] & 0xf0) == 0) && (Ip->Addr[Index - 1] != 0)) {
      DEBUG ((DEBUG_VERBOSE, "0"));
    }

    DEBUG ((DEBUG_VERBOSE, "%x", Ip->Addr[Index]));

    if (Index < 15) {
      DEBUG ((DEBUG_VERBOSE, ":"));
    }
  }

  DEBUG ((DEBUG_VERBOSE, "\n"));
}

/**
  Dump data

  @param[in] Data Pointer to data.
  @param[in] Size size of data to dump.
**/
VOID
InternalDumpData (
  IN UINT8  *Data,
  IN UINTN  Size
  )
{
  UINTN  Index;

  for (Index = 0; Index < Size; Index++) {
    DEBUG ((DEBUG_VERBOSE, "%02x ", (UINTN)Data[Index]));
  }
}

/**
  Dump hex data

  @param[in] Data Pointer to hex data.
  @param[in] Size size of hex data to dump.
**/
VOID
InternalDumpHex (
  IN UINT8  *Data,
  IN UINTN  Size
  )
{
  UINTN  Index;
  UINTN  Count;
  UINTN  Left;

  Count = Size / VERBOSE_COLUME_SIZE;
  Left  = Size % VERBOSE_COLUME_SIZE;
  for (Index = 0; Index < Count; Index++) {
    InternalDumpData (Data + Index * VERBOSE_COLUME_SIZE, VERBOSE_COLUME_SIZE);
    DEBUG ((DEBUG_VERBOSE, "\n"));
  }

  if (Left != 0) {
    InternalDumpData (Data + Index * VERBOSE_COLUME_SIZE, Left);
    DEBUG ((DEBUG_VERBOSE, "\n"));
  }

  DEBUG ((DEBUG_VERBOSE, "\n"));
}

/**
  Dump Redfish over IP protocol data

  @param[in] RedfishProtocolData     Pointer to REDFISH_OVER_IP_PROTOCOL_DATA
  @param[in] RedfishProtocolDataSize size of data to dump.
**/
VOID
DumpRedfishIpProtocolData (
  IN REDFISH_OVER_IP_PROTOCOL_DATA  *RedfishProtocolData,
  IN UINT8                          RedfishProtocolDataSize
  )
{
  CHAR16  Hostname[16];

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData: \n"));
  InternalDumpHex ((UINT8 *)RedfishProtocolData, RedfishProtocolDataSize);

  DEBUG ((DEBUG_VERBOSE, "Parsing as below: \n"));

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->ServiceUuid - %g\n", &(RedfishProtocolData->ServiceUuid)));

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->HostIpAssignmentType - %d\n", RedfishProtocolData->HostIpAssignmentType));

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->HostIpAddressFormat - %d\n", RedfishProtocolData->HostIpAddressFormat));

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->HostIpAddress: \n"));
  if (RedfishProtocolData->HostIpAddressFormat == 0x01) {
    InternalDumpIp4Addr ((EFI_IPv4_ADDRESS *)(RedfishProtocolData->HostIpAddress));
  } else {
    InternalDumpIp6Addr ((EFI_IPv6_ADDRESS *)(RedfishProtocolData->HostIpAddress));
  }

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->HostIpMask: \n"));
  if (RedfishProtocolData->HostIpAddressFormat == 0x01) {
    InternalDumpIp4Addr ((EFI_IPv4_ADDRESS *)(RedfishProtocolData->HostIpMask));
  } else {
    InternalDumpIp6Addr ((EFI_IPv6_ADDRESS *)(RedfishProtocolData->HostIpMask));
  }

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->RedfishServiceIpDiscoveryType - %d\n", RedfishProtocolData->RedfishServiceIpDiscoveryType));

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->RedfishServiceIpAddressFormat - %d\n", RedfishProtocolData->RedfishServiceIpAddressFormat));

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->RedfishServiceIpAddress: \n"));
  if (RedfishProtocolData->RedfishServiceIpAddressFormat == 0x01) {
    InternalDumpIp4Addr ((EFI_IPv4_ADDRESS *)(RedfishProtocolData->RedfishServiceIpAddress));
  } else {
    InternalDumpIp6Addr ((EFI_IPv6_ADDRESS *)(RedfishProtocolData->RedfishServiceIpAddress));
  }

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->RedfishServiceIpMask: \n"));
  if (RedfishProtocolData->RedfishServiceIpAddressFormat == 0x01) {
    InternalDumpIp4Addr ((EFI_IPv4_ADDRESS *)(RedfishProtocolData->RedfishServiceIpMask));
  } else {
    InternalDumpIp6Addr ((EFI_IPv6_ADDRESS *)(RedfishProtocolData->RedfishServiceIpMask));
  }

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->RedfishServiceIpPort - %d\n", RedfishProtocolData->RedfishServiceIpPort));

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->RedfishServiceVlanId - %d\n", RedfishProtocolData->RedfishServiceVlanId));

  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->RedfishServiceHostnameLength - %d\n", RedfishProtocolData->RedfishServiceHostnameLength));

  AsciiStrToUnicodeStrS ((CHAR8 *)RedfishProtocolData->RedfishServiceHostname, Hostname, sizeof (Hostname) / sizeof (Hostname[0]));
  DEBUG ((DEBUG_VERBOSE, "RedfishProtocolData->RedfishServiceHostname - %s\n", Hostname));
}

/**
  Get Redfish host interface protocol data from variale.

  @param[out] RedfishProtocolData  Pointer to retrieve REDFISH_OVER_IP_PROTOCOL_DATA.
  @param[out] RedfishProtocolDataSize  Size of REDFISH_OVER_IP_PROTOCOL_DATA.

  @retval EFI_SUCESS   REDFISH_OVER_IP_PROTOCOL_DATA is returned successfully.
**/
EFI_STATUS
GetRedfishRecordFromVariable (
  OUT REDFISH_OVER_IP_PROTOCOL_DATA  **RedfishProtocolData,
  OUT UINT8                          *RedfishProtocolDataSize
  )
{
  EFI_STATUS        Status;
  UINT8             HostIpAssignmentType;
  UINTN             HostIpAssignmentTypeSize;
  EFI_IPv4_ADDRESS  HostIpAddress;
  UINTN             IPv4DataSize;
  EFI_IPv4_ADDRESS  HostIpMask;
  EFI_IPv4_ADDRESS  RedfishServiceIpAddress;
  EFI_IPv4_ADDRESS  RedfishServiceIpMask;
  UINT16            RedfishServiceIpPort;
  UINTN             IpPortDataSize;
  UINT8             HostNameSize;
  CHAR8             RedfishHostName[20];

  if ((RedfishProtocolData == NULL) || (RedfishProtocolDataSize == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // 1. Retrieve Address Information from variable.
  //
  Status = gRT->GetVariable (
                  L"HostIpAssignmentType",
                  &gEmuRedfishServiceGuid,
                  NULL,
                  &HostIpAssignmentTypeSize,
                  &HostIpAssignmentType
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "RedfishPlatformDxe: GetVariable HostIpAssignmentType - %r\n", Status));
    return Status;
  }

  IPv4DataSize = sizeof (EFI_IPv4_ADDRESS);
  if (HostIpAssignmentType == 1 ) {
    Status = gRT->GetVariable (
                    L"HostIpAddress",
                    &gEmuRedfishServiceGuid,
                    NULL,
                    &IPv4DataSize,
                    &HostIpAddress
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "RedfishPlatformDxe: GetVariable HostIpAddress - %r\n", Status));
      return Status;
    }

    Status = gRT->GetVariable (
                    L"HostIpMask",
                    &gEmuRedfishServiceGuid,
                    NULL,
                    &IPv4DataSize,
                    &HostIpMask
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "RedfishPlatformDxe: GetVariable HostIpMask - %r\n", Status));
      return Status;
    }
  }

  Status = gRT->GetVariable (
                  L"RedfishServiceIpAddress",
                  &gEmuRedfishServiceGuid,
                  NULL,
                  &IPv4DataSize,
                  &RedfishServiceIpAddress
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "RedfishPlatformDxe: GetVariable RedfishServiceIpAddress - %r\n", Status));
    return Status;
  }

  Status = gRT->GetVariable (
                  L"RedfishServiceIpMask",
                  &gEmuRedfishServiceGuid,
                  NULL,
                  &IPv4DataSize,
                  &RedfishServiceIpMask
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "RedfishPlatformDxe: GetVariable RedfishServiceIpMask - %r\n", Status));
    return Status;
  }

  Status = gRT->GetVariable (
                  L"RedfishServiceIpPort",
                  &gEmuRedfishServiceGuid,
                  NULL,
                  &IpPortDataSize,
                  &RedfishServiceIpPort
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "RedfishPlatformDxe: GetVariable RedfishServiceIpPort - %r\n", Status));
    return Status;
  }

  AsciiSPrint (
    RedfishHostName,
    sizeof (RedfishHostName),
    "%d.%d.%d.%d",
    RedfishServiceIpAddress.Addr[0],
    RedfishServiceIpAddress.Addr[1],
    RedfishServiceIpAddress.Addr[2],
    RedfishServiceIpAddress.Addr[3]
    );

  HostNameSize = (UINT8)AsciiStrLen (RedfishHostName) + 1;

  //
  // 2. Protocol Data Size.
  //
  *RedfishProtocolDataSize = sizeof (REDFISH_OVER_IP_PROTOCOL_DATA) - 1 + HostNameSize;

  //
  // 3. Protocol Data.
  //
  *RedfishProtocolData = (REDFISH_OVER_IP_PROTOCOL_DATA *)AllocateZeroPool (*RedfishProtocolDataSize);
  if (*RedfishProtocolData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  CopyGuid (&(*RedfishProtocolData)->ServiceUuid, &gEmuRedfishServiceGuid);

  (*RedfishProtocolData)->HostIpAssignmentType = HostIpAssignmentType;
  (*RedfishProtocolData)->HostIpAddressFormat  = 1;  // Only support IPv4

  if (HostIpAssignmentType == 1 ) {
    (*RedfishProtocolData)->HostIpAddress[0] = HostIpAddress.Addr[0];
    (*RedfishProtocolData)->HostIpAddress[1] = HostIpAddress.Addr[1];
    (*RedfishProtocolData)->HostIpAddress[2] = HostIpAddress.Addr[2];
    (*RedfishProtocolData)->HostIpAddress[3] = HostIpAddress.Addr[3];

    (*RedfishProtocolData)->HostIpMask[0] = HostIpMask.Addr[0];
    (*RedfishProtocolData)->HostIpMask[1] = HostIpMask.Addr[1];
    (*RedfishProtocolData)->HostIpMask[2] = HostIpMask.Addr[2];
    (*RedfishProtocolData)->HostIpMask[3] = HostIpMask.Addr[3];
  }

  (*RedfishProtocolData)->RedfishServiceIpDiscoveryType = 1;  // Use static IP address
  (*RedfishProtocolData)->RedfishServiceIpAddressFormat = 1;  // Only support IPv4

  (*RedfishProtocolData)->RedfishServiceIpAddress[0] = RedfishServiceIpAddress.Addr[0];
  (*RedfishProtocolData)->RedfishServiceIpAddress[1] = RedfishServiceIpAddress.Addr[1];
  (*RedfishProtocolData)->RedfishServiceIpAddress[2] = RedfishServiceIpAddress.Addr[2];
  (*RedfishProtocolData)->RedfishServiceIpAddress[3] = RedfishServiceIpAddress.Addr[3];

  (*RedfishProtocolData)->RedfishServiceIpMask[0] = RedfishServiceIpMask.Addr[0];
  (*RedfishProtocolData)->RedfishServiceIpMask[1] = RedfishServiceIpMask.Addr[1];
  (*RedfishProtocolData)->RedfishServiceIpMask[2] = RedfishServiceIpMask.Addr[2];
  (*RedfishProtocolData)->RedfishServiceIpMask[3] = RedfishServiceIpMask.Addr[3];

  (*RedfishProtocolData)->RedfishServiceIpPort = RedfishServiceIpPort;
  (*RedfishProtocolData)->RedfishServiceVlanId = 0xffffffff;

  (*RedfishProtocolData)->RedfishServiceHostnameLength = HostNameSize;
  AsciiStrCpyS ((CHAR8 *)((*RedfishProtocolData)->RedfishServiceHostname), HostNameSize, RedfishHostName);

  return Status;
}

/**
  Construct Redfish host interface protocol data.

  @param ImageHandle     The image handle.
  @param SystemTable     The system table.

  @retval  EFI_SUCEESS  Install Boot manager menu success.
  @retval  Other        Return error status.

**/
EFI_STATUS
EFIAPI
RedfishPlatformHostInterfaceConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;

  Status = GetRedfishRecordFromVariable (&mRedfishOverIpProtocolData, &mRedfishProtocolDataSize);
  DEBUG ((DEBUG_INFO, "%a: GetRedfishRecordFromVariable() - %r\n", __func__, Status));
  if (!EFI_ERROR (Status)) {
    DumpRedfishIpProtocolData (mRedfishOverIpProtocolData, mRedfishProtocolDataSize);
  }

  return EFI_SUCCESS;
}

/**
  Get the EFI protocol GUID installed by platform library which
  indicates the necessary information is ready for building
  SMBIOS 42h record.

  @param[out] InformationReadinessGuid  Pointer to retrive the protocol
                                        GUID.

  @retval EFI_SUCCESS          Notification is required for building up
                               SMBIOS type 42h record.
  @retval EFI_UNSUPPORTED      Notification is not required for building up
                               SMBIOS type 42h record.
  @retval EFI_ALREADY_STARTED  Platform host information is already ready.
  @retval Others               Other errors.
**/
EFI_STATUS
RedfishPlatformHostInterfaceNotification (
  OUT EFI_GUID  **InformationReadinessGuid
  )
{
  return EFI_UNSUPPORTED;
}
