diff --git a/RedfishPkg/PrivateInclude/Library/RedfishLib.h b/RedfishPkg/PrivateInclude/Library/RedfishLib.h new file mode 100644 index 0000000000..315b1ec137 --- /dev/null +++ b/RedfishPkg/PrivateInclude/Library/RedfishLib.h @@ -0,0 +1,611 @@ +/** @file + This library provides a set of utility APIs that allow to create/read/update/delete + (CRUD) Redfish resources and provide basic query abilities by using [URI/RedPath] + (https://github.com/DMTF/libredfish). + + The query language is based on XPath (https://www.w3.org/TR/1999/REC-xpath-19991116/). + This library and query language essentially treat the entire Redfish Service like it + was a single JSON document. In other words whenever it encounters an odata.id in JSON + document, it will retrieve the new JSON document (if needed). We name the path as + RedPath: + + Expression Description + + nodename Selects the JSON entity with the name "nodename". + If the value of nodename is an object with "@odata.id", + it will continue get the value from "@odata.id". + + / Selects from the root node + + [index] Selects the index number JSON entity from an array or + object. If the JSON entity is one collection (has + Members & Members@odata.count), means to get the index + member in "Members". Index number >=1, 1 means to return + the first instance. + + [XXX] Operation on JSON entity. + If the JSON entity is one collection (has Members & + Members@odata.count), means to get all elements in + "Members". If the JSON entity is one array, means to + get all elements in array. Others will match the nodename + directly (e.g. JSON_OBJECT, JSON_STRING, JSON_TRUE, + JSON_FALSE, JSON_INTEGER). + + [nodename] Selects all the elements from an JSON entity that + contain a property named "nodename" + + [name=value] Selects all the elements from an JSON entity where + the property "name" is equal to "value" + + [name~value] Selects all the elements from an JSON entity where + the string property "name" is equal to "value" using + case insensitive comparison. + + [namevalue] Selects all the elements from an JSON entity where + the property "name" is greater than "value" + + [name>=value] Selects all the elements from an JSON entity where + the property "name" is greater than or equal to "value" + + Some examples: + + /v1/Chassis[1] - Will return the first Chassis instance. + /v1/Chassis[SKU=1234] - Will return all Chassis instances with a SKU field equal to 1234. + /v1/Systems[Storage] - Will return all the System instances that have Storage field populated. + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef REDFISH_LIB_H_ +#define REDFISH_LIB_H_ + +#include + +#include +#include + +#define ODATA_TYPE_NAME_MAX_SIZE 128 +#define ODATA_TYPE_MAX_SIZE 128 + +/// +/// Library class public defines +/// +typedef VOID* REDFISH_SERVICE; +typedef VOID* REDFISH_PAYLOAD; + +/// +/// Library class public structures/unions +/// +typedef struct { + EFI_HTTP_STATUS_CODE *StatusCode; + UINTN HeaderCount; + EFI_HTTP_HEADER *Headers; + REDFISH_PAYLOAD Payload; +} REDFISH_RESPONSE; + +/// +/// Odata type-name mapping structure. +/// +typedef struct { + CONST CHAR8 OdataTypeName [ODATA_TYPE_NAME_MAX_SIZE]; + CONST CHAR8 OdataType [ODATA_TYPE_MAX_SIZE]; +} REDFISH_ODATA_TYPE_MAPPING; + +/** + This function uses REST EX protocol provided in RedfishConfigServiceInfo. + The service enumerator will also handle the authentication flow automatically + if HTTP basic auth or Redfish session login is configured to use. + + Callers are responsible for freeing the returned service by RedfishCleanupService(). + + @param[in] RedfishConfigServiceInfo Redfish service information the EFI Redfish + feature driver communicates with. + + @return New created Redfish Service, or NULL if error happens. + +**/ +REDFISH_SERVICE +EFIAPI +RedfishCreateService ( + IN REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo + ); + +/** + Free the Service and all its related resources. + + @param[in] RedfishService The Service to access the Redfish resources. + +**/ +VOID +EFIAPI +RedfishCleanupService ( + IN REDFISH_SERVICE RedfishService + ); + +/** + Create REDFISH_PAYLOAD instance in local with JSON represented resource value and + the Redfish Service. + + The returned REDFISH_PAYLOAD can be used to create or update Redfish resource in + server side. + + Callers are responsible for freeing the returned payload by RedfishCleanupPayload(). + + @param[in] Value JSON Value of the redfish resource. + @param[in] RedfishService The Service to access the Redfish resources. + + @return REDFISH_PAYLOAD instance of the resource, or NULL if error happens. + +**/ +REDFISH_PAYLOAD +EFIAPI +RedfishCreatePayload ( + IN EDKII_JSON_VALUE Value, + IN REDFISH_SERVICE RedfishService + ); + +/** + Free the RedfishPayload and all its related resources. + + @param[in] Payload Payload to be freed. + +**/ +VOID +EFIAPI +RedfishCleanupPayload ( + IN REDFISH_PAYLOAD Payload + ); + +/** + This function returns the decoded JSON value of a REDFISH_PAYLOAD. + + Caller doesn't need to free the returned JSON value because it will be released + in corresponding RedfishCleanupPayload() function. + + @param[in] Payload A REDFISH_PAYLOAD instance. + + @return Decoded JSON value of the payload. + +**/ +EDKII_JSON_VALUE +EFIAPI +RedfishJsonInPayload ( + IN REDFISH_PAYLOAD Payload + ); + +/** + Fill the input RedPath string with system UUID from SMBIOS table or use the customized + ID if FromSmbios == FALSE. + + This is a helper function to build a RedPath string which can be used to address + a Redfish resource for this computer system. The input PathString must have a Systems + note in format of "Systems[UUID=%g]" or "Systems[UUID~%g]" to fill the UUID value. + + Example: + Use "/v1/Systems[UUID=%g]/Bios" to build a RedPath to address the "Bios" resource + for this computer system. + + @param[in] RedPath RedPath format to be build. + @param[in] FromSmbios Get system UUID from SMBIOS as computer system instance ID. + @param[in] IdString The computer system instance ID. + + @return Full RedPath with system UUID inside, or NULL if error happens. + +**/ +CHAR8 * +EFIAPI +RedfishBuildPathWithSystemUuid ( + IN CONST CHAR8 *RedPath, + IN BOOLEAN FromSmbios, + IN CHAR8 *IdString OPTIONAL + ); + +/** + Get a redfish response addressed by a RedPath string, including HTTP StatusCode, Headers + and Payload which record any HTTP response messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] RedfishService The Service to access the Redfish resources. + @param[in] RedPath RedPath string to address a resource, must start + from the root node. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The corresponding redfish resource has + been returned in Payload within RedResponse. + @retval EFI_INVALID_PARAMETER RedfishService, RedPath, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned Payload is NULL, indicates any error happen. + 2. If the returned StatusCode is NULL, indicates any error happen. + 3. If the returned StatusCode is not 2XX, indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishGetByService ( + IN REDFISH_SERVICE RedfishService, + IN CONST CHAR8 *RedPath, + OUT REDFISH_RESPONSE *RedResponse + ); + +/** + Get a redfish response addressed by URI, including HTTP StatusCode, Headers + and Payload which record any HTTP response messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] RedfishService The Service to access the URI resources. + @param[in] URI String to address a resource. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The corresponding redfish resource has + been returned in Payload within RedResponse. + @retval EFI_INVALID_PARAMETER RedfishService, RedPath, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned Payload is NULL, indicates any error happen. + 2. If the returned StatusCode is NULL, indicates any error happen. + 3. If the returned StatusCode is not 2XX, indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishGetByUri ( + IN REDFISH_SERVICE RedfishService, + IN CONST CHAR8 *Uri, + OUT REDFISH_RESPONSE *RedResponse + ); + +/** + Get a redfish response addressed by the input Payload and relative RedPath string, + including HTTP StatusCode, Headers and Payload which record any HTTP response messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] Payload A existing REDFISH_PAYLOAD instance. + @param[in] RedPath Relative RedPath string to address a resource inside Payload. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful: + 1. The HTTP StatusCode is NULL and the returned Payload in + RedResponse is not NULL, indicates the Redfish resource has + been parsed from the input payload directly. + 2. The HTTP StatusCode is not NULL and the value is 2XX, + indicates the corresponding redfish resource has been returned + in Payload within RedResponse. + @retval EFI_INVALID_PARAMETER Payload, RedPath, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned Payload is NULL, indicates any error happen. + 2. If StatusCode is not NULL and the returned value of StatusCode + is not 2XX, indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishGetByPayload ( + IN REDFISH_PAYLOAD Payload, + IN CONST CHAR8 *RedPath, + OUT REDFISH_RESPONSE *RedResponse + ); + +/** + Use HTTP PATCH to perform updates on pre-existing Redfish resource. + + This function uses the RedfishService to patch a Redfish resource addressed by + Uri (only the relative path is required). Changes to one or more properties within + the target resource are represented in the input Content, properties not specified + in Content won't be changed by this request. The corresponding redfish response will + returned, including HTTP StatusCode, Headers and Payload which record any HTTP response + messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] RedfishService The Service to access the Redfish resources. + @param[in] Uri Relative path to address the resource. + @param[in] Content JSON represented properties to be update. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The Redfish resource will be returned + in Payload within RedResponse if server send it back in the HTTP + response message body. + @retval EFI_INVALID_PARAMETER RedfishService, Uri, Content, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned StatusCode is NULL, indicates any error happen. + 2. If the returned StatusCode is not NULL and the value is not 2XX, + indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishPatchToUri ( + IN REDFISH_SERVICE RedfishService, + IN CONST CHAR8 *Uri, + IN CONST CHAR8 *Content, + OUT REDFISH_RESPONSE *RedResponse + ); + +/** + Use HTTP PATCH to perform updates on target payload. Patch to odata.id in Payload directly. + + This function uses the Payload to patch the Target. Changes to one or more properties + within the target resource are represented in the input Payload, properties not specified + in Payload won't be changed by this request. The corresponding redfish response will + returned, including HTTP StatusCode, Headers and Payload which record any HTTP response + messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] Target The target payload to be updated. + @param[in] Payload Palyoad with properties to be changed. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The Redfish resource will be returned + in Payload within RedResponse if server send it back in the HTTP + response message body. + @retval EFI_INVALID_PARAMETER Target, Payload, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned StatusCode is NULL, indicates any error happen. + 2. If the returned StatusCode is not NULL and the value is not 2XX, + indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishPatchToPayload ( + IN REDFISH_PAYLOAD Target, + IN REDFISH_PAYLOAD Payload, + OUT REDFISH_RESPONSE *RedResponse + ); + +/** + Use HTTP POST to create a new resource in target payload. + + The POST request should be submitted to the Resource Collection in which the new resource + is to belong. The Resource Collection is addressed by Target payload. The Redfish may + ignore any service controlled properties. The corresponding redfish response will returned, + including HTTP StatusCode, Headers and Payload which record any HTTP response messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] Target Target payload of the Resource Collection. + @param[in] Payload The new resource to be created. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The Redfish resource will be returned + in Payload within RedResponse if server send it back in the HTTP + response message body. + @retval EFI_INVALID_PARAMETER Target, Payload, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned StatusCode is NULL, indicates any error happen. + 2. If the returned StatusCode is not NULL and the value is not 2XX, + indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishPostToPayload ( + IN REDFISH_PAYLOAD Target, + IN REDFISH_PAYLOAD Payload, + OUT REDFISH_RESPONSE *RedResponse + ); + +/** + Use HTTP DELETE to remove a resource. + + This function uses the RedfishService to remove a Redfish resource which is addressed + by input Uri (only the relative path is required). The corresponding redfish response will + returned, including HTTP StatusCode, Headers and Payload which record any HTTP response + messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] RedfishService The Service to access the Redfish resources. + @param[in] Uri Relative path to address the resource. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX, the Redfish resource has been removed. + If there is any message returned from server, it will be returned + in Payload within RedResponse. + @retval EFI_INVALID_PARAMETER RedfishService, Uri, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned StatusCode is NULL, indicates any error happen. + 2. If the returned StatusCode is not NULL and the value is not 2XX, + indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishDeleteByUri ( + IN REDFISH_SERVICE RedfishService, + IN CONST CHAR8 *Uri, + OUT REDFISH_RESPONSE *RedResponse + ); + +/** + Dump text in fractions. + + @param[in] String ASCII string to dump. + +**/ +VOID +RedfishDumpJsonStringFractions ( + IN CHAR8 *String + ); + +/** + Extract the JSON text content from REDFISH_PAYLOAD and dump to debug console. + + @param[in] Payload The Redfish payload to dump. + +**/ +VOID +RedfishDumpPayload ( + IN REDFISH_PAYLOAD Payload + ); +/** + Dump text in JSON value. + + @param[in] JsonValue The Redfish JSON value to dump. + +**/ +VOID +RedfishDumpJson ( + IN EDKII_JSON_VALUE JsonValue + ); +/** + This function will cleanup the HTTP header and Redfish payload resources. + + @param[in] StatusCode The status code in HTTP response message. + @param[in] HeaderCount Number of HTTP header structures in Headers list. + @param[in] Headers Array containing list of HTTP headers. + @param[in] Payload The Redfish payload to dump. + +**/ +VOID +RedfishFreeResponse ( + IN EFI_HTTP_STATUS_CODE *StatusCode, + IN UINTN HeaderCount, + IN EFI_HTTP_HEADER *Headers, + IN REDFISH_PAYLOAD Payload + ); + +/** + Check if the "@odata.type" in Payload is valid or not. + + @param[in] Payload The Redfish payload to be checked. + @param[in] OdataTypeName OdataType will be retrived from mapping list. + @param[in] OdataTypeMappingList The list of OdataType. + @param[in] OdataTypeMappingListSize The number of mapping list + + @return TRUE if the "@odata.type" in Payload is valid, otherwise FALSE. + +**/ +BOOLEAN +RedfishIsValidOdataType ( + IN REDFISH_PAYLOAD Payload, + IN CONST CHAR8 *OdataTypeName, + IN REDFISH_ODATA_TYPE_MAPPING *OdataTypeMappingList, + IN UINTN OdataTypeMappingListSize + ); + +/** + Check if the payload is collection + + @param[in] Payload The Redfish payload to be checked. + + @return TRUE if the payload is collection. + +**/ +BOOLEAN +RedfishIsPayloadCollection ( + IN REDFISH_PAYLOAD Payload +); +/** + Get collection size. + + @param[in] Payload The Redfish collection payload + @param[in] CollectionSize Size of this collection + + @return EFI_SUCCESS Coolection size is returned in CollectionSize + @return EFI_INVALID_PARAMETER The payload is not a collection. +**/ +EFI_STATUS +RedfishGetCollectionSize( + IN REDFISH_PAYLOAD Payload, + IN UINTN *CollectionSize +); +/** + Get Redfish payload of collection member + + @param[in] Payload The Redfish collection payload + @param[in] Index Index of collection member + + @return NULL Fail to get collection member. + @return Non NULL Payload is returned. +**/ +REDFISH_PAYLOAD +RedfishGetPayloadByIndex ( + IN REDFISH_PAYLOAD Payload, + IN UINTN Index +); + +/** + Check and return Redfish resource of the given Redpath. + + @param[in] RedfishService Pointer to REDFISH_SERVICE + @param[in] Redpath Redpath of the resource. + @param[in] Response Optional return the resource. + + @return EFI_STATUS +**/ +EFI_STATUS +RedfishCheckIfRedpathExist ( + IN REDFISH_SERVICE RedfishService, + IN CHAR8 *Redpath, + IN REDFISH_RESPONSE *Response OPTIONAL +); + +/** + This function returns the string of Redfish service version. + + @param[in] RedfishService Redfish service instance. + @param[out] ServiceVersionStr Redfish service string. + + @return EFI_STATUS + +**/ +EFI_STATUS +RedfishGetServiceVersion( + IN REDFISH_SERVICE RedfishService, + OUT CHAR8 **ServiceVersionStr + ); + +/** + This function returns the string of Redfish service version. + + @param[in] ServiceVerisonStr The string of Redfish service version. + @param[in] Url The URL to build Redpath with ID. + Start with "/", for example "/Registries" + @param[in] Id ID string + @param[out] Redpath Pointer to retrive Redpath, caller has to free + the memory allocated for this string. + @return EFI_STATUS + +**/ +EFI_STATUS +RedfishBuildRedpathUseId ( + IN CHAR8 *ServiceVerisonStr, + IN CHAR8 *Url, + IN CHAR8 *Id, + OUT CHAR8 **Redpath + ); +#endif diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/RedfishLib.c b/RedfishPkg/PrivateLibrary/RedfishLib/RedfishLib.c new file mode 100644 index 0000000000..18aa4646e8 --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/RedfishLib.c @@ -0,0 +1,993 @@ +/** @file + Provides a set of utility APIs that allow to create/read/update/delete + (CRUD) Redfish resources and provide basic query. + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "RedfishMisc.h" + +/** + This function uses REST EX protocol provided in RedfishConfigServiceInfo. + The service enumerator will also handle the authentication flow automatically + if HTTP basic auth or Redfish session login is configured to use. + + Callers are responsible for freeing the returned service by RedfishCleanupService(). + + @param[in] RedfishConfigServiceInfo Redfish service information the EFI Redfish + feature driver communicates with. + + @return New created Redfish Service, or NULL if error happens. + +**/ +REDFISH_SERVICE +EFIAPI +RedfishCreateService ( + IN REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo + ) +{ + REDFISH_SERVICE RedfishService; + EDKII_REDFISH_AUTH_METHOD AuthMethod; + CHAR8 *UserId; + CHAR8 *Password; + EFI_STATUS Status; + + RedfishService = NULL; + UserId = NULL; + Password = NULL; + + // + // Check Input Parameters. + // + if (RedfishConfigServiceInfo == NULL) { + return NULL; + } + + // + // Get Authentication Configuration. + // + Status = RedfishGetAuthInfo (&AuthMethod, &UserId, &Password); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + // + // Create a redfish service node based on Redfish network host interface. + // + RedfishService = RedfishCreateLibredfishService ( + RedfishConfigServiceInfo, + AuthMethod, + UserId, + Password + ); + +ON_EXIT: + if (UserId != NULL) { + FreePool (UserId); + } + if (Password!= NULL) { + FreePool (Password); + } + + return RedfishService; +} + +/** + Free the Service and all its related resources. + + @param[in] RedfishService The Service to access the Redfish resources. + +**/ +VOID +EFIAPI +RedfishCleanupService ( + IN REDFISH_SERVICE RedfishService + ) +{ + if (RedfishService == NULL) { + return; + } + + cleanupServiceEnumerator (RedfishService); +} +/** + Create REDFISH_PAYLOAD instance in local with JSON represented resource value and + the Redfish Service. + + The returned REDFISH_PAYLOAD can be used to create or update Redfish resource in + server side. + + Callers are responsible for freeing the returned payload by RedfishCleanupPayload(). + + @param[in] Value JSON Value of the redfish resource. + @param[in] RedfishService The Service to access the Redfish resources. + + @return REDFISH_PAYLOAD instance of the resource, or NULL if error happens. + +**/ +REDFISH_PAYLOAD +EFIAPI +RedfishCreatePayload ( + IN EDKII_JSON_VALUE Value, + IN REDFISH_SERVICE RedfishService + ) +{ + EDKII_JSON_VALUE CopyValue; + + CopyValue = JsonValueClone (Value); + return createRedfishPayload (CopyValue, RedfishService); +} + +/** + Free the RedfishPayload and all its related resources. + + @param[in] Payload Payload to be freed. + +**/ +VOID +EFIAPI +RedfishCleanupPayload ( + IN REDFISH_PAYLOAD Payload + ) +{ + if (Payload == NULL) { + return; + } + + cleanupPayload ((redfishPayload *) Payload); +} + +/** + This function returns the decoded JSON value of a REDFISH_PAYLOAD. + + Caller doesn't need to free the returned JSON value because it will be released + in corresponding RedfishCleanupPayload() function. + + @param[in] Payload A REDFISH_PAYLOAD instance. + + @return Decoded JSON value of the payload. + +**/ +EDKII_JSON_VALUE +EFIAPI +RedfishJsonInPayload ( + IN REDFISH_PAYLOAD Payload + ) +{ + if (Payload == NULL) { + return NULL; + } + + return ((redfishPayload*)Payload)->json; +} + +/** + Fill the input RedPath string with system UUID from SMBIOS table or use the customized + ID if FromSmbios == FALSE. + + This is a helper function to build a RedPath string which can be used to address + a Redfish resource for this computer system. The input PathString must have a Systems + note in format of "Systems[UUID=%g]" or "Systems[UUID~%g]" to fill the UUID value. + + Example: + Use "/v1/Systems[UUID=%g]/Bios" to build a RedPath to address the "Bios" resource + for this computer system. + + @param[in] RedPath RedPath format to be build. + @param[in] FromSmbios Get system UUID from SMBIOS as computer system instance ID. + @param[in] IdString The computer system instance ID. + + @return Full RedPath with system UUID inside, or NULL if error happens. + +**/ +CHAR8 * +EFIAPI +RedfishBuildPathWithSystemUuid ( + IN CONST CHAR8 *RedPath, + IN BOOLEAN FromSmbios, + IN CHAR8 *IdString OPTIONAL + ) +{ + UINTN BufSize; + CHAR8* RetRedPath; + EFI_GUID SystemUuid; + EFI_STATUS Status; + + if (RedPath == NULL) { + return NULL; + } + + // + // Find system UUID from SMBIOS table. + // + if (FromSmbios) { + Status = NetLibGetSystemGuid(&SystemUuid); + if (EFI_ERROR (Status)) { + return NULL; + } + // AsciiStrLen ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") = 36 + BufSize = AsciiStrSize (RedPath) + AsciiStrLen ("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"); + } else { + BufSize = AsciiStrSize (RedPath) + AsciiStrLen (IdString); + } + + RetRedPath = AllocateZeroPool (BufSize); + if (RetRedPath == NULL) { + return NULL; + } + if (FromSmbios) { + AsciiSPrint (RetRedPath, BufSize, RedPath, &SystemUuid); + } else { + AsciiSPrint (RetRedPath, BufSize, RedPath, IdString); + } + return RetRedPath; +} +/** + Get a redfish response addressed by a RedPath string, including HTTP StatusCode, Headers + and Payload which record any HTTP response messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] RedfishService The Service to access the Redfish resources. + @param[in] RedPath RedPath string to address a resource, must start + from the root node. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The corresponding redfish resource has + been returned in Payload within RedResponse. + @retval EFI_INVALID_PARAMETER RedfishService, RedPath, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned Payload is NULL, indicates any error happen. + 2. If the returned StatusCode is NULL, indicates any error happen. + 3. If the returned StatusCode is not 2XX, indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishGetByService ( + IN REDFISH_SERVICE RedfishService, + IN CONST CHAR8 *RedPath, + OUT REDFISH_RESPONSE *RedResponse + ) +{ + if (RedfishService == NULL || RedPath == NULL || RedResponse == NULL) { + return EFI_INVALID_PARAMETER; + } + + ZeroMem (RedResponse, sizeof (REDFISH_RESPONSE)); + + RedResponse->Payload = (REDFISH_PAYLOAD) getPayloadByPath (RedfishService, RedPath, &(RedResponse->StatusCode)); + + // + // 1. If the returned Payload is NULL, indicates any error happen. + // 2. If the returned StatusCode is NULL, indicates any error happen. + // + if (RedResponse->Payload == NULL || RedResponse->StatusCode == NULL) { + return EFI_DEVICE_ERROR; + } + + // + // 3. If the returned StatusCode is not 2XX, indicates any error happen. + // NOTE: If there is any error message returned from server, it will be returned in + // Payload within RedResponse. + // + if (*(RedResponse->StatusCode) < HTTP_STATUS_200_OK || \ + *(RedResponse->StatusCode) > HTTP_STATUS_206_PARTIAL_CONTENT) { + return EFI_DEVICE_ERROR; + } + + return EFI_SUCCESS; +} +/** + Get a redfish response addressed by URI, including HTTP StatusCode, Headers + and Payload which record any HTTP response messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] RedfishService The Service to access the URI resources. + @param[in] Uri String to address a resource. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The corresponding redfish resource has + been returned in Payload within RedResponse. + @retval EFI_INVALID_PARAMETER RedfishService, RedPath, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned Payload is NULL, indicates any error happen. + 2. If the returned StatusCode is NULL, indicates any error happen. + 3. If the returned StatusCode is not 2XX, indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishGetByUri ( + IN REDFISH_SERVICE RedfishService, + IN CONST CHAR8 *Uri, + OUT REDFISH_RESPONSE *RedResponse + ) +{ + EDKII_JSON_VALUE JsonValue; + + if (RedfishService == NULL || Uri == NULL || RedResponse == NULL) { + return EFI_INVALID_PARAMETER; + } + + ZeroMem (RedResponse, sizeof (REDFISH_RESPONSE)); + + JsonValue = getUriFromService (RedfishService, Uri, &RedResponse->StatusCode); + RedResponse->Payload = createRedfishPayload(JsonValue, RedfishService); + + // + // 1. If the returned Payload is NULL, indicates any error happen. + // 2. If the returned StatusCode is NULL, indicates any error happen. + // + if (RedResponse->Payload == NULL || RedResponse->StatusCode == NULL) { + return EFI_DEVICE_ERROR; + } + + // + // 3. If the returned StatusCode is not 2XX, indicates any error happen. + // NOTE: If there is any error message returned from server, it will be returned in + // Payload within RedResponse. + // + if (*(RedResponse->StatusCode) < HTTP_STATUS_200_OK || \ + *(RedResponse->StatusCode) > HTTP_STATUS_206_PARTIAL_CONTENT) { + return EFI_DEVICE_ERROR; + } + return EFI_SUCCESS; +} +/** + Get a redfish response addressed by the input Payload and relative RedPath string, + including HTTP StatusCode, Headers and Payload which record any HTTP response messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] Payload A existing REDFISH_PAYLOAD instance. + @param[in] RedPath Relative RedPath string to address a resource inside Payload. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful: + 1. The HTTP StatusCode is NULL and the returned Payload in + RedResponse is not NULL, indicates the Redfish resource has + been parsed from the input payload directly. + 2. The HTTP StatusCode is not NULL and the value is 2XX, + indicates the corresponding redfish resource has been returned + in Payload within RedResponse. + @retval EFI_INVALID_PARAMETER Payload, RedPath, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned Payload is NULL, indicates any error happen. + 2. If StatusCode is not NULL and the returned value of StatusCode + is not 2XX, indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishGetByPayload ( + IN REDFISH_PAYLOAD Payload, + IN CONST CHAR8 *RedPath, + OUT REDFISH_RESPONSE *RedResponse + ) +{ + if (Payload == NULL || RedPath == NULL || RedResponse == NULL) { + return EFI_INVALID_PARAMETER; + } + + ZeroMem (RedResponse, sizeof (REDFISH_RESPONSE)); + + RedResponse->Payload = (REDFISH_PAYLOAD) getPayloadForPathString (Payload, RedPath, &(RedResponse->StatusCode)); + + // + // 1. If the returned Payload is NULL, indicates any error happen. + // + if (RedResponse->Payload == NULL) { + return EFI_DEVICE_ERROR; + } + + // + // 2. If StatusCode is not NULL and the returned value of StatusCode is not 2XX, indicates any + // error happen. + // NOTE: If there is any error message returned from server, it will be returned in + // Payload within RedResponse. + // + if (RedResponse->StatusCode != NULL && \ + (*(RedResponse->StatusCode) < HTTP_STATUS_200_OK || \ + *(RedResponse->StatusCode) > HTTP_STATUS_206_PARTIAL_CONTENT + )) { + return EFI_DEVICE_ERROR; + } + + return EFI_SUCCESS; +} +/** + Use HTTP PATCH to perform updates on pre-existing Redfish resource. + + This function uses the RedfishService to patch a Redfish resource addressed by + Uri (only the relative path is required). Changes to one or more properties within + the target resource are represented in the input Content, properties not specified + in Content won't be changed by this request. The corresponding redfish response will + returned, including HTTP StatusCode, Headers and Payload which record any HTTP response + messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] RedfishService The Service to access the Redfish resources. + @param[in] Uri Relative path to address the resource. + @param[in] Content JSON represented properties to be update. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The Redfish resource will be returned + in Payload within RedResponse if server send it back in the HTTP + response message body. + @retval EFI_INVALID_PARAMETER RedfishService, Uri, Content, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned StatusCode is NULL, indicates any error happen. + 2. If the returned StatusCode is not NULL and the value is not 2XX, + indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishPatchToUri ( + IN REDFISH_SERVICE RedfishService, + IN CONST CHAR8 *Uri, + IN CONST CHAR8 *Content, + OUT REDFISH_RESPONSE *RedResponse + ) +{ + EFI_STATUS Status; + EDKII_JSON_VALUE JsonValue; + + Status = EFI_SUCCESS; + JsonValue = NULL; + + if (RedfishService == NULL || Uri == NULL || Content == NULL || RedResponse == NULL) { + return EFI_INVALID_PARAMETER; + } + + ZeroMem (RedResponse, sizeof (REDFISH_RESPONSE)); + + JsonValue = (EDKII_JSON_VALUE) patchUriFromService ( + RedfishService, + Uri, + Content, + &(RedResponse->StatusCode) + ); + + // + // 1. If the returned StatusCode is NULL, indicates any error happen. + // + if (RedResponse->StatusCode == NULL) { + Status = EFI_DEVICE_ERROR; + goto ON_EXIT; + } + + // + // 2. If the returned StatusCode is not NULL and the value is not 2XX, indicates any error happen. + // NOTE: If there is any error message returned from server, it will be returned in + // Payload within RedResponse. + // + if (*(RedResponse->StatusCode) < HTTP_STATUS_200_OK || \ + *(RedResponse->StatusCode) > HTTP_STATUS_206_PARTIAL_CONTENT) { + Status = EFI_DEVICE_ERROR; + } + +ON_EXIT: + if (JsonValue != NULL) { + RedResponse->Payload = createRedfishPayload (JsonValue, RedfishService); + if (RedResponse->Payload == NULL) { + // + // Ignore the error when create RedfishPayload, just free the JsonValue since it's not what + // we care about if the returned StatusCode is 2XX. + // + JsonValueFree (JsonValue); + } + } + + return Status; +} +/** + Use HTTP PATCH to perform updates on target payload. Patch to odata.id in Payload directly. + + This function uses the Payload to patch the Target. Changes to one or more properties + within the target resource are represented in the input Payload, properties not specified + in Payload won't be changed by this request. The corresponding redfish response will + returned, including HTTP StatusCode, Headers and Payload which record any HTTP response + messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] Target The target payload to be updated. + @param[in] Payload Palyoad with properties to be changed. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The Redfish resource will be returned + in Payload within RedResponse if server send it back in the HTTP + response message body. + @retval EFI_INVALID_PARAMETER Target, Payload, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned StatusCode is NULL, indicates any error happen. + 2. If the returned StatusCode is not NULL and the value is not 2XX, + indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishPatchToPayload ( + IN REDFISH_PAYLOAD Target, + IN REDFISH_PAYLOAD Payload, + OUT REDFISH_RESPONSE *RedResponse + ) +{ + if (Target == NULL || Payload == NULL || RedResponse == NULL) { + return EFI_INVALID_PARAMETER; + } + + ZeroMem (RedResponse, sizeof (REDFISH_RESPONSE)); + + RedResponse->Payload = (REDFISH_PAYLOAD) patchPayload ( + Target, + Payload, + &(RedResponse->StatusCode) + ); + + // + // 1. If the returned StatusCode is NULL, indicates any error happen. + // + if (RedResponse->StatusCode == NULL) { + return EFI_DEVICE_ERROR; + } + + // + // 2. If the returned StatusCode is not NULL and the value is not 2XX, indicates any error happen. + // NOTE: If there is any error message returned from server, it will be returned in + // Payload within RedResponse. + // + if (*(RedResponse->StatusCode) < HTTP_STATUS_200_OK || \ + *(RedResponse->StatusCode) > HTTP_STATUS_206_PARTIAL_CONTENT) { + return EFI_DEVICE_ERROR; + } + + return EFI_SUCCESS; +} +/** + Use HTTP POST to create a new resource in target payload. + + The POST request should be submitted to the Resource Collection in which the new resource + is to belong. The Resource Collection is addressed by Target payload. The Redfish may + ignore any service controlled properties. The corresponding redfish response will returned, + including HTTP StatusCode, Headers and Payload which record any HTTP response messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] Target Target payload of the Resource Collection. + @param[in] Payload The new resource to be created. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX. The Redfish resource will be returned + in Payload within RedResponse if server send it back in the HTTP + response message body. + @retval EFI_INVALID_PARAMETER Target, Payload, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned StatusCode is NULL, indicates any error happen. + 2. If the returned StatusCode is not NULL and the value is not 2XX, + indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishPostToPayload ( + IN REDFISH_PAYLOAD Target, + IN REDFISH_PAYLOAD Payload, + OUT REDFISH_RESPONSE *RedResponse + ) +{ + if (Target == NULL || Payload == NULL || RedResponse == NULL) { + return EFI_INVALID_PARAMETER; + } + + ZeroMem (RedResponse, sizeof (REDFISH_RESPONSE)); + + RedResponse->Payload = (REDFISH_PAYLOAD) postPayload ( + Target, + Payload, + &(RedResponse->StatusCode) + ); + + // + // 1. If the returned StatusCode is NULL, indicates any error happen. + // + if (RedResponse->StatusCode == NULL) { + return EFI_DEVICE_ERROR; + } + + // + // 2. If the returned StatusCode is not NULL and the value is not 2XX, indicates any error happen. + // NOTE: If there is any error message returned from server, it will be returned in + // Payload within RedResponse. + // + if (*(RedResponse->StatusCode) < HTTP_STATUS_200_OK || \ + *(RedResponse->StatusCode) > HTTP_STATUS_206_PARTIAL_CONTENT) { + return EFI_DEVICE_ERROR; + } + + return EFI_SUCCESS; +} +/** + Use HTTP DELETE to remove a resource. + + This function uses the RedfishService to remove a Redfish resource which is addressed + by input Uri (only the relative path is required). The corresponding redfish response will + returned, including HTTP StatusCode, Headers and Payload which record any HTTP response + messages. + + Callers are responsible for freeing the HTTP StatusCode, Headers and Payload returned in + redfish response data. + + @param[in] RedfishService The Service to access the Redfish resources. + @param[in] Uri Relative path to address the resource. + @param[out] RedResponse Pointer to the Redfish response data. + + @retval EFI_SUCCESS The opeartion is successful, indicates the HTTP StatusCode is not + NULL and the value is 2XX, the Redfish resource has been removed. + If there is any message returned from server, it will be returned + in Payload within RedResponse. + @retval EFI_INVALID_PARAMETER RedfishService, Uri, or RedResponse is NULL. + @retval EFI_DEVICE_ERROR An unexpected system or network error occurred. Callers can get + more error info from returned HTTP StatusCode, Headers and Payload + within RedResponse: + 1. If the returned StatusCode is NULL, indicates any error happen. + 2. If the returned StatusCode is not NULL and the value is not 2XX, + indicates any error happen. +**/ +EFI_STATUS +EFIAPI +RedfishDeleteByUri ( + IN REDFISH_SERVICE RedfishService, + IN CONST CHAR8 *Uri, + OUT REDFISH_RESPONSE *RedResponse + ) +{ + EFI_STATUS Status; + EDKII_JSON_VALUE JsonValue; + + Status = EFI_SUCCESS; + JsonValue = NULL; + + if (RedfishService == NULL || Uri == NULL || RedResponse == NULL) { + return EFI_INVALID_PARAMETER; + } + + ZeroMem (RedResponse, sizeof (REDFISH_RESPONSE)); + + JsonValue = (EDKII_JSON_VALUE) deleteUriFromService ( + RedfishService, + Uri, + &(RedResponse->StatusCode) + ); + + // + // 1. If the returned StatusCode is NULL, indicates any error happen. + // + if (RedResponse->StatusCode == NULL) { + Status = EFI_DEVICE_ERROR; + goto ON_EXIT; + } + + // + // 2. If the returned StatusCode is not NULL and the value is not 2XX, indicates any error happen. + // NOTE: If there is any error message returned from server, it will be returned in + // Payload within RedResponse. + // + if (*(RedResponse->StatusCode) < HTTP_STATUS_200_OK || \ + *(RedResponse->StatusCode) > HTTP_STATUS_206_PARTIAL_CONTENT) { + Status = EFI_DEVICE_ERROR; + } + +ON_EXIT: + if (JsonValue != NULL) { + RedResponse->Payload = createRedfishPayload (JsonValue, RedfishService); + if (RedResponse->Payload == NULL) { + // + // Ignore the error when create RedfishPayload, just free the JsonValue since it's not what + // we care about if the returned StatusCode is 2XX. + // + JsonValueFree (JsonValue); + } + } + + return Status; +} +/** + Dump text in fractions. + + @param[in] String ASCII string to dump. + +**/ +VOID +RedfishDumpJsonStringFractions ( + IN CHAR8 *String + ) +{ + CHAR8 *NextFraction; + UINTN StringFractionSize; + UINTN StrLen; + UINTN Count; + CHAR8 BackupChar; + + StringFractionSize = 200; + if (String == NULL) { + return ; + } + + DEBUG((DEBUG_INFO, "JSON text:\n")); + NextFraction = String; + StrLen = AsciiStrLen (String); + if (StrLen == 0) { + return; + } + for (Count = 0; Count < (StrLen / StringFractionSize); Count++) { + BackupChar = *(NextFraction + StringFractionSize); + *(NextFraction + StringFractionSize) = 0; + DEBUG((DEBUG_INFO, "%a", NextFraction)); + *(NextFraction + StringFractionSize) = BackupChar; + NextFraction += StringFractionSize; + } + if ((StrLen % StringFractionSize) != 0) { + DEBUG((DEBUG_INFO, "%a\n\n", NextFraction)); + } +} +/** + Dump text in JSON value. + + @param[in] JsonValue The Redfish JSON value to dump. + +**/ +VOID +RedfishDumpJson ( + IN EDKII_JSON_VALUE JsonValue + ) +{ + CHAR8 *String; + + String = JsonDumpString (JsonValue, 0); + if (String == NULL) { + return; + } + RedfishDumpJsonStringFractions (String); + FreePool(String); +} +/** + Extract the JSON text content from REDFISH_PAYLOAD and dump to debug console. + + @param[in] Payload The Redfish payload to dump. + +**/ +VOID +RedfishDumpPayload ( + IN REDFISH_PAYLOAD Payload + ) +{ + EDKII_JSON_VALUE JsonValue; + CHAR8 *String; + + JsonValue = NULL; + String = NULL; + + if (Payload == NULL) { + return; + } + + JsonValue = RedfishJsonInPayload (Payload); + if (JsonValue == NULL) { + return; + } + + String = JsonDumpString (JsonValue, 0); + if (String == NULL) { + return; + } + + RedfishDumpJsonStringFractions (String); + FreePool(String); +} +/** + This function will cleanup the HTTP header and Redfish payload resources. + + @param[in] StatusCode The status code in HTTP response message. + @param[in] HeaderCount Number of HTTP header structures in Headers list. + @param[in] Headers Array containing list of HTTP headers. + @param[in] Payload The Redfish payload to dump. + +**/ +VOID +RedfishFreeResponse ( + IN EFI_HTTP_STATUS_CODE *StatusCode, + IN UINTN HeaderCount, + IN EFI_HTTP_HEADER *Headers, + IN REDFISH_PAYLOAD Payload + ) +{ + if (StatusCode != NULL) { + FreePool (StatusCode); + StatusCode = NULL; + } + + if (HeaderCount != 0 && Headers != NULL) { + HttpFreeHeaderFields(Headers, HeaderCount); + Headers = NULL; + } + + if (Payload != NULL) { + RedfishCleanupPayload (Payload); + Payload = NULL; + } +} +/** + Check if the "@odata.type" in Payload is valid or not. + + @param[in] Payload The Redfish payload to be checked. + @param[in] OdataTypeName OdataType will be retrived from mapping list. + @param[in] OdataTypeMappingList The list of OdataType. + @param[in] OdataTypeMappingListSize The number of mapping list + + @return TRUE if the "@odata.type" in Payload is valid, otherwise FALSE. + +**/ +BOOLEAN +RedfishIsValidOdataType ( + IN REDFISH_PAYLOAD Payload, + IN CONST CHAR8 *OdataTypeName, + IN REDFISH_ODATA_TYPE_MAPPING *OdataTypeMappingList, + IN UINTN OdataTypeMappingListSize + ) +{ + UINTN Index; + EDKII_JSON_VALUE OdataType; + EDKII_JSON_VALUE JsonValue; + + if (Payload == NULL || OdataTypeName == NULL) { + return FALSE; + } + + JsonValue = RedfishJsonInPayload (Payload); + if (!JsonValueIsObject (JsonValue)) { + return FALSE; + } + + OdataType = JsonObjectGetValue (JsonValueGetObject (JsonValue), "@odata.type"); + if (!JsonValueIsString (OdataType) || JsonValueGetAsciiString (OdataType) == NULL) { + return FALSE; + } + + for (Index = 0; Index < OdataTypeMappingListSize; Index ++) { + if (AsciiStrCmp (OdataTypeMappingList[Index].OdataTypeName, OdataTypeName) == 0 && + AsciiStrCmp (OdataTypeMappingList[Index].OdataType, JsonValueGetAsciiString (OdataType)) == 0) { + return TRUE; + } + } + DEBUG ((DEBUG_INFO, "%a: This Odata type is not in the list.\n", __FUNCTION__)); + return FALSE; +} +/** + Check if the payload is collection + + @param[in] Payload The Redfish payload to be checked. + + @return TRUE if the payload is collection. + +**/ +BOOLEAN +RedfishIsPayloadCollection ( + IN REDFISH_PAYLOAD Payload +) +{ + return isPayloadCollection (Payload); +} +/** + Get collection size. + + @param[in] Payload The Redfish collection payload + @param[in] CollectionSize Size of this collection + + @return EFI_SUCCESS Coolection size is returned in CollectionSize + @return EFI_INVALID_PARAMETER The payload is not a collection. +**/ +EFI_STATUS +RedfishGetCollectionSize( + IN REDFISH_PAYLOAD Payload, + IN UINTN *CollectionSize + ) +{ + if (Payload == NULL || CollectionSize == NULL) { + return EFI_INVALID_PARAMETER; + } + if (!RedfishIsPayloadCollection(Payload)) { + return EFI_INVALID_PARAMETER; + } + + *CollectionSize = (UINTN)getCollectionSize(Payload); + return EFI_SUCCESS; +} +/** + Get Redfish payload of collection member + + @param[in] Payload The Redfish collection payload + @param[in] Index Index of collection member + + @return NULL Fail to get collection member. + @return Non NULL Payload is returned. +**/ +REDFISH_PAYLOAD +RedfishGetPayloadByIndex ( + IN REDFISH_PAYLOAD Payload, + IN UINTN Index +) +{ + REDFISH_RESPONSE RedfishResponse; + REDFISH_PAYLOAD PayloadReturn; + + PayloadReturn = (VOID *)getPayloadByIndex (Payload, Index, &RedfishResponse.StatusCode); + if(PayloadReturn == NULL || + (*(RedfishResponse.StatusCode) < HTTP_STATUS_200_OK && *(RedfishResponse.StatusCode) > HTTP_STATUS_206_PARTIAL_CONTENT)){ + return NULL; + } + return PayloadReturn; +} +/** + Check and return Redfish resource of the given Redpath. + + @param[in] RedfishService Pointer to REDFISH_SERVICE + @param[in] Redpath Redpath of the resource. + @param[in] Response Optional return the resource. + + @return EFI_STATUS +**/ +EFI_STATUS +RedfishCheckIfRedpathExist ( + IN REDFISH_SERVICE RedfishService, + IN CHAR8 *Redpath, + IN REDFISH_RESPONSE *Response OPTIONAL + ) +{ + EFI_STATUS Status; + REDFISH_RESPONSE TempResponse; + + if (Redpath == NULL) { + return EFI_INVALID_PARAMETER; + } + Status = RedfishGetByService (RedfishService, Redpath, &TempResponse); + if (EFI_ERROR (Status)) { + return Status; + } + if (Response == NULL) { + RedfishFreeResponse( + TempResponse.StatusCode, + TempResponse.HeaderCount, + TempResponse.Headers, + TempResponse.Payload + ); + } else { + CopyMem ((VOID *)Response, (VOID *)&TempResponse, sizeof (REDFISH_RESPONSE)); + } + return EFI_SUCCESS; +} diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/RedfishLib.inf b/RedfishPkg/PrivateLibrary/RedfishLib/RedfishLib.inf new file mode 100644 index 0000000000..128d511df7 --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/RedfishLib.inf @@ -0,0 +1,60 @@ +## @file +# RedfishLib Library implementation. +# +# Copyright (c) 2019, Intel Corporation. All rights reserved.
+# (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001b + BASE_NAME = DxeRedfishLib + FILE_GUID = 9C2CA9CF-4F79-11E8-A7D1-8CDCD426C973 + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + LIBRARY_CLASS = RedfishLib| DXE_DRIVER UEFI_APPLICATION UEFI_DRIVER + +# +# VALID_ARCHITECTURES = IA32 X64 ARM AARCH64 RISCV64 +# + +[Sources] + edk2libredfish/src/redpath.c + edk2libredfish/src/service.c + edk2libredfish/src/payload.c + edk2libredfish/include/redfish.h + edk2libredfish/include/redfishPayload.h + edk2libredfish/include/redfishService.h + edk2libredfish/include/redpath.h + RedfishLib.c + RedfishMisc.h + RedfishMisc.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + NetworkPkg/NetworkPkg.dec + RedfishPkg/RedfishPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + HttpLib + MemoryAllocationLib + NetLib + RedfishContentCodingLib + RedfishCrtLib + UefiBootServicesTableLib + UefiLib + +[Protocols] + gEfiRestExServiceBindingProtocolGuid ## Consumed + gEfiRestExProtocolGuid ## Consumed + gEdkIIRedfishCredentialProtocolGuid ## Consumed + +[BuildOptions] + MSFT:*_*_*_CC_FLAGS = /U_WIN32 /UWIN64 /U_MSC_VER + GCC:*_*_*_CC_FLAGS = -Wno-unused-function -Wno-unused-but-set-variable diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/RedfishMisc.c b/RedfishPkg/PrivateLibrary/RedfishLib/RedfishMisc.c new file mode 100644 index 0000000000..7077c37154 --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/RedfishMisc.c @@ -0,0 +1,201 @@ +/** @file + Internal Functions for RedfishLib. + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "RedfishMisc.h" + +EDKII_REDFISH_CREDENTIAL_PROTOCOL *mCredentialProtocol = NULL; + +/** + This function returns the string of Redfish service version. + + @param[in] RedfishService Redfish service instance. + @param[out] ServiceVersionStr Redfish service string. + + @return EFI_STATUS + +**/ +EFI_STATUS +RedfishGetServiceVersion ( + IN REDFISH_SERVICE RedfishService, + OUT CHAR8 **ServiceVersionStr + ) +{ + redfishService *Redfish; + CHAR8 **KeysArray; + UINTN KeysNum; + + if (RedfishService == NULL || ServiceVersionStr == NULL) { + return EFI_INVALID_PARAMETER; + } + Redfish = (redfishService *)RedfishService; + if (Redfish->versions == NULL) { + return EFI_INVALID_PARAMETER; + } + KeysArray = JsonObjectGetKeys (Redfish->versions, &KeysNum); + if (KeysNum == 0 || KeysArray == NULL) { + return EFI_NOT_FOUND; + } + *ServiceVersionStr = *KeysArray; + return EFI_SUCCESS; +} + +/** + Creates a REDFISH_SERVICE which can be later used to access the Redfish resources. + + This function will configure REST EX child according to parameters described in + Redfish network host interface in SMBIOS type 42 record. The service enumerator will also + handle the authentication flow automatically if HTTP basic auth or Redfish session + login is configured to use. + + @param[in] RedfishConfigServiceInfo Redfish service information the EFI Redfish + feature driver communicates with. + @param[in] AuthMethod None, HTTP basic auth, or Redfish session login. + @param[in] UserId User Name used for authentication. + @param[in] Password Password used for authentication. + + @return New created Redfish service, or NULL if error happens. + +**/ +REDFISH_SERVICE +RedfishCreateLibredfishService ( + IN REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo, + IN EDKII_REDFISH_AUTH_METHOD AuthMethod, + IN CHAR8 *UserId, + IN CHAR8 *Password + ) +{ + + UINTN Flags; + enumeratorAuthentication Auth; + redfishService* Redfish; + + Redfish = NULL; + + ZeroMem (&Auth, sizeof (Auth)); + if (AuthMethod == AuthMethodHttpBasic) { + Auth.authType = REDFISH_AUTH_BASIC; + } else if (AuthMethod == AuthMethodRedfishSession) { + Auth.authType = REDFISH_AUTH_SESSION; + } + Auth.authCodes.userPass.username = UserId; + Auth.authCodes.userPass.password = Password; + + Flags = REDFISH_FLAG_SERVICE_NO_VERSION_DOC; + + if (AuthMethod != AuthMethodNone) { + Redfish = createServiceEnumerator(RedfishConfigServiceInfo, NULL, &Auth, (unsigned int ) Flags); + } else { + Redfish = createServiceEnumerator(RedfishConfigServiceInfo, NULL, NULL, (unsigned int) Flags); + } + + // + // Zero the Password after use. + // + if (Password != NULL) { + ZeroMem (Password, AsciiStrLen(Password)); + } + + return (REDFISH_SERVICE) Redfish; +} + +/** + Retrieve platform's Redfish authentication information. + + This functions returns the Redfish authentication method together with the user + Id and password. + For AuthMethodNone, UserId and Password will point to NULL which means authentication + is not required to access the Redfish service. + For AuthMethodHttpBasic, the UserId and Password could be used for + HTTP header authentication as defined by RFC7235. For AuthMethodRedfishSession, + the UserId and Password could be used for Redfish session login as defined by + Redfish API specification (DSP0266). + + Callers are responsible for freeing the returned string storage pointed by UserId + and Password. + + @param[out] AuthMethod Type of Redfish authentication method. + @param[out] UserId The pointer to store the returned UserId string. + @param[out] Password The pointer to store the returned Password string. + + @retval EFI_SUCCESS Get the authentication information successfully. + @retval EFI_INVALID_PARAMETER AuthMethod or UserId or Password is NULL. + @retval EFI_UNSUPPORTED Unsupported authentication method is found. +**/ +EFI_STATUS +RedfishGetAuthInfo ( + OUT EDKII_REDFISH_AUTH_METHOD *AuthMethod, + OUT CHAR8 **UserId, + OUT CHAR8 **Password + ) +{ + EFI_STATUS Status; + + if (AuthMethod == NULL || UserId == NULL || Password == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // Locate Redfish Credential Protocol. + // + if (mCredentialProtocol == NULL) { + Status = gBS->LocateProtocol (&gEdkIIRedfishCredentialProtocolGuid, NULL, (VOID **)&mCredentialProtocol); + if (EFI_ERROR (Status)) { + return EFI_UNSUPPORTED; + } + } + + ASSERT (mCredentialProtocol != NULL); + + Status = mCredentialProtocol->GetAuthInfo (mCredentialProtocol, AuthMethod, UserId, Password); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "RedfishGetAuthInfo: failed to retrieve Redfish credential - %r\n", Status)); + return Status; + } + + return Status; +} +/** + This function returns the string of Redfish service version. + + @param[in] ServiceVerisonStr The string of Redfish service version. + @param[in] Url The URL to build Redpath with ID. + Start with "/", for example "/Registries" + @param[in] Id ID string + @param[out] Redpath Pointer to retrive Redpath, caller has to free + the memory allocated for this string. + @return EFI_STATUS + +**/ +EFI_STATUS +RedfishBuildRedpathUseId ( + IN CHAR8 *ServiceVerisonStr, + IN CHAR8 *Url, + IN CHAR8 *Id, + OUT CHAR8 **Redpath + ) +{ + UINTN RedpathSize; + + if (Redpath == NULL || ServiceVerisonStr == NULL || Url == NULL || Id == NULL) { + return EFI_INVALID_PARAMETER; + } + + RedpathSize = AsciiStrLen ("/") + + AsciiStrLen (ServiceVerisonStr) + + AsciiStrLen (Url) + + AsciiStrLen ("[Id=]") + + AsciiStrLen (Id) + 1; + *Redpath = AllocatePool(RedpathSize); + if (*Redpath == NULL) { + return EFI_OUT_OF_RESOURCES; + } + AsciiSPrint (*Redpath, RedpathSize, "/%a%a[Id=%a]", ServiceVerisonStr, Url, Id); + return EFI_SUCCESS; +} diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/RedfishMisc.h b/RedfishPkg/PrivateLibrary/RedfishLib/RedfishMisc.h new file mode 100644 index 0000000000..d01a433d1a --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/RedfishMisc.h @@ -0,0 +1,82 @@ +/** @file + Internal Functions for RedfishLib. + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef DXE_REDFISH_MISC_LIB_H_ +#define DXE_REDFISH_MISC_LIB_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ARRAY_SIZE(Array) (sizeof (Array) / sizeof ((Array)[0])) + +/** + Creates a REDFISH_SERVICE which can be later used to access the Redfish resources. + + This function will configure REST EX child according to parameters described in + Redfish network host interface in SMBIOS type 42 record. The service enumerator will also + handle the authentication flow automatically if HTTP basic auth or Redfish session + login is configured to use. + + @param[in] RedfishConfigServiceInfo Redfish service information the EFI Redfish + feature driver communicates with. + @param[in] AuthMethod None, HTTP basic auth, or Redfish session login. + @param[in] UserId User Name used for authentication. + @param[in] Password Password used for authentication. + + @return New created Redfish service, or NULL if error happens. + +**/ +REDFISH_SERVICE +RedfishCreateLibredfishService ( + IN REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo, + IN EDKII_REDFISH_AUTH_METHOD AuthMethod, + IN CHAR8 *UserId, + IN CHAR8 *Password + ); + +/** + Retrieve platform's Redfish authentication information. + + This functions returns the Redfish authentication method together with the user + Id and password. + For AuthMethodNone, UserId and Password will point to NULL which means authentication + is not required to access the Redfish service. + For AuthMethodHttpBasic, the UserId and Password could be used for + HTTP header authentication as defined by RFC7235. For AuthMethodRedfishSession, + the UserId and Password could be used for Redfish session login as defined by + Redfish API specification (DSP0266). + + Callers are responsible for freeing the returned string storage pointed by UserId + and Password. + + @param[out] AuthMethod Type of Redfish authentication method. + @param[out] UserId The pointer to store the returned UserId string. + @param[out] Password The pointer to store the returned Password string. + + @retval EFI_SUCCESS Get the authentication information successfully. + @retval EFI_INVALID_PARAMETER AuthMethod or UserId or Password is NULL. + @retval EFI_UNSUPPORTED Unsupported authentication method is found. +**/ +EFI_STATUS +RedfishGetAuthInfo ( + OUT EDKII_REDFISH_AUTH_METHOD *AuthMethod, + OUT CHAR8 **UserId, + OUT CHAR8 **Password + ); + +#endif diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfish.h b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfish.h new file mode 100644 index 0000000000..de1feb22fb --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfish.h @@ -0,0 +1,24 @@ +/** @file + This file is cloned from DMTF libredfish library tag v1.0.0 and maintained + by EDKII. + +//---------------------------------------------------------------------------- +// Copyright Notice: +// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md +//---------------------------------------------------------------------------- + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#ifndef LIBREDFISH_REDFISH_H_ +#define LIBREDFISH_REDFISH_H_ + +#include +#include +#include + +#endif diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfishPayload.h b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfishPayload.h new file mode 100644 index 0000000000..03380d9394 --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfishPayload.h @@ -0,0 +1,39 @@ +/** @file + This file is cloned from DMTF libredfish library tag v1.0.0 and maintained + by EDKII. + +//---------------------------------------------------------------------------- +// Copyright Notice: +// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md +//---------------------------------------------------------------------------- + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#ifndef LIBREDFISH_REDFISH_PAYLOAD_H_ +#define LIBREDFISH_REDFISH_PAYLOAD_H_ + +#include + +#include +#include +#include + +redfishPayload* createRedfishPayload(json_t* value, redfishService* service); +redfishPayload* getPayloadByNodeName(redfishPayload* payload, const char* nodeName, EFI_HTTP_STATUS_CODE** StatusCode); +redfishPayload* getPayloadByIndex(redfishPayload* payload, size_t index, EFI_HTTP_STATUS_CODE** StatusCode); +redfishPayload* getPayloadForPath(redfishPayload* payload, redPathNode* redpath, EFI_HTTP_STATUS_CODE** StatusCode); +redfishPayload* getPayloadForPathString(redfishPayload* payload, const char* string, EFI_HTTP_STATUS_CODE** StatusCode); +redfishPayload* patchPayload(redfishPayload* target, redfishPayload* payload, EFI_HTTP_STATUS_CODE** StatusCode); +redfishPayload* postContentToPayload(redfishPayload* target, const char* data, size_t dataSize, const char* contentType, EFI_HTTP_STATUS_CODE** StatusCode); +redfishPayload* postPayload(redfishPayload* target, redfishPayload* payload, EFI_HTTP_STATUS_CODE** StatusCode); +void cleanupPayload(redfishPayload* payload); +bool isPayloadCollection (redfishPayload *Payload); +size_t getCollectionSize(redfishPayload* payload); +redfishPayload* getPayloadByIndex (redfishPayload* payload, size_t index, EFI_HTTP_STATUS_CODE** StatusCode); + +#endif diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfishService.h b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfishService.h new file mode 100644 index 0000000000..5bcb381c05 --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redfishService.h @@ -0,0 +1,101 @@ +/** @file + This file is cloned from DMTF libredfish library tag v1.0.0 and maintained + by EDKII. + +//---------------------------------------------------------------------------- +// Copyright Notice: +// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md +//---------------------------------------------------------------------------- + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef LIBREDFISH_REDFISH_SERVICE_H_ +#define LIBREDFISH_REDFISH_SERVICE_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +typedef struct { + char* host; + json_t* versions; + unsigned int flags; + char* sessionToken; + char* basicAuthStr; + // + // point to the part in above "host" field, which will be put into + // the "Host" header of HTTP request message. + // + char* HostHeaderValue; + EFI_REST_EX_PROTOCOL *RestEx; +} redfishService; + +typedef struct { + json_t* json; + redfishService* service; +} redfishPayload; + +#define REDFISH_AUTH_BASIC 0 +#define REDFISH_AUTH_BEARER_TOKEN 1 +#define REDFISH_AUTH_SESSION 2 + +#define REDFISH_HTTP_RESPONSE_TIMEOUT 5000 /// 5 seconds in uints of millisecond. + +/// +/// Library class public defines +/// +#define HTTP_FLAG L"http://" +#define HTTPS_FLAG L"https://" + +/// +/// The redfish first URL should be "/redfish/v1/", while we use "/redfish/v1" here without "/" +/// in the end is to avoid the 301 Perment redirect response from Redfish profile simulator. +/// +#define REDFISH_FIRST_URL L"/redfish/v1" + +typedef struct { + unsigned int authType; + union { + struct { + char* username; + char* password; + } userPass; + struct { + char* token; + } authToken; + } authCodes; +} enumeratorAuthentication; + +//Values for flags +#define REDFISH_FLAG_SERVICE_NO_VERSION_DOC 0x00000001 //The Redfish Service lacks the version document (in violation of the Redfish spec) +redfishService* createServiceEnumerator(REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo, const char* rootUri, enumeratorAuthentication* auth, unsigned int flags); +json_t* getUriFromService(redfishService* service, const char* uri, EFI_HTTP_STATUS_CODE** StatusCode); +json_t* patchUriFromService(redfishService* service, const char* uri, const char* content, EFI_HTTP_STATUS_CODE** StatusCode); +json_t* postUriFromService(redfishService* service, const char* uri, const char* content, size_t contentLength, const char* contentType, EFI_HTTP_STATUS_CODE** StatusCode); +json_t* deleteUriFromService(redfishService* service, const char* uri, EFI_HTTP_STATUS_CODE** StatusCode); +redfishPayload* getRedfishServiceRoot(redfishService* service, const char* version, EFI_HTTP_STATUS_CODE** StatusCode); +redfishPayload* getPayloadByPath(redfishService* service, const char* path, EFI_HTTP_STATUS_CODE** StatusCode); +void cleanupServiceEnumerator(redfishService* service); + +#endif diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redpath.h b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redpath.h new file mode 100644 index 0000000000..bdec6098e5 --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/include/redpath.h @@ -0,0 +1,42 @@ +/** @file + This file is cloned from DMTF libredfish library tag v1.0.0 and maintained + by EDKII. + +//---------------------------------------------------------------------------- +// Copyright Notice: +// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md +//---------------------------------------------------------------------------- + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#ifndef LIBREDFISH_REDPATH_H_ +#define LIBREDFISH_REDPATH_H_ + +#include + +#include + +typedef struct _redPathNode +{ + bool isRoot; + bool isIndex; + + char* version; + char* nodeName; + size_t index; + char* op; + char* propName; + char* value; + + struct _redPathNode* next; +} redPathNode; + +redPathNode* parseRedPath(const char* path); +void cleanupRedPath(redPathNode* node); + +#endif diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/payload.c b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/payload.c new file mode 100644 index 0000000000..3d60acd926 --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/payload.c @@ -0,0 +1,732 @@ +/** @file + This file is cloned from DMTF libredfish library tag v1.0.0 and maintained + by EDKII. + +//---------------------------------------------------------------------------- +// Copyright Notice: +// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md +//---------------------------------------------------------------------------- + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#include + +static redfishPayload* getOpResult(redfishPayload* payload, const char* propName, const char* op, const char* value, EFI_HTTP_STATUS_CODE** StatusCode); +static redfishPayload* collectionEvalOp(redfishPayload* payload, const char* propName, const char* op, const char* value, EFI_HTTP_STATUS_CODE** StatusCode); +static redfishPayload* arrayEvalOp(redfishPayload* payload, const char* propName, const char* op, const char* value, EFI_HTTP_STATUS_CODE** StatusCode); +static redfishPayload* createCollection(redfishService* service, size_t count, redfishPayload** payloads); +static json_t* json_object_get_by_index(json_t* json, size_t index); + +bool isPayloadCollection(redfishPayload* payload) +{ + json_t* members; + json_t* count; + + if(!payload || !json_is_object(payload->json)) + { + return false; + } + members = json_object_get(payload->json, "Members"); + count = json_object_get(payload->json, "Members@odata.count"); + return ((members != NULL) && (count != NULL)); +} + +size_t getCollectionSize(redfishPayload* payload) +{ + json_t* members; + json_t* count; + + if(!payload || !json_is_object(payload->json)) + { + return 0; + } + members = json_object_get(payload->json, "Members"); + count = json_object_get(payload->json, "Members@odata.count"); + if(!members || !count) + { + return 0; + } + return (size_t)json_integer_value(count); +} + +bool isPayloadArray(redfishPayload* payload) +{ + if(!payload || !json_is_array(payload->json)) + { + return false; + } + return true; +} + +char* payloadToString(redfishPayload* payload, bool prettyPrint) +{ + size_t flags = 0; + if(!payload) + { + return NULL; + } + if(prettyPrint) + { + flags = JSON_INDENT(2); + } + return json_dumps(payload->json, flags); +} + +redfishPayload* createRedfishPayload(json_t* value, redfishService* service) +{ + redfishPayload* payload; + payload = (redfishPayload*)malloc(sizeof(redfishPayload)); + if(payload != NULL) + { + payload->json = value; + payload->service = service; + } + return payload; +} + +redfishPayload* getPayloadByNodeName(redfishPayload* payload, const char* nodeName, EFI_HTTP_STATUS_CODE** StatusCode) +{ + json_t* value; + json_t* odataId; + const char* uri; + + if(!payload || !nodeName || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + value = json_object_get(payload->json, nodeName); + if(value == NULL) + { + return NULL; + } + json_incref(value); + if(json_object_size(value) == 1) + { + odataId = json_object_get(value, "@odata.id"); + if(odataId != NULL) + { + json_incref(odataId); + uri = json_string_value(odataId); + json_decref(value); + value = getUriFromService(payload->service, uri, StatusCode); + json_decref(odataId); + if(value == NULL || *StatusCode == NULL) + { + return NULL; + } + } + } + if (*StatusCode == NULL || (**StatusCode >= HTTP_STATUS_200_OK && **StatusCode <= HTTP_STATUS_206_PARTIAL_CONTENT)) { + if(json_is_string(value)) + { + odataId = json_object(); + json_object_set(odataId, nodeName, value); + json_decref(value); + value = odataId; + } + } + + return createRedfishPayload(value, payload->service); +} + +redfishPayload* getPayloadByIndex(redfishPayload* payload, size_t index, EFI_HTTP_STATUS_CODE** StatusCode) +{ + json_t* value = NULL; + json_t* odataId; + const char* uri; + BOOLEAN FromServerFlag = FALSE; + + if(!payload || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + if(isPayloadCollection(payload)) + { + redfishPayload* members = getPayloadByNodeName(payload, "Members", StatusCode); + if ((*StatusCode == NULL && members == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + return members; + } + + if (*StatusCode != NULL) { + // + // The Payload (members) are retrived from server. + // + FreePool (*StatusCode); + *StatusCode = NULL; + FromServerFlag = TRUE; + } + + redfishPayload* ret = getPayloadByIndex(members, index, StatusCode); + if (*StatusCode == NULL && ret != NULL && FromServerFlag) { + // + // In such a case, the Redfish resource is parsed from the input payload (members) directly. + // Since the members are retrived from server, we still return HTTP_STATUS_200_OK. + // + *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE)); + if (*StatusCode == NULL) { + ret = NULL; + } else { + **StatusCode = HTTP_STATUS_200_OK; + } + } + + cleanupPayload(members); + return ret; + } + + if(json_is_array(payload->json)) + { + // + // The valid range for index is from 0 to the return value of json_array_size() minus 1 + // + value = json_array_get(payload->json, index); + } + else if(json_is_object(payload->json)) + { + value = json_object_get_by_index(payload->json, index); + } + + if(value == NULL) + { + return NULL; + } + + json_incref(value); + if(json_object_size(value) == 1) + { + odataId = json_object_get(value, "@odata.id"); + if(odataId != NULL) + { + uri = json_string_value(odataId); + json_decref(value); + value = getUriFromService(payload->service, uri, StatusCode); + if(value == NULL) + { + return NULL; + } + } + } + return createRedfishPayload(value, payload->service); +} + +redfishPayload* getPayloadForPath(redfishPayload* payload, redPathNode* redpath, EFI_HTTP_STATUS_CODE** StatusCode) +{ + redfishPayload* ret = NULL; + redfishPayload* tmp; + + if(!payload || !redpath || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + BOOLEAN FromServerFlag = FALSE; + + if(redpath->nodeName) + { + ret = getPayloadByNodeName(payload, redpath->nodeName, StatusCode); + if ((*StatusCode == NULL && ret == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + // + // Any error happen, return directly. + // + return ret; + } + } + else if(redpath->isIndex) + { + ASSERT (redpath->index >= 1); + ret = getPayloadByIndex(payload, redpath->index - 1, StatusCode); + if ((*StatusCode == NULL && ret == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + // + // Any error happen, return directly. + // + return ret; + } + } + else if(redpath->op) + { + ret = getOpResult(payload, redpath->propName, redpath->op, redpath->value, StatusCode); + if ((*StatusCode == NULL && ret == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + // + // Any error happen, return directly. + // + return ret; + } + } + else + { + return NULL; + } + + if(redpath->next == NULL || ret == NULL) + { + return ret; + } + else + { + if (*StatusCode != NULL) { + FreePool (*StatusCode); + *StatusCode = NULL; + FromServerFlag = TRUE; + } + + tmp = getPayloadForPath(ret, redpath->next, StatusCode); + if (*StatusCode == NULL && tmp != NULL && FromServerFlag) { + // + // In such a case, the Redfish resource is parsed from the input payload (ret) directly. + // Since the ret are retrived from server, we still return HTTP_STATUS_200_OK. + // + *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE)); + if (*StatusCode == NULL) { + tmp = NULL; + } else { + **StatusCode = HTTP_STATUS_200_OK; + } + } + + cleanupPayload(ret); + return tmp; + } +} + +redfishPayload* getPayloadForPathString(redfishPayload* payload, const char* string, EFI_HTTP_STATUS_CODE** StatusCode) +{ + redPathNode* redpath; + redfishPayload* ret; + + if(!string || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + redpath = parseRedPath(string); + if(redpath == NULL) + { + return NULL; + } + ret = getPayloadForPath(payload, redpath, StatusCode); + cleanupRedPath(redpath); + return ret; +} + +redfishPayload* patchPayload(redfishPayload* target, redfishPayload* payload, EFI_HTTP_STATUS_CODE** StatusCode) +{ + json_t* json; + char* content; + char* uri; + + if(!target || !payload || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + json = json_object_get(target->json, "@odata.id"); + if(json == NULL) + { + return NULL; + } + uri = strdup(json_string_value(json)); + + content = json_dumps(payload->json, 0); + json_decref(json); + + json = patchUriFromService(target->service, uri, content, StatusCode); + free(uri); + free(content); + if(json == NULL) + { + return NULL; + } + + return createRedfishPayload(json, target->service); +} + +redfishPayload* postContentToPayload(redfishPayload* target, const char* data, size_t dataSize, const char* contentType, EFI_HTTP_STATUS_CODE** StatusCode) +{ + json_t* json; + char* uri; + + if(!target || !data || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + json = json_object_get(target->json, "@odata.id"); + if(json == NULL) + { + json = json_object_get(target->json, "target"); + if(json == NULL) + { + return NULL; + } + } + uri = strdup(json_string_value(json)); + json = postUriFromService(target->service, uri, data, dataSize, contentType, StatusCode); + free(uri); + if(json == NULL) + { + return NULL; + } + + return createRedfishPayload(json, target->service); +} + +redfishPayload* postPayload(redfishPayload* target, redfishPayload* payload, EFI_HTTP_STATUS_CODE** StatusCode) +{ + char* content; + redfishPayload* ret; + + if(!target || !payload || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + if(!json_is_object(payload->json)) + { + return NULL; + } + content = payloadToString(payload, false); + ret = postContentToPayload(target, content, strlen(content), NULL, StatusCode); + free(content); + return ret; +} + +void cleanupPayload(redfishPayload* payload) +{ + if(!payload) + { + return; + } + json_decref(payload->json); + //Don't free payload->service, let the caller handle cleaning up the service + free(payload); +} + +static redfishPayload* getOpResult(redfishPayload* payload, const char* propName, const char* op, const char* value, EFI_HTTP_STATUS_CODE** StatusCode) +{ + const char* propStr; + json_t* stringProp; + bool ret = false; + redfishPayload* prop; + long long intVal, intPropVal; + json_type jsonType; + + if(isPayloadCollection(payload)) + { + return collectionEvalOp(payload, propName, op, value, StatusCode); + } + if(isPayloadArray(payload)) + { + return arrayEvalOp(payload, propName, op, value, StatusCode); + } + + prop = getPayloadByNodeName(payload, propName, StatusCode); + if ((*StatusCode == NULL && prop == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + return prop; + } + stringProp = prop->json; + jsonType = prop->json->type; + switch(jsonType) + { + case JSON_OBJECT: + stringProp = json_object_get(prop->json, propName); + case JSON_STRING: + if(strcmp(op, "=") == 0) + { + propStr = json_string_value(stringProp); + if(propStr == NULL) + { + cleanupPayload(prop); + return NULL; + } + ret = (strcmp(propStr, value) == 0); + } else if(strcmp(op, "~") == 0) + { + propStr = json_string_value(stringProp); + if(propStr == NULL) + { + cleanupPayload(prop); + return NULL; + } + ret = (strcasecmp(propStr, value) == 0); + } + break; + case JSON_TRUE: + if(strcmp(op, "=") == 0) + { + ret = (strcmp(value, "true") == 0); + } + break; + case JSON_FALSE: + if(strcmp(op, "=") == 0) + { + ret = (strcmp(value, "false") == 0); + } + break; + case JSON_INTEGER: + intPropVal = json_integer_value(prop->json); + intVal = strtoll(value, NULL, 0); + if(strcmp(op, "=") == 0) + { + ret = (intPropVal == intVal); + } + else if(strcmp(op, "<") == 0) + { + ret = (intPropVal < intVal); + } + else if(strcmp(op, ">") == 0) + { + ret = (intPropVal > intVal); + } + else if(strcmp(op, "<=") == 0) + { + ret = (intPropVal <= intVal); + } + else if(strcmp(op, ">=") == 0) + { + ret = (intPropVal >= intVal); + } + break; + default: + break; + } + cleanupPayload(prop); + if(ret) + { + return payload; + } + else + { + return NULL; + } +} + +static redfishPayload* collectionEvalOp(redfishPayload* payload, const char* propName, const char* op, const char* value, EFI_HTTP_STATUS_CODE** StatusCode) +{ + redfishPayload* ret; + redfishPayload* tmp; + redfishPayload* members; + redfishPayload** valid; + size_t validMax; + size_t validCount = 0; + size_t i; + + validMax = getCollectionSize(payload); + if(validMax == 0) + { + return NULL; + } + + valid = (redfishPayload**)calloc(validMax, sizeof(redfishPayload*)); + if(valid == NULL) + { + return NULL; + } + /*Technically getPayloadByIndex would do this, but this optimizes things*/ + members = getPayloadByNodeName(payload, "Members", StatusCode); + if ((*StatusCode == NULL && members == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + return members; + } + + for(i = 0; i < validMax; i++) + { + if (*StatusCode != NULL) { + FreePool (*StatusCode); + *StatusCode = NULL; + } + + tmp = getPayloadByIndex(members, i, StatusCode); + if ((*StatusCode == NULL && tmp == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + return tmp; + } + + if (*StatusCode != NULL) { + FreePool (*StatusCode); + *StatusCode = NULL; + } + + valid[validCount] = getOpResult(tmp, propName, op, value, StatusCode); + /* + if ((*StatusCode == NULL && valid[validCount] == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + return valid[validCount]; + } + */ + if(valid[validCount] != NULL) + { + validCount++; + } + else + { + cleanupPayload(tmp); + } + } + cleanupPayload(members); + if(validCount == 0) + { + free(valid); + return NULL; + } + if(validCount == 1) + { + ret = valid[0]; + free(valid); + return ret; + } + else + { + ret = createCollection(payload->service, validCount, valid); + free(valid); + return ret; + } +} + +static redfishPayload* arrayEvalOp(redfishPayload* payload, const char* propName, const char* op, const char* value, EFI_HTTP_STATUS_CODE** StatusCode) +{ + redfishPayload* ret; + redfishPayload* tmp; + redfishPayload** valid; + size_t validMax; + size_t validCount = 0; + size_t i; + + validMax = json_array_size(payload->json); + if(validMax == 0) + { + return NULL; + } + + valid = (redfishPayload**)calloc(validMax, sizeof(redfishPayload*)); + if(valid == NULL) + { + return NULL; + } + for(i = 0; i < validMax; i++) + { + if (*StatusCode != NULL) { + FreePool (*StatusCode); + *StatusCode = NULL; + } + + tmp = getPayloadByIndex(payload, i, StatusCode); + if ((*StatusCode == NULL && tmp == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + return tmp; + } + + if (*StatusCode != NULL) { + FreePool (*StatusCode); + *StatusCode = NULL; + } + + valid[validCount] = getOpResult(tmp, propName, op, value, StatusCode); + /* + if ((*StatusCode == NULL && valid[validCount] == NULL) || + (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) { + return valid[validCount]; + } + */ + + if(valid[validCount] != NULL) + { + validCount++; + } + else + { + cleanupPayload(tmp); + } + } + if(validCount == 0) + { + free(valid); + return NULL; + } + if(validCount == 1) + { + ret = valid[0]; + free(valid); + return ret; + } + else + { + ret = createCollection(payload->service, validCount, valid); + free(valid); + return ret; + } +} + +static redfishPayload* createCollection(redfishService* service, size_t count, redfishPayload** payloads) +{ + redfishPayload* ret; + json_t* collectionJson = json_object(); + json_t* jcount = json_integer((json_int_t)count); + json_t* members = json_array(); + size_t i; + + if(!collectionJson) + { + return NULL; + } + if(!members) + { + json_decref(collectionJson); + return NULL; + } + json_object_set(collectionJson, "Members@odata.count", jcount); + json_decref(jcount); + for(i = 0; i < count; i++) + { + json_array_append(members, payloads[i]->json); + cleanupPayload(payloads[i]); + } + json_object_set(collectionJson, "Members", members); + json_decref(members); + + ret = createRedfishPayload(collectionJson, service); + return ret; +} + +static json_t* json_object_get_by_index(json_t* json, size_t index) +{ + void* iter; + size_t i; + + iter = json_object_iter(json); + for(i = 0; i < index; i++) + { + iter = json_object_iter_next(json, iter); + if(iter == NULL) break; + } + if(iter == NULL) + { + return NULL; + } + return json_object_iter_value(iter); +} +/* vim: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/redpath.c b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/redpath.c new file mode 100644 index 0000000000..1fb4346c2b --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/redpath.c @@ -0,0 +1,192 @@ +/** @file + This file is cloned from DMTF libredfish library tag v1.0.0 and maintained + by EDKII. + +//---------------------------------------------------------------------------- +// Copyright Notice: +// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md +//---------------------------------------------------------------------------- + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ +#include + +static char* getVersion(const char* path, char** end); +static void parseNode(const char* path, redPathNode* node, redPathNode** end); + +static char* getStringTill(const char* string, const char* terminator, char** retEnd); + +redPathNode* parseRedPath(const char* path) +{ + redPathNode* node; + redPathNode* endNode; + char* curPath; + char* end; + + if(!path || strlen(path) == 0) + { + return NULL; + } + + node = (redPathNode*)calloc(1, sizeof(redPathNode)); + if(!node) + { + return NULL; + } + if(path[0] == '/') + { + node->isRoot = true; + if(path[1] == 'v') + { + node->version = getVersion(path+1, &curPath); + if(curPath == NULL) + { + return node; + } + if(curPath[0] == '/') + { + curPath++; + } + node->next = parseRedPath(curPath); + } + else + { + node->next = parseRedPath(path+1); + } + return node; + } + node->isRoot = false; + curPath = getStringTill(path, "/", &end); + endNode = node; + parseNode(curPath, node, &endNode); + free(curPath); + if(end != NULL) + { + endNode->next = parseRedPath(end+1); + } + return node; +} + +void cleanupRedPath(redPathNode* node) +{ + if(!node) + { + return; + } + cleanupRedPath(node->next); + node->next = NULL; + if(node->version) + { + free(node->version); + } + if(node->nodeName) + { + free(node->nodeName); + } + if(node->op) + { + free(node->op); + } + if(node->propName) + { + free(node->propName); + } + if(node->value) + { + free(node->value); + } + free(node); +} + +static char* getVersion(const char* path, char** end) +{ + return getStringTill(path, "/", end); +} + +static void parseNode(const char* path, redPathNode* node, redPathNode** end) +{ + char* indexStart; + char* index; + char* indexEnd; + char* nodeName = getStringTill(path, "[", &indexStart); + size_t tmpIndex; + char* opChars; + + node->nodeName = nodeName; + if(indexStart == NULL) + { + *end = node; + return; + } + node->next = (redPathNode*)calloc(1, sizeof(redPathNode)); + if(!node->next) + { + return; + } + //Skip past [ + indexStart++; + *end = node->next; + index = getStringTill(indexStart, "]", NULL); + tmpIndex = (size_t)strtoull(index, &indexEnd, 0); + if(indexEnd != index) + { + free(index); + node->next->index = tmpIndex; + node->next->isIndex = true; + return; + } + opChars = strpbrk(index, "<>=~"); + if(opChars == NULL) + { + //TODO handle last() and position() + node->next->op = strdup("exists"); + node->next->propName = index; + return; + } + node->next->propName = (char*)malloc((opChars - index)+1); + memcpy(node->next->propName, index, (opChars - index)); + node->next->propName[(opChars - index)] = 0; + + tmpIndex = 1; + while(1) + { + if(opChars[tmpIndex] == '=' || opChars[tmpIndex] == '<' || opChars[tmpIndex] == '>' || opChars[tmpIndex] == '~') + { + tmpIndex++; + continue; + } + break; + } + + node->next->op = (char*)malloc(tmpIndex+1); + memcpy(node->next->op, opChars, tmpIndex); + node->next->op[tmpIndex] = 0; + + node->next->value = strdup(opChars+tmpIndex); + free(index); +} + +static char* getStringTill(const char* string, const char* terminator, char** retEnd) +{ + char* ret; + char* end; + end = strstr((char*)string, terminator); + if(retEnd) + { + *retEnd = end; + } + if(end == NULL) + { + //No terminator + return strdup(string); + } + ret = (char*)malloc((end-string)+1); + memcpy(ret, string, (end-string)); + ret[(end-string)] = 0; + return ret; +} diff --git a/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/service.c b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/service.c new file mode 100644 index 0000000000..7713f89e6d --- /dev/null +++ b/RedfishPkg/PrivateLibrary/RedfishLib/edk2libredfish/src/service.c @@ -0,0 +1,1396 @@ +/** @file + This file is cloned from DMTF libredfish library tag v1.0.0 and maintained + by EDKII. + +//---------------------------------------------------------------------------- +// Copyright Notice: +// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md +//---------------------------------------------------------------------------- + + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ (C) Copyright 2021 Hewlett Packard Enterprise Development LP
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include + +static int initRest(redfishService* service, void * restProtocol); +static redfishService* createServiceEnumeratorNoAuth(const char* host, const char* rootUri, bool enumerate, unsigned int flags, void * restProtocol); +static redfishService* createServiceEnumeratorBasicAuth(const char* host, const char* rootUri, const char* username, const char* password, unsigned int flags, void * restProtocol); +static redfishService* createServiceEnumeratorSessionAuth(const char* host, const char* rootUri, const char* username, const char* password, unsigned int flags, void * restProtocol); +static char* makeUrlForService(redfishService* service, const char* uri); +static json_t* getVersions(redfishService* service, const char* rootUri); +static void addStringToJsonObject(json_t* object, const char* key, const char* value); + +CHAR16* +C8ToC16 (CHAR8 *AsciiStr) +{ + CHAR16 *Str; + UINTN BufLen; + + BufLen = (AsciiStrLen (AsciiStr) + 1) * 2; + Str = AllocatePool (BufLen); + ASSERT (Str != NULL); + + AsciiStrToUnicodeStrS (AsciiStr, Str, AsciiStrLen (AsciiStr) + 1); + + return Str; +} + +VOID +RestConfigFreeHttpRequestData ( + IN EFI_HTTP_REQUEST_DATA *RequestData + ) +{ + if (RequestData == NULL) { + return ; + } + + if (RequestData->Url != NULL) { + FreePool (RequestData->Url); + } + + FreePool (RequestData); +} + +VOID +RestConfigFreeHttpMessage ( + IN EFI_HTTP_MESSAGE *Message, + IN BOOLEAN IsRequest + ) +{ + if (Message == NULL) { + return ; + } + + if (IsRequest) { + RestConfigFreeHttpRequestData (Message->Data.Request); + Message->Data.Request = NULL; + } else { + if (Message->Data.Response != NULL) { + FreePool (Message->Data.Response); + Message->Data.Response = NULL; + } + } + + if (Message->Headers != NULL) { + FreePool (Message->Headers); + Message->Headers = NULL; + } + if (Message->Body != NULL) { + FreePool (Message->Body); + Message->Body = NULL; + } +} + +/** + Converts the Unicode string to ASCII string to a new allocated buffer. + + @param[in] String Unicode string to be converted. + + @return Buffer points to ASCII string, or NULL if error happens. + +**/ + +CHAR8 * +UnicodeStrDupToAsciiStr ( + CONST CHAR16 *String + ) +{ + CHAR8 *AsciiStr; + UINTN BufLen; + EFI_STATUS Status; + + BufLen = StrLen (String) + 1; + AsciiStr = AllocatePool (BufLen); + if (AsciiStr == NULL) { + return NULL; + } + + Status = UnicodeStrToAsciiStrS (String, AsciiStr, BufLen); + if (EFI_ERROR (Status)) { + return NULL; + } + + return AsciiStr; +} +/** + This function encodes the content. + + @param[in] ContentEncodedValue HTTP conent encoded value. + @param[in] OriginalContent Original content. + @param[out] EncodedContent Pointer to receive encoded content. + @param[out] EncodedContentLength Length of encoded content. + + @retval EFI_SUCCESS Content encoded successfully. + @retval EFI_UNSUPPORTED No source encoding funciton, + @retval EFI_INVALID_PARAMETER One of the given parameter is invalid. + +**/ +EFI_STATUS +EncodeRequestContent ( + IN CHAR8 *ContentEncodedValue, + IN CHAR8 *OriginalContent, + OUT VOID **EncodedContent, + OUT UINTN *EncodedContentLength +) +{ + EFI_STATUS Status; + VOID *EncodedPointer; + UINTN EncodedLength; + + if (OriginalContent == NULL || EncodedContent == NULL || EncodedContentLength == NULL) { + return EFI_INVALID_PARAMETER; + } + Status = RedfishContentEncode ( + ContentEncodedValue, + OriginalContent, + AsciiStrLen (OriginalContent), + &EncodedPointer, + &EncodedLength + ); + if (Status == EFI_SUCCESS) { + *EncodedContent = EncodedPointer; + *EncodedContentLength = EncodedLength; + return EFI_SUCCESS; + } + return Status; +} + +/** + This function decodes the content. The Memory block pointed by + ContentPointer would be freed and replaced with the cooked Redfish + payload. + + @param[in] ContentEncodedValue HTTP conent encoded value. + @param[in, out] ContentPointer Pointer to encoded content. + Pointer of decoded content when out. + @param[in, out] ContentLength Pointer to the length of encoded content. + Length of decoded content when out. + + @retval EFI_SUCCESS Functinos found. + @retval EFI_UNSUPPORTED No functions found. + @retval EFI_INVALID_PARAMETER One of the given parameter is invalid. + +**/ +EFI_STATUS +DecodeResponseContent ( + IN CHAR8 *ContentEncodedValue, + IN OUT VOID **ContentPointer, + IN OUT UINTN *ContentLength +) +{ + EFI_STATUS Status; + VOID *DecodedPointer; + UINTN DecodedLength; + + if (ContentEncodedValue == NULL) { + return EFI_INVALID_PARAMETER; + } + Status = RedfishContentDecode ( + ContentEncodedValue, + *ContentPointer, + *ContentLength, + &DecodedPointer, + &DecodedLength + ); + if (Status == EFI_SUCCESS) { + FreePool (*ContentPointer); + *ContentPointer = DecodedPointer; + *ContentLength = DecodedLength; + } + return Status; +} + +/** + Create a HTTP URL string for specific Redfish resource. + + This function build a URL string from the Redfish Host interface record and caller specified + relative path of the resource. + + Callers are responsible for freeing the returned string storage pointed by HttpUrl. + + @param[in] RedfishData Redfish network host interface record. + @param[in] RelativePath Relative path of a resource. + @param[out] HttpUrl The pointer to store the returned URL string. + + @retval EFI_SUCCESS Build the URL string successfully. + @retval EFI_INVALID_PARAMETER RedfishData or HttpUrl is NULL. + @retval EFI_OUT_OF_RESOURCES There are not enough memory resources. + +**/ +EFI_STATUS +RedfishBuildUrl ( + IN REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo, + IN CHAR16 *RelativePath, OPTIONAL + OUT CHAR16 **HttpUrl + ) +{ + CHAR16 *Url; + CHAR16 *UrlHead; + UINTN UrlLength; + UINTN PathLen; + + if ((RedfishConfigServiceInfo == NULL) || (HttpUrl == NULL)) { + return EFI_INVALID_PARAMETER; + } + + // + // RFC2616: http_URL = "http(s):" "//" host [ ":" port ] [ abs_path [ "?" query ]] + // + if (RelativePath == NULL) { + PathLen = 0; + } else { + PathLen = StrLen (RelativePath); + } + UrlLength = StrLen (HTTPS_FLAG) + StrLen (REDFISH_FIRST_URL) + 1 + StrLen(RedfishConfigServiceInfo->RedfishServiceLocation) + PathLen; + Url = AllocateZeroPool (UrlLength * sizeof (CHAR16)); + if (Url == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + UrlHead = Url; + // + // Copy "http://" or "https://" according RedfishServiceIpPort. + // + if (!RedfishConfigServiceInfo->RedfishServiceUseHttps) { + StrCpyS (Url, StrLen (HTTPS_FLAG) + 1, HTTP_FLAG); + Url = Url + StrLen (HTTP_FLAG); + } else { + StrCpyS (Url, StrLen (HTTPS_FLAG) + 1, HTTPS_FLAG); + Url = Url + StrLen (HTTPS_FLAG); + } + + StrCpyS (Url, StrLen (RedfishConfigServiceInfo->RedfishServiceLocation) + 1, RedfishConfigServiceInfo->RedfishServiceLocation); + Url = Url + StrLen (RedfishConfigServiceInfo->RedfishServiceLocation); + + // + // Copy abs_path + // + if (RelativePath != NULL && PathLen != 0 ) { + StrnCpyS (Url, UrlLength, RelativePath, PathLen); + } + *HttpUrl = UrlHead; + return EFI_SUCCESS; +} + +redfishService* createServiceEnumerator(REDFISH_CONFIG_SERVICE_INFORMATION *RedfishConfigServiceInfo, const char* rootUri, enumeratorAuthentication* auth, unsigned int flags) +{ + EFI_STATUS Status; + CHAR16 *HttpUrl; + CHAR8 *AsciiHost; + EFI_REST_EX_PROTOCOL *RestEx; + redfishService *ret; + + HttpUrl = NULL; + AsciiHost = NULL; + RestEx = NULL; + ret = NULL; + + if (RedfishConfigServiceInfo->RedfishServiceRestExHandle == NULL) { + goto ON_EXIT; + } + Status = RedfishBuildUrl(RedfishConfigServiceInfo, NULL, &HttpUrl); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + ASSERT (HttpUrl != NULL); + + AsciiHost = UnicodeStrDupToAsciiStr (HttpUrl); + if (AsciiHost == NULL) { + goto ON_EXIT; + } + + Status = gBS->HandleProtocol ( + RedfishConfigServiceInfo->RedfishServiceRestExHandle, + &gEfiRestExProtocolGuid, + (VOID **)&RestEx + ); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + if(auth == NULL) { + ret = createServiceEnumeratorNoAuth(AsciiHost, rootUri, true, flags, RestEx); + } else if(auth->authType == REDFISH_AUTH_BASIC) { + ret = createServiceEnumeratorBasicAuth(AsciiHost, rootUri, auth->authCodes.userPass.username, auth->authCodes.userPass.password, flags, RestEx); + } else if(auth->authType == REDFISH_AUTH_SESSION) { + ret = createServiceEnumeratorSessionAuth(AsciiHost, rootUri, auth->authCodes.userPass.username, auth->authCodes.userPass.password, flags, RestEx); + } else { + goto ON_EXIT; + } + + ret->RestEx = RestEx; +ON_EXIT: + if (HttpUrl != NULL) { + FreePool (HttpUrl); + } + + if (AsciiHost != NULL) { + FreePool (AsciiHost); + } + + return ret; +} + +json_t* getUriFromService(redfishService* service, const char* uri, EFI_HTTP_STATUS_CODE** StatusCode) +{ + char* url; + json_t* ret; + HTTP_IO_HEADER *HttpIoHeader = NULL; + EFI_STATUS Status; + EFI_HTTP_REQUEST_DATA *RequestData = NULL; + EFI_HTTP_MESSAGE *RequestMsg = NULL; + EFI_HTTP_MESSAGE ResponseMsg; + EFI_HTTP_HEADER *ContentEncodedHeader; + + if(service == NULL || uri == NULL || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + url = makeUrlForService(service, uri); + if(!url) + { + return NULL; + } + + DEBUG((DEBUG_INFO, "libredfish: getUriFromService(): %a\n", url)); + + // + // Step 1: Create HTTP request message with 4 headers: + // + HttpIoHeader = HttpIoCreateHeader ((service->sessionToken || service->basicAuthStr) ? 6 : 5); + if (HttpIoHeader == NULL) { + ret = NULL; + goto ON_EXIT; + } + + if(service->sessionToken) + { + Status = HttpIoSetHeader (HttpIoHeader, "X-Auth-Token", service->sessionToken); + ASSERT_EFI_ERROR (Status); + } else if (service->basicAuthStr) { + Status = HttpIoSetHeader (HttpIoHeader, "Authorization", service->basicAuthStr); + ASSERT_EFI_ERROR (Status); + } + + Status = HttpIoSetHeader (HttpIoHeader, "Host", service->HostHeaderValue); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "OData-Version", "4.0"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "Accept", "application/json"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "User-Agent", "libredfish"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "Connection", "Keep-Alive"); + ASSERT_EFI_ERROR (Status); + + // + // Step 2: build the rest of HTTP request info. + // + RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA)); + if (RequestData == NULL) { + ret = NULL; + goto ON_EXIT; + } + + RequestData->Method = HttpMethodGet; + RequestData->Url = C8ToC16 (url); + + // + // Step 3: fill in EFI_HTTP_MESSAGE + // + RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE)); + if (RequestMsg == NULL) { + ret = NULL; + goto ON_EXIT; + } + + RequestMsg->Data.Request = RequestData; + RequestMsg->HeaderCount = HttpIoHeader->HeaderCount; + RequestMsg->Headers = HttpIoHeader->Headers; + + ZeroMem (&ResponseMsg, sizeof (ResponseMsg)); + + // + // Step 4: call RESTEx to get response from REST service. + // + Status = service->RestEx->SendReceive (service->RestEx, RequestMsg, &ResponseMsg); + if (EFI_ERROR (Status)) { + ret = NULL; + goto ON_EXIT; + } + + // + // Step 5: Return the HTTP StatusCode and Body message. + // + if (ResponseMsg.Data.Response != NULL) { + *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE)); + if (*StatusCode == NULL) { + ret = NULL; + goto ON_EXIT; + } + + // + // The caller shall take the responsibility to free the buffer. + // + **StatusCode = ResponseMsg.Data.Response->StatusCode; + } + + if (ResponseMsg.BodyLength != 0 && ResponseMsg.Body != NULL) { + // + // Check if data is encoded. + // + ContentEncodedHeader = HttpFindHeader (ResponseMsg.HeaderCount, ResponseMsg.Headers, HTTP_HEADER_CONTENT_ENCODING); + if (ContentEncodedHeader != NULL) { + // + // The content is encoded. + // + Status = DecodeResponseContent (ContentEncodedHeader->FieldValue, &ResponseMsg.Body, &ResponseMsg.BodyLength); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Failed to decompress the response content %r\n.", __FUNCTION__, Status)); + ret = NULL; + goto ON_EXIT; + } + } + ret = json_loadb (ResponseMsg.Body, ResponseMsg.BodyLength, 0, NULL); + } else { + // + // There is no message body returned from server. + // + ret = NULL; + } + +ON_EXIT: + if (url != NULL) { + free (url); + } + + if (HttpIoHeader != NULL) { + HttpIoFreeHeader (HttpIoHeader); + } + + if (RequestData != NULL) { + RestConfigFreeHttpRequestData (RequestData); + } + + if (RequestMsg != NULL) { + FreePool (RequestMsg); + } + + RestConfigFreeHttpMessage (&ResponseMsg, FALSE); + + return ret; +} + +json_t* patchUriFromService(redfishService* service, const char* uri, const char* content, EFI_HTTP_STATUS_CODE** StatusCode) +{ + char* url; + json_t* ret; + HTTP_IO_HEADER *HttpIoHeader = NULL; + EFI_STATUS Status; + EFI_HTTP_REQUEST_DATA *RequestData = NULL; + EFI_HTTP_MESSAGE *RequestMsg = NULL; + EFI_HTTP_MESSAGE ResponseMsg; + CHAR8 ContentLengthStr[80]; + CHAR8 *EncodedContent; + UINTN EncodedContentLen; + + if(service == NULL || uri == NULL || content == NULL || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + url = makeUrlForService(service, uri); + if(!url) + { + return NULL; + } + + DEBUG((DEBUG_INFO, "libredfish: patchUriFromService(): %a\n", url)); + + // + // Step 1: Create HTTP request message with 4 headers: + // + HttpIoHeader = HttpIoCreateHeader ((service->sessionToken || service->basicAuthStr) ? 9 : 8); + if (HttpIoHeader == NULL) { + ret = NULL; + goto ON_EXIT; + } + + if(service->sessionToken) + { + Status = HttpIoSetHeader (HttpIoHeader, "X-Auth-Token", service->sessionToken); + ASSERT_EFI_ERROR (Status); + } else if (service->basicAuthStr) { + Status = HttpIoSetHeader (HttpIoHeader, "Authorization", service->basicAuthStr); + ASSERT_EFI_ERROR (Status); + } + + Status = HttpIoSetHeader (HttpIoHeader, "Host", service->HostHeaderValue); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "Content-Type", "application/json"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "Accept", "application/json"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "User-Agent", "libredfish"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "Connection", "Keep-Alive"); + ASSERT_EFI_ERROR (Status); + + AsciiSPrint( + ContentLengthStr, + sizeof (ContentLengthStr), + "%lu", + (UINT64) strlen(content) + ); + Status = HttpIoSetHeader (HttpIoHeader, "Content-Length", ContentLengthStr); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "OData-Version", "4.0"); + ASSERT_EFI_ERROR (Status); + + // + // Step 2: build the rest of HTTP request info. + // + RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA)); + if (RequestData == NULL) { + ret = NULL; + goto ON_EXIT; + } + + RequestData->Method = HttpMethodPatch; + RequestData->Url = C8ToC16 (url); + + // + // Step 3: fill in EFI_HTTP_MESSAGE + // + RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE)); + if (RequestMsg == NULL) { + ret = NULL; + goto ON_EXIT; + } + + EncodedContent = (CHAR8 *)content; + EncodedContentLen = strlen(content); + // + // We currently only support gzip Content-Encoding. + // + Status = EncodeRequestContent ((CHAR8 *)HTTP_CONTENT_ENCODING_GZIP, (CHAR8 *)content, (VOID **)&EncodedContent, &EncodedContentLen); + if (Status == EFI_INVALID_PARAMETER) { + DEBUG((DEBUG_ERROR, "%a: Error to encode content.\n", __FUNCTION__)); + ret = NULL; + goto ON_EXIT; + } else if (Status == EFI_UNSUPPORTED) { + DEBUG((DEBUG_INFO, "No content coding for %a! Use raw data instead.\n", HTTP_CONTENT_ENCODING_GZIP)); + Status = HttpIoSetHeader (HttpIoHeader, "Content-Encoding", HTTP_CONTENT_ENCODING_IDENTITY); + ASSERT_EFI_ERROR (Status); + } else { + Status = HttpIoSetHeader (HttpIoHeader, "Content-Encoding", HTTP_CONTENT_ENCODING_GZIP); + ASSERT_EFI_ERROR (Status); + } + + RequestMsg->Data.Request = RequestData; + RequestMsg->HeaderCount = HttpIoHeader->HeaderCount; + RequestMsg->Headers = HttpIoHeader->Headers; + RequestMsg->BodyLength = EncodedContentLen; + RequestMsg->Body = (VOID*) EncodedContent; + + ZeroMem (&ResponseMsg, sizeof (ResponseMsg)); + + // + // Step 4: call RESTEx to get response from REST service. + // + Status = service->RestEx->SendReceive (service->RestEx, RequestMsg, &ResponseMsg); + if (EFI_ERROR (Status)) { + ret = NULL; + goto ON_EXIT; + } + + // + // Step 5: Return the HTTP StatusCode and Body message. + // + if (ResponseMsg.Data.Response != NULL) { + *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE)); + if (*StatusCode == NULL) { + ret = NULL; + goto ON_EXIT; + } + + // + // The caller shall take the responsibility to free the buffer. + // + **StatusCode = ResponseMsg.Data.Response->StatusCode; + } + + if (EncodedContent != content) { + FreePool (EncodedContent); + } + + + if (ResponseMsg.BodyLength != 0 && ResponseMsg.Body != NULL) { + ret = json_loadb (ResponseMsg.Body, ResponseMsg.BodyLength, 0, NULL); + } else { + // + // There is no message body returned from server. + // + ret = NULL; + } + +ON_EXIT: + if (url != NULL) { + free (url); + } + + if (HttpIoHeader != NULL) { + HttpIoFreeHeader (HttpIoHeader); + } + + if (RequestData != NULL) { + RestConfigFreeHttpRequestData (RequestData); + } + + if (RequestMsg != NULL) { + FreePool (RequestMsg); + } + + RestConfigFreeHttpMessage (&ResponseMsg, FALSE); + + return ret; +} + +json_t* postUriFromService(redfishService* service, const char* uri, const char* content, size_t contentLength, const char* contentType, EFI_HTTP_STATUS_CODE** StatusCode) +{ + char* url = NULL; + json_t* ret; + HTTP_IO_HEADER *HttpIoHeader = NULL; + EFI_STATUS Status; + EFI_HTTP_REQUEST_DATA *RequestData = NULL; + EFI_HTTP_MESSAGE *RequestMsg = NULL; + EFI_HTTP_MESSAGE ResponseMsg; + CHAR8 ContentLengthStr[80]; + EFI_HTTP_HEADER *HttpHeader = NULL; + + ret = NULL; + + if(service == NULL || uri == NULL || content == NULL || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + url = makeUrlForService(service, uri); + if(!url) + { + return NULL; + } + + DEBUG((DEBUG_INFO, "libredfish: postUriFromService(): %a\n", url)); + + if(contentLength == 0) + { + contentLength = strlen(content); + } + + // + // Step 1: Create HTTP request message with 4 headers: + // + HttpIoHeader = HttpIoCreateHeader ((service->sessionToken || service->basicAuthStr) ? 8 : 7); + if (HttpIoHeader == NULL) { + goto ON_EXIT; + } + + if(service->sessionToken) + { + Status = HttpIoSetHeader (HttpIoHeader, "X-Auth-Token", service->sessionToken); + ASSERT_EFI_ERROR (Status); + } else if (service->basicAuthStr) { + Status = HttpIoSetHeader (HttpIoHeader, "Authorization", service->basicAuthStr); + ASSERT_EFI_ERROR (Status); + } + + if(contentType == NULL) { + Status = HttpIoSetHeader (HttpIoHeader, "Content-Type", "application/json"); + ASSERT_EFI_ERROR (Status); + } else { + Status = HttpIoSetHeader (HttpIoHeader, "Content-Type", (CHAR8 *) contentType); + ASSERT_EFI_ERROR (Status); + } + Status = HttpIoSetHeader (HttpIoHeader, "Host", service->HostHeaderValue); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "Accept", "application/json"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "User-Agent", "libredfish"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "Connection", "Keep-Alive"); + ASSERT_EFI_ERROR (Status); + AsciiSPrint( + ContentLengthStr, + sizeof (ContentLengthStr), + "%lu", + (UINT64) contentLength + ); + Status = HttpIoSetHeader (HttpIoHeader, "Content-Length", ContentLengthStr); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "OData-Version", "4.0"); + ASSERT_EFI_ERROR (Status); + + // + // Step 2: build the rest of HTTP request info. + // + RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA)); + if (RequestData == NULL) { + goto ON_EXIT; + } + + RequestData->Method = HttpMethodPost; + RequestData->Url = C8ToC16 (url); + + // + // Step 3: fill in EFI_HTTP_MESSAGE + // + RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE)); + if (RequestMsg == NULL) { + goto ON_EXIT; + } + + RequestMsg->Data.Request = RequestData; + RequestMsg->HeaderCount = HttpIoHeader->HeaderCount; + RequestMsg->Headers = HttpIoHeader->Headers; + RequestMsg->BodyLength = contentLength; + RequestMsg->Body = (VOID*) content; + + ZeroMem (&ResponseMsg, sizeof (ResponseMsg)); + + // + // Step 4: call RESTEx to get response from REST service. + // + Status = service->RestEx->SendReceive (service->RestEx, RequestMsg, &ResponseMsg); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + // + // Step 5: Return the HTTP StatusCode and Body message. + // + if (ResponseMsg.Data.Response != NULL) { + *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE)); + if (*StatusCode == NULL) { + goto ON_EXIT; + } + + // + // The caller shall take the responsibility to free the buffer. + // + **StatusCode = ResponseMsg.Data.Response->StatusCode; + } + + if (ResponseMsg.BodyLength != 0 && ResponseMsg.Body != NULL) { + ret = json_loadb (ResponseMsg.Body, ResponseMsg.BodyLength, 0, NULL); + } + + // + // Step 6: Parsing the HttpHeader to retrive the X-Auth-Token if the HTTP StatusCode is correct. + // + if (ResponseMsg.Data.Response->StatusCode == HTTP_STATUS_200_OK || + ResponseMsg.Data.Response->StatusCode == HTTP_STATUS_204_NO_CONTENT) { + HttpHeader = HttpFindHeader (ResponseMsg.HeaderCount, ResponseMsg.Headers, "X-Auth-Token"); + if (HttpHeader != NULL) { + if(service->sessionToken) + { + free(service->sessionToken); + } + service->sessionToken = AllocateCopyPool (AsciiStrSize (HttpHeader->FieldValue), HttpHeader->FieldValue); + } + + /* + // + // Below opeation seems to be unnecessary. + // Besides, the FieldValue for the Location is the full HTTP URI (Http://0.0.0.0:5000/XXX), so we can't use it as the + // parameter of getUriFromService () directly. + // + HttpHeader = HttpFindHeader (ResponseMsg.HeaderCount, ResponseMsg.Headers, "Location"); + if (HttpHeader != NULL) { + ret = getUriFromService(service, HttpHeader->FieldValue); + goto ON_EXIT; + } + */ + } + +ON_EXIT: + if (url != NULL) { + free (url); + } + + if (HttpIoHeader != NULL) { + HttpIoFreeHeader (HttpIoHeader); + } + + if (RequestData != NULL) { + RestConfigFreeHttpRequestData (RequestData); + } + + if (RequestMsg != NULL) { + FreePool (RequestMsg); + } + + RestConfigFreeHttpMessage (&ResponseMsg, FALSE); + + return ret; +} + +json_t* deleteUriFromService(redfishService* service, const char* uri, EFI_HTTP_STATUS_CODE** StatusCode) +{ + char* url; + json_t* ret; + HTTP_IO_HEADER *HttpIoHeader = NULL; + EFI_STATUS Status; + EFI_HTTP_REQUEST_DATA *RequestData = NULL; + EFI_HTTP_MESSAGE *RequestMsg = NULL; + EFI_HTTP_MESSAGE ResponseMsg; + + ret = NULL; + + if(service == NULL || uri == NULL || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + url = makeUrlForService(service, uri); + if(!url) + { + return NULL; + } + + DEBUG((DEBUG_INFO, "libredfish: deleteUriFromService(): %a\n", url)); + + // + // Step 1: Create HTTP request message with 4 headers: + // + HttpIoHeader = HttpIoCreateHeader ((service->sessionToken || service->basicAuthStr) ? 5 : 4); + if (HttpIoHeader == NULL) { + ret = NULL; + goto ON_EXIT; + } + + if(service->sessionToken) + { + Status = HttpIoSetHeader (HttpIoHeader, "X-Auth-Token", service->sessionToken); + ASSERT_EFI_ERROR (Status); + } else if (service->basicAuthStr) { + Status = HttpIoSetHeader (HttpIoHeader, "Authorization", service->basicAuthStr); + ASSERT_EFI_ERROR (Status); + } + Status = HttpIoSetHeader (HttpIoHeader, "Host", service->HostHeaderValue); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "User-Agent", "libredfish"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "OData-Version", "4.0"); + ASSERT_EFI_ERROR (Status); + Status = HttpIoSetHeader (HttpIoHeader, "Connection", "Keep-Alive"); + ASSERT_EFI_ERROR (Status); + + // + // Step 2: build the rest of HTTP request info. + // + RequestData = AllocateZeroPool (sizeof (EFI_HTTP_REQUEST_DATA)); + if (RequestData == NULL) { + ret = NULL; + goto ON_EXIT; + } + + RequestData->Method = HttpMethodDelete; + RequestData->Url = C8ToC16 (url); + + // + // Step 3: fill in EFI_HTTP_MESSAGE + // + RequestMsg = AllocateZeroPool (sizeof (EFI_HTTP_MESSAGE)); + if (RequestMsg == NULL) { + ret = NULL; + goto ON_EXIT; + } + + RequestMsg->Data.Request = RequestData; + RequestMsg->HeaderCount = HttpIoHeader->HeaderCount; + RequestMsg->Headers = HttpIoHeader->Headers; + + ZeroMem (&ResponseMsg, sizeof (ResponseMsg)); + + // + // Step 4: call RESTEx to get response from REST service. + // + Status = service->RestEx->SendReceive (service->RestEx, RequestMsg, &ResponseMsg); + if (EFI_ERROR (Status)) { + ret = NULL; + goto ON_EXIT; + } + + // + // Step 5: Return the HTTP StatusCode and Body message. + // + if (ResponseMsg.Data.Response != NULL) { + *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE)); + if (*StatusCode == NULL) { + ret = NULL; + goto ON_EXIT; + } + + // + // The caller shall take the responsibility to free the buffer. + // + **StatusCode = ResponseMsg.Data.Response->StatusCode; + } + + if (ResponseMsg.BodyLength != 0 && ResponseMsg.Body != NULL) { + ret = json_loadb (ResponseMsg.Body, ResponseMsg.BodyLength, 0, NULL); + } + +ON_EXIT: + if (url != NULL) { + free (url); + } + + if (HttpIoHeader != NULL) { + HttpIoFreeHeader (HttpIoHeader); + } + + if (RequestData != NULL) { + RestConfigFreeHttpRequestData (RequestData); + } + + if (RequestMsg != NULL) { + FreePool (RequestMsg); + } + + RestConfigFreeHttpMessage (&ResponseMsg, FALSE); + + return ret; +} + +redfishPayload* getRedfishServiceRoot(redfishService* service, const char* version, EFI_HTTP_STATUS_CODE** StatusCode) +{ + json_t* value; + json_t* versionNode; + const char* verUrl; + + if(version == NULL) + { + versionNode = json_object_get(service->versions, "v1"); + } + else + { + versionNode = json_object_get(service->versions, version); + } + if(versionNode == NULL) + { + return NULL; + } + verUrl = json_string_value(versionNode); + if(verUrl == NULL) + { + return NULL; + } + value = getUriFromService(service, verUrl, StatusCode); + if(value == NULL) + { + if((service->flags & REDFISH_FLAG_SERVICE_NO_VERSION_DOC) == 0) + { + json_decref(versionNode); + } + return NULL; + } + return createRedfishPayload(value, service); +} + +redfishPayload* getPayloadByPath(redfishService* service, const char* path, EFI_HTTP_STATUS_CODE** StatusCode) +{ + redPathNode* redpath; + redfishPayload* root; + redfishPayload* ret; + + if(!service || !path || StatusCode == NULL) + { + return NULL; + } + + *StatusCode = NULL; + + redpath = parseRedPath(path); + if(!redpath) + { + return NULL; + } + if(!redpath->isRoot) + { + cleanupRedPath(redpath); + return NULL; + } + root = getRedfishServiceRoot(service, redpath->version, StatusCode); + if (*StatusCode == NULL || **StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT) { + cleanupRedPath(redpath); + return root; + } + + if(redpath->next == NULL) + { + cleanupRedPath(redpath); + return root; + } + + FreePool (*StatusCode); + *StatusCode = NULL; + + ret = getPayloadForPath(root, redpath->next, StatusCode); + if (*StatusCode == NULL && ret != NULL) { + // + // In such a case, the Redfish resource is parsed from the input payload (root) directly. + // So, we still return HTTP_STATUS_200_OK. + // + *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE)); + if (*StatusCode == NULL) { + ret = NULL; + } else { + **StatusCode = HTTP_STATUS_200_OK; + } + } + cleanupPayload(root); + cleanupRedPath(redpath); + return ret; +} + +void cleanupServiceEnumerator(redfishService* service) +{ + if(!service) + { + return; + } + free(service->host); + json_decref(service->versions); + if(service->sessionToken != NULL) + { + ZeroMem (service->sessionToken, (UINTN)strlen(service->sessionToken)); + FreePool(service->sessionToken); + } + if (service->basicAuthStr != NULL) { + ZeroMem (service->basicAuthStr, (UINTN)strlen(service->basicAuthStr)); + FreePool (service->basicAuthStr); + } + free(service); +} + +static int initRest(redfishService* service, void * restProtocol) +{ + service->RestEx = restProtocol; + return 0; +} + +static redfishService* createServiceEnumeratorNoAuth(const char* host, const char* rootUri, bool enumerate, unsigned int flags, void * restProtocol) +{ + redfishService* ret; + char *HostStart; + + ret = (redfishService*)calloc(1, sizeof(redfishService)); + ZeroMem (ret, sizeof(redfishService)); + if(initRest(ret, restProtocol) != 0) + { + free(ret); + return NULL; + } + ret->host = AllocateCopyPool(AsciiStrSize(host), host); + ret->flags = flags; + if(enumerate) + { + ret->versions = getVersions(ret, rootUri); + } + HostStart = strstr (ret->host, "//"); + if (HostStart != NULL && (*(HostStart + 2) != '\0')) { + ret->HostHeaderValue = HostStart + 2; + } + + return ret; +} + +EFI_STATUS +createBasicAuthStr ( + IN redfishService* service, + IN CONST CHAR8 *UserId, + IN CONST CHAR8 *Password + ) +{ + EFI_STATUS Status; + CHAR8 *RawAuthValue; + UINTN RawAuthBufSize; + CHAR8 *EnAuthValue; + UINTN EnAuthValueSize; + CHAR8 *BasicWithEnAuthValue; + UINTN BasicBufSize; + + EnAuthValue = NULL; + EnAuthValueSize = 0; + + RawAuthBufSize = AsciiStrLen (UserId) + AsciiStrLen (Password) + 2; + RawAuthValue = AllocatePool (RawAuthBufSize); + if (RawAuthValue == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Build raw AuthValue (UserId:Password). + // + AsciiSPrint ( + RawAuthValue, + RawAuthBufSize, + "%a:%a", + UserId, + Password + ); + + // + // Encoding RawAuthValue into Base64 format. + // + Status = Base64Encode ( + (CONST UINT8 *) RawAuthValue, + AsciiStrLen (RawAuthValue), + EnAuthValue, + &EnAuthValueSize + ); + if (Status == EFI_BUFFER_TOO_SMALL) { + EnAuthValue = (CHAR8 *) AllocateZeroPool (EnAuthValueSize); + if (EnAuthValue == NULL) { + Status = EFI_OUT_OF_RESOURCES; + return Status; + } + + Status = Base64Encode ( + (CONST UINT8 *) RawAuthValue, + AsciiStrLen (RawAuthValue), + EnAuthValue, + &EnAuthValueSize + ); + } + + if (EFI_ERROR (Status)) { + goto Exit; + } + + BasicBufSize = AsciiStrLen ("Basic ") + AsciiStrLen(EnAuthValue) + 2; + BasicWithEnAuthValue = AllocatePool (BasicBufSize); + if (BasicWithEnAuthValue == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Exit; + } + + // + // Build encoded EnAuthValue with Basic (Basic EnAuthValue). + // + AsciiSPrint ( + BasicWithEnAuthValue, + BasicBufSize, + "%a %a", + "Basic", + EnAuthValue + ); + + service->basicAuthStr = BasicWithEnAuthValue; + +Exit: + if (RawAuthValue != NULL) { + ZeroMem (RawAuthValue, RawAuthBufSize); + FreePool (RawAuthValue); + } + + if (EnAuthValue != NULL) { + ZeroMem (EnAuthValue, EnAuthValueSize); + FreePool (EnAuthValue); + } + + return Status; +} + +static redfishService* createServiceEnumeratorBasicAuth(const char* host, const char* rootUri, const char* username, const char* password, unsigned int flags, void * restProtocol) +{ + redfishService* ret; + EFI_STATUS Status; + + ret = createServiceEnumeratorNoAuth(host, rootUri, false, flags, restProtocol); + + // add basic auth str + Status = createBasicAuthStr (ret, username, password); + if (EFI_ERROR(Status)) { + cleanupServiceEnumerator (ret); + return NULL; + } + + ret->versions = getVersions(ret, rootUri); + return ret; +} + +static redfishService* createServiceEnumeratorSessionAuth(const char* host, const char* rootUri, const char* username, const char* password, unsigned int flags, void * restProtocol) +{ + redfishService* ret; + redfishPayload* payload; + redfishPayload* links; + json_t* sessionPayload; + json_t* session; + json_t* odataId; + const char* uri; + json_t* post; + char* content; + EFI_HTTP_STATUS_CODE *StatusCode; + + content = NULL; + StatusCode = NULL; + + ret = createServiceEnumeratorNoAuth(host, rootUri, true, flags, restProtocol); + if(ret == NULL) + { + return NULL; + } + payload = getRedfishServiceRoot(ret, NULL, &StatusCode); + if(StatusCode == NULL || *StatusCode < HTTP_STATUS_200_OK || *StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT) + { + if (StatusCode != NULL) { + FreePool (StatusCode); + } + + if (payload != NULL) { + cleanupPayload(payload); + } + cleanupServiceEnumerator(ret); + return NULL; + } + + if (StatusCode != NULL) { + FreePool (StatusCode); + StatusCode = NULL; + } + + links = getPayloadByNodeName(payload, "Links", &StatusCode); + cleanupPayload(payload); + if(links == NULL) + { + cleanupServiceEnumerator(ret); + return NULL; + } + session = json_object_get(links->json, "Sessions"); + if(session == NULL) + { + cleanupPayload(links); + cleanupServiceEnumerator(ret); + return NULL; + } + odataId = json_object_get(session, "@odata.id"); + if(odataId == NULL) + { + cleanupPayload(links); + cleanupServiceEnumerator(ret); + return NULL; + } + uri = json_string_value(odataId); + post = json_object(); + addStringToJsonObject(post, "UserName", username); + addStringToJsonObject(post, "Password", password); + content = json_dumps(post, 0); + json_decref(post); + sessionPayload = postUriFromService(ret, uri, content, 0, NULL, &StatusCode); + + if (content != NULL) { + ZeroMem (content, (UINTN)strlen(content)); + free(content); + } + + if(sessionPayload == NULL || StatusCode == NULL || *StatusCode < HTTP_STATUS_200_OK || *StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT) + { + //Failed to create session! + + cleanupPayload(links); + cleanupServiceEnumerator(ret); + + if (StatusCode != NULL) { + FreePool (StatusCode); + } + + if (sessionPayload != NULL) { + json_decref(sessionPayload); + } + + return NULL; + } + json_decref(sessionPayload); + cleanupPayload(links); + FreePool (StatusCode); + return ret; +} + +static char* makeUrlForService(redfishService* service, const char* uri) +{ + char* url; + if(service->host == NULL) + { + return NULL; + } + url = (char*)malloc(strlen(service->host)+strlen(uri)+1); + strcpy(url, service->host); + strcat(url, uri); + return url; +} + +static json_t* getVersions(redfishService* service, const char* rootUri) +{ + json_t* ret = NULL; + EFI_HTTP_STATUS_CODE* StatusCode = NULL; + + if(service->flags & REDFISH_FLAG_SERVICE_NO_VERSION_DOC) + { + service->versions = json_object(); + if(service->versions == NULL) + { + return NULL; + } + addStringToJsonObject(service->versions, "v1", "/redfish/v1"); + return service->versions; + } + if(rootUri != NULL) + { + ret = getUriFromService(service, rootUri, &StatusCode); + } + else + { + ret = getUriFromService(service, "/redfish", &StatusCode); + } + + if (ret == NULL || StatusCode == NULL || *StatusCode < HTTP_STATUS_200_OK || *StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT) { + if (ret != NULL) { + json_decref(ret); + } + ret = NULL; + } + + if (StatusCode != NULL) { + FreePool (StatusCode); + } + + return ret; +} + +static void addStringToJsonObject(json_t* object, const char* key, const char* value) +{ + json_t* jValue = json_string(value); + + json_object_set(object, key, jValue); + + json_decref(jValue); +} diff --git a/RedfishPkg/RedfishLibs.dsc.inc b/RedfishPkg/RedfishLibs.dsc.inc index 103a596753..50e5dda773 100644 --- a/RedfishPkg/RedfishLibs.dsc.inc +++ b/RedfishPkg/RedfishLibs.dsc.inc @@ -5,7 +5,7 @@ # by using "!include RedfishPkg/RedfisLibs.dsc.inc" to specify the library instances # of EDKII network library classes. # -# (C) Copyright 2020 Hewlett Packard Enterprise Development LP
+# (C) Copyright 2021 Hewlett Packard Enterprise Development LP
# # SPDX-License-Identifier: BSD-2-Clause-Patent # @@ -16,5 +16,6 @@ BaseSortLib|MdeModulePkg/Library/BaseSortLib/BaseSortLib.inf RedfishCrtLib|RedfishPkg/PrivateLibrary/RedfishCrtLib/RedfishCrtLib.inf JsonLib|RedfishPkg/Library/JsonLib/JsonLib.inf + RedfishLib|RedfishPkg/PrivateLibrary/RedfishLib/RedfishLib.inf !endif diff --git a/RedfishPkg/RedfishPkg.ci.yaml b/RedfishPkg/RedfishPkg.ci.yaml index fde6fa89bc..1fe9bdb8d1 100644 --- a/RedfishPkg/RedfishPkg.ci.yaml +++ b/RedfishPkg/RedfishPkg.ci.yaml @@ -1,7 +1,7 @@ ## @file # CI configuration for NetworkPkg # -# (C) Copyright 2020 Hewlett Packard Enterprise Development LP
+# (C) Copyright 2021 Hewlett Packard Enterprise Development LP
# SPDX-License-Identifier: BSD-2-Clause-Patent ## { @@ -41,7 +41,18 @@ ## load.c is overrided from open source. "Library/JsonLib/load.c", "Library/JsonLib/jansson_config.h", - "Library/JsonLib/jansson_private_config.h" + "Library/JsonLib/jansson_private_config.h", + ## + ## For libredfish open source + ## The files under edk2libredfish are cloned + ## from DMTF open source + "PrivateLibrary/RedfishLib/edk2libredfish/include/redfish.h", + "PrivateLibrary/RedfishLib/edk2libredfish/include/redfishPayload.h", + "PrivateLibrary/RedfishLib/edk2libredfish/include/redfishService.h", + "PrivateLibrary/RedfishLib/edk2libredfish/include/redpath.h", + "PrivateLibrary/RedfishLib/edk2libredfish/src/payload.c", + "PrivateLibrary/RedfishLib/edk2libredfish/src/redpath.c", + "PrivateLibrary/RedfishLib/edk2libredfish/src/service.c" ] }, "CompilerPlugin": { diff --git a/RedfishPkg/RedfishPkg.dec b/RedfishPkg/RedfishPkg.dec index a6fd81cdae..b3e25268a0 100644 --- a/RedfishPkg/RedfishPkg.dec +++ b/RedfishPkg/RedfishPkg.dec @@ -62,6 +62,10 @@ # project). RedfishCrtLib|PrivateInclude/Library/RedfishCrtLib.h + ## @libraryclass Redfish Helper Library + # Library provides Redfish helper functions. + RedfishLib|PrivateInclude/Library/RedfishLib.h + [Protocols] ## Include/Protocol/RedfishDiscover.h gEfiRedfishDiscoverProtocolGuid = { 0x5db12509, 0x4550, 0x4347, { 0x96, 0xb3, 0x73, 0xc0, 0xff, 0x6e, 0x86, 0x9f }} diff --git a/RedfishPkg/RedfishPkg.dsc b/RedfishPkg/RedfishPkg.dsc index 2cae8a46d8..e0052290b5 100644 --- a/RedfishPkg/RedfishPkg.dsc +++ b/RedfishPkg/RedfishPkg.dsc @@ -55,5 +55,6 @@ RedfishPkg/Library/BaseUcs2Utf8Lib/BaseUcs2Utf8Lib.inf RedfishPkg/PrivateLibrary/RedfishCrtLib/RedfishCrtLib.inf RedfishPkg/Library/JsonLib/JsonLib.inf + RedfishPkg/PrivateLibrary/RedfishLib/RedfishLib.inf !include RedfishPkg/Redfish.dsc.inc