redfish: Refactor plugin and add some unit tests

This commit is contained in:
Richard Hughes 2021-06-25 16:56:53 +01:00
parent 5458480356
commit a923ae2804
24 changed files with 2290 additions and 440 deletions

View File

@ -21,6 +21,10 @@ cp -R ../../../!(build|dist) .
popd
chown nobody . -R
# install and run the custom redfish simulator
pacman -Syu --noconfirm python-flask
../plugins/redfish/tests/redfish.py &
# install and run TPM simulator necessary for plugins/uefi-capsule/uefi-self-test
pacman -Syu --noconfirm swtpm tpm2-tools
swtpm socket --tpm2 --server port=2321 --ctrl type=tcp,port=2322 --flags not-need-init --tpmstate "dir=$PWD" &

View File

@ -23,8 +23,15 @@ This plugin supports the following protocol ID:
GUID Generation
---------------
These devices use the provided GUID provided in the `SoftwareId` parameter
without modification. Devices without GUIDs are not supported.
These devices use the provided GUID provided in the `SoftwareId` property
without modification if it is a valid GUID. If the property is not a GUID then
the vendor instance ID is used instead:
* `REDFISH\\VENDOR_${RedfishManufacturer}&SOFTWAREID_${RedfishSoftwareId}`
Additionally, this Instance ID is added for quirk and parent matching:
* `REDFISH\VENDOR_${RedfishManufacturer}&ID_${RedfishId}`
Update Behavior
---------------
@ -41,7 +48,7 @@ Setting Service IP Manually
---------------------------
The service IP may not be automatically discoverable due to the absence of
Type 0x42 entry in SMBIOS. In this case, you have to specify the service IP
Type 42 entry in SMBIOS. In this case, you have to specify the service IP
to RedfishUri in /etc/fwupd/redfish.conf
Take HPE Gen10 for example, the service IP can be found with the following

View File

