| /** @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; | |
| } |