redfish: Implement updates for Supermicro machines

This commit is contained in:
Kai Michaelis 2022-10-06 13:49:49 +02:00 committed by GitHub
parent f579c03223
commit 6054d09f35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 712 additions and 21 deletions

View File

@ -360,6 +360,8 @@ fwupd_device_problem_to_string(FwupdDeviceProblem device_problem)
return "lid-is-closed";
if (device_problem == FWUPD_DEVICE_PROBLEM_IS_EMULATED)
return "is-emulated";
if (device_problem == FWUPD_DEVICE_PROBLEM_MISSING_LICENSE)
return "missing-license";
if (device_problem == FWUPD_DEVICE_PROBLEM_UNKNOWN)
return "unknown";
return NULL;
@ -394,6 +396,8 @@ fwupd_device_problem_from_string(const gchar *device_problem)
return FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED;
if (g_strcmp0(device_problem, "is-emulated") == 0)
return FWUPD_DEVICE_PROBLEM_IS_EMULATED;
if (g_strcmp0(device_problem, "missing-license") == 0)
return FWUPD_DEVICE_PROBLEM_MISSING_LICENSE;
return FWUPD_DEVICE_PROBLEM_UNKNOWN;
}

View File

@ -607,6 +607,14 @@ typedef guint64 FwupdDeviceFlags;
* Since 1.8.3
*/
#define FWUPD_DEVICE_PROBLEM_IS_EMULATED (1u << 6)
/**
* FWUPD_DEVICE_PROBLEM_MISSING_LICENSE:
*
* The device cannot be updated due to missing vendor's license.
*
* Since 1.8.6
*/
#define FWUPD_DEVICE_PROBLEM_MISSING_LICENSE (1u << 7)
/**
* FWUPD_DEVICE_PROBLEM_UNKNOWN:
*

View File

@ -2827,6 +2827,8 @@ fu_device_problem_to_inhibit_reason(FuDevice *self, guint64 device_problem)
return g_strdup("Device cannot be used while the lid is closed");
if (device_problem == FWUPD_DEVICE_PROBLEM_IS_EMULATED)
return g_strdup("Device is emulated");
if (device_problem == FWUPD_DEVICE_PROBLEM_MISSING_LICENSE)
return g_strdup("Device does not have the necessary license installed");
if (device_problem == FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW) {
if (priv->ctx == NULL)
return g_strdup("System power is too low to perform the update");

View File

@ -16,6 +16,7 @@
#include "fu-redfish-multipart-device.h"
#include "fu-redfish-request.h"
#include "fu-redfish-smbios.h"
#include "fu-redfish-smc-device.h"
struct _FuRedfishBackend {
FuBackend parent_instance;
@ -221,6 +222,24 @@ fu_redfish_backend_set_push_uri_path(FuRedfishBackend *self, const gchar *push_u
self->push_uri_path = g_strdup(push_uri_path);
}
static gboolean
fu_redfish_backend_has_smc_update_path(JsonObject *update_svc)
{
JsonObject *tmp_obj;
const gchar *tmp_str;
if (!json_object_has_member(update_svc, "Actions"))
return FALSE;
tmp_obj = json_object_get_object_member(update_svc, "Actions");
if (tmp_obj == NULL || !json_object_has_member(tmp_obj, "#UpdateService.StartUpdate"))
return FALSE;
tmp_obj = json_object_get_object_member(tmp_obj, "#UpdateService.StartUpdate");
if (tmp_obj == NULL || !json_object_has_member(tmp_obj, "target"))
return FALSE;
tmp_str = json_object_get_string_member(tmp_obj, "target");
return g_str_equal(tmp_str, "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate");
}
static gboolean
fu_redfish_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **error)
{
@ -250,10 +269,15 @@ fu_redfish_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **e
return FALSE;
}
}
if (json_object_has_member(json_obj, "MultipartHttpPushUri")) {
if (self->push_uri_path == NULL &&
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;
if (fu_redfish_backend_has_smc_update_path(json_obj)) {
self->device_gtype = FU_TYPE_REDFISH_SMC_DEVICE;
} else {
self->device_gtype = FU_TYPE_REDFISH_MULTIPART_DEVICE;
}
fu_redfish_backend_set_push_uri_path(self, tmp);
}
}

View File

@ -360,6 +360,26 @@ fu_redfish_device_set_vendor(FuRedfishDevice *self, const gchar *vendor)
fu_device_add_vendor_id(FU_DEVICE(self), vendor_id);
}
static void
fu_redfish_backend_smc_license_check(FuDevice *device)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE(device);
FuRedfishBackend *backend = fu_redfish_device_get_backend(self);
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(backend);
g_autoptr(GError) error_local = NULL;
/* see if we don't get an license error */
if (!fu_redfish_request_perform(request,
fu_redfish_backend_get_push_uri_path(backend),
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
&error_local)) {
if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED))
fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_MISSING_LICENSE);
else
g_debug("supermicro license check returned %s\n", error_local->message);
}
}
static gboolean
fu_redfish_device_probe(FuDevice *dev, GError **error)
{
@ -504,6 +524,10 @@ fu_redfish_device_probe(FuDevice *dev, GError **error)
}
}
/* for Supermicro check whether we have a proper Redfish license installed */
if (g_strcmp0("SMCI", fu_device_get_vendor(dev)) == 0)
fu_redfish_backend_smc_license_check(dev);
/* success */
return TRUE;
}