@ -17,27 +17,20 @@ struct FuPluginData {
FuRedfishBackend *backend;
};
gboolean
fu_plugin_update (FuPlugin *plugin,
FuDevice *device,
GBytes *blob_fw,
FwupdInstallFlags flags,
GError **error)
{
FuPluginData *data = fu_plugin_get_data (plugin);
return fu_redfish_backend_update (data->backend, device, blob_fw, error);
}
gboolean
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
{
FuPluginData *data = fu_plugin_get_data (plugin);
g_autoptr(GPtrArray) devices = NULL;
g_autoptr(GError) error_local = NULL;
/* get the list of devices */
if (!fu_backend_coldplug (FU_BACKEND (data->backend), error))
if (!fu_backend_coldplug (FU_BACKEND (data->backend), &error_local)) {
if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED))
fu_plugin_add_flag (plugin, FWUPD_PLUGIN_FLAG_AUTH_REQUIRED);
g_propagate_error (error, g_steal_pointer (&error_local));
return FALSE;
}
devices = fu_backend_get_devices (FU_BACKEND (data->backend));
for (guint i = 0; i < devices->len; i++) {
FuDevice *device = g_ptr_array_index (devices, i);
@ -103,14 +96,22 @@ fu_redfish_plugin_discover_smbios_table (FuPlugin *plugin, GError **error)
{
FuPluginData *data = fu_plugin_get_data (plugin);
FuContext *ctx = fu_plugin_get_context (plugin);
const gchar *smbios_data_fn;
g_autofree gchar *hostname = NULL;
g_autoptr(FuRedfishSmbios) redfish_smbios = fu_redfish_smbios_new ();
g_autoptr(GBytes) smbios_data = NULL;
/* is optional */
smbios_data = fu_context_get_smbios_data (ctx, REDFISH_SMBIOS_TABLE_TYPE);
if (smbios_data == NULL)
return TRUE;
/* is optional if not in self tests */
smbios_data_fn = g_getenv ("FWUPD_REDFISH_SMBIOS_DATA");
if (smbios_data_fn != NULL) {
smbios_data = fu_common_get_contents_bytes (smbios_data_fn, error);
if (smbios_data == NULL)
return FALSE;
} else {
smbios_data = fu_context_get_smbios_data (ctx, REDFISH_SMBIOS_TABLE_TYPE);
if (smbios_data == NULL)
return TRUE;
}
if (!fu_firmware_parse (FU_FIRMWARE (redfish_smbios),
smbios_data, FWUPD_INSTALL_FLAG_NONE, error)) {
g_prefix_error (error, "failed to parse SMBIOS table entry type 42: ");

View File

@ -6,163 +6,93 @@
#include "config.h"
#include <curl/curl.h>
#include <string.h>
#include <fwupdplugin.h>
#include "fu-redfish-backend.h"
#include "fu-redfish-common.h"
#include "fu-redfish-multipart-device.h"
#include "fu-redfish-legacy-device.h"
#include "fu-redfish-request.h"
#include "fu-redfish-smbios.h"
struct _FuRedfishBackend
{
FuBackend parent_instance;
CURL *curl;
gchar *hostname;
gchar *username;
gchar *password;
guint port;
gchar *update_uri_path;
gchar *push_uri_path;
gboolean use_https;
gboolean cacheck;
gint64 max_image_size; /* bytes */
GType device_gtype;
GHashTable *request_cache; /* str:GByteArray */
CURLSH *curlsh;
};
G_DEFINE_TYPE (FuRedfishBackend, fu_redfish_backend, FU_TYPE_BACKEND)
#ifdef HAVE_LIBCURL_7_62_0
typedef gchar curlptr;
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curlptr, curl_free)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup)
#endif
static size_t
fu_redfish_backend_fetch_data_cb (char *ptr, size_t size, size_t nmemb, void *userdata)
{
GByteArray *buf = (GByteArray *) userdata;
gsize realsize = size * nmemb;
g_byte_array_append (buf, (const guint8 *) ptr, realsize);
return realsize;
}
static GBytes *
fu_redfish_backend_fetch_data (FuRedfishBackend *self, const gchar *uri_path, GError **error)
{
CURLcode res;
FuRedfishRequest *
fu_redfish_backend_request_new (FuRedfishBackend *self)
{
FuRedfishRequest *request = g_object_new (FU_TYPE_REDFISH_REQUEST, NULL);
CURL *curl;
CURLU *uri;
g_autofree gchar *user_agent = NULL;
g_autofree gchar *port = g_strdup_printf ("%u", self->port);
g_autoptr(GByteArray) buf = g_byte_array_new ();
#ifdef HAVE_LIBCURL_7_62_0
g_autoptr(CURLU) uri = NULL;
#else
g_autofree gchar *uri = NULL;
#endif
/* create URI */
#ifdef HAVE_LIBCURL_7_62_0
uri = curl_url ();
/* set the cache location */
fu_redfish_request_set_cache (request, self->request_cache);
fu_redfish_request_set_curlsh (request, self->curlsh);
/* set up defaults */
curl = fu_redfish_request_get_curl (request);
uri = fu_redfish_request_get_uri (request);
curl_url_set (uri, CURLUPART_SCHEME, self->use_https ? "https" : "http", 0);
curl_url_set (uri, CURLUPART_PATH, uri_path, 0);
curl_url_set (uri, CURLUPART_HOST, self->hostname, 0);
curl_url_set (uri, CURLUPART_PORT, port, 0);
if (curl_easy_setopt (self->curl, CURLOPT_CURLU, uri) != CURLE_OK) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to create message for URI");
return NULL;
}
#else
uri = g_strdup_printf ("%s://%s:%s%s",
self->use_https ? "https" : "http",
self->hostname,
port,
uri_path);
if (curl_easy_setopt (self->curl, CURLOPT_URL, uri) != CURLE_OK) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to create message for URI");
return NULL;
}
#endif
curl_easy_setopt (self->curl, CURLOPT_WRITEFUNCTION, fu_redfish_backend_fetch_data_cb);
curl_easy_setopt (self->curl, CURLOPT_WRITEDATA, buf);
res = curl_easy_perform (self->curl);
if (res != CURLE_OK) {
glong status_code = 0;
#ifdef HAVE_LIBCURL_7_62_0
g_autoptr(curlptr) uri_str = NULL;
#endif
curl_easy_getinfo (self->curl, CURLINFO_RESPONSE_CODE, &status_code);
#ifdef HAVE_LIBCURL_7_62_0
curl_url_get (uri, CURLUPART_URL, &uri_str, 0);
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to download %s: %s",
uri_str, curl_easy_strerror (res));
#else
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to download %s: %s",
uri, curl_easy_strerror (res));
#endif
return NULL;
}
curl_easy_setopt (curl, CURLOPT_CURLU, uri);
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
}
/* since DSP0266 makes Basic Authorization a requirement,
* it is safe to use Basic Auth for all implementations */
curl_easy_setopt (curl, CURLOPT_HTTPAUTH, (glong) CURLAUTH_BASIC);
curl_easy_setopt (curl, CURLOPT_USERNAME, self->username);
curl_easy_setopt (curl, CURLOPT_PASSWORD, self->password);
/* setup networking */
user_agent = g_strdup_printf ("%s/%s", PACKAGE_NAME, PACKAGE_VERSION);
curl_easy_setopt (curl, CURLOPT_USERAGENT , user_agent);
curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 60L);
if (!self->cacheck)
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
/* success */
return request;
}
static gboolean
fu_redfish_backend_coldplug_member (FuRedfishBackend *self,
JsonObject *member,
GError **error)
{
g_autoptr(FuDevice) dev = fu_device_new ();
const gchar *guid = NULL;
g_autofree gchar *guid_lower = NULL;
g_autofree gchar *id = NULL;
g_autoptr(FuDevice) dev = NULL;
g_autoptr(FuDeviceLocker) locker = NULL;
if (json_object_has_member (member, "SoftwareId")) {
guid = json_object_get_string_member (member, "SoftwareId");
} else if (json_object_has_member (member, "Oem")) {
JsonObject *oem = json_object_get_object_member (member, "Oem");
if (oem != NULL && json_object_has_member (oem, "Hpe")) {
JsonObject *hpe = json_object_get_object_member (oem, "Hpe");
if (hpe != NULL && json_object_has_member (hpe, "DeviceClass"))
guid = json_object_get_string_member (hpe, "DeviceClass");
}
}
/* skip the devices without guid */
if (guid == NULL)
return TRUE;
id = g_strdup_printf ("Redfish-Inventory-%s",
json_object_get_string_member (member, "Id"));
fu_device_set_id (dev, id);
fu_device_add_protocol (dev, "org.dmtf.redfish");
guid_lower = g_ascii_strdown (guid, -1);
fu_device_add_guid (dev, guid_lower);
if (json_object_has_member (member, "Name"))
fu_device_set_name (dev, json_object_get_string_member (member, "Name"));
fu_device_set_summary (dev, "Redfish device");
if (json_object_has_member (member, "Version"))
fu_device_set_version (dev, json_object_get_string_member (member, "Version"));
if (json_object_has_member (member, "LowestSupportedVersion"))
fu_device_set_version_lowest (dev, json_object_get_string_member (member, "LowestSupportedVersion"));
if (json_object_has_member (member, "Description"))
fu_device_set_description (dev, json_object_get_string_member (member, "Description"));
if (json_object_has_member (member, "Updateable")) {
if (json_object_get_boolean_member (member, "Updateable"))
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
} else {
/* assume the device is updatable */
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
}
/* success */
/* create of the correct type */
dev = g_object_new (self->device_gtype,
"context", fu_backend_get_context (FU_BACKEND (self)),
"backend", self,
"member", member,
NULL);
locker = fu_device_locker_new (dev, error);
if (locker == NULL)
return FALSE;
if (self->max_image_size != 0)
fu_device_set_firmware_size_max (dev, (guint64) self->max_image_size);
fu_backend_device_added (FU_BACKEND (self), dev);
return TRUE;
}
@ -172,16 +102,12 @@ fu_redfish_backend_coldplug_collection (FuRedfishBackend *self,
JsonObject *collection,
GError **error)
{
JsonArray *members;
JsonNode *node_root;
JsonObject *member;
members = json_object_get_array_member (collection, "Members");
JsonArray *members = json_object_get_array_member (collection, "Members");
for (guint i = 0; i < json_array_get_length (members); i++) {
g_autoptr(JsonParser) parser = json_parser_new ();
g_autoptr(GBytes) blob = NULL;
JsonObject *json_obj;
JsonObject *member_id;
const gchar *member_uri;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
member_id = json_array_get_object_element (members, i);
member_uri = json_object_get_string_member (member_id, "@odata.id");
@ -193,39 +119,15 @@ fu_redfish_backend_coldplug_collection (FuRedfishBackend *self,
return FALSE;
}
/* try to connect */
blob = fu_redfish_backend_fetch_data (self, member_uri, error);
if (blob == NULL)
return FALSE;
/* get the member object */
if (!json_parser_load_from_data (parser,
g_bytes_get_data (blob, NULL),
(gssize) g_bytes_get_size (blob),
error)) {
g_prefix_error (error, "failed to parse node: ");
return FALSE;
}
node_root = json_parser_get_root (parser);
if (node_root == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no root node");
return FALSE;
}
member = json_node_get_object (node_root);
if (member == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no member object");
return FALSE;
}
/* Create the device for the member */
if (!fu_redfish_backend_coldplug_member (self, member, error))
return FALSE;
/* create the device for the member */
if (!fu_redfish_request_perform (request,
member_uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
if (!fu_redfish_backend_coldplug_member (self, json_obj, error))
return FALSE;
}
return TRUE;
}
@ -235,11 +137,9 @@ fu_redfish_backend_coldplug_inventory (FuRedfishBackend *self,
JsonObject *inventory,
GError **error)
{
g_autoptr(JsonParser) parser = json_parser_new ();
g_autoptr(GBytes) blob = NULL;
JsonNode *node_root;
JsonObject *collection;
JsonObject *json_obj;
const gchar *collection_uri;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
if (inventory == NULL) {
g_set_error_literal (error,
@ -258,47 +158,21 @@ fu_redfish_backend_coldplug_inventory (FuRedfishBackend *self,
return FALSE;
}
/* try to connect */
blob = fu_redfish_backend_fetch_data (self, collection_uri, error);
if (blob == NULL)
return FALSE;
/* get the inventory object */
if (!json_parser_load_from_data (parser,
g_bytes_get_data (blob, NULL),
(gssize) g_bytes_get_size (blob),
error)) {
g_prefix_error (error, "failed to parse node: ");
return FALSE;
}
node_root = json_parser_get_root (parser);
if (node_root == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no root node");
return FALSE;
}
collection = json_node_get_object (node_root);
if (collection == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no collection object");
return FALSE;
}
return fu_redfish_backend_coldplug_collection (self, collection, error);
if (!fu_redfish_request_perform (request,
collection_uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
return fu_redfish_backend_coldplug_collection (self, json_obj, error);
}
static gboolean
fu_redfish_backend_coldplug (FuBackend *backend, GError **error)
{
FuRedfishBackend *self = FU_REDFISH_BACKEND (backend);
JsonNode *node_root;
JsonObject *obj_root = NULL;
g_autoptr(GBytes) blob = NULL;
g_autoptr(JsonParser) parser = json_parser_new ();
JsonObject *json_obj;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
/* nothing set */
if (self->update_uri_path == NULL) {
@ -309,182 +183,72 @@ fu_redfish_backend_coldplug (FuBackend *backend, GError **error)
return FALSE;
}
/* try to connect */
blob = fu_redfish_backend_fetch_data (self, self->update_uri_path, error);
if (blob == NULL)
return FALSE;
/* get the update service */
if (!json_parser_load_from_data (parser,
g_bytes_get_data (blob, NULL),
(gssize) g_bytes_get_size (blob),
error)) {
g_prefix_error (error, "failed to parse node: ");
return FALSE;
if (!fu_redfish_request_perform (request,
self->update_uri_path,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
if (!json_object_has_member (json_obj, "ServiceEnabled")) {
if (!json_object_get_boolean_member (json_obj, "ServiceEnabled")) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"service is not enabled");
return FALSE;
}
}
node_root = json_parser_get_root (parser);
if (node_root == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no root node");
return FALSE;
if (json_object_has_member (json_obj, "MultipartHttpPushUri")) {
const gchar *tmp = json_object_get_string_member (json_obj, "MultipartHttpPushUri");
if (tmp != NULL) {
self->device_gtype = FU_TYPE_REDFISH_MULTIPART_DEVICE;
self->push_uri_path = g_strdup (tmp);
}
}
obj_root = json_node_get_object (node_root);
if (obj_root == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no root object");
return FALSE;
if (self->push_uri_path == NULL &&
json_object_has_member (json_obj, "HttpPushUri")) {
const gchar *tmp = json_object_get_string_member (json_obj, "HttpPushUri");
if (tmp != NULL) {
self->device_gtype = FU_TYPE_REDFISH_LEGACY_DEVICE;
self->push_uri_path = g_strdup (tmp);
}
}
if (!json_object_get_boolean_member (obj_root, "ServiceEnabled")) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"service is not enabled");
return FALSE;
}
if (!json_object_has_member (obj_root, "HttpPushUri")) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"HttpPushUri is not available");
return FALSE;
}
self->push_uri_path = g_strdup (json_object_get_string_member (obj_root, "HttpPushUri"));
if (self->push_uri_path == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"HttpPushUri is invalid");
"HttpPushUri and MultipartHttpPushUri are invalid");
return FALSE;
}
if (json_object_has_member (obj_root, "FirmwareInventory")) {
JsonObject *tmp = json_object_get_object_member (obj_root, "FirmwareInventory");
if (json_object_has_member (json_obj, "MaxImageSizeBytes")) {
self->max_image_size =
json_object_get_int_member (json_obj, "MaxImageSizeBytes");
}
if (json_object_has_member (json_obj, "FirmwareInventory")) {
JsonObject *tmp = json_object_get_object_member (json_obj, "FirmwareInventory");
return fu_redfish_backend_coldplug_inventory (self, tmp, error);
}
if (json_object_has_member (obj_root, "SoftwareInventory")) {
JsonObject *tmp = json_object_get_object_member (obj_root, "SoftwareInventory");
if (json_object_has_member (json_obj, "SoftwareInventory")) {
JsonObject *tmp = json_object_get_object_member (json_obj, "SoftwareInventory");
return fu_redfish_backend_coldplug_inventory (self, tmp, error);
}
return TRUE;
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curl_mime, curl_mime_free)
gboolean
fu_redfish_backend_update (FuRedfishBackend *self, FuDevice *device, GBytes *blob_fw,
GError **error)
{
CURLcode res;
FwupdRelease *release;
curl_mimepart *part;
g_autofree gchar *filename = NULL;
g_autofree gchar *port = g_strdup_printf ("%u", self->port);
#ifdef HAVE_LIBCURL_7_62_0
g_autoptr(CURLU) uri = curl_url ();
#else
g_autofree gchar *uri = NULL;
#endif
g_autoptr(curl_mime) mime = curl_mime_init (self->curl);
/* Get the update version */
release = fwupd_device_get_release_default (FWUPD_DEVICE (device));
if (release != NULL) {
filename = g_strdup_printf ("%s-%s.bin",
fu_device_get_name (device),
fwupd_release_get_version (release));
} else {
filename = g_strdup_printf ("%s.bin",
fu_device_get_name (device));
}
/* create URI */
#ifdef HAVE_LIBCURL_7_62_0
curl_url_set (uri, CURLUPART_SCHEME, self->use_https ? "https" : "http", 0);
curl_url_set (uri, CURLUPART_PATH, self->push_uri_path, 0);
curl_url_set (uri, CURLUPART_HOST, self->hostname, 0);
curl_url_set (uri, CURLUPART_PORT, port, 0);
if (curl_easy_setopt (self->curl, CURLOPT_CURLU, uri) != CURLE_OK) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to create message for URI");
return FALSE;
}
#else
uri = g_strdup_printf ("%s://%s:%s%s",
self->use_https ? "https" : "http",
self->hostname,
port,
self->push_uri_path);
if (curl_easy_setopt (self->curl, CURLOPT_URL, uri) != CURLE_OK) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to create message for URI");
return FALSE;
}
#endif
/* Create the multipart request */
curl_easy_setopt (self->curl, CURLOPT_MIMEPOST, mime);
part = curl_mime_addpart (mime);
curl_mime_data (part, g_bytes_get_data (blob_fw, NULL), g_bytes_get_size (blob_fw));
curl_mime_type (part, "application/octet-stream");
res = curl_easy_perform (self->curl);
if (res != CURLE_OK) {
glong status_code = 0;
#ifdef HAVE_LIBCURL_7_62_0
g_autoptr(curlptr) uri_str = NULL;
#endif
curl_easy_getinfo (self->curl, CURLINFO_RESPONSE_CODE, &status_code);
#ifdef HAVE_LIBCURL_7_62_0
curl_url_get (uri, CURLUPART_URL, &uri_str, 0);
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to upload %s to %s: %s",
filename, uri_str,
curl_easy_strerror (res));
return FALSE;
#else
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to upload %s to %s: %s",
filename, uri,
curl_easy_strerror (res));
return FALSE;
#endif
}
return TRUE;
}
static gboolean
fu_redfish_backend_setup (FuBackend *backend, GError **error)
{
FuRedfishBackend *self = FU_REDFISH_BACKEND (backend);
JsonNode *node_root;
JsonObject *obj_root = NULL;
JsonObject *obj_update_service = NULL;
JsonObject *json_obj;
JsonObject *json_update_service = NULL;
const gchar *data_id;
const gchar *version = NULL;
g_autofree gchar *user_agent = NULL;
g_autoptr(GBytes) blob = NULL;
g_autoptr(JsonParser) parser = json_parser_new ();
const gchar *uuid = NULL;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
/* sanity check */
if (self->port == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"no port specified");
return FALSE;
}
if (self->port > 0xffff) {
if (self->port == 0 || self->port > G_MAXUINT16) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
@ -493,67 +257,33 @@ fu_redfish_backend_setup (FuBackend *backend, GError **error)
return FALSE;
}
/* setup networking */
user_agent = g_strdup_printf ("%s/%s", PACKAGE_NAME, PACKAGE_VERSION);
curl_easy_setopt (self->curl, CURLOPT_USERAGENT , user_agent);
curl_easy_setopt (self->curl, CURLOPT_CONNECTTIMEOUT, 60L);
if (self->cacheck == FALSE)
curl_easy_setopt (self->curl, CURLOPT_SSL_VERIFYPEER , 0L);
if (self->hostname != NULL)
g_debug ("Hostname: %s", self->hostname);
if (self->port != 0)
g_debug ("Port: %u", self->port);
/* try to connect */
blob = fu_redfish_backend_fetch_data (self, "/redfish/v1/", error);
if (blob == NULL)
if (!fu_redfish_request_perform (request,
"/redfish/v1/",
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
if (json_object_has_member (json_obj, "ServiceVersion")) {
version = json_object_get_string_member (json_obj, "ServiceVersion");
} else if (json_object_has_member (json_obj, "RedfishVersion")) {
version = json_object_get_string_member (json_obj, "RedfishVersion");
}
if (json_object_has_member (json_obj, "UUID"))
uuid = json_object_get_string_member (json_obj, "UUID");
g_debug ("Version: %s", version);
g_debug ("UUID: %s", uuid);
/* get the update service */
if (!json_parser_load_from_data (parser,
g_bytes_get_data (blob, NULL),
(gssize) g_bytes_get_size (blob),
error)) {
g_prefix_error (error, "failed to parse node: ");
return FALSE;
}
node_root = json_parser_get_root (parser);
if (node_root == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no root node");
return FALSE;
}
obj_root = json_node_get_object (node_root);
if (obj_root == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no root object");
return FALSE;
}
if (json_object_has_member (obj_root, "ServiceVersion")) {
version = json_object_get_string_member (obj_root,
"ServiceVersion");
} else if (json_object_has_member (obj_root, "RedfishVersion")) {
version = json_object_get_string_member (obj_root,
"RedfishVersion");
}
g_debug ("Version: %s", version);
g_debug ("UUID: %s",
json_object_get_string_member (obj_root, "UUID"));
if (json_object_has_member (obj_root, "UpdateService"))
obj_update_service = json_object_get_object_member (obj_root, "UpdateService");
if (obj_update_service == NULL) {
if (json_object_has_member (json_obj, "UpdateService"))
json_update_service = json_object_get_object_member (json_obj, "UpdateService");
if (json_update_service == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no UpdateService object");
return FALSE;
}
data_id = json_object_get_string_member (obj_update_service, "@odata.id");
data_id = json_object_get_string_member (json_update_service, "@odata.id");
if (data_id == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
@ -593,24 +323,34 @@ fu_redfish_backend_set_cacheck (FuRedfishBackend *self, gboolean cacheck)
void
fu_redfish_backend_set_username (FuRedfishBackend *self, const gchar *username)
{
curl_easy_setopt (self->curl, CURLOPT_USERNAME, username);
g_free (self->username);
self->username = g_strdup (username);
}
void
fu_redfish_backend_set_password (FuRedfishBackend *self, const gchar *password)
{
curl_easy_setopt (self->curl, CURLOPT_PASSWORD, password);
g_free (self->password);
self->password = g_strdup (password);
}
const gchar *
fu_redfish_backend_get_push_uri_path (FuRedfishBackend *self)
{
return self->push_uri_path;
}
static void
fu_redfish_backend_finalize (GObject *object)
{
FuRedfishBackend *self = FU_REDFISH_BACKEND (object);
if (self->curl != NULL)
curl_easy_cleanup (self->curl);
g_hash_table_unref (self->request_cache);
curl_share_cleanup (self->curlsh);
g_free (self->update_uri_path);
g_free (self->push_uri_path);
g_free (self->hostname);
g_free (self->username);
g_free (self->password);
G_OBJECT_CLASS (fu_redfish_backend_parent_class)->finalize (object);
}
@ -627,11 +367,15 @@ fu_redfish_backend_class_init (FuRedfishBackendClass *klass)
static void
fu_redfish_backend_init (FuRedfishBackend *self)
{
self->curl = curl_easy_init ();
/* since DSP0266 makes Basic Authorization a requirement,
* it is safe to use Basic Auth for all implementations */
curl_easy_setopt (self->curl, CURLOPT_HTTPAUTH, (glong) CURLAUTH_BASIC);
self->use_https = TRUE;
self->device_gtype = FU_TYPE_REDFISH_DEVICE;
self->request_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) g_byte_array_unref);
self->curlsh = curl_share_init ();
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
}
FuRedfishBackend *

View File

@ -8,6 +8,8 @@
#include <fwupdplugin.h>
#include "fu-redfish-request.h"
#define FU_REDFISH_TYPE_BACKEND (fu_redfish_backend_get_type ())
G_DECLARE_FINAL_TYPE (FuRedfishBackend, fu_redfish_backend, FU, REDFISH_BACKEND, FuBackend)
@ -25,7 +27,5 @@ void fu_redfish_backend_set_https (FuRedfishBackend *self,
gboolean use_https);
void fu_redfish_backend_set_cacheck (FuRedfishBackend *self,
gboolean cacheck);
gboolean fu_redfish_backend_update (FuRedfishBackend *self,
FuDevice *device,
GBytes *blob_fw,
GError **error);
const gchar *fu_redfish_backend_get_push_uri_path (FuRedfishBackend *self);
FuRedfishRequest *fu_redfish_backend_request_new (FuRedfishBackend *self);

View File

@ -48,4 +48,86 @@ fu_redfish_common_buffer_to_mac (const guint8 *buffer)
return g_string_free (str, FALSE);
}
/* vim: set noexpandtab: */
gchar *
fu_redfish_common_fix_version (const gchar *version)
{
g_auto(GStrv) split = NULL;
g_return_val_if_fail (version != NULL, NULL);
/* not valid */
if (g_strcmp0 (version, "-*") == 0)
return NULL;
/* find the section preficed with "v" */
split = g_strsplit (version, " ", -1);
for (guint i = 0; split[i] != NULL; i++) {
if (g_str_has_prefix (split[i], "v")) {
g_debug ("using %s for %s", split[i] + 1, version);
return g_strdup (split[i] + 1);
}
}
/* find the thing with dots */
for (guint i = 0; split[i] != NULL; i++) {
if (g_strstr_len (split[i], -1, ".")) {
g_debug ("using %s for %s", split[i], version);
return g_strdup (split[i]);
}
}
/* we failed to do anything clever */
return g_strdup (version);
}
/* parses a Lenovo XCC-format version like "11A-1.02" */
gboolean
fu_redfish_common_parse_version_lenovo (const gchar *version,
gchar **out_build, /* out */
gchar **out_version, /* out */
GError **error)
{
g_auto(GStrv) versplit = g_strsplit (version, "-", -1);
/* sanity check */
if (g_strv_length (versplit) != 2) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"not two sections");
return FALSE;
}
if (strlen (versplit[0]) != 3) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid length first section");
return FALSE;
}
/* milestone */
if (!g_ascii_isdigit (versplit[0][0]) ||
!g_ascii_isdigit (versplit[0][1])) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"milestone number invalid");
return FALSE;
}
/* build is only one letter from A -> Z */
if (!g_ascii_isalpha (versplit[0][2])) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"build letter invalid");
return FALSE;
}
/* success */
if (out_build != NULL)
*out_build = g_strdup (versplit[0]);
if (out_version != NULL)
*out_version = g_strdup (versplit[1]);
return TRUE;
}

