From 0ce2012c6c8bc371b7739d12251b53af906a70dd Mon Sep 17 00:00:00 2001 From: Nickle Wang Date: Thu, 1 Feb 2024 12:53:31 +0800 Subject: RedfishPkg: implement Redfish HTTP protocol implement Redfish HTTP protocol driver. Signed-off-by: Nickle Wang Co-authored-by: Igor Kulchytskyy Cc: Abner Chang Cc: Igor Kulchytskyy Cc: Nick Ramirez Reviewed-by: Abner Chang Reviewed-by: Igor Kulchytskyy Reviewed-by: Mike Maslenkin --- RedfishPkg/Redfish.fdf.inc | 3 +- RedfishPkg/RedfishComponents.dsc.inc | 3 +- RedfishPkg/RedfishHttpDxe/RedfishHttpData.c | 667 +++++++++++ RedfishPkg/RedfishHttpDxe/RedfishHttpData.h | 256 +++++ RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.c | 1344 ++++++++++++++++++++++ RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.h | 44 + RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf | 73 ++ RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.c | 692 +++++++++++ RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.h | 77 ++ RedfishPkg/RedfishPkg.dec | 7 +- RedfishPkg/RedfishPkg.dsc | 2 + 11 files changed, 3164 insertions(+), 4 deletions(-) create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpData.c create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpData.h create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.c create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.h create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.inf create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.c create mode 100644 RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.h (limited to 'RedfishPkg') 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
-# 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 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
-# 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/RedfishHttpDxe/RedfishHttpData.c b/RedfishPkg/RedfishHttpDxe/RedfishHttpData.c new file mode 100644 index 0000000000..d1d403c74a --- /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 = ASCII_STR_DUPLICATE (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 = ASCII_STR_DUPLICATE (Host); + if (NewService->Host == NULL) { + goto ON_ERROR; + } + + NewService->HostName = ASCII_STR_DUPLICATE (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 = ASCII_STR_DUPLICATE (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; + } + + DstResponse->HeaderCount = SrcResponse->HeaderCount; + + for (Index = 0; Index < SrcResponse->HeaderCount; Index++) { + DstResponse->Headers[Index].FieldName = ASCII_STR_DUPLICATE (SrcResponse->Headers[Index].FieldName); + if (DstResponse->Headers[Index].FieldName == NULL) { + goto ON_ERROR; + } + + DstResponse->Headers[Index].FieldValue = ASCII_STR_DUPLICATE (SrcResponse->Headers[Index].FieldValue); + if (DstResponse->Headers[Index].FieldValue == NULL) { + goto ON_ERROR; + } + } + } + + // + // 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/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.c b/RedfishPkg/RedfishHttpDxe/RedfishHttpDxe.c new file mode 100644 index 0000000000..8dcdf55c62 --- /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 Resource 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 Resource 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 Resource 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 Resource 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 Resource 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 Resource 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 Resource 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 Resource 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/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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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/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/RedfishHttpOperation.c b/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.c new file mode 100644 index 0000000000..8110985add --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.c @@ -0,0 +1,692 @@ +/** @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; + } + + *DstHeaderCount = SrcHeaderCount; + for (Index = 0; Index < SrcHeaderCount; Index++) { + (*DstHeaders)[Index].FieldName = ASCII_STR_DUPLICATE (SrcHeaders[Index].FieldName); + if ((*DstHeaders)[Index].FieldName == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + (*DstHeaders)[Index].FieldValue = ASCII_STR_DUPLICATE (SrcHeaders[Index].FieldValue); + if ((*DstHeaders)[Index].FieldValue == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } + + 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 Resource 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 Resource 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 Resource 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/RedfishHttpDxe/RedfishHttpOperation.h b/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.h new file mode 100644 index 0000000000..79954744ad --- /dev/null +++ b/RedfishPkg/RedfishHttpDxe/RedfishHttpOperation.h @@ -0,0 +1,77 @@ +/** @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" +#define ASCII_STR_DUPLICATE(a) (AllocateCopyPool (AsciiStrSize ((a)), (a))) + +/** + 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 Resource 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 Resource 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/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/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 -- cgit v1.2.3