/** @file
  RestExDxe support functions implementation.

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

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

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

/**
  Create a new TLS session becuase the previous on is closed.
  status.

  @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_INFO, "%a: TCP connection is finished. Could be TSL session closure, reset HTTP instance for the new TLS session.\n", __FUNCTION__));

  Status = Instance->HttpIo.Http->Configure (Instance->HttpIo.Http, NULL);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Error to reset HTTP instance.\n", __FUNCTION__));
    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", __FUNCTION__));
  }
  return Status;
}
/**
  This function check

  @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 sucessfully.
  @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 (EFI_ERROR (HttpIoReceiveStatus) && HttpIoReceiveStatus != EFI_CONNECTION_FIN) {
    if ((Instance->Flags & RESTEX_INSTANCE_FLAGS_TCP_ERROR_RETRY) == 0) {
      DEBUG ((DEBUG_ERROR, "%a: TCP error, reset HTTP session.\n", __FUNCTION__));
      Instance->Flags |= RESTEX_INSTANCE_FLAGS_TCP_ERROR_RETRY;
      gBS->Stall (500);
      Status = ResetHttpTslSession (Instance);
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "%a: Reset HTTP instance fail.\n", __FUNCTION__));
        ReturnStatus = EFI_DEVICE_ERROR;
      } else {
        return EFI_NOT_READY;
      }
    } else {
      ReturnStatus = EFI_DEVICE_ERROR;
    }
  } else {
      if (HttpIoReceiveStatus == EFI_CONNECTION_FIN) {
        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", __FUNCTION__));
          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", __FUNCTION__));
          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 (PreservedRequestHeaders != NULL) {
    *PreservedRequestHeaders = RequestMessage->Headers;
  }

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

  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;
}