View File

@ -43,3 +43,9 @@
gchar *fu_redfish_common_buffer_to_ipv4 (const guint8 *buffer);
gchar *fu_redfish_common_buffer_to_ipv6 (const guint8 *buffer);
gchar *fu_redfish_common_buffer_to_mac (const guint8 *buffer);
gchar *fu_redfish_common_fix_version (const gchar *version);
gboolean fu_redfish_common_parse_version_lenovo (const gchar *version,
gchar **out_build,
gchar **out_version,
GError **error);

View File

@ -0,0 +1,836 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-redfish-backend.h"
#include "fu-redfish-common.h"
#include "fu-redfish-device.h"
typedef struct {
FuRedfishBackend *backend;
JsonObject *member;
guint64 milestone;
gchar *build;
} FuRedfishDevicePrivate;
enum {
PROP_0,
PROP_BACKEND,
PROP_MEMBER,
PROP_LAST
};
G_DEFINE_TYPE_WITH_PRIVATE (FuRedfishDevice, fu_redfish_device, FU_TYPE_DEVICE)
#define GET_PRIVATE(o) (fu_redfish_device_get_instance_private (o))
/**
* FU_REDFISH_DEVICE_FLAG_IS_BACKUP:
*
* The device is the other half of a dual image firmware.
*/
#define FU_REDFISH_DEVICE_FLAG_IS_BACKUP (1 << 0)
/**
* FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD:
*
* Use unsigned development builds.
*/
#define FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD (1 << 1)
static void
fu_redfish_device_to_string (FuDevice *device, guint idt, GString *str)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE (device);
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
if (priv->milestone > 0x0)
fu_common_string_append_kx (str, idt, "Milestone", priv->milestone);
if (priv->build != NULL)
fu_common_string_append_kv (str, idt, "Build", priv->build);
}
static void
fu_redfish_device_set_device_class (FuRedfishDevice *self, const gchar *tmp)
{
if (g_strcmp0 (tmp, "NetworkController") == 0) {
fu_device_add_icon (FU_DEVICE (self), "network-wired");
return;
}
if (g_strcmp0 (tmp, "MassStorageController") == 0) {
fu_device_add_icon (FU_DEVICE (self), "drive-multidisk");
return;
}
if (g_strcmp0 (tmp, "DisplayController") == 0) {
fu_device_add_icon (FU_DEVICE (self), "video-display");
return;
}
if (g_strcmp0 (tmp, "DockingStation") == 0) {
fu_device_add_icon (FU_DEVICE (self), "dock");
return;
}
if (g_strcmp0 (tmp, "WirelessController") == 0) {
fu_device_add_icon (FU_DEVICE (self), "network-wireless");
return;
}
g_debug ("no icon mapping for %s", tmp);
fu_device_add_icon (FU_DEVICE (self), "audio-card");
}
static gboolean
fu_redfish_device_probe_related_pcie_item (FuRedfishDevice *self,
const gchar *uri,
GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
JsonObject *json_obj;
const gchar *subsystem = "PCI";
guint64 vendor_id = 0x0;
guint64 model_id = 0x0;
guint64 subsystem_vendor_id = 0x0;
guint64 subsystem_model_id = 0x0;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (priv->backend);
/* get URI */
if (!fu_redfish_request_perform (request, uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
/* optional properties */
if (json_object_has_member (json_obj, "DeviceClass")) {
const gchar *tmp = json_object_get_string_member (json_obj, "DeviceClass");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_device_class (self, tmp);
}
if (json_object_has_member (json_obj, "VendorId")) {
const gchar *tmp = json_object_get_string_member (json_obj, "VendorId");
if (tmp != NULL && tmp[0] != '\0')
vendor_id = fu_common_strtoull (tmp);
}
if (json_object_has_member (json_obj, "DeviceId")) {
const gchar *tmp = json_object_get_string_member (json_obj, "DeviceId");
if (tmp != NULL && tmp[0] != '\0')
model_id = fu_common_strtoull (tmp);
}
if (json_object_has_member (json_obj, "SubsystemVendorId")) {
const gchar *tmp = json_object_get_string_member (json_obj, "SubsystemVendorId");
if (tmp != NULL && tmp[0] != '\0')
subsystem_vendor_id = fu_common_strtoull (tmp);
}
if (json_object_has_member (json_obj, "SubsystemId")) {
const gchar *tmp = json_object_get_string_member (json_obj, "SubsystemId");
if (tmp != NULL && tmp[0] != '\0')
subsystem_model_id = fu_common_strtoull (tmp);
}
/* add vendor ID */
if (vendor_id != 0x0) {
g_autofree gchar *vendor_id_str = NULL;
vendor_id_str = g_strdup_printf ("PCI:0x%04X", (guint) vendor_id);
fu_device_add_vendor_id (FU_DEVICE (self), vendor_id_str);
}
/* add more instance IDs if possible */
if (vendor_id != 0x0 && model_id != 0x0) {
g_autofree gchar *devid1 = NULL;
devid1 = g_strdup_printf ("%s\\VEN_%04X&DEV_%04X",
subsystem, (guint) vendor_id, (guint) model_id);
fu_device_add_instance_id (FU_DEVICE (self), devid1);
}
if (vendor_id != 0x0 && model_id != 0x0 &&
subsystem_vendor_id != 0x0 && subsystem_model_id != 0x0) {
g_autofree gchar *devid2 = NULL;
devid2 = g_strdup_printf ("%s\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X",
subsystem, (guint) vendor_id, (guint) model_id,
(guint) subsystem_vendor_id,
(guint) subsystem_model_id);
fu_device_add_instance_id (FU_DEVICE (self), devid2);
}
/* success */
return TRUE;
}
static gboolean
fu_redfish_device_probe_related_pcie_functions (FuRedfishDevice *self,
const gchar *uri,
GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
JsonObject *json_obj;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (priv->backend);
/* get URI */
if (!fu_redfish_request_perform (request, uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
if (json_object_has_member (json_obj, "Members")) {
JsonArray *members_array = json_object_get_array_member (json_obj, "Members");
for (guint i = 0; i < json_array_get_length (members_array); i++) {
JsonObject *related_item;
related_item = json_array_get_object_element (members_array, i);
if (json_object_has_member (related_item, "@odata.id")) {
const gchar *id = json_object_get_string_member (related_item, "@odata.id");
if (!fu_redfish_device_probe_related_pcie_item (self, id, error))
return FALSE;
}
}
}
/* success */
return TRUE;
}
static gboolean
fu_redfish_device_probe_related_item (FuRedfishDevice *self,
const gchar *uri,
GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
JsonObject *json_obj;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (priv->backend);
/* get URI */
if (!fu_redfish_request_perform (request, uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object (request);
/* optional properties */
if (json_object_has_member (json_obj, "SerialNumber")) {
const gchar *tmp = json_object_get_string_member (json_obj, "SerialNumber");
if (tmp != NULL && tmp[0] != '\0' && g_strcmp0 (tmp, "N/A") != 0)
fu_device_set_serial (FU_DEVICE (self), tmp);
}
/* sometimes an array, sometimes an object! */
if (json_object_has_member (json_obj, "PCIeFunctions")) {
JsonNode *pcie_functions = json_object_get_member (json_obj, "PCIeFunctions");
if (JSON_NODE_HOLDS_OBJECT (pcie_functions)) {
JsonObject *obj = json_node_get_object (pcie_functions);
if (json_object_has_member (obj, "@odata.id")) {
const gchar *id = json_object_get_string_member (obj, "@odata.id");
if (!fu_redfish_device_probe_related_pcie_functions (self, id, error))
return FALSE;
}
}
}
return TRUE;
}
/* parses a Lenovo XCC-format version like "11A-1.02" */
static gboolean
fu_redfish_device_set_version_lenovo (FuRedfishDevice *self,
const gchar *version,
GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
g_autofree gchar *out_build = NULL;
g_autofree gchar *out_version = NULL;
/* split up Lenovo format */
if (!fu_redfish_common_parse_version_lenovo (version,
&out_build,
&out_version,
error))
return FALSE;
/* split out milestone */
priv->milestone = g_ascii_strtoull (out_build, NULL, 10);
if (priv->milestone == 0) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"version milestone invalid");
return FALSE;
}
/* odd numbered builds are unsigned */
if (priv->milestone % 2 != 0) {
fu_device_add_private_flag (FU_DEVICE (self),
FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD);
}
/* build is only one letter from A -> Z */
if (!g_ascii_isalpha (out_build[2])) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"build letter invalid");
return FALSE;
}
priv->build = g_strndup (out_build + 2, 1);
fu_device_set_version (FU_DEVICE (self), out_version);
fu_device_set_version_format (FU_DEVICE (self),
fu_common_version_guess_format (out_version));
return TRUE;
}
static void
fu_redfish_device_set_version (FuRedfishDevice *self, const gchar *tmp)
{
/* OEM specific */
if (g_strcmp0 (fu_device_get_vendor (FU_DEVICE (self)), "Lenovo") == 0) {
g_autoptr(GError) error_local = NULL;
if (!fu_redfish_device_set_version_lenovo (self, tmp, &error_local)) {
g_debug ("failed to parse Lenovo version %s: %s",
tmp, error_local->message);
}
}
/* fallback */
if (fu_device_get_version (FU_DEVICE (self)) == NULL) {
g_autofree gchar *ver = fu_redfish_common_fix_version (tmp);
if (ver != NULL) {
fu_device_set_version (FU_DEVICE (self), ver);
fu_device_set_version_format (FU_DEVICE (self), fu_common_version_guess_format (ver));
}
}
}
static void
fu_redfish_device_set_version_lowest (FuRedfishDevice *self, const gchar *tmp)
{
/* OEM specific */
if (g_strcmp0 (fu_device_get_vendor (FU_DEVICE (self)), "Lenovo") == 0) {
g_autoptr(GError) error_local = NULL;
g_autofree gchar *out_version = NULL;
if (!fu_redfish_common_parse_version_lenovo (tmp, NULL,
&out_version,
&error_local)) {
g_debug ("failed to parse Lenovo version %s: %s",
tmp, error_local->message);
}
fu_device_set_version_lowest (FU_DEVICE (self), out_version);
}
/* fallback */
if (fu_device_get_version_lowest (FU_DEVICE (self)) == NULL) {
g_autofree gchar *ver = fu_redfish_common_fix_version (tmp);
fu_device_set_version_lowest (FU_DEVICE (self), ver);
}
}
static void
fu_redfish_device_set_name (FuRedfishDevice *self, const gchar *name)
{
/* useless */
if (g_str_has_prefix (name, "Firmware:"))
name += 9;
/* device type */
if (g_str_has_prefix (name, "DEVICE-")) {
name += 7;
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
} else if (g_str_has_prefix (name, "DISK-")) {
name += 5;
fu_device_add_icon (FU_DEVICE (self), "drive-harddisk");
} else if (g_str_has_prefix (name, "POWER-")) {
name += 6;
fu_device_add_icon (FU_DEVICE (self), "ac-adapter");
fu_device_set_summary (FU_DEVICE (self), "Redfish power supply unit");
} else {
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
}
/* heuristics */
if (g_strcmp0 (name, "BMC") == 0)
fu_device_set_summary (FU_DEVICE (self), "Redfish baseboard management controller");
if (g_str_has_suffix (name, "HBA") == 0)
fu_device_set_summary (FU_DEVICE (self), "Redfish host bus adapter");
/* success */
fu_device_set_name (FU_DEVICE (self), name);
}
static void
fu_redfish_device_set_vendor (FuRedfishDevice *self, const gchar *vendor)
{
g_autofree gchar *vendor_upper = NULL;
g_autofree gchar *vendor_id = NULL;
/* fixup a common mistake */
if (g_strcmp0 (vendor, "LEN") == 0 ||
g_strcmp0 (vendor, "LNVO") == 0)
vendor = "Lenovo";
fu_device_set_vendor (FU_DEVICE (self), vendor);
/* add vendor-id */
vendor_upper = g_ascii_strup (vendor, -1);
g_strdelimit (vendor_upper, " ", '_');
vendor_id = g_strdup_printf ("REDFISH:%s", vendor_upper);
fu_device_add_vendor_id (FU_DEVICE (self), vendor_id);
}
static gboolean
fu_redfish_device_probe (FuDevice *dev, GError **error)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE (dev);
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
JsonObject *member = priv->member;
const gchar *guid = NULL;
g_autofree gchar *guid_lower = NULL;
/* required to POST later */
if (!json_object_has_member (member, "@odata.id")) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no @odata.id string");
return FALSE;
}
fu_device_set_physical_id (dev, "Redfish-Inventory");
fu_device_set_logical_id (dev, json_object_get_string_member (member, "@odata.id"));
if (json_object_has_member (member, "Id")) {
const gchar *tmp = json_object_get_string_member (member, "Id");
if (tmp != NULL)
fu_device_set_backend_id (dev, tmp);
}
/* get SoftwareId, falling back to vendor-specific versions */
if (json_object_has_member (member, "SoftwareId")) {
guid = json_object_get_string_member (member, "SoftwareId");
} else if (json_object_has_member (member, "Oem")) {
JsonObject *oem = json_object_get_object_member (member, "Oem");
if (oem != NULL && json_object_has_member (oem, "Hpe")) {
JsonObject *hpe = json_object_get_object_member (oem, "Hpe");
if (hpe != NULL && json_object_has_member (hpe, "DeviceClass"))
guid = json_object_get_string_member (hpe, "DeviceClass");
}
}
/* GUID is required */
if (guid == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no GUID for device");
return FALSE;
}
/* device properties */
if (json_object_has_member (member, "Manufacturer")) {
const gchar *tmp = json_object_get_string_member (member, "Manufacturer");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_vendor (self, tmp);
}
/* the version can encode the instance ID suffix */
if (json_object_has_member (member, "Version")) {
const gchar *tmp = json_object_get_string_member (member, "Version");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_version (self, tmp);
}
/* some vendors use a GUID, others use an ID like BMC-AFBT-10 */
guid_lower = g_ascii_strdown (guid, -1);
if (fwupd_guid_is_valid (guid_lower)) {
fu_device_add_guid (dev, guid_lower);
} else if (fu_device_get_vendor (dev) != NULL) {
const gchar *instance_id_suffix = "";
g_autofree gchar *instance_id = NULL;
if (fu_device_has_private_flag (dev, FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD))
instance_id_suffix = "&TYPE_UNSIGNED";
instance_id = g_strdup_printf ("REDFISH\\VENDOR_%s&SOFTWAREID_%s%s",
fu_device_get_vendor (dev),
guid,
instance_id_suffix);
g_strdelimit (instance_id, " ", '_');
fu_device_add_instance_id (dev, instance_id);
}
/* used for quirking and parenting */
if (fu_device_get_vendor (dev) != NULL &&
fu_device_get_backend_id (dev) != NULL) {
g_autofree gchar *instance_id = NULL;
instance_id = g_strdup_printf ("REDFISH\\VENDOR_%s&ID_%s",
fu_device_get_vendor (dev),
fu_device_get_backend_id (dev));
fu_device_add_instance_id (dev, instance_id);
}
if (json_object_has_member (member, "Name")) {
const gchar *tmp = json_object_get_string_member (member, "Name");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_name (self, tmp);
}
if (json_object_has_member (member, "LowestSupportedVersion")) {
const gchar *tmp = json_object_get_string_member (member, "LowestSupportedVersion");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_version_lowest (self, tmp);
}
if (json_object_has_member (member, "Description")) {
const gchar *tmp = json_object_get_string_member (member, "Description");
if (tmp != NULL && tmp[0] != '\0')
fu_device_set_description (dev, tmp);
}
/* reasons why the device might not be updatable */
if (json_object_has_member (member, "Updateable")) {
if (!json_object_get_boolean_member (member, "Updateable"))
fu_device_remove_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
}
if (fu_device_has_private_flag (dev, FU_REDFISH_DEVICE_FLAG_IS_BACKUP))
fu_device_remove_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
/* use related items to set extra instance IDs */
if (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE) &&
json_object_has_member (member, "RelatedItem")) {
JsonArray *related_item_array = json_object_get_array_member (member, "RelatedItem");
for (guint i = 0; i < json_array_get_length (related_item_array); i++) {
JsonObject *related_item;
related_item = json_array_get_object_element (related_item_array, i);
if (json_object_has_member (related_item, "@odata.id")) {
const gchar *id = json_object_get_string_member (related_item, "@odata.id");
if (!fu_redfish_device_probe_related_item (self, id, error))
return FALSE;
}
}
}
/* success */
return TRUE;
}
FuRedfishBackend *
fu_redfish_device_get_backend (FuRedfishDevice *self)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
return priv->backend;
}
typedef struct {
FwupdError error_code;
gchar *location;
gboolean completed;
GHashTable *messages_seen;
} FuRedfishDevicePollCtx;
static void
fu_redfish_device_poll_set_message_id (FuRedfishDevice *self,
FuRedfishDevicePollCtx *ctx,
const gchar *message_id)
{
/* ignore */
if (g_strcmp0 (message_id, "TaskEvent.1.0.TaskProgressChanged") == 0 ||
g_strcmp0 (message_id, "TaskEvent.1.0.TaskCompletedWarning") == 0 ||
g_strcmp0 (message_id, "TaskEvent.1.0.TaskCompletedOK") == 0 ||
g_strcmp0 (message_id, "Base.1.6.Success") == 0)
return;
/* set flags */
if (g_strcmp0 (message_id, "Base.1.10.ResetRequired") == 0) {
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
return;
}
/* set error code */
if (g_strcmp0 (message_id, "Update.1.0.AwaitToActivate") == 0) {
ctx->error_code = FWUPD_ERROR_NEEDS_USER_ACTION;
return;
}
if (g_strcmp0 (message_id, "Update.1.0.TransferFailed") == 0) {
ctx->error_code = FWUPD_ERROR_WRITE;
return;
}
if (g_strcmp0 (message_id, "Update.1.0.ActivateFailed") == 0) {
ctx->error_code = FWUPD_ERROR_INVALID_FILE;
return;
}
if (g_strcmp0 (message_id, "Update.1.0.VerificationFailed") == 0 ||
g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyFailed") == 0) {
ctx->error_code = FWUPD_ERROR_INVALID_FILE;
return;
}
if (g_strcmp0 (message_id, "Update.1.0.ApplyFailed") == 0) {
ctx->error_code = FWUPD_ERROR_WRITE;
return;
}
/* set status */
if (g_strcmp0 (message_id, "Update.1.1.TargetDetermined") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_LOADING);
return;
}
if (g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateAssignment") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_LOADING);
return;
}
if (g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyInProgress") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
return;
}
if (g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyCompleted") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_IDLE);
return;
}
if (g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyInProgress") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY);
return;
}
if (g_strcmp0 (message_id, "Update.1.1.TransferringToComponent") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_LOADING);
return;
}
if (g_strcmp0 (message_id, "Update.1.1.VerifyingAtComponent") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY);
return;
}
if (g_strcmp0 (message_id, "Update.1.1.UpdateInProgress") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
return;
}
if (g_strcmp0 (message_id, "Update.1.1.UpdateSuccessful") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_IDLE);
return;
}
if (g_strcmp0 (message_id, "Update.1.1.InstallingOnComponent") == 0) {
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
return;
}
}
static gboolean
fu_redfish_device_poll_task_once (FuRedfishDevice *self,
FuRedfishDevicePollCtx *ctx,
GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
JsonObject *json_obj;
const gchar *message = "Unknown failure";
const gchar *state_tmp;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (priv->backend);
/* create URI and poll */
if (!fu_redfish_request_perform (request, ctx->location,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
/* percentage is optional */
json_obj = fu_redfish_request_get_json_object (request);
if (json_object_has_member (json_obj, "PercentComplete")) {
gint64 pc = json_object_get_int_member (json_obj, "PercentComplete");
if (pc >= 0 && pc <= 100)
fu_device_set_progress (FU_DEVICE (self), (guint) pc);
}
/* print all messages we've not seen yet */
if (json_object_has_member (json_obj, "Messages")) {
JsonArray *json_msgs = json_object_get_array_member (json_obj, "Messages");
guint json_msgs_sz = json_array_get_length (json_msgs);
for (guint i = 0; i < json_msgs_sz; i++) {
JsonObject *json_message = json_array_get_object_element (json_msgs, i);
const gchar *message_id = NULL;
g_autofree gchar *message_key = NULL;
/* set additional device properties */
if (json_object_has_member (json_message, "MessageId"))
message_id = json_object_get_string_member (json_message, "MessageId");
if (json_object_has_member (json_message, "Message"))
message = json_object_get_string_member (json_message, "Message");
/* ignore messages we've seen before */
message_key = g_strdup_printf ("%s;%s", message_id, message);
if (g_hash_table_contains (ctx->messages_seen, message_key)) {
g_debug ("ignoring %s", message_key);
continue;
}
g_hash_table_add (ctx->messages_seen, g_steal_pointer (&message_key));
/* use the message */
g_debug ("message #%u [%s]: %s", i, message_id, message);
fu_redfish_device_poll_set_message_id (self, ctx, message_id);
}
}
/* use taskstate to set context */
if (!json_object_has_member (json_obj, "TaskState")) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no TaskState for task manager");
return FALSE;
}
state_tmp = json_object_get_string_member (json_obj, "TaskState");
g_debug ("TaskState now %s", state_tmp);
if (g_strcmp0 (state_tmp, "Completed") == 0) {
ctx->completed = TRUE;
return TRUE;
}
if (g_strcmp0 (state_tmp, "Cancelled") == 0) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_CANCELLED,
"Task was cancelled");
return FALSE;
}
if (g_strcmp0 (state_tmp, "Exception") == 0 ||
g_strcmp0 (state_tmp, "UserIntervention") == 0) {
g_set_error_literal (error, FWUPD_ERROR, ctx->error_code, message);
return FALSE;
}
/* try again */
return TRUE;
}
static FuRedfishDevicePollCtx *
fu_redfish_device_poll_ctx_new (const gchar *location)
{
FuRedfishDevicePollCtx *ctx = g_new0 (FuRedfishDevicePollCtx, 1);
ctx->messages_seen = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
ctx->location = g_strdup (location);
ctx->error_code = FWUPD_ERROR_INTERNAL;
return ctx;
}
static void
fu_redfish_device_poll_ctx_free (FuRedfishDevicePollCtx *ctx)
{
g_hash_table_unref (ctx->messages_seen);
g_free (ctx->location);
g_free (ctx);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuRedfishDevicePollCtx, fu_redfish_device_poll_ctx_free)
#pragma clang diagnostic pop
gboolean
fu_redfish_device_poll_task (FuRedfishDevice *self,
const gchar *location,
GError **error)
{
const guint timeout = 2400;
g_autoptr(GTimer) timer = g_timer_new ();
g_autoptr(FuRedfishDevicePollCtx) ctx = fu_redfish_device_poll_ctx_new (location);
/* sleep and then reprobe hardware */
do {
g_usleep (G_USEC_PER_SEC);
if (!fu_redfish_device_poll_task_once (self, ctx, error))
return FALSE;
if (ctx->completed)
return TRUE;
} while (g_timer_elapsed (timer, NULL) < timeout);
/* success */
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to poll %s for success after %u seconds",
location, timeout);
return FALSE;
}
static void
fu_redfish_device_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE (object);
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
switch (prop_id) {
case PROP_BACKEND:
g_value_set_object (value, priv->backend);
break;
case PROP_MEMBER:
g_value_set_pointer (value, priv->member);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
fu_redfish_device_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE (object);
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
switch (prop_id) {
case PROP_BACKEND:
g_set_object (&priv->backend, g_value_get_object (value));
break;
case PROP_MEMBER:
priv->member = json_object_ref (g_value_get_pointer (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
fu_redfish_device_init (FuRedfishDevice *self)
{
fu_device_set_summary (FU_DEVICE (self), "Redfish device");
fu_device_add_protocol (FU_DEVICE (self), "org.dmtf.redfish");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME);
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT);
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON);
fu_device_register_private_flag (FU_DEVICE (self),
FU_REDFISH_DEVICE_FLAG_IS_BACKUP,
"is-backup");
fu_device_register_private_flag (FU_DEVICE (self),
FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD,
"unsigned-build");
}
static void
fu_redfish_device_finalize (GObject *object)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE (object);
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
if (priv->backend != NULL)
g_object_unref (priv->backend);
if (priv->member != NULL)
json_object_unref (priv->member);
g_free (priv->build);
G_OBJECT_CLASS (fu_redfish_device_parent_class)->finalize (object);
}
static void
fu_redfish_device_class_init (FuRedfishDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
object_class->get_property = fu_redfish_device_get_property;
object_class->set_property = fu_redfish_device_set_property;
object_class->finalize = fu_redfish_device_finalize;
klass_device->to_string = fu_redfish_device_to_string;
klass_device->probe = fu_redfish_device_probe;
pspec = g_param_spec_object ("backend", NULL, NULL,
FU_TYPE_BACKEND,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_BACKEND, pspec);
pspec = g_param_spec_pointer ("member", NULL, NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_MEMBER, pspec);
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#include "fu-redfish-backend.h"
#define FU_TYPE_REDFISH_DEVICE (fu_redfish_device_get_type ())
G_DECLARE_DERIVABLE_TYPE (FuRedfishDevice, fu_redfish_device, FU, REDFISH_DEVICE, FuDevice)
struct _FuRedfishDeviceClass
{
FuDeviceClass parent_class;
};
FuRedfishBackend *fu_redfish_device_get_backend (FuRedfishDevice *self);
gboolean fu_redfish_device_poll_task (FuRedfishDevice *self,
const gchar *location,
GError **error);

View File

@ -0,0 +1,167 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <curl/curl.h>
#include "fu-redfish-legacy-device.h"
#include "fu-redfish-request.h"
struct _FuRedfishLegacyDevice {
FuRedfishDevice parent_instance;
};
G_DEFINE_TYPE (FuRedfishLegacyDevice, fu_redfish_legacy_device, FU_TYPE_REDFISH_DEVICE)
static gboolean
fu_redfish_legacy_device_detach (FuDevice *dev, GError **error)
{
FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE (dev);
FuRedfishBackend *backend = fu_redfish_device_get_backend (FU_REDFISH_DEVICE (self));
CURL *curl;
struct curl_slist *hs = NULL;
g_autoptr(FuRedfishRequest) request = NULL;
g_autoptr(GString) str = g_string_new (NULL);
g_autoptr(JsonBuilder) builder = json_builder_new ();
g_autoptr(JsonGenerator) json_generator = json_generator_new ();
g_autoptr(JsonNode) json_root = NULL;
/* create header */
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "HttpPushUriTargetsBusy");
json_builder_add_boolean_value (builder, TRUE);
json_builder_set_member_name (builder, "HttpPushUriTargets");
json_builder_begin_array (builder);
json_builder_add_string_value (builder, fu_device_get_logical_id (FU_DEVICE (self)));
json_builder_end_array (builder);
json_builder_end_object (builder);
/* export as a string */
json_root = json_builder_get_root (builder);
json_generator_set_pretty (json_generator, TRUE);
json_generator_set_root (json_generator, json_root);
json_generator_to_gstring (json_generator, str);
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL)
g_debug ("request: %s", str->str);
/* patch the two fields */
request = fu_redfish_backend_request_new (backend);
curl = fu_redfish_request_get_curl (request);
curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, str->str);
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (long) str->len);
hs = curl_slist_append (hs, "Content-Type: application/json");
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, hs);
return fu_redfish_request_perform (request, "/redfish/v1/UpdateService",
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error);
}
static gboolean
fu_redfish_legacy_device_attach (FuDevice *dev, GError **error)
{
FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE (dev);
FuRedfishBackend *backend = fu_redfish_device_get_backend (FU_REDFISH_DEVICE (self));
CURL *curl;
struct curl_slist *hs = NULL;
g_autoptr(FuRedfishRequest) request = NULL;
g_autoptr(GString) str = g_string_new (NULL);
g_autoptr(JsonBuilder) builder = json_builder_new ();
g_autoptr(JsonGenerator) json_generator = json_generator_new ();
g_autoptr(JsonNode) json_root = NULL;
/* create header */
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "HttpPushUriTargetsBusy");
json_builder_add_boolean_value (builder, FALSE);
json_builder_set_member_name (builder, "HttpPushUriTargets");
json_builder_begin_array (builder);
json_builder_end_array (builder);
json_builder_end_object (builder);
/* export as a string */
json_root = json_builder_get_root (builder);
json_generator_set_pretty (json_generator, TRUE);
json_generator_set_root (json_generator, json_root);
json_generator_to_gstring (json_generator, str);
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL)
g_debug ("request: %s", str->str);
/* patch the two fields */
request = fu_redfish_backend_request_new (backend);
curl = fu_redfish_request_get_curl (request);
curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, str->str);
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (long) str->len);
hs = curl_slist_append (hs, "Content-Type: application/json");
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, hs);
return fu_redfish_request_perform (request, "/redfish/v1/UpdateService",
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error);
}
static gboolean
fu_redfish_legacy_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE (device);
FuRedfishBackend *backend = fu_redfish_device_get_backend (FU_REDFISH_DEVICE (self));
CURL *curl;
JsonObject *json_obj;
const gchar *location;
g_autoptr(FuRedfishRequest) request = NULL;
g_autoptr(GBytes) fw = NULL;
/* get default image */
fw = fu_firmware_get_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* POST data */
request = fu_redfish_backend_request_new (backend);
curl = fu_redfish_request_get_curl (request);
curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, g_bytes_get_data (fw, NULL));
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (long) g_bytes_get_size (fw));
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
if (!fu_redfish_request_perform (request,
fu_redfish_backend_get_push_uri_path (backend),
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
/* poll the task for progress */
json_obj = fu_redfish_request_get_json_object (request);
if (!json_object_has_member (json_obj, "@odata.id")) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no task returned for %s",
fu_redfish_backend_get_push_uri_path (backend));
return FALSE;
}
location = json_object_get_string_member (json_obj, "@odata.id");
return fu_redfish_device_poll_task (FU_REDFISH_DEVICE (self),
location, error);
}
static void
fu_redfish_legacy_device_init (FuRedfishLegacyDevice *self)
{
fu_device_set_summary (FU_DEVICE (self), "Redfish legacy device");
}
static void
fu_redfish_legacy_device_class_init (FuRedfishLegacyDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
klass_device->attach = fu_redfish_legacy_device_attach;
klass_device->detach = fu_redfish_legacy_device_detach;
klass_device->write_firmware = fu_redfish_legacy_device_write_firmware;
}

