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