/
githubmirror
/
fwupd
Обзор
Документация
Войти
/
githubmirror
/
fwupd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/fu-engine-emulator.c
412 строк
12 KB
Peter Hutterer
treewide: Use FuInputStream throughout
13 июл 2026, 22:08
13 июл 2026, 22:08
54f29d2
Код
Авторство
О чём код?
/* * Copyright 2024 Richard Hughes <richard@hughsie.com> * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include "fu-backend.h" #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-engine-emulator.h" #include "fu-input-stream.h" struct _FuEngineEmulator { GObject parent_instance; FuEngine *engine; GHashTable *phase_blobs; /* (element-type utf-8 GBytes) */ }; G_DEFINE_TYPE(FuEngineEmulator, fu_engine_emulator, G_TYPE_OBJECT) enum { PROP_0, PROP_ENGINE, PROP_LAST }; /* [composite_cnt:]{phase}[-write_cnt].json */ static gchar * fu_engine_emulator_phase_to_filename(guint composite_cnt, FuEngineEmulatorPhase phase, guint write_cnt) { g_autoptr(GString) fn = g_string_new(NULL); if (composite_cnt != 0) g_string_append_printf(fn, "%u:", composite_cnt); g_string_append(fn, fu_engine_emulator_phase_to_string(phase)); if (write_cnt != FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT) g_string_append_printf(fn, "-%u", write_cnt); g_string_append(fn, ".json"); return g_string_free(g_steal_pointer(&fn), FALSE); } gboolean fu_engine_emulator_save(FuEngineEmulator *self, GOutputStream *stream, GError **error) { GHashTableIter iter; gboolean got_json = FALSE; gpointer key; gpointer value; g_autofree gchar *fn_setup = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) archive = fu_zip_firmware_new(); g_return_val_if_fail(FU_IS_ENGINE_EMULATOR(self), FALSE); g_return_val_if_fail(G_IS_OUTPUT_STREAM(stream), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ fn_setup = fu_engine_emulator_phase_to_filename(0, FU_ENGINE_EMULATOR_PHASE_SETUP, FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT); if (!g_hash_table_contains(self->phase_blobs, fn_setup)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no enumeration data, perhaps the device was not replugged?"); return FALSE; } g_hash_table_iter_init(&iter, self->phase_blobs); while (g_hash_table_iter_next(&iter, &key, &value)) { g_autoptr(FuFirmware) img = fu_zip_file_new(); fu_zip_file_set_compression(FU_ZIP_FILE(img), FU_ZIP_COMPRESSION_DEFLATE); fu_firmware_set_id(img, (const gchar *)key); fu_firmware_set_bytes(img, (GBytes *)value); if (!fu_firmware_add_image(archive, img, error)) return FALSE; got_json = TRUE; } if (!got_json) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no emulation data, perhaps no devices have been added?"); return FALSE; } /* write */ blob = fu_firmware_write(archive, error); if (blob == NULL) return FALSE; if (!fu_output_stream_write_bytes(stream, blob, NULL, error)) return FALSE; if (!g_output_stream_flush(stream, NULL, error)) { fwupd_error_convert(error); return FALSE; } /* success */ g_hash_table_remove_all(self->phase_blobs); return TRUE; } static gboolean fu_engine_emulator_load_json_blob(FuEngineEmulator *self, GBytes *json_blob, GError **error) { GPtrArray *backends = fu_context_get_backends(fu_engine_get_context(self->engine)); g_autoptr(FwupdJsonNode) json_node = NULL; g_autoptr(FwupdJsonObject) json_obj = NULL; g_autoptr(FwupdJsonParser) json_parser = fwupd_json_parser_new(); /* set appropriate limits */ fwupd_json_parser_set_max_depth(json_parser, 50); fwupd_json_parser_set_max_items(json_parser, 5000000); /* yes, this big! */ fwupd_json_parser_set_max_quoted(json_parser, 1000000); /* parse */ json_node = fwupd_json_parser_load_from_bytes(json_parser, json_blob, FWUPD_JSON_LOAD_FLAG_TRUSTED | FWUPD_JSON_LOAD_FLAG_STATIC_KEYS, error); if (json_node == NULL) return FALSE; json_obj = fwupd_json_node_get_object(json_node, error); if (json_obj == NULL) return FALSE; /* load into all backends */ for (guint i = 0; i < backends->len; i++) { FuBackend *backend = g_ptr_array_index(backends, i); if (!fwupd_codec_from_json(FWUPD_CODEC(backend), json_obj, error)) return FALSE; } /* success */ return TRUE; } gboolean fu_engine_emulator_load_phase(FuEngineEmulator *self, guint composite_cnt, FuEngineEmulatorPhase phase, guint write_cnt, GError **error) { GBytes *json_blob; g_autofree gchar *fn = NULL; fn = fu_engine_emulator_phase_to_filename(composite_cnt, phase, write_cnt); json_blob = g_hash_table_lookup(self->phase_blobs, fn); if (json_blob == NULL) { g_debug("emulator not loading %s, as not found", fn); return TRUE; } g_debug("emulator loading %s", fn); return fu_engine_emulator_load_json_blob(self, json_blob, error); } static void fu_engine_emulator_to_json(FuEngineEmulator *self, GPtrArray *devices, FwupdJsonObject *json_obj) { g_autoptr(FwupdJsonArray) json_arr = fwupd_json_array_new(); /* not always correct, but we want to remain compatible with all the old emulation files */ fwupd_json_object_add_string(json_obj, "FwupdVersion", PACKAGE_VERSION); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); g_autoptr(FwupdJsonObject) json_device = fwupd_json_object_new(); /* interesting? */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) continue; fu_device_add_json(device, json_device, FWUPD_CODEC_FLAG_NONE); fwupd_json_array_add_object(json_arr, json_device); } fwupd_json_object_add_array(json_obj, "UsbDevices", json_arr); /* we've recorded these, now drop them */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) continue; fu_device_clear_events(device); } } gboolean fu_engine_emulator_save_phase(FuEngineEmulator *self, guint composite_cnt, FuEngineEmulatorPhase phase, guint write_cnt, GError **error) { GBytes *blob_old; g_autofree gchar *blob_new_safe = NULL; g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob_new = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(FwupdJsonObject) json_obj = fwupd_json_object_new(); /* all devices in all backends */ devices = fu_engine_get_devices(self->engine, error); if (devices == NULL) return FALSE; fu_engine_emulator_to_json(self, devices, json_obj); fn = fu_engine_emulator_phase_to_filename(composite_cnt, phase, write_cnt); g_debug("saving %s", fn); blob_old = g_hash_table_lookup(self->phase_blobs, fn); blob_new = fwupd_json_object_to_bytes(json_obj, FWUPD_JSON_EXPORT_FLAG_INDENT | FWUPD_JSON_EXPORT_FLAG_TRAILING_NEWLINE); if (g_bytes_get_size(blob_new) == 0) { g_info("no data for phase %s [%u]", fu_engine_emulator_phase_to_string(phase), write_cnt); return TRUE; } if (blob_old != NULL && g_bytes_compare(blob_old, blob_new) == 0) { g_info("JSON unchanged for phase %s [%u]", fu_engine_emulator_phase_to_string(phase), write_cnt); return TRUE; } blob_new_safe = fu_strsafe_bytes(blob_new, 8000); g_info("JSON %s for phase %s [%u]: %s…", blob_old == NULL ? "added" : "changed", fu_engine_emulator_phase_to_string(phase), write_cnt, blob_new_safe); g_hash_table_insert(self->phase_blobs, g_steal_pointer(&fn), g_steal_pointer(&blob_new)); /* success */ return TRUE; } static gboolean fu_engine_emulator_load_phases(FuEngineEmulator *self, FuFirmware *archive, guint composite_cnt, guint write_cnt, gboolean *got_json, GError **error) { for (FuEngineEmulatorPhase phase = FU_ENGINE_EMULATOR_PHASE_SETUP; phase < FU_ENGINE_EMULATOR_PHASE_LAST; phase++) { g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob = NULL; /* not found */ fn = fu_engine_emulator_phase_to_filename(composite_cnt, phase, write_cnt); blob = fu_firmware_get_image_by_id_bytes(archive, fn, NULL); if (blob == NULL || g_bytes_get_size(blob) == 0) continue; *got_json = TRUE; g_info("emulation for phase %s [%u]", fu_engine_emulator_phase_to_string(phase), write_cnt); if (composite_cnt == 0 && write_cnt == FU_ENGINE_EMULATOR_WRITE_COUNT_DEFAULT && phase == FU_ENGINE_EMULATOR_PHASE_SETUP) { if (!fu_engine_emulator_load_json_blob(self, blob, error)) return FALSE; } else { g_hash_table_insert(self->phase_blobs, g_steal_pointer(&fn), g_steal_pointer(&blob)); } } /* success */ return TRUE; } gboolean fu_engine_emulator_load(FuEngineEmulator *self, FuInputStream *stream, GError **error) { gboolean got_json = FALSE; const gchar *json_empty = "{\"UsbDevices\":[]}"; g_autoptr(FuFirmware) archive = fu_zip_firmware_new(); g_autoptr(GBytes) json_blob = g_bytes_new_static(json_empty, strlen(json_empty)); g_autoptr(GError) error_archive = NULL; g_return_val_if_fail(FU_IS_ENGINE_EMULATOR(self), FALSE); g_return_val_if_fail(FU_IS_INPUT_STREAM(stream), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* unload any existing devices */ if (!fu_engine_emulator_load_json_blob(self, json_blob, error)) return FALSE; g_hash_table_remove_all(self->phase_blobs); /* load archive */ if (!fu_firmware_parse_stream(archive, stream, 0x0, FU_FIRMWARE_PARSE_FLAG_NONE, &error_archive)) { g_autoptr(GBytes) blob = NULL; g_debug("no archive found, using JSON as phase setup: %s", error_archive->message); blob = fu_input_stream_read_bytes(stream, 0, G_MAXSIZE, NULL, error); if (blob == NULL) return FALSE; return fu_engine_emulator_load_json_blob(self, blob, error); } /* load JSON files from archive */ for (guint composite_cnt = 0; composite_cnt < FU_ENGINE_EMULATOR_COMPOSITE_MAX; composite_cnt++) { for (guint write_cnt = 0; write_cnt < FU_ENGINE_EMULATOR_WRITE_COUNT_MAX; write_cnt++) { if (!fu_engine_emulator_load_phases(self, archive, composite_cnt, write_cnt, &got_json, error)) return FALSE; } } if (!got_json) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no emulation data found in archive"); return FALSE; } /* success */ return TRUE; } static void fu_engine_emulator_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuEngineEmulator *self = FU_ENGINE_EMULATOR(object); switch (prop_id) { case PROP_ENGINE: g_value_set_object(value, self->engine); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_engine_emulator_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuEngineEmulator *self = FU_ENGINE_EMULATOR(object); switch (prop_id) { case PROP_ENGINE: self->engine = g_value_dup_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_engine_emulator_init(FuEngineEmulator *self) { self->phase_blobs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_bytes_unref); } static void fu_engine_emulator_finalize(GObject *obj) { FuEngineEmulator *self = FU_ENGINE_EMULATOR(obj); g_hash_table_unref(self->phase_blobs); G_OBJECT_CLASS(fu_engine_emulator_parent_class)->finalize(obj); } static void fu_engine_emulator_dispose(GObject *obj) { FuEngineEmulator *self = FU_ENGINE_EMULATOR(obj); g_clear_object(&self->engine); G_OBJECT_CLASS(fu_engine_emulator_parent_class)->dispose(obj); } static void fu_engine_emulator_class_init(FuEngineEmulatorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_engine_emulator_finalize; object_class->dispose = fu_engine_emulator_dispose; object_class->get_property = fu_engine_emulator_get_property; object_class->set_property = fu_engine_emulator_set_property; pspec = g_param_spec_object("engine", NULL, NULL, FU_TYPE_ENGINE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_ENGINE, pspec); } FuEngineEmulator * fu_engine_emulator_new(FuEngine *engine) { return FU_ENGINE_EMULATOR(g_object_new(FU_TYPE_ENGINE_EMULATOR, "engine", engine, NULL)); }