View File

@ -0,0 +1,12 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-redfish-device.h"
#define FU_TYPE_REDFISH_LEGACY_DEVICE (fu_redfish_legacy_device_get_type ())
G_DECLARE_FINAL_TYPE (FuRedfishLegacyDevice, fu_redfish_legacy_device, FU, REDFISH_LEGACY_DEVICE, FuRedfishDevice)

View File

@ -0,0 +1,142 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <curl/curl.h>
#include "fu-redfish-multipart-device.h"
#include "fu-redfish-request.h"
struct _FuRedfishMultipartDevice {
FuRedfishDevice parent_instance;
};
G_DEFINE_TYPE (FuRedfishMultipartDevice, fu_redfish_multipart_device, FU_TYPE_REDFISH_DEVICE)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curl_mime, curl_mime_free)
static GString *
fu_redfish_multipart_device_get_parameters (FuRedfishMultipartDevice *self)
{
g_autoptr(GString) str = g_string_new (NULL);
g_autoptr(JsonBuilder) builder = json_builder_new ();
g_autoptr(JsonGenerator) json_generator = json_generator_new ();
g_autoptr(JsonNode) json_root = NULL;
/* create header */
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "Targets");
json_builder_begin_array (builder);
json_builder_add_string_value (builder, fu_device_get_logical_id (FU_DEVICE (self)));
json_builder_end_array (builder);
json_builder_set_member_name (builder, "@Redfish.OperationApplyTime");
json_builder_add_string_value (builder, "Immediate");
json_builder_end_object (builder);
/* export as a string */
json_root = json_builder_get_root (builder);
json_generator_set_pretty (json_generator, TRUE);
json_generator_set_root (json_generator, json_root);
json_generator_to_gstring (json_generator, str);
return g_steal_pointer (&str);
}
static gboolean
fu_redfish_multipart_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuRedfishMultipartDevice *self = FU_REDFISH_MULTIPART_DEVICE (device);
FuRedfishBackend *backend = fu_redfish_device_get_backend (FU_REDFISH_DEVICE (self));
CURL *curl;
JsonObject *json_obj;
curl_mimepart *part;
const gchar *location;
g_autofree gchar *filename = NULL;
g_autoptr(curl_mime) mime = NULL;
g_autoptr(FuRedfishRequest) request = NULL;
g_autoptr(GBytes) fw = NULL;
g_autoptr(GString) params = NULL;
/* get default image */
fw = fu_firmware_get_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* Get the update version */
filename = g_strdup_printf ("%s.bin", fu_device_get_name (FU_DEVICE (self)));
/* create the multipart request */
request = fu_redfish_backend_request_new (backend);
curl = fu_redfish_request_get_curl (request);
mime = curl_mime_init (curl);
curl_easy_setopt (curl, CURLOPT_MIMEPOST, mime);
params = fu_redfish_multipart_device_get_parameters (self);
part = curl_mime_addpart (mime);
curl_mime_name (part, "UpdateParameters");
curl_mime_type (part, "application/json");
curl_mime_data (part, params->str, CURL_ZERO_TERMINATED);
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL)
g_debug ("request: %s", params->str);
part = curl_mime_addpart (mime);
curl_mime_name (part, "UpdateFile");
curl_mime_type (part, "application/octet-stream");
curl_mime_filedata (part, filename);
curl_mime_data (part, g_bytes_get_data (fw, NULL), g_bytes_get_size (fw));
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
if (!fu_redfish_request_perform (request,
fu_redfish_backend_get_push_uri_path (backend),
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
if (fu_redfish_request_get_status_code (request) != 202) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to upload %s: %li",
filename,
fu_redfish_request_get_status_code (request));
return FALSE;
}
/* prefer the header, otherwise fall back to the response */
json_obj = fu_redfish_request_get_json_object (request);
if (json_object_has_member (json_obj, "TaskMonitor")) {
const gchar *tmp = json_object_get_string_member (json_obj, "TaskMonitor");
g_debug ("task manager for cleanup is %s", tmp);
}
/* poll the task for progress */
if (!json_object_has_member (json_obj, "@odata.id")) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no task returned for %s",
fu_redfish_backend_get_push_uri_path (backend));
return FALSE;
}
location = json_object_get_string_member (json_obj, "@odata.id");
return fu_redfish_device_poll_task (FU_REDFISH_DEVICE (self),
location, error);
}
static void
fu_redfish_multipart_device_init (FuRedfishMultipartDevice *self)
{
fu_device_set_summary (FU_DEVICE (self), "Redfish multipart device");
}
static void
fu_redfish_multipart_device_class_init (FuRedfishMultipartDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
klass_device->write_firmware = fu_redfish_multipart_device_write_firmware;
}