View File

@ -126,6 +126,15 @@ fu_redfish_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **erro
return TRUE;
}
void
fu_redfish_plugin_set_credentials(FuPlugin *plugin, const gchar *username, const gchar *password)
{
FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin);
fu_redfish_backend_set_username(self->backend, username);
fu_redfish_backend_set_password(self->backend, password);
}
static gboolean
fu_redfish_plugin_discover_uefi_credentials(FuPlugin *plugin, GError **error)
{

View File

@ -9,3 +9,6 @@
#include <fwupdplugin.h>
G_DECLARE_FINAL_TYPE(FuRedfishPlugin, fu_redfish_plugin, FU, REDFISH_PLUGIN, FuPlugin)
void
fu_redfish_plugin_set_credentials(FuPlugin *plugin, const gchar *username, const gchar *password);

View File

@ -140,6 +140,12 @@ fu_redfish_request_load_json(FuRedfishRequest *self, GByteArray *buf, GError **e
error_code = FWUPD_ERROR_AUTH_FAILED;
else if (g_strcmp0(id, "Base.1.8.PasswordChangeRequired") == 0)
error_code = FWUPD_ERROR_AUTH_EXPIRED;
else if (g_strcmp0(id, "SMC.1.0.OemLicenseNotPassed") == 0)
error_code = FWUPD_ERROR_NOT_SUPPORTED;
else if (g_strcmp0(id, "SMC.1.0.OemFirmwareAlreadyInUpdateMode") == 0)
error_code = FWUPD_ERROR_ALREADY_PENDING;
else if (g_strcmp0(id, "SMC.1.0.OemBiosUpdateIsInProgress") == 0)
error_code = FWUPD_ERROR_ALREADY_PENDING;
g_set_error_literal(error, FWUPD_ERROR, error_code, msg);
return FALSE;
}

View File

@ -0,0 +1,242 @@
/*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-redfish-backend.h"
#include "fu-redfish-common.h"
#include "fu-redfish-smc-device.h"
struct _FuRedfishSmcDevice {
FuRedfishDevice parent_instance;
};
G_DEFINE_TYPE(FuRedfishSmcDevice, fu_redfish_smc_device, FU_TYPE_REDFISH_DEVICE)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curl_mime, curl_mime_free)
static const gchar *
fu_redfish_smc_device_get_task(JsonObject *json_obj)
{
JsonObject *tmp_obj;
JsonArray *tmp_ary;
const gchar *msgid;
if (!json_object_has_member(json_obj, "Accepted"))
return NULL;
tmp_obj = json_object_get_object_member(json_obj, "Accepted");
if (tmp_obj == NULL || !json_object_has_member(tmp_obj, "@Message.ExtendedInfo"))
return NULL;
tmp_ary = json_object_get_array_member(tmp_obj, "@Message.ExtendedInfo");
if (tmp_ary == NULL || json_array_get_length(tmp_ary) != 1)
return NULL;
tmp_obj = json_array_get_object_element(tmp_ary, 0);
if (tmp_obj == NULL)
return NULL;
msgid = json_object_get_string_member(tmp_obj, "MessageId");
if (g_strcmp0(msgid, "SMC.1.0.OemSimpleupdateAcceptedMessage") != 0)
return NULL;
tmp_ary = json_object_get_array_member(tmp_obj, "MessageArgs");
if (tmp_ary == NULL)
return NULL;
if (json_array_get_length(tmp_ary) != 1)
return NULL;
return json_array_get_string_element(tmp_ary, 0);
}
static GString *
fu_redfish_smc_device_get_parameters(FuRedfishSmcDevice *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 */
/* https://supermicro.com/manuals/other/RedishRefGuide.pdf */
json_builder_begin_object(builder);
json_builder_set_member_name(builder, "Targets");
json_builder_begin_array(builder);
json_builder_add_string_value(builder, "/redfish/v1/Systems/1/Bios");
json_builder_end_array(builder);
json_builder_set_member_name(builder, "@Redfish.OperationApplyTime");
json_builder_add_string_value(builder, "OnStartUpdateRequest");
json_builder_set_member_name(builder, "Oem");
json_builder_begin_object(builder);
json_builder_set_member_name(builder, "Supermicro");
json_builder_begin_object(builder);
json_builder_set_member_name(builder, "BIOS");
json_builder_begin_object(builder);
json_builder_set_member_name(builder, "PreserveME");
json_builder_add_boolean_value(builder, TRUE);
json_builder_set_member_name(builder, "PreserveNVRAM");
json_builder_add_boolean_value(builder, TRUE);
json_builder_set_member_name(builder, "PreserveSMBIOS");
json_builder_add_boolean_value(builder, TRUE);
json_builder_set_member_name(builder, "BackupBIOS");
json_builder_add_boolean_value(builder, FALSE);
json_builder_end_object(builder);
json_builder_end_object(builder);
json_builder_end_object(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);
return g_steal_pointer(&str);
}
static gboolean
fu_redfish_smc_device_start_update(FuDevice *device, FuProgress *progress, GError **error)
{
FuRedfishBackend *backend = fu_redfish_device_get_backend(FU_REDFISH_DEVICE(device));
JsonObject *json_obj;
CURL *curl;
const gchar *location = NULL;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(backend);
curl = fu_redfish_request_get_curl(request);
(void)curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
if (!fu_redfish_request_perform(
request,
"/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate",
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error)) {
if (g_error_matches(*error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_PENDING);
}
return FALSE;
}
json_obj = fu_redfish_request_get_json_object(request);
location = fu_redfish_smc_device_get_task(json_obj);
if (location == NULL) {
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;
}
return fu_redfish_device_poll_task(FU_REDFISH_DEVICE(device), location, progress, error);
}
static gboolean
fu_redfish_smc_device_write_firmware(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuRedfishSmcDevice *self = FU_REDFISH_SMC_DEVICE(device);
FuRedfishBackend *backend = fu_redfish_device_get_backend(FU_REDFISH_DEVICE(self));
CURL *curl;
JsonObject *json_obj;
curl_mimepart *part;
const gchar *location = NULL;
gboolean ret;
g_autoptr(curl_mime) mime = NULL;
g_autoptr(FuRedfishRequest) request = NULL;
g_autoptr(GBytes) fw = NULL;
g_autoptr(GString) params = NULL;
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50, "apply");
/* get default image */
fw = fu_firmware_get_bytes(firmware, error);
if (fw == NULL)
return FALSE;
/* create the multipart for uploading the image request */
request = fu_redfish_backend_request_new(backend);
curl = fu_redfish_request_get_curl(request);
mime = curl_mime_init(curl);
(void)curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
params = fu_redfish_smc_device_get_parameters(self);
part = curl_mime_addpart(mime);
curl_mime_name(part, "UpdateParameters");
(void)curl_mime_type(part, "application/json");
(void)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");
(void)curl_mime_type(part, "application/octet-stream");
(void)curl_mime_filedata(part, "firmware.bin");
(void)curl_mime_data(part, g_bytes_get_data(fw, NULL), g_bytes_get_size(fw));
fu_progress_set_status(progress, 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)) {
if (g_error_matches(*error, FWUPD_ERROR, FWUPD_ERROR_ALREADY_PENDING)) {
fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_PENDING);
}
return FALSE;
}
if (fu_redfish_request_get_status_code(request) != 202) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to upload: %li",
fu_redfish_request_get_status_code(request));
return FALSE;
}
json_obj = fu_redfish_request_get_json_object(request);
/* poll the verify task for progress */
location = fu_redfish_smc_device_get_task(json_obj);
if (location == NULL) {
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;
}
if (!fu_redfish_device_poll_task(FU_REDFISH_DEVICE(self),
location,
fu_progress_get_child(progress),
error))
return FALSE;
fu_progress_step_done(progress);
ret = fu_redfish_smc_device_start_update(device, fu_progress_get_child(progress), error);
fu_progress_step_done(progress);
return ret;
}
static void
fu_redfish_smc_device_set_progress(FuDevice *self, FuProgress *progress)
{
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload");
}
static void
fu_redfish_smc_device_init(FuRedfishSmcDevice *self)
{
fu_device_set_summary(FU_DEVICE(self), "Redfish Supermicro device");
}
static void
fu_redfish_smc_device_class_init(FuRedfishSmcDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->write_firmware = fu_redfish_smc_device_write_firmware;
klass_device->set_progress = fu_redfish_smc_device_set_progress;
}

