Reviewed-by: Igor Kulchytskyy <ig...@ami.com> -----Original Message----- From: Nickle Wang <nick...@nvidia.com> Sent: Thursday, February 22, 2024 4:11 AM To: devel@edk2.groups.io Cc: Igor Kulchytskyy <ig...@ami.com>; Abner Chang <abner.ch...@amd.com>; Nick Ramirez <nrami...@nvidia.com> Subject: [EXTERNAL] [PATCH v2 2/6] RedfishPkg: implement Redfish HTTP protocol
**CAUTION: The e-mail below is from an external source. Please exercise caution before opening attachments, clicking links, or following guidance.** implement Redfish HTTP protocol driver. Signed-off-by: Nickle Wang <nick...@nvidia.com> Co-authored-by: Igor Kulchytskyy <ig...@ami.com> Cc: Abner Chang <abner.ch...@amd.com> Cc: Igor Kulchytskyy <ig...@ami.com> Cc: Nick Ramirez <nrami...@nvidia.com> --- RedfishPkg/RedfishPkg.dec | 7 +- RedfishPkg/RedfishComponents.dsc.inc | 3 +- RedfishPkg/RedfishPkg.dsc | 2 + RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf | 73 + RedfishPkg/RedfishHttpDxe/RedfishHttpData.h | 256 ++++ RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.h | 44 + .../RedfishHttpDxe/RedfishHttpOperation.h | 76 + RedfishPkg/RedfishHttpDxe/RedfishHttpData.c | 667 ++++++++ RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.c | 1344 +++++++++++++++++ .../RedfishHttpDxe/RedfishHttpOperation.c | 693 +++++++++ RedfishPkg/Redfish.fdf.inc | 3 +- 11 files changed, 3164 insertions(+), 4 deletions(-) create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpData.h create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.h create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.h create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpData.c create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.c create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.c diff --git a/RedfishPkg/RedfishPkg.dec b/RedfishPkg/RedfishPkg.dec index 9b424efdf3..114f8d2ad8 100644 --- a/RedfishPkg/RedfishPkg.dec +++ b/RedfishPkg/RedfishPkg.dec @@ -157,8 +157,11 @@ # set to EFI_REST_EX_PROTOCOL. # gEfiRedfishPkgTokenSpaceGuid.PcdRedfishSendReceiveTimeout|5000|UINT32|0x00001009 - ## This is used to enable HTTP content encoding on Redfish communication. - gEfiRedfishPkgTokenSpaceGuid.PcdRedfishServiceContentEncoding|TRUE|BOOLEAN|0x0000100A + # + # This PCD string is introduced for platform developer to set the encoding method supported by BMC Redfish. + # Currently only "None" and "gzip" are supported. + # + gEfiRedfishPkgTokenSpaceGuid.PcdRedfishServiceContentEncoding|"None"|VOID*|0x0000100A # # Use below PCDs to control Redfhs HTTP protocol. # diff --git a/RedfishPkg/RedfishComponents.dsc.inc b/RedfishPkg/RedfishComponents.dsc.inc index 464ffc8606..d6c5b73d7f 100644 --- a/RedfishPkg/RedfishComponents.dsc.inc +++ b/RedfishPkg/RedfishComponents.dsc.inc @@ -7,7 +7,7 @@ # "RedfishDefines.dsc.inc". # # (C) Copyright 2020-2021 Hewlett Packard Enterprise Development LP<BR> -# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: BSD-2-Clause-Patent # @@ -28,4 +28,5 @@ RedfishPkg/RedfishConfigHandler/RedfishConfigHandlerDriver.inf RedfishPkg/RedfishPlatformConfigDxe/RedfishPlatformConfigDxe.inf MdeModulePkg/Universal/RegularExpressionDxe/RegularExpressionDxe.inf + RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf !endif diff --git a/RedfishPkg/RedfishPkg.dsc b/RedfishPkg/RedfishPkg.dsc index 25ed193182..5849e7cf9e 100644 --- a/RedfishPkg/RedfishPkg.dsc +++ b/RedfishPkg/RedfishPkg.dsc @@ -45,6 +45,8 @@ UefiHiiServicesLib|MdeModulePkg/Library/UefiHiiServicesLib/UefiHiiServicesLib.inf RedfishPlatformCredentialLib|RedfishPkg/Library/PlatformCredentialLibNull/PlatformCredentialLibNull.inf RedfishContentCodingLib|RedfishPkg/Library/RedfishContentCodingLibNull/RedfishContentCodingLibNull.inf + ReportStatusCodeLib|MdeModulePkg/Library/DxeReportStatusCodeLib/DxeReportStatusCodeLib.inf + SortLib|MdeModulePkg/Library/UefiSortLib/UefiSortLib.inf # NULL instance of IPMI related library. IpmiLib|MdeModulePkg/Library/BaseIpmiLibNull/BaseIpmiLibNull.inf diff --git a/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf b/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf new file mode 100644 index 0000000000..c7dfdffacf --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf @@ -0,0 +1,73 @@ +## @file +# RedfishHttpDxe is the DXE driver which provides +# EdkIIRedfishHttpProtocol to EDK2 Redfish Feature +# drivers for HTTP operation. +# +# Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001000b + BASE_NAME = RedfishHttpDxe + FILE_GUID = 85ADB2F1-DA93-47D4-AF4F-3D920D9BD2C0 + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = RedfishHttpEntryPoint + UNLOAD_IMAGE = RedfishHttpDriverUnload + +# +# VALID_ARCHITECTURES = IA32 X64 ARM AARCH64 RISCV64 +# + +[Sources] + RedfishHttpData.c + RedfishHttpData.h + RedfishHttpDxe.c + RedfishHttpDxe.h + RedfishHttpOperation.c + RedfishHttpOperation.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + NetworkPkg/NetworkPkg.dec + RedfishPkg/RedfishPkg.dec + +[LibraryClasses.ARM] + ArmSoftFloatLib + +[LibraryClasses] + BaseLib + BaseMemoryLib + RedfishContentCodingLib + DebugLib + HttpLib + JsonLib + MemoryAllocationLib + PrintLib + RedfishDebugLib + ReportStatusCodeLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + +[Protocols] + gEdkIIRedfishHttpProtocolGuid ## PRODUCED + gEdkIIRedfishCredentialProtocolGuid ## CONSUMES + gEfiRestExProtocolGuid ## CONSUEMS + +[Pcd] + gEfiRedfishPkgTokenSpaceGuid.PcdHttpGetRetry + gEfiRedfishPkgTokenSpaceGuid.PcdHttpPutRetry + gEfiRedfishPkgTokenSpaceGuid.PcdHttpPatchRetry + gEfiRedfishPkgTokenSpaceGuid.PcdHttpPostRetry + gEfiRedfishPkgTokenSpaceGuid.PcdHttpDeleteRetry + gEfiRedfishPkgTokenSpaceGuid.PcdHttpRetryWaitInSecond + gEfiRedfishPkgTokenSpaceGuid.PcdHttpCacheDisabled + gEfiRedfishPkgTokenSpaceGuid.PcdRedfishServiceContentEncoding + +[Depex] + TRUE diff --git a/RedfishPkg/RedfishHttpDxe/RedfishHttpData.h b/RedfishPkg/RedfishHttpDxe/RedfishHttpData.h new file mode 100644 index 0000000000..6be610142e --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpData.h @@ -0,0 +1,256 @@ +/** @file + Definitions of RedfishHttpData + + Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef EDKII_REDFISH_HTTP_DATA_H_ +#define EDKII_REDFISH_HTTP_DATA_H_ + +#include "RedfishHttpDxe.h" + +#define REDFISH_HTTP_DRIVER_SIGNATURE SIGNATURE_32 ('r', 'f', 'h', 'p') +#define REDFISH_HTTP_CACHE_SIGNATURE SIGNATURE_32 ('r', 'f', 'c', 'h') +#define REDFISH_HTTP_SERVICE_SIGNATURE SIGNATURE_32 ('r', 'f', 's', 'v') +#define REDFISH_HTTP_PAYLOAD_SIGNATURE SIGNATURE_32 ('r', 'f', 'p', 'l') +#define REDFISH_HTTP_BASIC_AUTH_STR "Basic " + +/// +/// REDFISH_SERVICE_PRIVATE definition. +/// +typedef struct { + UINT32 Signature; + CHAR8 *Host; + CHAR8 *HostName; + CHAR8 *BasicAuth; + CHAR8 *SessionToken; + EFI_REST_EX_PROTOCOL *RestEx; +} REDFISH_SERVICE_PRIVATE; + +/// +/// REDFISH_PAYLOAD_PRIVATE definition. +/// +typedef struct { + UINT32 Signature; + REDFISH_SERVICE_PRIVATE *Service; + EDKII_JSON_VALUE JsonValue; +} REDFISH_PAYLOAD_PRIVATE; + +/// +/// Definition of REDFISH_HTTP_CACHE_DATA +/// +typedef struct { + UINT32 Signature; + LIST_ENTRY List; + EFI_STRING Uri; + UINTN HitCount; + REDFISH_RESPONSE *Response; +} REDFISH_HTTP_CACHE_DATA; + +#define REDFISH_HTTP_CACHE_FROM_LIST(a) CR (a, REDFISH_HTTP_CACHE_DATA, List, REDFISH_HTTP_CACHE_SIGNATURE) + +/// +/// Definition of REDFISH_HTTP_CACHE_LIST +/// +typedef struct { + LIST_ENTRY Head; + UINTN Count; + UINTN Capacity; +} REDFISH_HTTP_CACHE_LIST; + +/// +/// Definition of REDFISH_HTTP_RETRY_SETTING +/// +typedef struct { + UINT16 MaximumRetryGet; + UINT16 MaximumRetryPut; + UINT16 MaximumRetryPost; + UINT16 MaximumRetryPatch; + UINT16 MaximumRetryDelete; + UINTN RetryWait; +} REDFISH_HTTP_RETRY_SETTING; + +/// +/// Definition of REDFISH_HTTP_CACHE_PRIVATE +/// +typedef struct { + UINT32 Signature; + EFI_HANDLE ImageHandle; + BOOLEAN CacheDisabled; + EFI_EVENT NotifyEvent; + REDFISH_HTTP_CACHE_LIST CacheList; + EDKII_REDFISH_HTTP_PROTOCOL Protocol; + EDKII_REDFISH_CREDENTIAL_PROTOCOL *CredentialProtocol; + REDFISH_HTTP_RETRY_SETTING RetrySetting; +} REDFISH_HTTP_CACHE_PRIVATE; + +#define REDFISH_HTTP_CACHE_PRIVATE_FROM_THIS(a) CR (a, REDFISH_HTTP_CACHE_PRIVATE, Protocol, REDFISH_HTTP_DRIVER_SIGNATURE) + +/** + Search on given ListHeader for given URI string. + + @param[in] ListHeader Target list to search. + @param[in] Uri Target URI to search. + + @retval REDFISH_HTTP_CACHE_DATA Target cache data is found. + @retval NULL No cache data with given URI is found. + +**/ +REDFISH_HTTP_CACHE_DATA * +FindHttpCacheData ( + IN LIST_ENTRY *ListHeader, + IN EFI_STRING Uri + ); + +/** + This function copy the data in SrcResponse to DstResponse. + + @param[in] SrcResponse Source Response to copy. + @param[out] DstResponse Destination Response. + + @retval EFI_SUCCESS Response is copied successfully. + @retval Others Error occurs. + +**/ +EFI_STATUS +CopyRedfishResponse ( + IN REDFISH_RESPONSE *SrcResponse, + OUT REDFISH_RESPONSE *DstResponse + ); + +/** + Release all cache from list. + + @param[in] CacheList The list to be released. + + @retval EFI_SUCCESS All cache data are released. + @retval EFI_INVALID_PARAMETER CacheList is NULL. + +**/ +EFI_STATUS +ReleaseCacheList ( + IN REDFISH_HTTP_CACHE_LIST *CacheList + ); + +/** + Add new cache by given URI and HTTP response to specify List. + + @param[in] List Target cache list to add. + @param[in] Uri The URI string matching to this cache data. + @param[in] Response HTTP response. + + @retval EFI_SUCCESS Cache data is added. + @retval Others Fail to add cache data. + +**/ +EFI_STATUS +AddHttpCacheData ( + IN REDFISH_HTTP_CACHE_LIST *List, + IN EFI_STRING Uri, + IN REDFISH_RESPONSE *Response + ); + +/** + Delete a cache data by given cache instance. + + @param[in] List Target cache list to be removed. + @param[in] Data Pointer to the instance to be deleted. + + @retval EFI_SUCCESS Cache data is removed. + @retval Others Fail to remove cache data. + +**/ +EFI_STATUS +DeleteHttpCacheData ( + IN REDFISH_HTTP_CACHE_LIST *List, + IN REDFISH_HTTP_CACHE_DATA *Data + ); + +/** + This function release Redfish Payload. + + @param[in] Payload Pointer to payload instance. + + @retval EFI_SUCCESS Payload is released. + @retval Others Error occurs. + +**/ +EFI_STATUS +ReleaseRedfishPayload ( + IN REDFISH_PAYLOAD_PRIVATE *Payload + ); + +/** + This function creat new payload. Server and JsonObj are + copied to newly created payload. + + @param[in] Service Pointer to Service instance. + @param[in] JsonObj Pointer to JSON object. + + @retval REDFISH_PAYLOAD_PRIVATE Newly created payload. + @retval NULL Error occurs. + +**/ +REDFISH_PAYLOAD_PRIVATE * +CreateRedfishPayload ( + IN REDFISH_SERVICE_PRIVATE *Service, + IN EDKII_JSON_VALUE JsonValue + ); + +/** + This function release Redfish Service. + + @param[in] Service Pointer to service instance. + + @retval EFI_SUCCESS Service is released. + @retval Others Error occurs. + +**/ +EFI_STATUS +ReleaseRedfishService ( + IN REDFISH_SERVICE_PRIVATE *Service + ); + +/** + This function creat new service. Host and HostName are copied to + newly created service instance. + + @param[in] Host Host string. + @param[in] HostName Hostname string. + @param[in] BasicAuth Basic Authorization string. + @param[in] SessionToken Session token string. + @param[in] RestEx Rest EX protocol instance. + + @retval REDFISH_PAYLOAD_PRIVATE Newly created service. + @retval NULL Error occurs. + +**/ +REDFISH_SERVICE_PRIVATE * +CreateRedfishService ( + IN CHAR8 *Host, + IN CHAR8 *HostName, + IN CHAR8 *BasicAuth OPTIONAL, + IN CHAR8 *SessionToken OPTIONAL, + IN EFI_REST_EX_PROTOCOL *RestEx + ); + +/** + This function update session token in Redfish Service. + + @param[in] Service Pointer to service instance. + @param[in] Token Session token. + + @retval EFI_SUCCESS Session token is updated. + @retval Others Error occurs. + +**/ +EFI_STATUS +UpdateSessionToken ( + IN REDFISH_SERVICE_PRIVATE *Service, + IN CHAR8 *Token + ); + +#endif diff --git a/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.h b/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.h new file mode 100644 index 0000000000..cf6ba9cb47 --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.h @@ -0,0 +1,44 @@ +/** @file + Definitions of RedfishHttpDxe + + Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef EDKII_REDFISH_HTTP_DXE_H_ +#define EDKII_REDFISH_HTTP_DXE_H_ + +#include <Uefi.h> +#include <IndustryStandard/Http11.h> + +#include <Library/UefiLib.h> +#include <Library/BaseLib.h> +#include <Library/BaseMemoryLib.h> +#include <Library/RedfishContentCodingLib.h> +#include <Library/DebugLib.h> +#include <Library/HttpLib.h> +#include <Library/JsonLib.h> +#include <Library/UefiBootServicesTableLib.h> +#include <Library/MemoryAllocationLib.h> +#include <Library/RedfishDebugLib.h> +#include <Library/ReportStatusCodeLib.h> +#include <Library/PrintLib.h> + +#include <Protocol/Http.h> +#include <Protocol/EdkIIRedfishHttpProtocol.h> +#include <Protocol/EdkIIRedfishCredential.h> +#include <Protocol/RestEx.h> + +#define IS_EMPTY_STRING(a) ((a) == NULL || (a)[0] == '\0') +#define REDFISH_HTTP_CACHE_LIST_SIZE 0x80 +#define REDFISH_ERROR_MSG_MAX 128 +#define REDFISH_DEBUG_STRING_LENGTH 200 +#define REDFISH_HOST_NAME_MAX 64 // IPv6 maximum length (39) + "https://" (8) + port number (maximum 5) +#define REDFISH_HTTP_ERROR_REPORT "Redfish HTTP %a failure(0x%x): %s" +#define REDFISH_HTTP_CACHE_DEBUG DEBUG_MANAGEABILITY +#define REDFISH_HTTP_CACHE_DEBUG_DUMP DEBUG_MANAGEABILITY +#define REDFISH_HTTP_CACHE_DEBUG_REQUEST DEBUG_MANAGEABILITY + +#endif diff --git a/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.h b/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.h new file mode 100644 index 0000000000..d2f7cf4c27 --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.h @@ -0,0 +1,76 @@ +/** @file + Definitions of RedfishHttpOperation + + Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef EDKII_REDFISH_HTTP_OPERATION_H_ +#define EDKII_REDFISH_HTTP_OPERATION_H_ + +#include "RedfishHttpDxe.h" + +#define REDFISH_CONTENT_LENGTH_SIZE 80 +#define REDFISH_COMMON_HEADER_SIZE 5 +#define REDFISH_HTTP_HEADER_ODATA_VERSION_STR "OData-Version" +#define REDFISH_HTTP_HEADER_ODATA_VERSION_VALUE "4.0" +#define REDFISH_HTTP_HEADER_USER_AGENT_VALUE "edk2redfish" +#define REDFISH_HTTP_HEADER_CONNECTION_STR "Connection" +#define REDFISH_HTTP_HEADER_CONNECTION_VALUE "Keep-Alive" +#define REDFISH_HTTP_CONTENT_ENCODING_NONE "None" + +/** + This function free resources in Request. Request is no longer available + after this function returns successfully. + + @param[in] Request HTTP request to be released. + + @retval EFI_SUCCESS Resrouce is released successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +ReleaseRedfishRequest ( + IN REDFISH_REQUEST *Request + ); + +/** + This function free resources in given Response. + + @param[in] Response HTTP response to be released. + + @retval EFI_SUCCESS Resrouce is released successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +ReleaseRedfishResponse ( + IN REDFISH_RESPONSE *Response + ); + +/** + This function send Redfish request to Redfish service by calling + Rest Ex protocol. + + @param[in] Service Pointer to Redfish service. + @param[in] Uri Uri of Redfish service. + @param[in] Method HTTP method. + @param[in] Request Request data. This is optional. + @param[out] Response Redfish response data. + + @retval EFI_SUCCESS Request is sent and received successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +HttpSendReceive ( + IN REDFISH_SERVICE Service, + IN EFI_STRING Uri, + IN EFI_HTTP_METHOD Method, + IN REDFISH_REQUEST *Request OPTIONAL, + OUT REDFISH_RESPONSE *Response + ); + +#endif diff --git a/RedfishPkg/RedfishHttpDxe/RedfishHttpData.c b/RedfishPkg/RedfishHttpDxe/RedfishHttpData.c new file mode 100644 index 0000000000..bf95e9f8d4 --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpData.c @@ -0,0 +1,667 @@ +/** @file + RedfishHttpData handles internal data to support Redfish HTTP protocol. + + Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "RedfishHttpData.h" +#include "RedfishHttpOperation.h" + +/** + This function update session token in Redfish Service. + + @param[in] Service Pointer to service instance. + @param[in] Token Session token. + + @retval EFI_SUCCESS Session token is updated. + @retval Others Error occurs. + +**/ +EFI_STATUS +UpdateSessionToken ( + IN REDFISH_SERVICE_PRIVATE *Service, + IN CHAR8 *Token + ) +{ + if ((Service == NULL) || IS_EMPTY_STRING (Token)) { + return EFI_INVALID_PARAMETER; + } + + if (Service->SessionToken != NULL) { + FreePool (Service->SessionToken); + } + + Service->SessionToken = AllocateCopyPool (AsciiStrSize (Token), Token); + if (Service->SessionToken == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + return EFI_SUCCESS; +} + +/** + This function release Redfish Service. + + @param[in] Service Pointer to service instance. + + @retval EFI_SUCCESS Service is released. + @retval Others Error occurs. + +**/ +EFI_STATUS +ReleaseRedfishService ( + IN REDFISH_SERVICE_PRIVATE *Service + ) +{ + if (Service == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (Service->Host != NULL) { + FreePool (Service->Host); + } + + if (Service->HostName != NULL) { + FreePool (Service->HostName); + } + + if (Service->BasicAuth != NULL) { + ZeroMem (Service->BasicAuth, AsciiStrSize (Service->BasicAuth)); + FreePool (Service->BasicAuth); + } + + if (Service->SessionToken != NULL) { + ZeroMem (Service->SessionToken, AsciiStrSize (Service->SessionToken)); + FreePool (Service->SessionToken); + } + + FreePool (Service); + + return EFI_SUCCESS; +} + +/** + This function creat new service. Host and HostName are copied to + newly created service instance. + + @param[in] Host Host string. + @param[in] HostName Hostname string. + @param[in] BasicAuth Basic Authorization string. + @param[in] SessionToken Session token string. + @param[in] RestEx Rest EX protocol instance. + + @retval REDFISH_PAYLOAD_PRIVATE Newly created service. + @retval NULL Error occurs. + +**/ +REDFISH_SERVICE_PRIVATE * +CreateRedfishService ( + IN CHAR8 *Host, + IN CHAR8 *HostName, + IN CHAR8 *BasicAuth OPTIONAL, + IN CHAR8 *SessionToken OPTIONAL, + IN EFI_REST_EX_PROTOCOL *RestEx + ) +{ + REDFISH_SERVICE_PRIVATE *NewService; + UINTN AuthStrSize; + + if (IS_EMPTY_STRING (Host) || IS_EMPTY_STRING (HostName) || (RestEx == NULL)) { + return NULL; + } + + NewService = AllocateZeroPool (sizeof (REDFISH_SERVICE_PRIVATE)); + if (NewService == NULL) { + return NULL; + } + + NewService->Signature = REDFISH_HTTP_SERVICE_SIGNATURE; + NewService->Host = AllocateCopyPool (AsciiStrSize (Host), Host); + if (NewService->Host == NULL) { + goto ON_ERROR; + } + + NewService->HostName = AllocateCopyPool (AsciiStrSize (HostName), HostName); + if (NewService->HostName == NULL) { + goto ON_ERROR; + } + + if (!IS_EMPTY_STRING (BasicAuth)) { + AuthStrSize = AsciiStrSize (BasicAuth) + AsciiStrLen (REDFISH_HTTP_BASIC_AUTH_STR); + NewService->BasicAuth = AllocateZeroPool (AuthStrSize); + if (NewService->BasicAuth == NULL) { + goto ON_ERROR; + } + + AsciiSPrint (NewService->BasicAuth, AuthStrSize, "%a%a", REDFISH_HTTP_BASIC_AUTH_STR, BasicAuth); + } + + if (!IS_EMPTY_STRING (SessionToken)) { + NewService->SessionToken = AllocateCopyPool (AsciiStrSize (SessionToken), SessionToken); + if (NewService->SessionToken == NULL) { + goto ON_ERROR; + } + } + + NewService->RestEx = RestEx; + + return NewService; + +ON_ERROR: + + ReleaseRedfishService (NewService); + + return NULL; +} + +/** + This function release Redfish Payload. + + @param[in] Payload Pointer to payload instance. + + @retval EFI_SUCCESS Payload is released. + @retval Others Error occurs. + +**/ +EFI_STATUS +ReleaseRedfishPayload ( + IN REDFISH_PAYLOAD_PRIVATE *Payload + ) +{ + if (Payload == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (Payload->Service != NULL) { + ReleaseRedfishService (Payload->Service); + } + + if (Payload->JsonValue != NULL) { + JsonValueFree (Payload->JsonValue); + } + + FreePool (Payload); + + return EFI_SUCCESS; +} + +/** + This function creat new payload. Server and JsonObj are + copied to newly created payload. + + @param[in] Service Pointer to Service instance. + @param[in] JsonValue Pointer to JSON value. + + @retval REDFISH_PAYLOAD_PRIVATE Newly created payload. + @retval NULL Error occurs. + +**/ +REDFISH_PAYLOAD_PRIVATE * +CreateRedfishPayload ( + IN REDFISH_SERVICE_PRIVATE *Service, + IN EDKII_JSON_VALUE JsonValue + ) +{ + REDFISH_PAYLOAD_PRIVATE *NewPayload; + + if ((Service == NULL) || (JsonValue == NULL)) { + return NULL; + } + + NewPayload = AllocateZeroPool (sizeof (REDFISH_PAYLOAD_PRIVATE)); + if (NewPayload == NULL) { + return NULL; + } + + NewPayload->Signature = REDFISH_HTTP_PAYLOAD_SIGNATURE; + NewPayload->Service = CreateRedfishService (Service->Host, Service->HostName, Service->BasicAuth, Service->SessionToken, Service->RestEx); + if (NewPayload->Service == NULL) { + goto ON_ERROR; + } + + NewPayload->JsonValue = JsonValueClone (JsonValue); + if (NewPayload->JsonValue == NULL) { + goto ON_ERROR; + } + + return NewPayload; + +ON_ERROR: + + ReleaseRedfishPayload (NewPayload); + + return NULL; +} + +/** + This function copy the data in SrcResponse to DstResponse. + + @param[in] SrcResponse Source Response to copy. + @param[out] DstResponse Destination Response. + + @retval EFI_SUCCESS Response is copied successfully. + @retval Others Error occurs. + +**/ +EFI_STATUS +CopyRedfishResponse ( + IN REDFISH_RESPONSE *SrcResponse, + OUT REDFISH_RESPONSE *DstResponse + ) +{ + REDFISH_PAYLOAD_PRIVATE *Payload; + UINTN Index; + + if ((SrcResponse == NULL) || (DstResponse == NULL)) { + return EFI_INVALID_PARAMETER; + } + + if (SrcResponse == DstResponse) { + return EFI_SUCCESS; + } + + // + // Status code + // + if (SrcResponse->StatusCode != NULL) { + DstResponse->StatusCode = AllocateCopyPool (sizeof (EFI_HTTP_STATUS_CODE), SrcResponse->StatusCode); + if (DstResponse->StatusCode == NULL) { + goto ON_ERROR; + } + } + + // + // Header + // + if ((SrcResponse->HeaderCount > 0) && (SrcResponse->Headers != NULL)) { + DstResponse->HeaderCount = 0; + DstResponse->Headers = AllocateZeroPool (sizeof (EFI_HTTP_HEADER) * SrcResponse->HeaderCount); + if (DstResponse->Headers == NULL) { + goto ON_ERROR; + } + + for (Index = 0; Index < SrcResponse->HeaderCount; Index++) { + DstResponse->Headers[Index].FieldName = AllocateCopyPool (AsciiStrSize (SrcResponse->Headers[Index].FieldName), SrcResponse->Headers[Index].FieldName); + if (DstResponse->Headers[Index].FieldName == NULL) { + goto ON_ERROR; + } + + DstResponse->Headers[Index].FieldValue = AllocateCopyPool (AsciiStrSize (SrcResponse->Headers[Index].FieldValue), SrcResponse->Headers[Index].FieldValue); + if (DstResponse->Headers[Index].FieldValue == NULL) { + goto ON_ERROR; + } + + DstResponse->HeaderCount += 1; + } + } + + // + // Payload + // + if (SrcResponse->Payload != NULL) { + Payload = (REDFISH_PAYLOAD_PRIVATE *)SrcResponse->Payload; + if (Payload->Signature != REDFISH_HTTP_PAYLOAD_SIGNATURE) { + DEBUG ((DEBUG_ERROR, "%a: signature check failure\n", __func__)); + goto ON_ERROR; + } + + DstResponse->Payload = CreateRedfishPayload (Payload->Service, Payload->JsonValue); + if (DstResponse->Payload == NULL) { + goto ON_ERROR; + } + } + + return EFI_SUCCESS; + +ON_ERROR: + + ReleaseRedfishResponse (DstResponse); + + return EFI_OUT_OF_RESOURCES; +} + +/** + This function clone input response and return to caller + + @param[in] Response Response to clone. + + @retval REDFISH_RESPONSE * Response is cloned. + @retval NULL Errors occur. + +**/ +REDFISH_RESPONSE * +CloneRedfishResponse ( + IN REDFISH_RESPONSE *Response + ) +{ + EFI_STATUS Status; + REDFISH_RESPONSE *NewResponse; + + if (Response == NULL) { + return NULL; + } + + NewResponse = AllocateZeroPool (sizeof (REDFISH_RESPONSE)); + if (NewResponse == NULL) { + return NULL; + } + + Status = CopyRedfishResponse (Response, NewResponse); + if (EFI_ERROR (Status)) { + FreePool (NewResponse); + return NULL; + } + + return NewResponse; +} + +/** + Release REDFISH_HTTP_CACHE_DATA resource + + @param[in] Data Pointer to REDFISH_HTTP_CACHE_DATA instance + + @retval EFI_SUCCESS REDFISH_HTTP_CACHE_DATA is released successfully. + @retval EFI_INVALID_PARAMETER Data is NULL + +**/ +EFI_STATUS +ReleaseHttpCacheData ( + IN REDFISH_HTTP_CACHE_DATA *Data + ) +{ + if (Data == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (Data->Uri != NULL) { + FreePool (Data->Uri); + } + + if (Data->Response != NULL) { + ReleaseRedfishResponse (Data->Response); + FreePool (Data->Response); + } + + FreePool (Data); + + return EFI_SUCCESS; +} + +/** + Create new cache data. + + @param[in] Uri The URI string matching to this cache data. + @param[in] Response HTTP response. + + @retval REDFISH_HTTP_CACHE_DATA * Pointer to newly created cache data. + @retval NULL No memory available. + +**/ +REDFISH_HTTP_CACHE_DATA * +NewHttpCacheData ( + IN EFI_STRING Uri, + IN REDFISH_RESPONSE *Response + ) +{ + REDFISH_HTTP_CACHE_DATA *NewData; + UINTN Size; + + if (IS_EMPTY_STRING (Uri) || (Response == NULL)) { + return NULL; + } + + NewData = AllocateZeroPool (sizeof (REDFISH_HTTP_CACHE_DATA)); + if (NewData == NULL) { + return NULL; + } + + NewData->Signature = REDFISH_HTTP_CACHE_SIGNATURE; + Size = StrSize (Uri); + NewData->Uri = AllocateCopyPool (Size, Uri); + if (NewData->Uri == NULL) { + goto ON_ERROR; + } + + NewData->Response = Response; + NewData->HitCount = 1; + + return NewData; + +ON_ERROR: + + if (NewData != NULL) { + ReleaseHttpCacheData (NewData); + } + + return NULL; +} + +/** + Search on given ListHeader for given URI string. + + @param[in] ListHeader Target list to search. + @param[in] Uri Target URI to search. + + @retval REDFISH_HTTP_CACHE_DATA Target cache data is found. + @retval NULL No cache data with given URI is found. + +**/ +REDFISH_HTTP_CACHE_DATA * +FindHttpCacheData ( + IN LIST_ENTRY *ListHeader, + IN EFI_STRING Uri + ) +{ + LIST_ENTRY *List; + REDFISH_HTTP_CACHE_DATA *Data; + + if (IS_EMPTY_STRING (Uri)) { + return NULL; + } + + if (IsListEmpty (ListHeader)) { + return NULL; + } + + Data = NULL; + List = GetFirstNode (ListHeader); + while (!IsNull (ListHeader, List)) { + Data = REDFISH_HTTP_CACHE_FROM_LIST (List); + + if (StrCmp (Data->Uri, Uri) == 0) { + return Data; + } + + List = GetNextNode (ListHeader, List); + } + + return NULL; +} + +/** + Search on given ListHeader and return cache data with minimum hit count. + + @param[in] ListHeader Target list to search. + + @retval REDFISH_HTTP_CACHE_DATA Target cache data is returned. + @retval NULL No cache data is found. + +**/ +REDFISH_HTTP_CACHE_DATA * +FindUnusedHttpCacheData ( + IN LIST_ENTRY *ListHeader + ) +{ + LIST_ENTRY *List; + REDFISH_HTTP_CACHE_DATA *Data; + REDFISH_HTTP_CACHE_DATA *UnusedData; + UINTN HitCount; + + if (IsListEmpty (ListHeader)) { + return NULL; + } + + Data = NULL; + UnusedData = NULL; + HitCount = 0; + + List = GetFirstNode (ListHeader); + Data = REDFISH_HTTP_CACHE_FROM_LIST (List); + UnusedData = Data; + HitCount = Data->HitCount; + List = GetNextNode (ListHeader, List); + + while (!IsNull (ListHeader, List)) { + Data = REDFISH_HTTP_CACHE_FROM_LIST (List); + + if (Data->HitCount < HitCount) { + HitCount = Data->HitCount; + UnusedData = Data; + } + + List = GetNextNode (ListHeader, List); + } + + return UnusedData; +} + +/** + Delete a cache data by given cache instance. + + @param[in] List Target cache list to be removed. + @param[in] Data Pointer to the instance to be deleted. + + @retval EFI_SUCCESS Cache data is removed. + @retval Others Fail to remove cache data. + +**/ +EFI_STATUS +DeleteHttpCacheData ( + IN REDFISH_HTTP_CACHE_LIST *List, + IN REDFISH_HTTP_CACHE_DATA *Data + ) +{ + if ((List == NULL) || (Data == NULL)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: delete: %s\n", __func__, Data->Uri)); + + RemoveEntryList (&Data->List); + --List->Count; + + return ReleaseHttpCacheData (Data); +} + +/** + Add new cache by given URI and HTTP response to specify List. + + @param[in] List Target cache list to add. + @param[in] Uri The URI string matching to this cache data. + @param[in] Response HTTP response. + + @retval EFI_SUCCESS Cache data is added. + @retval Others Fail to add cache data. + +**/ +EFI_STATUS +AddHttpCacheData ( + IN REDFISH_HTTP_CACHE_LIST *List, + IN EFI_STRING Uri, + IN REDFISH_RESPONSE *Response + ) +{ + REDFISH_HTTP_CACHE_DATA *NewData; + REDFISH_HTTP_CACHE_DATA *OldData; + REDFISH_HTTP_CACHE_DATA *UnusedData; + REDFISH_RESPONSE *NewResponse; + + if ((List == NULL) || IS_EMPTY_STRING (Uri) || (Response == NULL)) { + return EFI_INVALID_PARAMETER; + } + + // + // If same cache data exist, replace it with latest one. + // + OldData = FindHttpCacheData (&List->Head, Uri); + if (OldData != NULL) { + DeleteHttpCacheData (List, OldData); + } + + // + // Check capacity + // + if (List->Count >= List->Capacity) { + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: list is full and retire unused cache\n", __func__)); + UnusedData = FindUnusedHttpCacheData (&List->Head); + if (UnusedData == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + DeleteHttpCacheData (List, UnusedData); + } + + // + // Clone a local copy + // + NewResponse = CloneRedfishResponse (Response); + if (NewResponse == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + NewData = NewHttpCacheData (Uri, NewResponse); + if (NewData == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + InsertTailList (&List->Head, &NewData->List); + ++List->Count; + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: cache(%d/%d) %s\n", __func__, List->Count, List->Capacity, NewData->Uri)); + + return EFI_SUCCESS; +} + +/** + Release all cache from list. + + @param[in] CacheList The list to be released. + + @retval EFI_SUCCESS All cache data are released. + @retval EFI_INVALID_PARAMETER CacheList is NULL. + +**/ +EFI_STATUS +ReleaseCacheList ( + IN REDFISH_HTTP_CACHE_LIST *CacheList + ) +{ + LIST_ENTRY *List; + LIST_ENTRY *Next; + REDFISH_HTTP_CACHE_DATA *Data; + + if (CacheList == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (IsListEmpty (&CacheList->Head)) { + return EFI_SUCCESS; + } + + Data = NULL; + Next = NULL; + List = GetFirstNode (&CacheList->Head); + while (!IsNull (&CacheList->Head, List)) { + Data = REDFISH_HTTP_CACHE_FROM_LIST (List); + Next = GetNextNode (&CacheList->Head, List); + + DeleteHttpCacheData (CacheList, Data); + + List = Next; + } + + return EFI_SUCCESS; +} diff --git a/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.c b/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.c new file mode 100644 index 0000000000..39958d4865 --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.c @@ -0,0 +1,1344 @@ +/** @file + RedfishHttpDxe produces EdkIIRedfishHttpProtocol + for EDK2 Redfish Feature driver to do HTTP operations. + + Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "RedfishHttpDxe.h" +#include "RedfishHttpData.h" +#include "RedfishHttpOperation.h" + +REDFISH_HTTP_CACHE_PRIVATE *mRedfishHttpCachePrivate = NULL; + +/** + Debug output the cache list. + + @param[in] Msg Debug message string. + @param[in] ErrorLevel Output error level. + @param[in] CacheList Target list to dump. + + @retval EFI_SUCCESS Debug dump finished. + @retval EFI_INVALID_PARAMETER HttpCacheList is NULL. + +**/ +EFI_STATUS +DebugPrintHttpCacheList ( + IN CONST CHAR8 *Msg, + IN UINTN ErrorLevel, + IN REDFISH_HTTP_CACHE_LIST *CacheList + ) +{ + LIST_ENTRY *List; + REDFISH_HTTP_CACHE_DATA *Data; + UINTN Index; + + if (CacheList == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (!IS_EMPTY_STRING (Msg)) { + DEBUG ((ErrorLevel, "%a\n", Msg)); + } + + if (IsListEmpty (&CacheList->Head)) { + DEBUG ((ErrorLevel, "list is empty\n")); + return EFI_NOT_FOUND; + } + + DEBUG ((ErrorLevel, "list count: %d capacity: %d\n", CacheList->Count, CacheList->Capacity)); + Data = NULL; + Index = 0; + List = GetFirstNode (&CacheList->Head); + while (!IsNull (&CacheList->Head, List)) { + Data = REDFISH_HTTP_CACHE_FROM_LIST (List); + + DEBUG ((ErrorLevel, "%d) Uri: %s Hit: %d\n", ++Index, Data->Uri, Data->HitCount)); + + List = GetNextNode (&CacheList->Head, List); + } + + return EFI_SUCCESS; +} + +/** + + Check HTTP status code to see if we like to retry HTTP request or not. + + @param[in] StatusCode HTTP status code. + + @retval BOOLEAN Return true when we like to retry request. + Return false when we don't want to retry request. + +**/ +BOOLEAN +RedfishRetryRequired ( + IN EFI_HTTP_STATUS_CODE *StatusCode + ) +{ + if (StatusCode == NULL) { + return TRUE; + } + + if ((*StatusCode == HTTP_STATUS_500_INTERNAL_SERVER_ERROR) || + (*StatusCode == HTTP_STATUS_UNSUPPORTED_STATUS)) + { + return TRUE; + } + + return FALSE; +} + +/** + + Convert Unicode string to ASCII string. It's call responsibility to release returned buffer. + + @param[in] UnicodeStr Unicode string to convert. + + @retval CHAR8 * ASCII string returned. + @retval NULL Errors occur. + +**/ +CHAR8 * +StringUnicodeToAscii ( + IN EFI_STRING UnicodeStr + ) +{ + CHAR8 *AsciiStr; + UINTN AsciiStrSize; + EFI_STATUS Status; + + if (IS_EMPTY_STRING (UnicodeStr)) { + return NULL; + } + + AsciiStrSize = StrLen (UnicodeStr) + 1; + AsciiStr = AllocateZeroPool (AsciiStrSize); + if (AsciiStr == NULL) { + return NULL; + } + + Status = UnicodeStrToAsciiStrS (UnicodeStr, AsciiStr, AsciiStrSize); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "UnicodeStrToAsciiStrS failed: %r\n", Status)); + FreePool (AsciiStr); + return NULL; + } + + return AsciiStr; +} + +/** + Return HTTP method in ASCII string. Caller does not need + to free returned string buffer. + + @param[in] Method HTTP method. + + @retval CHAR8 * Method in string. +**/ +CHAR8 * +HttpMethodToString ( + IN EFI_HTTP_METHOD Method + ) +{ + switch (Method) { + case HttpMethodGet: + return HTTP_METHOD_GET; + break; + case HttpMethodPost: + return HTTP_METHOD_POST; + break; + case HttpMethodPatch: + return HTTP_METHOD_PATCH; + break; + case HttpMethodPut: + return HTTP_METHOD_PUT; + break; + case HttpMethodDelete: + return HTTP_METHOD_DELETE; + break; + default: + break; + } + + return "Unknown"; +} + +/** + Report HTTP communication error via report status code. + + @param[in] Method HTTP method. + @param[in] Uri The URI which has failure. + @param[in] HttpStatusCode HTTP status code. + +**/ +VOID +ReportHttpError ( + IN EFI_HTTP_METHOD Method, + IN EFI_STRING Uri, + IN EFI_HTTP_STATUS_CODE *HttpStatusCode OPTIONAL + ) +{ + CHAR8 ErrorMsg[REDFISH_ERROR_MSG_MAX]; + + if (IS_EMPTY_STRING (Uri)) { + DEBUG ((DEBUG_ERROR, "%a: no URI to report error status\n", __func__)); + return; + } + + // + // Report failure of URI and HTTP status code. + // + AsciiSPrint (ErrorMsg, sizeof (ErrorMsg), REDFISH_HTTP_ERROR_REPORT, HttpMethodToString (Method), (HttpStatusCode == NULL ? HTTP_STATUS_UNSUPPORTED_STATUS : *HttpStatusCode), Uri); + DEBUG ((DEBUG_ERROR, "%a\n", ErrorMsg)); + // + // TODO: + // Below PI status code is approved by PIWG and wait for specification published. + // We will uncomment below report status code after PI status code get published. + // REF: https://bugzilla.tianocore.org/show_bug.cgi?id=4483 + // + // REPORT_STATUS_CODE_WITH_EXTENDED_DATA ( + // EFI_ERROR_CODE | EFI_ERROR_MAJOR, + // EFI_COMPUTING_UNIT_MANAGEABILITY | EFI_MANAGEABILITY_EC_REDFISH_COMMUNICATION_ERROR, + // ErrorMsg, + // AsciiStrSize (ErrorMsg) + // ); +} + +/** + This function create Redfish service. It's caller's responsibility to free returned + Redfish service by calling FreeService (). + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] RedfishConfigServiceInfo Redfish config service information. + + @retval REDFISH_SERVICE Redfish service is created. + @retval NULL Errors occur. + +**/ +REDFISH_SERVICE +EFIAPI +RedfishCreateRedfishService ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo + ) +{ + EFI_STATUS Status; + REDFISH_HTTP_CACHE_PRIVATE *Private; + REDFISH_SERVICE_PRIVATE *NewService; + CHAR8 *AsciiLocation; + CHAR8 *Host; + CHAR8 *BasicAuthString; + UINTN BasicAuthStrSize; + CHAR8 *EncodedAuthString; + UINTN EncodedAuthStrSize; + EDKII_REDFISH_AUTH_METHOD AuthMethod; + CHAR8 *Username; + CHAR8 *Password; + UINTN UsernameSize; + UINTN PasswordSize; + EFI_REST_EX_PROTOCOL *RestEx; + + if ((This == NULL) || (RedfishConfigServiceInfo == NULL)) { + return NULL; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: service location: %s\n", __func__, RedfishConfigServiceInfo->RedfishServiceLocation)); + + Private = REDFISH_HTTP_CACHE_PRIVATE_FROM_THIS (This); + BasicAuthString = NULL; + EncodedAuthString = NULL; + Username = NULL; + Password = NULL; + NewService = NULL; + AsciiLocation = NULL; + Host = NULL; + BasicAuthStrSize = 0; + EncodedAuthStrSize = 0; + UsernameSize = 0; + PasswordSize = 0; + + // + // Build host and host name from service location + // + if (!IS_EMPTY_STRING (RedfishConfigServiceInfo->RedfishServiceLocation)) { + AsciiLocation = StringUnicodeToAscii (RedfishConfigServiceInfo->RedfishServiceLocation); + if (AsciiLocation == NULL) { + goto ON_RELEASE; + } + + Host = AllocateZeroPool (REDFISH_HOST_NAME_MAX); + if (AsciiLocation == NULL) { + goto ON_RELEASE; + } + + if (RedfishConfigServiceInfo->RedfishServiceUseHttps) { + AsciiSPrint (Host, REDFISH_HOST_NAME_MAX, "https://%a", AsciiLocation); + } else { + AsciiSPrint (Host, REDFISH_HOST_NAME_MAX, "http://%a", AsciiLocation); + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Host: %a\n", __func__, Host)); + } + + // + // Find Rest Ex protocol + // + if (RedfishConfigServiceInfo->RedfishServiceRestExHandle != NULL) { + Status = gBS->HandleProtocol ( + RedfishConfigServiceInfo->RedfishServiceRestExHandle, + &gEfiRestExProtocolGuid, + (VOID **)&RestEx + ); + } else { + DEBUG ((DEBUG_ERROR, "%a: Rest Ex protocol is not available\n", __func__)); + goto ON_RELEASE; + } + + // + // Get credential + // + if (Private->CredentialProtocol == NULL) { + // + // No credential available on this system. + // + DEBUG ((DEBUG_WARN, "%a: no credential protocol available\n", __func__)); + } else { + Status = Private->CredentialProtocol->GetAuthInfo ( + Private->CredentialProtocol, + &AuthMethod, + &Username, + &Password + ); + if (EFI_ERROR (Status) || IS_EMPTY_STRING (Username) || IS_EMPTY_STRING (Password)) { + DEBUG ((DEBUG_ERROR, "%a: cannot get authentication information: %r\n", __func__, Status)); + goto ON_RELEASE; + } else { + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Auth method: 0x%x username: %a password: %a\n", __func__, AuthMethod, Username, Password)); + + // + // Perform base64 encoding (RFC 7617) + // + UsernameSize = AsciiStrSize (Username); + PasswordSize = AsciiStrSize (Password); + BasicAuthStrSize = UsernameSize + PasswordSize; // one byte taken from null-terminator for ':' + BasicAuthString = AllocateZeroPool (BasicAuthStrSize); + if (BasicAuthString == NULL) { + goto ON_RELEASE; + } + + AsciiSPrint ( + BasicAuthString, + BasicAuthStrSize, + "%a:%a", + Username, + Password + ); + + Status = Base64Encode ( + (CONST UINT8 *)BasicAuthString, + BasicAuthStrSize, + EncodedAuthString, + &EncodedAuthStrSize + ); + if ((Status == EFI_BUFFER_TOO_SMALL) && (EncodedAuthStrSize > 0)) { + EncodedAuthString = AllocateZeroPool (EncodedAuthStrSize); + if (EncodedAuthString == NULL) { + goto ON_RELEASE; + } + + Status = Base64Encode ( + (CONST UINT8 *)BasicAuthString, + BasicAuthStrSize, + EncodedAuthString, + &EncodedAuthStrSize + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Base64Encode failure: %r\n", __func__, Status)); + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Basic authorization: %a\n", __func__, EncodedAuthString)); + } else { + DEBUG ((DEBUG_ERROR, "%a: Base64Encode failure: %r\n", __func__, Status)); + goto ON_RELEASE; + } + } + } + + NewService = CreateRedfishService (Host, AsciiLocation, EncodedAuthString, NULL, RestEx); + if (NewService == NULL) { + DEBUG ((DEBUG_ERROR, "%a: CreateRedfishService\n", __func__)); + } + +ON_RELEASE: + + if (BasicAuthString != NULL) { + ZeroMem (BasicAuthString, BasicAuthStrSize); + FreePool (BasicAuthString); + } + + if (EncodedAuthString != NULL) { + ZeroMem (BasicAuthString, EncodedAuthStrSize); + FreePool (EncodedAuthString); + } + + if (Username != NULL) { + ZeroMem (Username, UsernameSize); + FreePool (Username); + } + + if (Password != NULL) { + ZeroMem (Password, PasswordSize); + FreePool (Password); + } + + if (AsciiLocation != NULL) { + FreePool (AsciiLocation); + } + + if (Host != NULL) { + FreePool (Host); + } + + return NewService; +} + +/** + This function free resources in Redfish service. RedfishService is no longer available + after this function returns successfully. + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] RedfishService Pointer to Redfish service to be released. + + @retval EFI_SUCCESS Resrouce is released successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishFreeRedfishService ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_SERVICE RedfishService + ) +{ + REDFISH_SERVICE_PRIVATE *Service; + + if ((This == NULL) || (RedfishService == NULL)) { + return EFI_INVALID_PARAMETER; + } + + Service = (REDFISH_SERVICE_PRIVATE *)RedfishService; + if (Service->Signature != REDFISH_HTTP_SERVICE_SIGNATURE) { + DEBUG ((DEBUG_ERROR, "%a: signature check failure\n", __func__)); + } + + return ReleaseRedfishService (Service); +} + +/** + This function returns JSON value in given RedfishPayload. Returned JSON value + is a reference to the JSON value in RedfishPayload. Any modification to returned + JSON value will change JSON value in RedfishPayload. + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] RedfishPayload Pointer to Redfish payload. + + @retval EDKII_JSON_VALUE JSON value is returned. + @retval NULL Errors occur. + +**/ +EDKII_JSON_VALUE +EFIAPI +RedfishJsonInRedfishPayload ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_PAYLOAD RedfishPayload + ) +{ + REDFISH_PAYLOAD_PRIVATE *Payload; + + if ((This == NULL) || (RedfishPayload == NULL)) { + return NULL; + } + + Payload = (REDFISH_PAYLOAD_PRIVATE *)RedfishPayload; + if (Payload->Signature != REDFISH_HTTP_PAYLOAD_SIGNATURE) { + DEBUG ((DEBUG_ERROR, "%a: signature check failure\n", __func__)); + } + + return Payload->JsonValue; +} + +/** + Perform HTTP GET to Get redfish resource from given resource URI with + cache mechanism supported. It's caller's responsibility to free Response + by calling FreeResponse (). + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] Service Redfish service instance to perform HTTP GET. + @param[in] Uri Target resource URI. + @param[in] Request Additional request context. This is optional. + @param[out] Response HTTP response from redfish service. + @param[in] UseCache If it is TRUE, this function will search for + cache first. If it is FALSE, this function + will query Redfish URI directly. + + @retval EFI_SUCCESS Resrouce is returned successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishGetResource ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_SERVICE Service, + IN EFI_STRING Uri, + IN REDFISH_REQUEST *Request OPTIONAL, + OUT REDFISH_RESPONSE *Response, + IN BOOLEAN UseCache + ) +{ + EFI_STATUS Status; + REDFISH_HTTP_CACHE_DATA *CacheData; + UINTN RetryCount; + REDFISH_HTTP_CACHE_PRIVATE *Private; + + if ((This == NULL) || (Service == NULL) || (Response == NULL) || IS_EMPTY_STRING (Uri)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Get URI: %s cache: %a\n", __func__, Uri, (UseCache ? "true" : "false"))); + + Private = REDFISH_HTTP_CACHE_PRIVATE_FROM_THIS (This); + CacheData = NULL; + RetryCount = 0; + ZeroMem (Response, sizeof (REDFISH_RESPONSE)); + + if (Private->CacheDisabled) { + UseCache = FALSE; + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: cache is disabled by PCD!\n", __func__)); + } + + // + // Search for cache list. + // + if (UseCache) { + CacheData = FindHttpCacheData (&Private->CacheList.Head, Uri); + if (CacheData != NULL) { + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: cache hit! %s\n", __func__, Uri)); + + // + // Copy cached response to caller's buffer. + // + Status = CopyRedfishResponse (CacheData->Response, Response); + CacheData->HitCount += 1; + return Status; + } + } + + // + // Get resource from redfish service. + // + do { + RetryCount += 1; + Status = HttpSendReceive ( + Service, + Uri, + HttpMethodGet, + Request, + Response + ); + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: HTTP request: %s :%r\n", __func__, Uri, Status)); + if (!EFI_ERROR (Status) || (RetryCount >= Private->RetrySetting.MaximumRetryGet)) { + break; + } + + // + // Retry when BMC is not ready. + // + if ((Response->StatusCode != NULL)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + + if (!RedfishRetryRequired (Response->StatusCode)) { + break; + } + + // + // Release response for next round of request. + // + This->FreeResponse (This, Response); + } + + DEBUG ((DEBUG_WARN, "%a: RedfishGetByUriEx failed, retry (%d/%d)\n", __func__, RetryCount, Private->RetrySetting.MaximumRetryGet)); + if (Private->RetrySetting.RetryWait > 0) { + gBS->Stall (Private->RetrySetting.RetryWait); + } + } while (TRUE); + + if (EFI_ERROR (Status)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + // + // Report status code for Redfish failure + // + ReportHttpError (HttpMethodGet, Uri, Response->StatusCode); + DEBUG ((DEBUG_ERROR, "%a: get %s failed (%d/%d): %r\n", __func__, Uri, RetryCount, Private->RetrySetting.MaximumRetryGet, Status)); + goto ON_RELEASE; + } + + if (!Private->CacheDisabled) { + // + // Keep response in cache list + // + Status = AddHttpCacheData (&Private->CacheList, Uri, Response); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: failed to cache %s: %r\n", __func__, Uri, Status)); + goto ON_RELEASE; + } + + DEBUG_CODE ( + DebugPrintHttpCacheList (__func__, REDFISH_HTTP_CACHE_DEBUG_DUMP, &Private->CacheList); + ); + } + +ON_RELEASE: + + return Status; +} + +/** + This function free resources in Request. Request is no longer available + after this function returns successfully. + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] Request HTTP request to be released. + + @retval EFI_SUCCESS Resrouce is released successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishFreeRequest ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_REQUEST *Request + ) +{ + if ((This == NULL) || (Request == NULL)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: entry\n", __func__)); + + return ReleaseRedfishRequest (Request); +} + +/** + This function free resources in given Response. + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] Response HTTP response to be released. + + @retval EFI_SUCCESS Resrouce is released successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishFreeResponse ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_RESPONSE *Response + ) +{ + if ((This == NULL) || (Response == NULL)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: entry\n", __func__)); + + return ReleaseRedfishResponse (Response); +} + +/** + This function expire the cached response of given URI. + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] Uri Target response of URI. + + @retval EFI_SUCCESS Target response is expired successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishExpireResponse ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN EFI_STRING Uri + ) +{ + REDFISH_HTTP_CACHE_PRIVATE *Private; + REDFISH_HTTP_CACHE_DATA *CacheData; + + if ((This == NULL) || IS_EMPTY_STRING (Uri)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: expire URI: %s\n", __func__, Uri)); + + Private = REDFISH_HTTP_CACHE_PRIVATE_FROM_THIS (This); + + CacheData = FindHttpCacheData (&Private->CacheList.Head, Uri); + if (CacheData == NULL) { + return EFI_NOT_FOUND; + } + + return DeleteHttpCacheData (&Private->CacheList, CacheData); +} + +/** + Perform HTTP PATCH to send redfish resource to given resource URI. + It's caller's responsibility to free Response by calling FreeResponse (). + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] Service Redfish service instance to perform HTTP PATCH. + @param[in] Uri Target resource URI. + @param[in] Content Data to patch. + @param[in] ContentSize Size of the Content to be send to Redfish service. + This is optional. When ContentSize is 0, ContentSize + is the size of Content. + @param[in] ContentType Type of the Content to be send to Redfish service. + This is optional. When ContentType is NULL, content + type HTTP_CONTENT_TYPE_APP_JSON will be used. + @param[out] Response HTTP response from redfish service. + + @retval EFI_SUCCESS Resrouce is returned successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishPatchResource ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_SERVICE Service, + IN EFI_STRING Uri, + IN CHAR8 *Content, + IN UINTN ContentSize OPTIONAL, + IN CHAR8 *ContentType OPTIONAL, + OUT REDFISH_RESPONSE *Response + ) +{ + EFI_STATUS Status; + UINTN RetryCount; + REDFISH_REQUEST Request; + REDFISH_HTTP_CACHE_PRIVATE *Private; + + if ((This == NULL) || (Service == NULL) || (Response == NULL) || IS_EMPTY_STRING (Uri) || IS_EMPTY_STRING (Content)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Patch URI: %s\n", __func__, Uri)); + + Private = REDFISH_HTTP_CACHE_PRIVATE_FROM_THIS (This); + RetryCount = 0; + ZeroMem (Response, sizeof (REDFISH_RESPONSE)); + ZeroMem (&Request, sizeof (REDFISH_REQUEST)); + + Request.Content = Content; + Request.ContentLength = ContentSize; + Request.ContentType = ContentType; + + // + // Patch resource to redfish service. + // + do { + RetryCount += 1; + Status = HttpSendReceive ( + Service, + Uri, + HttpMethodPatch, + &Request, + Response + ); + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: HTTP request: %s :%r\n", __func__, Uri, Status)); + if (!EFI_ERROR (Status) || (RetryCount >= Private->RetrySetting.MaximumRetryPatch)) { + break; + } + + // + // Retry when BMC is not ready. + // + if ((Response->StatusCode != NULL)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + + if (!RedfishRetryRequired (Response->StatusCode)) { + break; + } + + // + // Release response for next round of request. + // + This->FreeResponse (This, Response); + } + + DEBUG ((DEBUG_WARN, "%a: RedfishPatchToUriEx failed, retry (%d/%d)\n", __func__, RetryCount, Private->RetrySetting.MaximumRetryPatch)); + if (Private->RetrySetting.RetryWait > 0) { + gBS->Stall (Private->RetrySetting.RetryWait); + } + } while (TRUE); + + // + // Redfish resource is updated. Automatically expire the cached response + // so application can directly get resource from Redfish service again. + // + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Resource is updated, expire URI: %s\n", __func__, Uri)); + RedfishExpireResponse (This, Uri); + + if (EFI_ERROR (Status)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + // + // Report status code for Redfish failure + // + ReportHttpError (HttpMethodPatch, Uri, Response->StatusCode); + DEBUG ((DEBUG_ERROR, "%a: patch %s failed (%d/%d): %r\n", __func__, Uri, RetryCount, Private->RetrySetting.MaximumRetryPatch, Status)); + goto ON_RELEASE; + } + +ON_RELEASE: + + return Status; +} + +/** + Perform HTTP PUT to send redfish resource to given resource URI. + It's caller's responsibility to free Response by calling FreeResponse (). + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] Service Redfish service instance to perform HTTP PUT. + @param[in] Uri Target resource URI. + @param[in] Content Data to put. + @param[in] ContentSize Size of the Content to be send to Redfish service. + This is optional. When ContentSize is 0, ContentSize + is the size of Content. + @param[in] ContentType Type of the Content to be send to Redfish service. + This is optional. When ContentType is NULL, content + type HTTP_CONTENT_TYPE_APP_JSON will be used. + @param[out] Response HTTP response from redfish service. + + @retval EFI_SUCCESS Resrouce is returned successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishPutResource ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_SERVICE Service, + IN EFI_STRING Uri, + IN CHAR8 *Content, + IN UINTN ContentSize OPTIONAL, + IN CHAR8 *ContentType OPTIONAL, + OUT REDFISH_RESPONSE *Response + ) +{ + EFI_STATUS Status; + UINTN RetryCount; + REDFISH_REQUEST Request; + REDFISH_HTTP_CACHE_PRIVATE *Private; + + if ((This == NULL) || (Service == NULL) || (Response == NULL) || IS_EMPTY_STRING (Uri) || IS_EMPTY_STRING (Content)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Put URI: %s\n", __func__, Uri)); + + Private = REDFISH_HTTP_CACHE_PRIVATE_FROM_THIS (This); + RetryCount = 0; + ZeroMem (Response, sizeof (REDFISH_RESPONSE)); + ZeroMem (&Request, sizeof (REDFISH_REQUEST)); + + Request.Content = Content; + Request.ContentLength = ContentSize; + Request.ContentType = ContentType; + + // + // Patch resource to redfish service. + // + do { + RetryCount += 1; + Status = HttpSendReceive ( + Service, + Uri, + HttpMethodPut, + &Request, + Response + ); + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: HTTP request: %s :%r\n", __func__, Uri, Status)); + if (!EFI_ERROR (Status) || (RetryCount >= Private->RetrySetting.MaximumRetryPut)) { + break; + } + + // + // Retry when BMC is not ready. + // + if ((Response->StatusCode != NULL)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + + if (!RedfishRetryRequired (Response->StatusCode)) { + break; + } + + // + // Release response for next round of request. + // + This->FreeResponse (This, Response); + } + + DEBUG ((DEBUG_WARN, "%a: RedfishPutToUri failed, retry (%d/%d)\n", __func__, RetryCount, Private->RetrySetting.MaximumRetryPut)); + if (Private->RetrySetting.RetryWait > 0) { + gBS->Stall (Private->RetrySetting.RetryWait); + } + } while (TRUE); + + // + // Redfish resource is updated. Automatically expire the cached response + // so application can directly get resource from Redfish service again. + // + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Resource is updated, expire URI: %s\n", __func__, Uri)); + RedfishExpireResponse (This, Uri); + + if (EFI_ERROR (Status)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + // + // Report status code for Redfish failure + // + ReportHttpError (HttpMethodPut, Uri, Response->StatusCode); + DEBUG ((DEBUG_ERROR, "%a: put %s failed (%d/%d): %r\n", __func__, Uri, RetryCount, Private->RetrySetting.MaximumRetryPut, Status)); + goto ON_RELEASE; + } + +ON_RELEASE: + + return Status; +} + +/** + Perform HTTP POST to send redfish resource to given resource URI. + It's caller's responsibility to free Response by calling FreeResponse (). + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] Service Redfish service instance to perform HTTP POST. + @param[in] Uri Target resource URI. + @param[in] Content Data to post. + @param[in] ContentSize Size of the Content to be send to Redfish service. + This is optional. When ContentSize is 0, ContentSize + is the size of Content. + @param[in] ContentType Type of the Content to be send to Redfish service. + This is optional. When ContentType is NULL, content + type HTTP_CONTENT_TYPE_APP_JSON will be used. + @param[out] Response HTTP response from redfish service. + + @retval EFI_SUCCESS Resrouce is returned successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishPostResource ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_SERVICE Service, + IN EFI_STRING Uri, + IN CHAR8 *Content, + IN UINTN ContentSize OPTIONAL, + IN CHAR8 *ContentType OPTIONAL, + OUT REDFISH_RESPONSE *Response + ) +{ + EFI_STATUS Status; + UINTN RetryCount; + REDFISH_REQUEST Request; + REDFISH_HTTP_CACHE_PRIVATE *Private; + + if ((This == NULL) || (Service == NULL) || (Response == NULL) || IS_EMPTY_STRING (Uri) || IS_EMPTY_STRING (Content)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Post URI: %s\n", __func__, Uri)); + + Private = REDFISH_HTTP_CACHE_PRIVATE_FROM_THIS (This); + RetryCount = 0; + ZeroMem (Response, sizeof (REDFISH_RESPONSE)); + ZeroMem (&Request, sizeof (REDFISH_REQUEST)); + + Request.Content = Content; + Request.ContentLength = ContentSize; + Request.ContentType = ContentType; + + // + // Patch resource to redfish service. + // + do { + RetryCount += 1; + Status = HttpSendReceive ( + Service, + Uri, + HttpMethodPost, + &Request, + Response + ); + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: HTTP request: %s :%r\n", __func__, Uri, Status)); + if (!EFI_ERROR (Status) || (RetryCount >= Private->RetrySetting.MaximumRetryPost)) { + break; + } + + // + // Retry when BMC is not ready. + // + if ((Response->StatusCode != NULL)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + + if (!RedfishRetryRequired (Response->StatusCode)) { + break; + } + + // + // Release response for next round of request. + // + This->FreeResponse (This, Response); + } + + DEBUG ((DEBUG_WARN, "%a: RedfishPostToUri failed, retry (%d/%d)\n", __func__, RetryCount, Private->RetrySetting.MaximumRetryPost)); + if (Private->RetrySetting.RetryWait > 0) { + gBS->Stall (Private->RetrySetting.RetryWait); + } + } while (TRUE); + + // + // Redfish resource is updated. Automatically expire the cached response + // so application can directly get resource from Redfish service again. + // + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Resource is updated, expire URI: %s\n", __func__, Uri)); + RedfishExpireResponse (This, Uri); + + if (EFI_ERROR (Status)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + // + // Report status code for Redfish failure + // + ReportHttpError (HttpMethodPost, Uri, Response->StatusCode); + DEBUG ((DEBUG_ERROR, "%a: post %s failed (%d/%d): %r\n", __func__, Uri, RetryCount, Private->RetrySetting.MaximumRetryPost, Status)); + goto ON_RELEASE; + } + +ON_RELEASE: + + return Status; +} + +/** + Perform HTTP DELETE to delete redfish resource on given resource URI. + It's caller's responsibility to free Response by calling FreeResponse (). + + @param[in] This Pointer to EDKII_REDFISH_HTTP_PROTOCOL instance. + @param[in] Service Redfish service instance to perform HTTP DELETE. + @param[in] Uri Target resource URI. + @param[in] Content JSON represented properties to be deleted. This is + optional. + @param[in] ContentSize Size of the Content to be send to Redfish service. + This is optional. When ContentSize is 0, ContentSize + is the size of Content if Content is not NULL. + @param[in] ContentType Type of the Content to be send to Redfish service. + This is optional. When Content is not NULL and + ContentType is NULL, content type HTTP_CONTENT_TYPE_APP_JSON + will be used. + @param[out] Response HTTP response from redfish service. + + @retval EFI_SUCCESS Resrouce is returned successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +EFIAPI +RedfishDeleteResource ( + IN EDKII_REDFISH_HTTP_PROTOCOL *This, + IN REDFISH_SERVICE Service, + IN EFI_STRING Uri, + IN CHAR8 *Content OPTIONAL, + IN UINTN ContentSize OPTIONAL, + IN CHAR8 *ContentType OPTIONAL, + OUT REDFISH_RESPONSE *Response + ) +{ + EFI_STATUS Status; + UINTN RetryCount; + REDFISH_REQUEST Request; + REDFISH_HTTP_CACHE_PRIVATE *Private; + + if ((This == NULL) || (Service == NULL) || (Response == NULL) || IS_EMPTY_STRING (Uri)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Delete URI: %s\n", __func__, Uri)); + + Private = REDFISH_HTTP_CACHE_PRIVATE_FROM_THIS (This); + RetryCount = 0; + ZeroMem (Response, sizeof (REDFISH_RESPONSE)); + ZeroMem (&Request, sizeof (REDFISH_REQUEST)); + + Request.Content = Content; + Request.ContentLength = ContentSize; + Request.ContentType = ContentType; + + // + // Patch resource to redfish service. + // + do { + RetryCount += 1; + Status = HttpSendReceive ( + Service, + Uri, + HttpMethodDelete, + &Request, + Response + ); + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: HTTP request: %s :%r\n", __func__, Uri, Status)); + if (!EFI_ERROR (Status) || (RetryCount >= Private->RetrySetting.MaximumRetryDelete)) { + break; + } + + // + // Retry when BMC is not ready. + // + if ((Response->StatusCode != NULL)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + + if (!RedfishRetryRequired (Response->StatusCode)) { + break; + } + + // + // Release response for next round of request. + // + This->FreeResponse (This, Response); + } + + DEBUG ((DEBUG_WARN, "%a: RedfishDeleteByUri failed, retry (%d/%d)\n", __func__, RetryCount, Private->RetrySetting.MaximumRetryDelete)); + if (Private->RetrySetting.RetryWait > 0) { + gBS->Stall (Private->RetrySetting.RetryWait); + } + } while (TRUE); + + // + // Redfish resource is updated. Automatically expire the cached response + // so application can directly get resource from Redfish service again. + // + DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: Resource is updated, expire URI: %s\n", __func__, Uri)); + RedfishExpireResponse (This, Uri); + + if (EFI_ERROR (Status)) { + DEBUG_CODE ( + DumpRedfishResponse (NULL, DEBUG_ERROR, Response); + ); + // + // Report status code for Redfish failure + // + ReportHttpError (HttpMethodDelete, Uri, Response->StatusCode); + DEBUG ((DEBUG_ERROR, "%a: delete %s failed (%d/%d): %r\n", __func__, Uri, RetryCount, Private->RetrySetting.MaximumRetryDelete, Status)); + goto ON_RELEASE; + } + +ON_RELEASE: + + return Status; +} + +EDKII_REDFISH_HTTP_PROTOCOL mEdkIIRedfishHttpProtocol = { + EDKII_REDFISH_HTTP_PROTOCOL_REVISION, + RedfishCreateRedfishService, + RedfishFreeRedfishService, + RedfishJsonInRedfishPayload, + RedfishGetResource, + RedfishPatchResource, + RedfishPutResource, + RedfishPostResource, + RedfishDeleteResource, + RedfishFreeRequest, + RedfishFreeResponse, + RedfishExpireResponse +}; + +/** + Unloads an image. + + @param[in] ImageHandle Handle that identifies the image to be unloaded. + + @retval EFI_SUCCESS The image has been unloaded. + @retval EFI_INVALID_PARAMETER ImageHandle is not a valid image handle. + +**/ +EFI_STATUS +EFIAPI +RedfishHttpDriverUnload ( + IN EFI_HANDLE ImageHandle + ) +{ + if (mRedfishHttpCachePrivate == NULL) { + return EFI_SUCCESS; + } + + if (!IsListEmpty (&mRedfishHttpCachePrivate->CacheList.Head)) { + ReleaseCacheList (&mRedfishHttpCachePrivate->CacheList); + } + + gBS->UninstallMultipleProtocolInterfaces ( + ImageHandle, + &gEdkIIRedfishHttpProtocolGuid, + &mRedfishHttpCachePrivate->Protocol, + NULL + ); + + FreePool (mRedfishHttpCachePrivate); + mRedfishHttpCachePrivate = NULL; + + return EFI_SUCCESS; +} + +/** + This is a EDKII_REDFISH_CREDENTIAL_PROTOCOL notification event handler. + + @param[in] Event Event whose notification function is being invoked. + @param[in] Context Pointer to the notification function's context. + +**/ +VOID +EFIAPI +CredentialProtocolInstalled ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + REDFISH_HTTP_CACHE_PRIVATE *Private; + + Private = (REDFISH_HTTP_CACHE_PRIVATE *)Context; + if (Private->Signature != REDFISH_HTTP_DRIVER_SIGNATURE) { + DEBUG ((DEBUG_ERROR, "%a: signature check failure\n", __func__)); + return; + } + + // + // Locate HII database protocol. + // + Status = gBS->LocateProtocol ( + &gEdkIIRedfishCredentialProtocolGuid, + NULL, + (VOID **)&Private->CredentialProtocol + ); + if (EFI_ERROR (Status)) { + return; + } + + gBS->CloseEvent (Event); +} + +/** + Main entry for this driver. + + @param[in] ImageHandle Image handle this driver. + @param[in] SystemTable Pointer to SystemTable. + + @retval EFI_SUCCESS This function always complete successfully. + +**/ +EFI_STATUS +EFIAPI +RedfishHttpEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + VOID *Registration; + + if (mRedfishHttpCachePrivate != NULL) { + return EFI_ALREADY_STARTED; + } + + mRedfishHttpCachePrivate = AllocateZeroPool (sizeof (REDFISH_HTTP_CACHE_PRIVATE)); + if (mRedfishHttpCachePrivate == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Initial cache list and protocol instance. + // + mRedfishHttpCachePrivate->Signature = REDFISH_HTTP_DRIVER_SIGNATURE; + mRedfishHttpCachePrivate->ImageHandle = ImageHandle; + CopyMem (&mRedfishHttpCachePrivate->Protocol, &mEdkIIRedfishHttpProtocol, sizeof (EDKII_REDFISH_HTTP_PROTOCOL)); + mRedfishHttpCachePrivate->CacheList.Capacity = REDFISH_HTTP_CACHE_LIST_SIZE; + mRedfishHttpCachePrivate->CacheList.Count = 0x00; + mRedfishHttpCachePrivate->CacheDisabled = PcdGetBool (PcdHttpCacheDisabled); + InitializeListHead (&mRedfishHttpCachePrivate->CacheList.Head); + + // + // Get retry settings + // + mRedfishHttpCachePrivate->RetrySetting.MaximumRetryGet = PcdGet16 (PcdHttpGetRetry); + mRedfishHttpCachePrivate->RetrySetting.MaximumRetryPut = PcdGet16 (PcdHttpPutRetry); + mRedfishHttpCachePrivate->RetrySetting.MaximumRetryPatch = PcdGet16 (PcdHttpPatchRetry); + mRedfishHttpCachePrivate->RetrySetting.MaximumRetryPost = PcdGet16 (PcdHttpPostRetry); + mRedfishHttpCachePrivate->RetrySetting.MaximumRetryDelete = PcdGet16 (PcdHttpDeleteRetry); + mRedfishHttpCachePrivate->RetrySetting.RetryWait = PcdGet16 (PcdHttpRetryWaitInSecond) * 1000000U; + + // + // Install the gEdkIIRedfishHttpProtocolGuid onto Handle. + // + Status = gBS->InstallMultipleProtocolInterfaces ( + &mRedfishHttpCachePrivate->ImageHandle, + &gEdkIIRedfishHttpProtocolGuid, + &mRedfishHttpCachePrivate->Protocol, + NULL + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: cannot install Redfish http protocol: %r\n", __func__, Status)); + RedfishHttpDriverUnload (ImageHandle); + return Status; + } + + // + // Install protocol notification if credential protocol is installed. + // + mRedfishHttpCachePrivate->NotifyEvent = EfiCreateProtocolNotifyEvent ( + &gEdkIIRedfishCredentialProtocolGuid, + TPL_CALLBACK, + CredentialProtocolInstalled, + mRedfishHttpCachePrivate, + &Registration + ); + if (mRedfishHttpCachePrivate->NotifyEvent == NULL) { + DEBUG ((DEBUG_ERROR, "%a: failed to create protocol notification for gEdkIIRedfishCredentialProtocolGuid\n", __func__)); + ASSERT (FALSE); + RedfishHttpDriverUnload (ImageHandle); + return Status; + } + + return EFI_SUCCESS; +} diff --git a/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.c b/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.c new file mode 100644 index 0000000000..5652818d16 --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.c @@ -0,0 +1,693 @@ +/** @file + RedfishHttpOperation handles HTTP operations. + + Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "RedfishHttpOperation.h" +#include "RedfishHttpData.h" + +/** + This function copies all headers in SrcHeaders to DstHeaders. + It's call responsibility to release returned DstHeaders. + + @param[in] SrcHeaders Source headers. + @param[in] SrcHeaderCount Number of header in source headers. + @param[out] DstHeaders Destination headers. + @param[out] DstHeaderCount Number of header in designation headers. + + @retval EFI_SUCCESS Headers are copied successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +CopyHttpHeaders ( + IN EFI_HTTP_HEADER *SrcHeaders, + IN UINTN SrcHeaderCount, + OUT EFI_HTTP_HEADER **DstHeaders, + OUT UINTN *DstHeaderCount + ) +{ + UINTN Index; + + if ((SrcHeaders == NULL) || (SrcHeaderCount == 0) || (DstHeaders == NULL) || (DstHeaderCount == NULL)) { + return EFI_INVALID_PARAMETER; + } + + *DstHeaderCount = 0; + *DstHeaders = AllocateZeroPool (sizeof (EFI_HTTP_HEADER) * SrcHeaderCount); + if (*DstHeaders == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + for (Index = 0; Index < SrcHeaderCount; Index++) { + (*DstHeaders)[Index].FieldName = AllocateCopyPool (AsciiStrSize (SrcHeaders[Index].FieldName), SrcHeaders[Index].FieldName); + if ((*DstHeaders)[Index].FieldName == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + (*DstHeaders)[Index].FieldValue = AllocateCopyPool (AsciiStrSize (SrcHeaders[Index].FieldValue), SrcHeaders[Index].FieldValue); + if ((*DstHeaders)[Index].FieldValue == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + *DstHeaderCount += 1; + } + + return EFI_SUCCESS; +} + +/** + This function free resources in Request. Request is no longer available + after this function returns successfully. + + @param[in] Request HTTP request to be released. + + @retval EFI_SUCCESS Resrouce is released successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +ReleaseRedfishRequest ( + IN REDFISH_REQUEST *Request + ) +{ + if (Request == NULL) { + return EFI_INVALID_PARAMETER; + } + + if ((Request->Headers != NULL) && (Request->HeaderCount > 0)) { + HttpFreeHeaderFields (Request->Headers, Request->HeaderCount); + Request->Headers = NULL; + Request->HeaderCount = 0; + } + + if (Request->Content != NULL) { + FreePool (Request->Content); + Request->Content = NULL; + } + + if (Request->ContentType != NULL) { + FreePool (Request->ContentType); + Request->ContentType = NULL; + } + + Request->ContentLength = 0; + + return EFI_SUCCESS; +} + +/** + This function free resources in given Response. + + @param[in] Response HTTP response to be released. + + @retval EFI_SUCCESS Resrouce is released successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +ReleaseRedfishResponse ( + IN REDFISH_RESPONSE *Response + ) +{ + if (Response == NULL) { + return EFI_INVALID_PARAMETER; + } + + if ((Response->Headers != NULL) && (Response->HeaderCount > 0)) { + HttpFreeHeaderFields (Response->Headers, Response->HeaderCount); + Response->Headers = NULL; + Response->HeaderCount = 0; + } + + if (Response->Payload != NULL) { + ReleaseRedfishPayload (Response->Payload); + Response->Payload = NULL; + } + + if (Response->StatusCode != NULL) { + FreePool (Response->StatusCode); + Response->StatusCode = NULL; + } + + return EFI_SUCCESS; +} + +/** + This function free resources in given HTTP message. + + @param[in] HttpMessage HTTP message to be released. + @param[in] IsRequest TRUE if this is request type of HTTP message. + FALSE if this is response type of HTTP message. + + @retval EFI_SUCCESS Resrouce is released successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +ReleaseHttpMessage ( + IN EFI_HTTP_MESSAGE *HttpMessage, + IN BOOLEAN IsRequest + ) +{ + if (HttpMessage == NULL) { + return EFI_INVALID_PARAMETER; + } + + if (IsRequest) { + if (HttpMessage->Data.Request != NULL) { + if (HttpMessage->Data.Request->Url != NULL) { + FreePool (HttpMessage->Data.Request->Url); + } + + FreePool (HttpMessage->Data.Request); + HttpMessage->Data.Request = NULL; + } + } else { + if (HttpMessage->Data.Response != NULL) { + FreePool (HttpMessage->Data.Response); + HttpMessage->Data.Response = NULL; + } + } + + if (HttpMessage->Body != NULL) { + FreePool (HttpMessage->Body); + HttpMessage->Body = NULL; + } + + if (HttpMessage->Headers != NULL) { + HttpFreeHeaderFields (HttpMessage->Headers, HttpMessage->HeaderCount); + HttpMessage->Headers = NULL; + HttpMessage->HeaderCount = 0; + } + + return EFI_SUCCESS; +} + +/** + This function build Redfish message for sending data to Redfish service. + It's call responsibility to properly release returned HTTP message by + calling ReleaseHttpMessage. + + @param[in] ServicePrivate Pointer to Redfish service private data. + @param[in] Uri Redfish service URI. + @param[in] Method HTTP method. + @param[in] Request Additional data to send to Redfish service. + This is optional. + @param[in] ContentEncoding Content encoding method to compress HTTP context. + This is optional. When ContentEncoding is NULL, + No compress method will be performed. + + @retval EFI_HTTP_MESSAGE * Pointer to newly created HTTP message. + @retval NULL Error occurred. + +**/ +EFI_HTTP_MESSAGE * +BuildRequestMessage ( + IN REDFISH_SERVICE_PRIVATE *ServicePrivate, + IN EFI_STRING Uri, + IN EFI_HTTP_METHOD Method, + IN REDFISH_REQUEST *Request OPTIONAL, + IN CHAR8 *ContentEncoding OPTIONAL + ) +{ + EFI_STATUS Status; + EFI_STRING Url; + UINTN UrlSize; + UINTN Index; + EFI_HTTP_MESSAGE *RequestMsg; + EFI_HTTP_REQUEST_DATA *RequestData; + UINTN HeaderCount; + UINTN HeaderIndex; + EFI_HTTP_HEADER *Headers; + CHAR8 ContentLengthStr[REDFISH_CONTENT_LENGTH_SIZE]; + VOID *Content; + UINTN ContentLength; + BOOLEAN HasContent; + BOOLEAN DoContentEncoding; + + RequestMsg = NULL; + RequestData = NULL; + Url = NULL; + UrlSize = 0; + Content = NULL; + ContentLength = 0; + HeaderCount = REDFISH_COMMON_HEADER_SIZE; + HeaderIndex = 0; + Headers = NULL; + HasContent = FALSE; + DoContentEncoding = FALSE; + + if ((ServicePrivate == NULL) || (IS_EMPTY_STRING (Uri))) { + return NULL; + } + + if (Method >= HttpMethodMax) { + return NULL; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: %s\n", __func__, Uri)); + + // + // Build full URL for HTTP query. + // + UrlSize = (AsciiStrLen (ServicePrivate->Host) + StrLen (Uri) + 1) * sizeof (CHAR16); + Url = AllocateZeroPool (UrlSize); + if (Url == NULL) { + return NULL; + } + + UnicodeSPrint (Url, UrlSize, L"%a%s", ServicePrivate->Host, Uri); + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: Url: %s\n", __func__, Url)); + + // + // Step 1: build the HTTP headers. + // + if (!IS_EMPTY_STRING (ServicePrivate->SessionToken) || !IS_EMPTY_STRING (ServicePrivate->BasicAuth)) { + HeaderCount++; + } + + if ((Request != NULL) && (Request->HeaderCount > 0)) { + HeaderCount += Request->HeaderCount; + } + + // + // Check and see if we will do content encoding or not + // + if (!IS_EMPTY_STRING (ContentEncoding)) { + if (AsciiStrCmp (ContentEncoding, REDFISH_HTTP_CONTENT_ENCODING_NONE) != 0) { + DoContentEncoding = TRUE; + } + } + + if ((Request != NULL) && !IS_EMPTY_STRING (Request->Content)) { + HeaderCount += 2; + HasContent = TRUE; + if (DoContentEncoding) { + HeaderCount += 1; + } + } + + Headers = AllocateZeroPool (HeaderCount * sizeof (EFI_HTTP_HEADER)); + if (Headers == NULL) { + goto ON_ERROR; + } + + if (!IS_EMPTY_STRING (ServicePrivate->SessionToken)) { + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_X_AUTH_TOKEN, ServicePrivate->SessionToken); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + } else if (!IS_EMPTY_STRING (ServicePrivate->BasicAuth)) { + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_AUTHORIZATION, ServicePrivate->BasicAuth); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + } + + if (Request != NULL) { + for (Index = 0; Index < Request->HeaderCount; Index++) { + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], Request->Headers[Index].FieldName, Request->Headers[Index].FieldValue); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + } + } + + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_HOST, ServicePrivate->HostName); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], REDFISH_HTTP_HEADER_ODATA_VERSION_STR, REDFISH_HTTP_HEADER_ODATA_VERSION_VALUE); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_ACCEPT, HTTP_CONTENT_TYPE_APP_JSON); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_USER_AGENT, REDFISH_HTTP_HEADER_USER_AGENT_VALUE); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], REDFISH_HTTP_HEADER_CONNECTION_STR, REDFISH_HTTP_HEADER_CONNECTION_VALUE); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + // + // Handle content header + // + if (HasContent) { + if (Request->ContentType == NULL) { + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_CONTENT_TYPE, HTTP_CONTENT_TYPE_APP_JSON); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + } else { + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_CONTENT_TYPE, Request->ContentType); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + } + + if (Request->ContentLength == 0) { + Request->ContentLength = AsciiStrLen (Request->Content); + } + + AsciiSPrint ( + ContentLengthStr, + sizeof (ContentLengthStr), + "%lu", + (UINT64)Request->ContentLength + ); + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_CONTENT_LENGTH, ContentLengthStr); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + + // + // Encoding + // + if (DoContentEncoding) { + // + // We currently only support gzip Content-Encoding. + // + Status = RedfishContentEncode ( + ContentEncoding, + Request->Content, + Request->ContentLength, + &Content, + &ContentLength + ); + if (Status == EFI_INVALID_PARAMETER) { + DEBUG ((DEBUG_ERROR, "%a: Error to encode content.\n", __func__)); + goto ON_ERROR; + } else if (Status == EFI_UNSUPPORTED) { + DoContentEncoding = FALSE; + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: No content coding for %a! Use raw data instead.\n", __func__, ContentEncoding)); + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_CONTENT_ENCODING, HTTP_CONTENT_ENCODING_IDENTITY); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + } else { + Status = HttpSetFieldNameAndValue (&Headers[HeaderIndex++], HTTP_HEADER_CONTENT_ENCODING, HTTP_CONTENT_ENCODING_GZIP); + if (EFI_ERROR (Status)) { + goto ON_ERROR; + } + } + } + + // + // When the content is from caller, we use our own copy so that we properly release it later. + // + if (!DoContentEncoding) { + Content = AllocateCopyPool (Request->ContentLength, Request->Content); + if (Content == NULL) { + goto ON_ERROR; + } + + ContentLength = Request->ContentLength; + } + } + + // + // Step 2: build the rest of HTTP request info. + // + RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA)); + if (RequestData == NULL) { + goto ON_ERROR; + } + + RequestData->Method = Method; + RequestData->Url = Url; + + // + // Step 3: fill in EFI_HTTP_MESSAGE + // + RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE)); + if (RequestMsg == NULL) { + goto ON_ERROR; + } + + ASSERT (HeaderIndex == HeaderCount); + RequestMsg->Data.Request = RequestData; + RequestMsg->HeaderCount = HeaderIndex; + RequestMsg->Headers = Headers; + + if (HasContent) { + RequestMsg->BodyLength = ContentLength; + RequestMsg->Body = Content; + } + + return RequestMsg; + +ON_ERROR: + + if (Headers != NULL) { + HttpFreeHeaderFields (Headers, HeaderIndex); + } + + if (RequestData != NULL) { + FreePool (RequestData); + } + + if (RequestMsg != NULL) { + FreePool (RequestMsg); + } + + if (Url != NULL) { + FreePool (Url); + } + + return NULL; +} + +/** + This function parse response message from Redfish service, and + build Redfish response for caller. It's call responsibility to + properly release Redfish response by calling ReleaseRedfishResponse. + + @param[in] ServicePrivate Pointer to Redfish service private data. + @param[in] ResponseMsg Response message from Redfish service. + @param[out] RedfishResponse Redfish response data. + + @retval EFI_SUCCESS Redfish response is returned successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +ParseResponseMessage ( + IN REDFISH_SERVICE_PRIVATE *ServicePrivate, + IN EFI_HTTP_MESSAGE *ResponseMsg, + OUT REDFISH_RESPONSE *RedfishResponse + ) +{ + EFI_STATUS Status; + EDKII_JSON_VALUE JsonData; + EFI_HTTP_HEADER *ContentEncodedHeader; + VOID *DecodedBody; + UINTN DecodedLength; + + if ((ServicePrivate == NULL) || (ResponseMsg == NULL) || (RedfishResponse == NULL)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a\n", __func__)); + + // + // Initialization + // + JsonData = NULL; + RedfishResponse->HeaderCount = 0; + RedfishResponse->Headers = NULL; + RedfishResponse->Payload = NULL; + RedfishResponse->StatusCode = NULL; + DecodedBody = NULL; + DecodedLength = 0; + + // + // Return the HTTP StatusCode. + // + if (ResponseMsg->Data.Response != NULL) { + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: status: %d\n", __func__, ResponseMsg->Data.Response->StatusCode)); + RedfishResponse->StatusCode = AllocateCopyPool (sizeof (EFI_HTTP_STATUS_CODE), &ResponseMsg->Data.Response->StatusCode); + if (RedfishResponse->StatusCode == NULL) { + DEBUG ((DEBUG_ERROR, "%a: Failed to create status code.\n", __func__)); + } + } + + // + // Return the HTTP headers. + // + if (ResponseMsg->Headers != NULL) { + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: header count: %d\n", __func__, ResponseMsg->HeaderCount)); + Status = CopyHttpHeaders ( + ResponseMsg->Headers, + ResponseMsg->HeaderCount, + &RedfishResponse->Headers, + &RedfishResponse->HeaderCount + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Failed to copy HTTP headers: %r\n", __func__, Status)); + } + } + + // + // Return the HTTP body. + // + if ((ResponseMsg->BodyLength != 0) && (ResponseMsg->Body != NULL)) { + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: body length: %d\n", __func__, ResponseMsg->BodyLength)); + // + // Check if data is encoded. + // + ContentEncodedHeader = HttpFindHeader (RedfishResponse->HeaderCount, RedfishResponse->Headers, HTTP_HEADER_CONTENT_ENCODING); + if (ContentEncodedHeader != NULL) { + // + // The content is encoded. + // + Status = RedfishContentDecode ( + ContentEncodedHeader->FieldValue, + ResponseMsg->Body, + ResponseMsg->BodyLength, + &DecodedBody, + &DecodedLength + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Failed to decompress the response content: %r decoding method: %a\n.", __func__, Status, ContentEncodedHeader->FieldValue)); + goto ON_ERROR; + } + + JsonData = JsonLoadBuffer (DecodedBody, DecodedLength, 0, NULL); + FreePool (DecodedBody); + } else { + JsonData = JsonLoadBuffer (ResponseMsg->Body, ResponseMsg->BodyLength, 0, NULL); + } + + if (!JsonValueIsNull (JsonData)) { + RedfishResponse->Payload = CreateRedfishPayload (ServicePrivate, JsonData); + if (RedfishResponse->Payload == NULL) { + DEBUG ((DEBUG_ERROR, "%a: Failed to create payload\n.", __func__)); + } + + JsonValueFree (JsonData); + } else { + DEBUG ((DEBUG_ERROR, "%a: No payload available\n", __func__)); + } + } + + return EFI_SUCCESS; + +ON_ERROR: + + if (RedfishResponse != NULL) { + ReleaseRedfishResponse (RedfishResponse); + } + + return Status; +} + +/** + This function send Redfish request to Redfish service by calling + Rest Ex protocol. + + @param[in] Service Pointer to Redfish service. + @param[in] Uri Uri of Redfish service. + @param[in] Method HTTP method. + @param[in] Request Request data. This is optional. + @param[out] Response Redfish response data. + + @retval EFI_SUCCESS Request is sent and received successfully. + @retval Others Errors occur. + +**/ +EFI_STATUS +HttpSendReceive ( + IN REDFISH_SERVICE Service, + IN EFI_STRING Uri, + IN EFI_HTTP_METHOD Method, + IN REDFISH_REQUEST *Request OPTIONAL, + OUT REDFISH_RESPONSE *Response + ) +{ + EFI_STATUS Status; + EFI_STATUS RestExStatus; + EFI_HTTP_MESSAGE *RequestMsg; + EFI_HTTP_MESSAGE ResponseMsg; + REDFISH_SERVICE_PRIVATE *ServicePrivate; + EFI_HTTP_HEADER *XAuthTokenHeader; + CHAR8 *HttpContentEncoding; + + if ((Service == NULL) || IS_EMPTY_STRING (Uri) || (Response == NULL)) { + return EFI_INVALID_PARAMETER; + } + + DEBUG ((REDFISH_HTTP_CACHE_DEBUG_REQUEST, "%a: Method: 0x%x %s\n", __func__, Method, Uri)); + + ServicePrivate = (REDFISH_SERVICE_PRIVATE *)Service; + if (ServicePrivate->Signature != REDFISH_HTTP_SERVICE_SIGNATURE) { + DEBUG ((DEBUG_ERROR, "%a: signature check failure\n", __func__)); + return EFI_INVALID_PARAMETER; + } + + ZeroMem (&ResponseMsg, sizeof (ResponseMsg)); + HttpContentEncoding = (CHAR8 *)PcdGetPtr (PcdRedfishServiceContentEncoding); + + RequestMsg = BuildRequestMessage (Service, Uri, Method, Request, HttpContentEncoding); + if (RequestMsg == NULL) { + DEBUG ((DEBUG_ERROR, "%a: cannot build request message for %s\n", __func__, Uri)); + return EFI_PROTOCOL_ERROR; + } + + // + // call RESTEx to get response from REST service. + // + RestExStatus = ServicePrivate->RestEx->SendReceive (ServicePrivate->RestEx, RequestMsg, &ResponseMsg); + if (EFI_ERROR (RestExStatus)) { + DEBUG ((DEBUG_ERROR, "%a: %s SendReceive failure: %r\n", __func__, Uri, RestExStatus)); + } + + // + // Return status code, headers and payload to caller as much as possible even when RestEx returns failure. + // + Status = ParseResponseMessage (ServicePrivate, &ResponseMsg, Response); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: %s parse response failure: %r\n", __func__, Uri, Status)); + } else { + // + // Capture session token in header + // + if ((Method == HttpMethodPost) && + (Response->StatusCode != NULL) && + ((*Response->StatusCode == HTTP_STATUS_200_OK) || (*Response->StatusCode == HTTP_STATUS_204_NO_CONTENT))) + { + XAuthTokenHeader = HttpFindHeader (ResponseMsg.HeaderCount, ResponseMsg.Headers, HTTP_HEADER_X_AUTH_TOKEN); + if (XAuthTokenHeader != NULL) { + Status = UpdateSessionToken (ServicePrivate, XAuthTokenHeader->FieldValue); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: update session token failure: %r\n", __func__, Status)); + } + } + } + } + + // + // Release resources + // + if (RequestMsg != NULL) { + ReleaseHttpMessage (RequestMsg, TRUE); + FreePool (RequestMsg); + } + + ReleaseHttpMessage (&ResponseMsg, FALSE); + + return RestExStatus; +} diff --git a/RedfishPkg/Redfish.fdf.inc b/RedfishPkg/Redfish.fdf.inc index 3e5a77766e..5cbe3592fd 100644 --- a/RedfishPkg/Redfish.fdf.inc +++ b/RedfishPkg/Redfish.fdf.inc @@ -6,7 +6,7 @@ # to be built in the firmware volume. # # (C) Copyright 2020-2021 Hewlett Packard Enterprise Development LP<BR> -# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: BSD-2-Clause-Patent # @@ -20,4 +20,5 @@ INF RedfishPkg/RedfishConfigHandler/RedfishConfigHandlerDriver.inf INF RedfishPkg/RedfishPlatformConfigDxe/RedfishPlatformConfigDxe.inf INF MdeModulePkg/Universal/RegularExpressionDxe/RegularExpressionDxe.inf + INF RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf !endif -- 2.34.1 -The information contained in this message may be confidential and proprietary to American Megatrends (AMI). This communication is intended to be read only by the individual or entity to whom it is addressed or by their designee. If the reader of this message is not the intended recipient, you are on notice that any distribution of this message, in any form, is strictly prohibited. Please promptly notify the sender by reply e-mail or by telephone at 770-246-8600, and then delete or destroy all copies of the transmission. -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#115820): https://edk2.groups.io/g/devel/message/115820 Mute This Topic: https://groups.io/mt/104505404/21656 Group Owner: devel+ow...@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-