View File

@ -0,0 +1,12 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include "fu-redfish-device.h"
#define FU_TYPE_REDFISH_MULTIPART_DEVICE (fu_redfish_multipart_device_get_type ())
G_DECLARE_FINAL_TYPE (FuRedfishMultipartDevice, fu_redfish_multipart_device, FU, REDFISH_MULTIPART_DEVICE, FuRedfishDevice)

View File

@ -0,0 +1,263 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-redfish-request.h"
struct _FuRedfishRequest {
GObject parent_instance;
CURL *curl;
CURLU *uri;
GByteArray *buf;
glong status_code;
JsonParser *json_parser;
JsonObject *json_obj;
GHashTable *cache; /* nullable */
};
G_DEFINE_TYPE (FuRedfishRequest, fu_redfish_request, G_TYPE_OBJECT)
typedef gchar curlptr;
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curlptr, curl_free)
JsonObject *
fu_redfish_request_get_json_object (FuRedfishRequest *self)
{
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), NULL);
return self->json_obj;
}
CURL *
fu_redfish_request_get_curl (FuRedfishRequest *self)
{
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), NULL);
return self->curl;
}
CURLU *
fu_redfish_request_get_uri (FuRedfishRequest *self)
{
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), NULL);
return self->uri;
}
glong
fu_redfish_request_get_status_code (FuRedfishRequest *self)
{
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), G_MAXLONG);
return self->status_code;
}
static gboolean
fu_redfish_request_load_json (FuRedfishRequest *self, GByteArray *buf, GError **error)
{
JsonNode *json_root;
/* load */
if (buf->data == NULL || buf->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"there was no JSON payload");
return FALSE;
}
if (!json_parser_load_from_data (self->json_parser,
(const gchar *) buf->data,
(gssize) buf->len,
error)) {
return FALSE;
}
json_root = json_parser_get_root (self->json_parser);
if (json_root == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no JSON root node");
return FALSE;
}
self->json_obj = json_node_get_object (json_root);
if (self->json_obj == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no JSON object");
return FALSE;
}
/* dump for humans */
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL) {
g_autoptr(GString) str = g_string_new (NULL);
g_autoptr(JsonBuilder) builder = json_builder_new ();
g_autoptr(JsonGenerator) json_generator = json_generator_new ();
json_generator_set_pretty (json_generator, TRUE);
json_generator_set_root (json_generator, json_root);
json_generator_to_gstring (json_generator, str);
g_debug ("response: %s", str->str);
}
/* unauthorized */
if (json_object_has_member (self->json_obj, "error")) {
FwupdError error_code = FWUPD_ERROR_INTERNAL;
JsonObject *json_error;
const gchar *id = NULL;
const gchar *msg = "Unknown failure";
/* extended error present */
json_error = json_object_get_object_member (self->json_obj, "error");
if (json_object_has_member (json_error, "@Message.ExtendedInfo")) {
JsonArray *json_error_array;
json_error_array = json_object_get_array_member (json_error, "@Message.ExtendedInfo");
if (json_array_get_length (json_error_array) > 0) {
JsonObject *json_error2;
json_error2 = json_array_get_object_element (json_error_array, 0);
if (json_object_has_member (json_error2, "MessageId"))
id = json_object_get_string_member (json_error2, "MessageId");
if (json_object_has_member (json_error2, "Message"))
msg = json_object_get_string_member (json_error2, "Message");
}
} else {
if (json_object_has_member (json_error, "code"))
id = json_object_get_string_member (json_error, "code");
if (json_object_has_member (json_error, "message"))
msg = json_object_get_string_member (json_error, "message");
}
if (g_strcmp0 (id, "Base.1.8.AccessDenied") == 0)
error_code = FWUPD_ERROR_AUTH_FAILED;
g_set_error_literal (error, FWUPD_ERROR, error_code, msg);
return FALSE;
}
/* success */
return TRUE;
}
gboolean
fu_redfish_request_perform (FuRedfishRequest *self,
const gchar *path,
FuRedfishRequestPerformFlags flags,
GError **error)
{
CURLcode res;
g_autoptr(curlptr) uri_str = NULL;
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), FALSE);
g_return_val_if_fail (path != NULL, FALSE);
g_return_val_if_fail (self->status_code == 0, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* already in cache? */
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE &&
self->cache != NULL) {
GByteArray *buf = g_hash_table_lookup (self->cache, path);
if (buf != NULL) {
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON)
return fu_redfish_request_load_json (self, buf, error);
g_byte_array_unref (self->buf);
self->buf = g_byte_array_ref (buf);
return TRUE;
}
}
/* do request */
curl_url_set (self->uri, CURLUPART_PATH, path, 0);
curl_url_get (self->uri, CURLUPART_URL, &uri_str, 0);
res = curl_easy_perform (self->curl);
curl_easy_getinfo (self->curl, CURLINFO_RESPONSE_CODE, &self->status_code);
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL) {
g_autofree gchar *str = NULL;
str = g_strndup ((const gchar *) self->buf->data,
self->buf->len);
g_debug ("%s: %s [%li]", uri_str, str, self->status_code);
}
/* check result */
if (res != CURLE_OK) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to request %s: %s",
uri_str, curl_easy_strerror (res));
return FALSE;
}
/* load JSON */
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON) {
if (!fu_redfish_request_load_json (self, self->buf, error)) {
g_prefix_error (error,
"failed to parse %s: ",
uri_str);
return FALSE;
}
}
/* save to cache */
if (self->cache != NULL && path != NULL) {
g_hash_table_insert (self->cache,
g_strdup (path),
g_byte_array_ref (self->buf));
}
/* success */
return TRUE;
}
static size_t
fu_redfish_request_write_cb (char *ptr, size_t size, size_t nmemb, void *userdata)
{
GByteArray *buf = (GByteArray *) userdata;
gsize realsize = size * nmemb;
g_byte_array_append (buf, (const guint8 *) ptr, realsize);
return realsize;
}
void
fu_redfish_request_set_cache (FuRedfishRequest *self, GHashTable *cache)
{
g_return_if_fail (FU_IS_REDFISH_REQUEST (self));
g_return_if_fail (cache != NULL);
g_return_if_fail (self->cache == NULL);
self->cache = g_hash_table_ref (cache);
}
void
fu_redfish_request_set_curlsh (FuRedfishRequest *self, CURLSH *curlsh)
{
g_return_if_fail (FU_IS_REDFISH_REQUEST (self));
g_return_if_fail (curlsh != NULL);
curl_easy_setopt (self->curl, CURLOPT_SHARE, curlsh);
}
static void
fu_redfish_request_init (FuRedfishRequest *self)
{
self->curl = curl_easy_init ();
self->uri = curl_url ();
self->buf = g_byte_array_new ();
self->json_parser = json_parser_new ();
curl_easy_setopt (self->curl, CURLOPT_WRITEFUNCTION, fu_redfish_request_write_cb);
curl_easy_setopt (self->curl, CURLOPT_WRITEDATA, self->buf);
}
static void
fu_redfish_request_finalize (GObject *object)
{
FuRedfishRequest *self = FU_REDFISH_REQUEST (object);
if (self->cache != NULL)
g_hash_table_unref (self->cache);
g_object_unref (self->json_parser);
g_byte_array_unref (self->buf);
curl_easy_cleanup (self->curl);
curl_url_cleanup (self->uri);
G_OBJECT_CLASS (fu_redfish_request_parent_class)->finalize (object);
}
static void
fu_redfish_request_class_init (FuRedfishRequestClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fu_redfish_request_finalize;
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#include <curl/curl.h>
#ifndef HAVE_LIBCURL_7_62_0
#error libcurl >= 7.62.0 required
#endif
#define FU_TYPE_REDFISH_REQUEST (fu_redfish_request_get_type ())
G_DECLARE_FINAL_TYPE (FuRedfishRequest, fu_redfish_request, FU, REDFISH_REQUEST, GObject)
typedef enum {
FU_REDFISH_REQUEST_PERFORM_FLAG_NONE = 0,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON = 1 << 0,
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE = 1 << 1,
} FuRedfishRequestPerformFlags;
gboolean fu_redfish_request_perform (FuRedfishRequest *self,
const gchar *path,
FuRedfishRequestPerformFlags flags,
GError **error);
JsonObject *fu_redfish_request_get_json_object (FuRedfishRequest *self);
CURL *fu_redfish_request_get_curl (FuRedfishRequest *self);
void fu_redfish_request_set_curlsh (FuRedfishRequest *self,
CURLSH *curlsh);
CURLU *fu_redfish_request_get_uri (FuRedfishRequest *self);
glong fu_redfish_request_get_status_code (FuRedfishRequest *self);
void fu_redfish_request_set_cache (FuRedfishRequest *self,
GHashTable *cache);

View File

@ -8,9 +8,51 @@
#include <fwupd.h>
#include "fu-context-private.h"
#include "fu-device-private.h"
#include "fu-plugin-private.h"
#include "fu-redfish-common.h"
#include "fu-redfish-network.h"
typedef struct {
FuPlugin *plugin;
} FuTest;
static void
fu_test_self_init (FuTest *self)
{
gboolean ret;
g_autoptr(FuContext) ctx = fu_context_new ();
g_autoptr(GError) error = NULL;
g_autofree gchar *pluginfn = NULL;
ret = fu_context_load_quirks (ctx,
FU_QUIRKS_LOAD_FLAG_NO_CACHE |
FU_QUIRKS_LOAD_FLAG_NO_VERIFY,
&error);
g_assert_no_error (error);
g_assert (ret);
self->plugin = fu_plugin_new (ctx);
pluginfn = g_build_filename (PLUGINBUILDDIR,
"libfu_plugin_redfish." G_MODULE_SUFFIX,
NULL);
ret = fu_plugin_open (self->plugin, pluginfn, &error);
g_assert_no_error (error);
g_assert (ret);
ret = fu_plugin_runner_startup (self->plugin, &error);
if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) {
g_test_skip ("no redfish.py running");
return;
}
g_assert_no_error (error);
g_assert (ret);
ret = fu_plugin_runner_coldplug (self->plugin, &error);
g_assert_no_error (error);
g_assert (ret);
}
static void
fu_test_redfish_common_func (void)
{
@ -28,6 +70,55 @@ fu_test_redfish_common_func (void)
g_assert_cmpstr (maca, ==, "00:01:02:03:04:05");
}
static void
fu_test_redfish_common_version_func (void)
{
struct {
const gchar *in;
const gchar *op;
} strs[] = {
{ "1.2.3", "1.2.3" },
{ "P50 v1.2.3 PROD", "1.2.3" },
{ "P50 1.2.3 DEV", "1.2.3" },
{ NULL, NULL }
};
for (guint i = 0; strs[i].in != NULL; i++) {
g_autofree gchar *tmp = fu_redfish_common_fix_version (strs[i].in);
g_assert_cmpstr (tmp, ==, strs[i].op);
}
}
static void
fu_test_redfish_common_lenovo_func (void)
{
struct {
const gchar *in;
gboolean ret;
const gchar *build;
const gchar *version;
} values[] = {
{ "11A-1.02", TRUE, "11A", "1.02" },
{ "11A-0.00", TRUE, "11A", "0.00" },
{ "99Z-9.99", TRUE, "99Z", "9.99" },
{ "9-9-9.99", FALSE, NULL, NULL },
{ "999-9.99", FALSE, NULL, NULL },
{ "ACB-9.99", FALSE, NULL, NULL },
{ NULL, FALSE, NULL, NULL }
};
for (guint i = 0; values[i].in != NULL; i++) {
gboolean ret;
g_autofree gchar *build = NULL;
g_autofree gchar *version = NULL;
ret = fu_redfish_common_parse_version_lenovo (values[i].in,
&build,
&version,
NULL);
g_assert_cmpint (ret, ==, values[i].ret);
g_assert_cmpstr (build, ==, values[i].build);
g_assert_cmpstr (version, ==, values[i].version);
}
}
static void
fu_test_redfish_network_mac_addr_func (void)
{
@ -60,15 +151,127 @@ fu_test_redfish_network_vid_pid_func (void)
g_assert_nonnull (ip_addr);
}
static void
fu_test_redfish_devices_func (gconstpointer user_data)
{
FuDevice *dev;
FuTest *self = (FuTest *) user_data;
GPtrArray *devices;
g_autofree gchar *devstr0 = NULL;
g_autofree gchar *devstr1 = NULL;
devices = fu_plugin_get_devices (self->plugin);
g_assert_nonnull (devices);
if (devices->len == 0) {
g_test_skip ("no redfish support");
return;
}
g_assert_cmpint (devices->len, ==, 2);
/* BMC */
dev = g_ptr_array_index (devices, 1);
devstr0 = fu_device_to_string (dev);
g_debug ("%s", devstr0);
g_assert_cmpstr (fu_device_get_id (dev), ==, "62c1cd95692c5225826cf8568a460427ea3b1827");
g_assert_cmpstr (fu_device_get_name (dev), ==, "BMC Firmware");
g_assert_cmpstr (fu_device_get_vendor (dev), ==, "Lenovo");
g_assert_cmpstr (fu_device_get_version (dev), ==, "1.02");
g_assert_cmpstr (fu_device_get_version_lowest (dev), ==, "0.12");
g_assert_cmpint (fu_device_get_version_format (dev), ==, FWUPD_VERSION_FORMAT_PAIR);
g_assert_true (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE));
g_assert_true (fu_device_has_protocol (dev, "org.dmtf.redfish"));
g_assert_true (fu_device_has_guid (dev, "REDFISH\\VENDOR_Lenovo&SOFTWAREID_UEFI-AFE1-6&TYPE_UNSIGNED"));
g_assert_true (fu_device_has_vendor_id (dev, "REDFISH:LENOVO"));
/* BIOS */
dev = g_ptr_array_index (devices, 0);
devstr1 = fu_device_to_string (dev);
g_debug ("%s", devstr1);
g_assert_cmpstr (fu_device_get_id (dev), ==, "562313e34c756a05a2e878861377765582bbf971");
g_assert_cmpstr (fu_device_get_name (dev), ==, "BIOS Firmware");
g_assert_cmpstr (fu_device_get_vendor (dev), ==, "Contoso");
g_assert_cmpstr (fu_device_get_version (dev), ==, "1.45");
g_assert_cmpstr (fu_device_get_serial (dev), ==, "12345");
g_assert_cmpstr (fu_device_get_version_lowest (dev), ==, "1.10");
g_assert_cmpint (fu_device_get_version_format (dev), ==, FWUPD_VERSION_FORMAT_PAIR);
g_assert_true (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE));
g_assert_true (fu_device_has_icon (dev, "network-wired"));
g_assert_true (fu_device_has_protocol (dev, "org.dmtf.redfish"));
g_assert_true (fu_device_has_guid (dev, "fee82a67-6ce2-4625-9f44-237ad2402c28"));
g_assert_true (fu_device_has_guid (dev, "a6d3294e-37e5-50aa-ae2f-c0c457af16f3"));
g_assert_true (fu_device_has_vendor_id (dev, "REDFISH:CONTOSO"));
}
static void
fu_test_redfish_update_func (gconstpointer user_data)
{
FuDevice *dev;
FuTest *self = (FuTest *) user_data;
GPtrArray *devices;
gboolean ret;
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) blob_fw = NULL;
devices = fu_plugin_get_devices (self->plugin);
g_assert_nonnull (devices);
if (devices->len == 0) {
g_test_skip ("no redfish support");
return;
}
g_assert_cmpint (devices->len, ==, 2);
/* BMC */
dev = g_ptr_array_index (devices, 1);
blob_fw = g_bytes_new_static ("hello", 5);
ret = fu_plugin_runner_update (self->plugin, dev, blob_fw,
FWUPD_INSTALL_FLAG_NONE, &error);
g_assert_no_error (error);
g_assert_true (ret);
g_assert_true (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT));
/* try again */
ret = fu_plugin_runner_update (self->plugin, dev, blob_fw,
FWUPD_INSTALL_FLAG_NONE, &error);
g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE);
g_assert_false (ret);
}
static void
fu_test_self_free (FuTest *self)
{
if (self->plugin != NULL)
g_object_unref (self->plugin);
g_free (self);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuTest, fu_test_self_free)
#pragma clang diagnostic pop
int
main (int argc, char **argv)
{
g_autoptr(GError) error = NULL;
g_autoptr(FuTest) self = g_new0 (FuTest, 1);
g_autofree gchar *smbios_data_fn = NULL;
g_setenv ("FWUPD_REDFISH_VERBOSE", "1", TRUE);
smbios_data_fn = g_build_filename (TESTDATADIR, "redfish-smbios.bin", NULL);
g_setenv ("FWUPD_REDFISH_SMBIOS_DATA", smbios_data_fn, TRUE);
g_setenv ("FWUPD_SYSFSFWDIR", TESTDATADIR, TRUE);
g_setenv ("CONFIGURATION_DIRECTORY", TESTDATADIR, TRUE);
g_test_init (&argc, &argv, NULL);
g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
fu_test_self_init (self);
g_test_add_func ("/redfish/common", fu_test_redfish_common_func);
g_test_add_func ("/redfish/common{version}", fu_test_redfish_common_version_func);
g_test_add_func ("/redfish/common{lenovo}", fu_test_redfish_common_lenovo_func);
g_test_add_func ("/redfish/network{mac_addr}", fu_test_redfish_network_mac_addr_func);
g_test_add_func ("/redfish/network{vid_pid}", fu_test_redfish_network_vid_pid_func);
g_test_add_data_func ("/redfish/plugin{devices}", self, fu_test_redfish_devices_func);
g_test_add_data_func ("/redfish/plugin{update}", self, fu_test_redfish_update_func);
return g_test_run ();
}

