/** @file
  RestExDxe support functions implementation.

  Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
  (C) Copyright 2020 Hewlett Packard Enterprise Development LP<BR>
  Copyright (c) 2023, American Megatrends International LLC.
  Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.

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

**/
#include <Uefi.h>
#include "RedfishRestExInternal.h"

/**
  Create a new TLS session because the previous one is closed.

  @param[in]  Instance            Pointer to EFI_REST_EX_PROTOCOL instance for a particular
                                  REST service.
  @retval EFI_SUCCESS             operation succeeded.
  @retval EFI_ERROR               Other errors.

**/
EFI_STATUS
ResetHttpTslSession (
  IN   RESTEX_INSTANCE  *Instance
  )
{
  EFI_STATUS  Status;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: TCP connection is finished. Could be TSL session closure, reset HTTP instance for the new TLS session.\n", __func__));

  Status = Instance->HttpIo.Http->Configure (Instance->HttpIo.Http, NULL);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Error to reset HTTP instance.\n", __func__));
    return Status;
  }

  Status = Instance->HttpIo.Http->Configure (Instance->HttpIo.Http, &((EFI_REST_EX_HTTP_CONFIG_DATA *)Instance->ConfigData)->HttpConfigData);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Error to re-initiate HTTP instance.\n", __func__));
  }

  return Status;
}

/**
  This function check Http receive status.

  @param[in]  Instance             Pointer to EFI_REST_EX_PROTOCOL instance for a particular
                                   REST service.
  @param[in]  HttpIoReceiveStatus  This is the status return from HttpIoRecvResponse

  @retval EFI_SUCCESS           The payload receive from Redfish service in successfully.
  @retval EFI_NOT_READY         May need to resend the HTTP request.
  @retval EFI_DEVICE_ERROR      Something wrong and can't be resolved.
  @retval Others                Other errors as indicated.

**/
EFI_STATUS
RedfishCheckHttpReceiveStatus (
  IN RESTEX_INSTANCE  *Instance,
  IN EFI_STATUS       HttpIoReceiveStatus
  )
{
  EFI_STATUS  Status;
  EFI_STATUS  ReturnStatus;

  if (!EFI_ERROR (HttpIoReceiveStatus)) {
    ReturnStatus = EFI_SUCCESS;
  } else if (HttpIoReceiveStatus != EFI_CONNECTION_FIN) {
    if ((Instance->Flags & RESTEX_INSTANCE_FLAGS_TCP_ERROR_RETRY) == 0) {
      DEBUG ((DEBUG_ERROR, "%a: TCP error, reset HTTP session.\n", __func__));
      Instance->Flags |= RESTEX_INSTANCE_FLAGS_TCP_ERROR_RETRY;
      gBS->Stall (500);
      Status = ResetHttpTslSession (Instance);
      if (!EFI_ERROR (Status)) {
        return EFI_NOT_READY;
      }

      DEBUG ((DEBUG_ERROR, "%a: Reset HTTP instance fail.\n", __func__));
    }

    ReturnStatus = EFI_DEVICE_ERROR;
  } else {
    if ((Instance->Flags & RESTEX_INSTANCE_FLAGS_TLS_RETRY) != 0) {
      DEBUG ((DEBUG_ERROR, "%a: REST_EX Send and receive fail even with a new TLS session.\n", __func__));
      ReturnStatus = EFI_DEVICE_ERROR;
    }

    Instance->Flags |= RESTEX_INSTANCE_FLAGS_TLS_RETRY;
    Status           = ResetHttpTslSession (Instance);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "%a: Reset HTTP instance fail.\n", __func__));
      ReturnStatus = EFI_DEVICE_ERROR;
    }

    return EFI_NOT_READY;
  }

  //
  // Clean TLS new session retry and error try flags.
  //
  Instance->Flags &= ~(RESTEX_INSTANCE_FLAGS_TLS_RETRY | RESTEX_INSTANCE_FLAGS_TCP_ERROR_RETRY);
  return ReturnStatus;
}

/**
  This function send the HTTP request without body to see
  if the write to URL is permitted by Redfish service. This function
  checks if the HTTP request has Content-length in HTTP header. If yes,
  set HTTP body to NULL and then send to service. Check the HTTP status
  for the firther actions.

  @param[in]  This                    Pointer to EFI_REST_EX_PROTOCOL instance for a particular
                                      REST service.
  @param[in]  RequestMessage          Pointer to the HTTP request data for this resource
  @param[in]  PreservedRequestHeaders The pointer to save the request headers
  @param[in]  ItsWrite                This is write method to URL.

  @retval EFI_INVALID_PARAMETER  Improper given parameters.
  @retval EFI_SUCCESS            This HTTP request is free to send to Redfish service.
  @retval EFI_OUT_OF_RESOURCES   NOt enough memory to process.
  @retval EFI_ACCESS_DENIED      Not allowed to write to this URL.

  @retval Others                 Other errors as indicated.

**/
EFI_STATUS
RedfishHttpAddExpectation (
  IN EFI_REST_EX_PROTOCOL  *This,
  IN EFI_HTTP_MESSAGE      *RequestMessage,
  IN EFI_HTTP_HEADER       **PreservedRequestHeaders,
  IN BOOLEAN               *ItsWrite
  )
{
  EFI_HTTP_HEADER  *NewHeaders;

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

  *ItsWrite = FALSE;

  if ((RequestMessage->Data.Request->Method != HttpMethodPut) && (RequestMessage->Data.Request->Method != HttpMethodPost) &&
      (RequestMessage->Data.Request->Method != HttpMethodPatch))
  {
    return EFI_SUCCESS;
  }

  *ItsWrite = TRUE;

  //
  // Check PCD before adding Expect header
  //
  if (FixedPcdGetBool (PcdRedfishRestExAddingExpect)) {
    if (PreservedRequestHeaders != NULL) {
      *PreservedRequestHeaders = RequestMessage->Headers;
    }

    NewHeaders = AllocateZeroPool ((RequestMessage->HeaderCount + 1) * sizeof (EFI_HTTP_HEADER));
    CopyMem ((VOID *)NewHeaders, (VOID *)RequestMessage->Headers, RequestMessage->HeaderCount * sizeof (EFI_HTTP_HEADER));
    HttpSetFieldNameAndValue (NewHeaders + RequestMessage->HeaderCount, HTTP_HEADER_EXPECT, HTTP_EXPECT_100_CONTINUE);
    RequestMessage->HeaderCount++;
    RequestMessage->Headers = NewHeaders;
  }

  return EFI_SUCCESS;
}