View File

@ -0,0 +1,21 @@
/*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#include "fu-redfish-backend.h"
#include "fu-redfish-device.h"
#define FU_TYPE_REDFISH_SMC_DEVICE (fu_redfish_smc_device_get_type())
G_DECLARE_FINAL_TYPE(FuRedfishSmcDevice,
fu_redfish_smc_device,
FU,
REDFISH_SMC_DEVICE,
FuRedfishDevice)
struct _FuRedfishSmcDeviceClass {
FuRedfishDeviceClass parent_class;
};

View File

@ -17,9 +17,12 @@
#include "fu-redfish-common.h"
#include "fu-redfish-network.h"
#include "fu-redfish-plugin.h"
#include "fu-redfish-smc-device.h"
typedef struct {
FuPlugin *plugin;
FuPlugin *smc_plugin;
FuPlugin *unlicensed_plugin;
} FuTest;
static void
@ -36,17 +39,50 @@ fu_test_self_init(FuTest *self)
g_assert_no_error(error);
g_assert_true(ret);
/* generic BMC */
self->plugin = fu_plugin_new_from_gtype(fu_redfish_plugin_get_type(), ctx);
ret = fu_plugin_runner_startup(self->plugin, progress, &error);
if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) {
g_test_skip("no redfish.py running");
return;
g_clear_error(&error);
} else {
g_assert_no_error(error);
g_assert_true(ret);
ret = fu_plugin_runner_coldplug(self->plugin, progress, &error);
g_assert_no_error(error);
g_assert_true(ret);
}
/* supermicro BMC */
self->smc_plugin = fu_plugin_new_from_gtype(fu_redfish_plugin_get_type(), ctx);
ret = fu_plugin_runner_startup(self->smc_plugin, progress, &error);
if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) {
g_test_skip("no redfish.py running");
g_clear_error(&error);
} else {
g_assert_no_error(error);
g_assert_true(ret);
fu_redfish_plugin_set_credentials(self->smc_plugin, "smc_username", "password2");
ret = fu_plugin_runner_coldplug(self->smc_plugin, progress, &error);
g_assert_no_error(error);
g_assert_true(ret);
}
/* unlicensed supermicro BMC */
self->unlicensed_plugin = fu_plugin_new_from_gtype(fu_redfish_plugin_get_type(), ctx);
ret = fu_plugin_runner_startup(self->unlicensed_plugin, progress, &error);
if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) {
g_test_skip("no redfish.py running");
} else {
g_assert_no_error(error);
g_assert_true(ret);
fu_redfish_plugin_set_credentials(self->unlicensed_plugin,
"unlicensed_username",
"password2");
ret = fu_plugin_runner_coldplug(self->unlicensed_plugin, progress, &error);
g_assert_no_error(error);
g_assert_true(ret);
}
g_assert_no_error(error);
g_assert_true(ret);
ret = fu_plugin_runner_coldplug(self->plugin, progress, &error);
g_assert_no_error(error);
g_assert_true(ret);
}
static void
@ -288,6 +324,56 @@ fu_test_redfish_devices_func(gconstpointer user_data)
g_assert_true(fu_device_has_vendor_id(dev, "REDFISH:CONTOSO"));
}
static void
fu_test_redfish_unlicensed_devices_func(gconstpointer user_data)
{
FuDevice *dev;
FuTest *self = (FuTest *)user_data;
GPtrArray *devices;
devices = fu_plugin_get_devices(self->unlicensed_plugin);
g_assert_nonnull(devices);
if (devices->len == 0) {
g_test_skip("no redfish support");
return;
}
g_assert_cmpint(devices->len, ==, 2);
dev = g_ptr_array_index(devices, 0);
g_assert_true(FU_IS_REDFISH_SMC_DEVICE(dev));
g_assert_true(fu_device_has_inhibit(
dev,
fwupd_device_problem_to_string(FWUPD_DEVICE_PROBLEM_MISSING_LICENSE)));
dev = g_ptr_array_index(devices, 1);
g_assert_true(FU_IS_REDFISH_SMC_DEVICE(dev));
g_assert_true(fu_device_has_inhibit(
dev,
fwupd_device_problem_to_string(FWUPD_DEVICE_PROBLEM_MISSING_LICENSE)));
}
static void
fu_test_redfish_smc_devices_func(gconstpointer user_data)
{
FuDevice *dev;
FuTest *self = (FuTest *)user_data;
GPtrArray *devices;
devices = fu_plugin_get_devices(self->smc_plugin);
g_assert_nonnull(devices);
if (devices->len == 0) {
g_test_skip("no redfish support");
return;
}
g_assert_cmpint(devices->len, ==, 2);
dev = g_ptr_array_index(devices, 0);
g_assert_true(FU_IS_REDFISH_SMC_DEVICE(dev));
dev = g_ptr_array_index(devices, 1);
g_assert_true(FU_IS_REDFISH_SMC_DEVICE(dev));
}
static void
fu_test_redfish_update_func(gconstpointer user_data)
{
@ -331,6 +417,51 @@ fu_test_redfish_update_func(gconstpointer user_data)
g_assert_false(ret);
}
static void
fu_test_redfish_smc_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;
g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
devices = fu_plugin_get_devices(self->smc_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_write_firmware(self->plugin,
dev,
blob_fw,
progress,
FWUPD_INSTALL_FLAG_NO_SEARCH,
&error);
g_assert_no_error(error);
g_assert_true(ret);
/* stuck update */
blob_fw = g_bytes_new_static("stuck", 5);
ret = fu_plugin_runner_write_firmware(self->plugin,
dev,
blob_fw,
progress,
FWUPD_INSTALL_FLAG_NO_SEARCH,
&error);
g_assert_false(ret);
g_assert_true(fu_device_has_inhibit(
dev,
fwupd_device_problem_to_string(FWUPD_DEVICE_PROBLEM_UPDATE_PENDING)));
}
static void
fu_test_self_free(FuTest *self)
{
@ -370,6 +501,13 @@ main(int argc, char **argv)
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/unlicensed_plugin{devices}",
self,
fu_test_redfish_unlicensed_devices_func);
g_test_add_data_func("/redfish/smc_plugin{devices}",
self,
fu_test_redfish_smc_devices_func);
g_test_add_data_func("/redfish/smc_plugin{update}", self, fu_test_redfish_smc_update_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

@ -15,6 +15,7 @@ plugin_builtin_redfish = static_library('fu_plugin_redfish',
'fu-redfish-backend.c',
'fu-redfish-common.c', # fuzzing
'fu-redfish-device.c',
'fu-redfish-smc-device.c',
'fu-redfish-legacy-device.c',
'fu-redfish-multipart-device.c',
'fu-redfish-network.c',

View File

@ -19,3 +19,6 @@ ParentGuid = REDFISH\VENDOR_Lenovo&ID_LXPM
[REDFISH\VENDOR_Lenovo&ID_LXPMWindowsDriver]
Flags = ~updatable
ParentGuid = REDFISH\VENDOR_Lenovo&ID_LXPM
[REDFISH\VENDOR_SMCI&ID_Backup_BIOS]
Flags = is-backup,no-probe

View File

@ -11,10 +11,13 @@ from flask import Flask, Response, request
app = Flask(__name__)
HARDCODED_USERNAME = "username2"
HARDCODED_SMC_USERNAME = "smc_username"
HARDCODED_UNL_USERNAME = "unlicensed_username"
HARDCODED_USERNAMES = {"username2", HARDCODED_SMC_USERNAME, HARDCODED_UNL_USERNAME}
HARDCODED_PASSWORD = "password2"
app._percentage: int = 0
app._percentage545: int = 0
app._percentage546: int = 0
def _failure(msg: str, status=400):
@ -28,12 +31,13 @@ def _failure(msg: str, status=400):
def index():
# reset counter
app._percentage = 0
app._percentage545 = 0
app._percentage546 = 0
# check password from the config file
try:
if (
request.authorization["username"] != HARDCODED_USERNAME
not request.authorization["username"] in HARDCODED_USERNAMES
or request.authorization["password"] != HARDCODED_PASSWORD
):
return _failure("unauthorised", status=401)
@ -58,7 +62,6 @@ def update_service():
"FirmwareInventory": {
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory"
},
"MultipartHttpPushUri": "/FWUpdate",
"HttpPushUri": "/FWUpdate",
"HttpPushUriOptions": {
"HttpPushUriApplyTime": {
@ -68,6 +71,24 @@ def update_service():
"HttpPushUriOptionsBusy": False,
"ServiceEnabled": True,
}
if request.authorization["username"] == HARDCODED_UNL_USERNAME:
res["MultipartHttpPushUri"] = "/FWUpdate-unlicensed"
res["Actions"] = {
"#UpdateService.StartUpdate": {
"target": "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate"
}
}
elif request.authorization["username"] == HARDCODED_SMC_USERNAME:
res["MultipartHttpPushUri"] = "/FWUpdate-smc"
res["Actions"] = {
"#UpdateService.StartUpdate": {
"target": "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate"
}
}
else:
res["MultipartHttpPushUri"] = "/FWUpdate"
return Response(json.dumps(res), status=200, mimetype="application/json")
@ -94,7 +115,6 @@ def firmware_inventory_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"}],
"SoftwareId": "UEFI-AFE1-6",
@ -103,6 +123,13 @@ def firmware_inventory_bmc():
"Version": "11A-1.02",
"ReleaseDate": "2019-03-15T00:00:00",
}
if request.authorization["username"] in {
HARDCODED_UNL_USERNAME,
HARDCODED_SMC_USERNAME,
}:
res["Manufacturer"] = "SMCI"
else:
res["Manufacturer"] = "Lenovo"
return Response(json.dumps(res), status=200, mimetype="application/json")
@ -164,7 +191,6 @@ def firmware_inventory_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",
@ -173,6 +199,13 @@ def firmware_inventory_bios():
"Version": "P79 v1.45",
"ReleaseDate": "2019-03-15T00:00:00Z",
}
if request.authorization["username"] in {
HARDCODED_UNL_USERNAME,
HARDCODED_SMC_USERNAME,
}:
res["Manufacturer"] = "SMCI"
else:
res["Manufacturer"] = "Contoso"
return Response(json.dumps(res), status=200, mimetype="application/json")
@ -188,18 +221,18 @@ def task_manager():
@app.route("/redfish/v1/TaskService/Tasks/545")
def task_status():
def task_status_545():
res = {
"@odata.id": "/redfish/v1/TaskService/Tasks/545",
"@odata.type": "#Task.v1_4_3.Task",
"Id": "545",
"Name": "Task 545",
"PercentComplete": app._percentage,
"PercentComplete": app._percentage545,
}
if app._percentage == 0:
if app._percentage545 == 0:
res["TaskState"] = "Running"
elif app._percentage in [25, 50, 75]:
elif app._percentage545 in [25, 50, 75]:
res["TaskState"] = "Running"
res["TaskStatus"] = "OK"
res["Messages"] = [
@ -208,7 +241,7 @@ def task_status():
"MessageId": "Update.1.1.TransferringToComponent",
}
]
elif app._percentage == 100:
elif app._percentage545 == 100:
res["TaskState"] = "Completed"
res["TaskStatus"] = "OK"
res["Messages"] = [
@ -235,10 +268,150 @@ def task_status():
"Severity": "Warning",
}
]
app._percentage += 25
app._percentage545 += 25
return Response(response=json.dumps(res), status=200, mimetype="application/json")
@app.route("/redfish/v1/TaskService/Tasks/546")
def task_status_546():
res = {
"@odata.type": "#Task.v1_4_3.Task",
"@odata.id": "/redfish/v1/TaskService/Tasks/546",
"Id": "546",
"Name": "BIOS Verify",
"TaskState": "Running",
"StartTime": "2022-09-29T14:50:54+00:00",
"PercentComplete": app._percentage546,
"HidePayload": True,
"TaskMonitor": "/redfish/v1/TaskMonitor/gd5n5ffS4gi9r6YKVZmgIIaj8ECLfnc",
"TaskStatus": "OK",
"Messages": [
{
"MessageId": "",
"RelatedProperties": [""],
"Message": "",
"MessageArgs": [""],
"Severity": "",
}
],
"Oem": {},
}
if app._percentage546 == 0:
res["TaskState"] = "Running"
elif app._percentage546 in [25, 50, 75]:
res["TaskState"] = "Running"
elif app._percentage546 == 100:
res["TaskState"] = "Completed"
app._percentage546 += 25
return Response(response=json.dumps(res), status=200, mimetype="application/json")
@app.route("/FWUpdate-unlicensed", methods=["GET"])
def fwupdate_unlicensed():
res = {
"error": {
"code": "Base.v1_4_0.GeneralError",
"Message": "A general error has occurred. See ExtendedInfo for more information.",
"@Message.ExtendedInfo": [
{
"MessageId": "SMC.1.0.OemLicenseNotPassed",
"Severity": "Warning",
"Resolution": "Please check if there was the next step with respective API to execute.",
"Message": "The BIOS firmware update was already in update mode.",
"MessageArgs": ["BIOS"],
"RelatedProperties": ["EnterUpdateMode_StatusCheck"],
}
],
}
}
return Response(json.dumps(res), status=405, mimetype="application/json")
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"},
)
@app.route("/FWUpdate-smc", methods=["POST"])
def fwupdate_smc():
data = json.loads(request.form["UpdateParameters"])
if data["@Redfish.OperationApplyTime"] != "OnStartUpdateRequest":
return _failure("apply invalid")
if data["Targets"][0] != "/redfish/v1/Systems/1/Bios":
return _failure("id invalid")
fileitem = request.files["UpdateFile"]
if not fileitem.filename.endswith(".bin"):
return _failure("filename invalid")
filecontents = fileitem.read().decode()
if filecontents == "hello":
app._percentage546 = 0
res = {
"Accepted": {
"code": "Base.v1_4_0.Accepted",
"Message": "Successfully Accepted Request. Please see the location header and ExtendedInfo for more information.",
"@Message.ExtendedInfo": [
{
"MessageId": "SMC.1.0.OemSimpleupdateAcceptedMessage",
"Severity": "Ok",
"Resolution": "No resolution was required.",
"Message": "Please also check Task Resource /redfish/v1/TaskService/Tasks/546 to see more information.",
"MessageArgs": ["/redfish/v1/TaskService/Tasks/546"],
"RelatedProperties": ["BiosVerifyAccepted"],
}
],
}
}
# 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/546"
},
)
elif filecontents == "stuck":
res = {
"error": {
"code": "Base.v1_4_0.GeneralError",
"Message": "A general error has occurred. See ExtendedInfo for more information.",
"@Message.ExtendedInfo": [
{
"MessageId": "SMC.1.0.OemFirmwareAlreadyInUpdateMode",
"Severity": "Warning",
"Resolution": "Please check if there was the next step with respective API to execute.",
"Message": "The BIOS firmware update was already in update mode.",
"MessageArgs": ["BIOS"],
"RelatedProperties": ["EnterUpdateMode_StatusCheck"],
}
],
}
}
return Response(json.dumps(res), status=405, mimetype="application/json")
else:
return _failure("data invalid")
@app.route("/FWUpdate", methods=["POST"])
def fwupdate():
@ -266,5 +439,34 @@ def fwupdate():
)
@app.route(
"/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate", methods=["POST"]
)
def startupdate():
res = {
"Accepted": {
"code": "Base.v1_4_0.Accepted",
"Message": "Successfully Accepted Request. Please see the location header and ExtendedInfo for more information.",
"@Message.ExtendedInfo": [
{
"MessageId": "SMC.1.0.OemSimpleupdateAcceptedMessage",
"Severity": "Ok",
"Resolution": "No resolution was required.",
"Message": "Please also check Task Resource /redfish/v1/TaskService/Tasks/546 to see more information.",
"MessageArgs": ["/redfish/v1/TaskService/Tasks/546"],
"RelatedProperties": ["BiosUpdateAccepted"],
}
],
}
}
app._percentage546 = 0
return Response(
json.dumps(res),
status=202,
mimetype="application/json",
headers={"Location": "http://localhost:4661/redfish/v1/TaskService/Tasks/546"},
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=4661)

View File

@ -1374,6 +1374,10 @@ fu_util_device_problem_to_string(FwupdClient *client, FwupdDevice *dev, FwupdDev
/* TRANSLATORS: emulated means we are pretending to be a different model */
return g_strdup(_("Device is emulated"));
}
if (problem == FWUPD_DEVICE_PROBLEM_MISSING_LICENSE) {
/* TRANSLATORS: The device cannot be updated due to missing vendor's license." */
return g_strdup(_("Device requires a software license to update"));
}
return NULL;
}