View File

@ -4,13 +4,21 @@ if not get_option('curl')
endif
cargs = ['-DG_LOG_DOMAIN="FuPluginRedfish"']
install_data(['redfish.quirk'],
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
)
shared_module('fu_plugin_redfish',
fu_hash,
sources : [
'fu-plugin-redfish.c',
'fu-redfish-backend.c',
'fu-redfish-common.c', # fuzzing
'fu-redfish-device.c',
'fu-redfish-legacy-device.c',
'fu-redfish-multipart-device.c',
'fu-redfish-network.c',
'fu-redfish-request.c',
'fu-redfish-smbios.c', # fuzzing
],
include_directories : [
@ -36,6 +44,13 @@ install_data(['redfish.conf'],
)
if get_option('tests')
testdatadir = join_paths(meson.current_source_dir(), 'tests')
testdatadirs = environment()
testdatadirs.set('G_TEST_SRCDIR', meson.current_source_dir())
testdatadirs.set('G_TEST_BUILDDIR', meson.current_build_dir())
testdatadirs.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var')
cargs += '-DTESTDATADIR="' + testdatadir + '"'
cargs += '-DPLUGINBUILDDIR="' + meson.current_build_dir() + '"'
e = executable(
'redfish-self-test',
fu_hash,
@ -43,7 +58,11 @@ if get_option('tests')
'fu-self-test.c',
'fu-redfish-backend.c',
'fu-redfish-common.c',
'fu-redfish-device.c',
'fu-redfish-legacy-device.c',
'fu-redfish-multipart-device.c',
'fu-redfish-network.c',
'fu-redfish-request.c',
'fu-redfish-smbios.c',
],
include_directories : [

View File

@ -0,0 +1,16 @@
[REDFISH\VENDOR_Lenovo&ID_BMC-Backup]
ParentGuid = REDFISH\VENDOR_Lenovo&ID_BMC-Primary
Flags = dual-image,is-backup
[REDFISH\VENDOR_Lenovo&ID_BMC-Primary]
ParentGuid = REDFISH\VENDOR_Lenovo&ID_BMC-Primary
Flags = dual-image
[REDFISH\VENDOR_Lenovo&ID_LXPM]
Flags = ~updatable
[REDFISH\VENDOR_Lenovo&ID_LXPMLinuxDriver]
Flags = ~updatable
ParentGuid = REDFISH\VENDOR_Lenovo&ID_LXPM
[REDFISH\VENDOR_Lenovo&ID_LXPMWindowsDriver]
Flags = ~updatable
ParentGuid = REDFISH\VENDOR_Lenovo&ID_LXPM

View File

@ -0,0 +1 @@
../../../data/daemon.conf

View File

@ -0,0 +1 @@
../../../src/fuzzing/firmware/redfish-smbios.bin

View File

@ -0,0 +1,4 @@
[redfish]
Uri=http://localhost:4661
Username=username2
Password=password2

269
plugins/redfish/tests/redfish.py Executable file
View File

@ -0,0 +1,269 @@
#!/usr/bin/python3
# pylint: disable=invalid-name,missing-docstring
#
# Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
#
# SPDX-License-Identifier: LGPL-2.1+
import json
from flask import Flask, Response, request, g
app = Flask(__name__)
HARDCODED_USERNAME = "username2"
HARDCODED_PASSWORD = "password2"
app._percentage: int = 0
def _failure(msg: str, status=400):
res = {
"error": {"message": msg},
}
return Response(response=json.dumps(msg), status=401, mimetype="application/json")
@app.route("/redfish/v1/")
def index():
# reset counter
app._percentage = 0
# check password from the config file
try:
if (
request.authorization["username"] != HARDCODED_USERNAME
or request.authorization["password"] != HARDCODED_PASSWORD
):
return _failure("unauthorised", status=401)
except (KeyError, TypeError):
return _failure("invalid")
res = {
"@odata.id": "/redfish/v1/",
"RedfishVersion": "1.6.0",
"UUID": "92384634-2938-2342-8820-489239905423",
"UpdateService": {"@odata.id": "/redfish/v1/UpdateService"},
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/UpdateService")
def update_service():
res = {
"@odata.id": "/redfish/v1/UpdateService",
"@odata.type": "#UpdateService.v1_8_0.UpdateService",
"FirmwareInventory": {
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory"
},
"MultipartHttpPushUri": "/FWUpdate",
"HttpPushUri": "/FWUpdate",
"HttpPushUriOptions": {
"HttpPushUriApplyTime": {
"ApplyTime": "Immediate",
}
},
"HttpPushUriOptionsBusy": False,
"ServiceEnabled": True,
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/UpdateService/FirmwareInventory")
def firmware_inventory():
res = {
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
"@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
"Members": [
{"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BMC"},
{"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BIOS"},
],
"Members@odata.count": 2,
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/UpdateService/FirmwareInventory/BMC")
def firmware_inventory_bmc():
res = {
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BMC",
"@odata.type": "#SoftwareInventory.v1_2_3.SoftwareInventory",
"Id": "BMC",
"LowestSupportedVersion": "11A-0.12",
"Manufacturer": "Lenovo",
"Name": "Lenovo BMC Firmware",
"RelatedItem": [{"@odata.id": "/redfish/v1/Managers/BMC"}],
"ReleaseDate": "2017-08-22T12:00:00",
"SoftwareId": "UEFI-AFE1-6",
"UefiDevicePaths": ["BMC(0x1,0x0ABCDEF)"],
"Updateable": True,
"Version": "11A-1.02",
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/Managers/BMC")
def redfish_managers_bmc():
res = {}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions/slot_2.00")
def firmware_chassis_pcie_function_slot2():
res = {
"VendorId": "0x14e4",
"FunctionId": 1,
"SubsystemId": "0x4042",
"DeviceClass": "NetworkController",
"SubsystemVendorId": "0x17aa",
"DeviceId": "0x165f",
"RevisionId": "0x00",
"ClassCode": "0x020000",
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions")
def firmware_chassis_pcie_functions():
res = {
"Members": [
{
"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions/slot_2.00"
}
],
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/Systems/437XR1138R2")
def firmware_systems_slot7():
res = {
"SerialNumber": "12345",
"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3",
"PCIeFunctions": {
"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions"
},
"DeviceType": "SingleFunction",
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/UpdateService/FirmwareInventory/BIOS")
def firmware_inventory_bios():
res = {
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BIOS",
"@odata.type": "#SoftwareInventory.v1_2_3.SoftwareInventory",
"Id": "BIOS",
"LowestSupportedVersion": "P79 v1.10",
"Manufacturer": "Contoso",
"Name": "Contoso BIOS Firmware",
"RelatedItem": [{"@odata.id": "/redfish/v1/Systems/437XR1138R2"}],
"ReleaseDate": "2017-12-06T12:00:00",
"SoftwareId": "FEE82A67-6CE2-4625-9F44-237AD2402C28",
"Updateable": True,
"Version": "P79 v1.45",
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/TaskService/999")
def task_manager():
res = {
"@odata.id": "/redfish/v1/TaskService/999",
"@odata.type": "#Task.v1_4_3.Task",
"Id": "545",
"Name": "Task 545",
}
return Response(json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/TaskService/Tasks/545")
def task_status():
res = {
"@odata.id": "/redfish/v1/TaskService/Tasks/545",
"@odata.type": "#Task.v1_4_3.Task",
"Id": "545",
"Name": "Task 545",
"PercentComplete": app._percentage,
}
if app._percentage == 0:
res["TaskState"] = "Running"
elif app._percentage in [25, 50, 75]:
res["TaskState"] = "Running"
res["TaskStatus"] = "OK"
res["Messages"] = [
{
"Message": "Applying image",
"MessageId": "Update.1.1.TransferringToComponent",
}
]
elif app._percentage == 100:
res["TaskState"] = "Completed"
res["TaskStatus"] = "OK"
res["Messages"] = [
{
"Message": "Applying image",
"MessageId": "Update.1.1.TransferringToComponent",
},
{
"Message": "A reset is required",
"MessageId": "Base.1.10.ResetRequired",
},
{
"Message": "Task completed OK",
"MessageId": "TaskEvent.1.0.TaskCompletedOK",
},
]
else:
res["TaskState"] = "Exception"
res["TaskStatus"] = "Warning"
res["Messages"] = [
{
"Message": "Error verifying image",
"MessageId": "Update.1.0.ApplyFailed",
"Severity": "Warning",
}
]
app._percentage += 25
return Response(response=json.dumps(res), status=200, mimetype="application/json")
@app.route("/FWUpdate", methods=["POST"])
def fwupdate():
data = json.loads(request.form["UpdateParameters"])
if data["@Redfish.OperationApplyTime"] != "Immediate":
return _failure("apply invalid")
if data["Targets"][0] != "/redfish/v1/UpdateService/FirmwareInventory/BMC":
return _failure("id invalid")
fileitem = request.files["UpdateFile"]
if not fileitem.filename.endswith(".bin"):
return _failure("filename invalid")
if fileitem.read().decode() != "hello":
return _failure("data invalid")
res = {
"Version": "P79 v1.45",
"@odata.id": "/redfish/v1/TaskService/Tasks/545",
"TaskMonitor": "/redfish/v1/TaskService/999",
}
# Location set to the URI of a task monitor.
return Response(
json.dumps(res),
status=202,
mimetype="application/json",
headers={"Location": "http://localhost:4661/redfish/v1/TaskService/Tasks/545"},
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=4661)