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