/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-chunk-private.h" #include "fu-common.h" #include "fu-firmware.h" #include "fu-mem.h" #include "fu-string.h" /** * FuFirmware: * * A firmware file which can have children which represent the images within. * * See also: [class@FuDfuFirmware], [class@FuIhexFirmware], [class@FuSrecFirmware] */ typedef struct { FuFirmwareFlags flags; FuFirmware *parent; /* noref */ GPtrArray *images; /* FuFirmware */ gchar *version; guint64 version_raw; GBytes *bytes; guint8 alignment; gchar *id; gchar *filename; guint64 idx; guint64 addr; guint64 offset; gsize size; GPtrArray *chunks; /* nullable, element-type FuChunk */ GPtrArray *patches; /* nullable, element-type FuFirmwarePatch */ } FuFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuFirmware, fu_firmware, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_firmware_get_instance_private(o)) enum { PROP_0, PROP_PARENT, PROP_LAST }; #define FU_FIRMWARE_SEARCH_MAGIC_BUFSZ_MAX (32 * 1024 * 1024) /** * fu_firmware_flag_to_string: * @flag: a #FuFirmwareFlags, e.g. %FU_FIRMWARE_FLAG_DEDUPE_ID * * Converts a #FuFirmwareFlags to a string. * * Returns: identifier string * * Since: 1.5.0 **/ const gchar * fu_firmware_flag_to_string(FuFirmwareFlags flag) { if (flag == FU_FIRMWARE_FLAG_NONE) return "none"; if (flag == FU_FIRMWARE_FLAG_DEDUPE_ID) return "dedupe-id"; if (flag == FU_FIRMWARE_FLAG_DEDUPE_IDX) return "dedupe-idx"; if (flag == FU_FIRMWARE_FLAG_HAS_CHECKSUM) return "has-checksum"; if (flag == FU_FIRMWARE_FLAG_HAS_VID_PID) return "has-vid-pid"; if (flag == FU_FIRMWARE_FLAG_DONE_PARSE) return "done-parse"; if (flag == FU_FIRMWARE_FLAG_HAS_STORED_SIZE) return "has-stored-size"; if (flag == FU_FIRMWARE_FLAG_ALWAYS_SEARCH) return "always-search"; return NULL; } /** * fu_firmware_flag_from_string: * @flag: a string, e.g. `dedupe-id` * * Converts a string to a #FuFirmwareFlags. * * Returns: enumerated value * * Since: 1.5.0 **/ FuFirmwareFlags fu_firmware_flag_from_string(const gchar *flag) { if (g_strcmp0(flag, "dedupe-id") == 0) return FU_FIRMWARE_FLAG_DEDUPE_ID; if (g_strcmp0(flag, "dedupe-idx") == 0) return FU_FIRMWARE_FLAG_DEDUPE_IDX; if (g_strcmp0(flag, "has-checksum") == 0) return FU_FIRMWARE_FLAG_HAS_CHECKSUM; if (g_strcmp0(flag, "has-vid-pid") == 0) return FU_FIRMWARE_FLAG_HAS_VID_PID; if (g_strcmp0(flag, "done-parse") == 0) return FU_FIRMWARE_FLAG_DONE_PARSE; if (g_strcmp0(flag, "has-stored-size") == 0) return FU_FIRMWARE_FLAG_HAS_STORED_SIZE; if (g_strcmp0(flag, "always-search") == 0) return FU_FIRMWARE_FLAG_ALWAYS_SEARCH; return FU_FIRMWARE_FLAG_NONE; } typedef struct { gsize offset; GBytes *blob; } FuFirmwarePatch; static void fu_firmware_patch_free(FuFirmwarePatch *ptch) { g_bytes_unref(ptch->blob); g_free(ptch); } /** * fu_firmware_add_flag: * @firmware: a #FuFirmware * @flag: the firmware flag * * Adds a specific firmware flag to the firmware. * * Since: 1.5.0 **/ void fu_firmware_add_flag(FuFirmware *firmware, FuFirmwareFlags flag) { FuFirmwarePrivate *priv = GET_PRIVATE(firmware); g_return_if_fail(FU_IS_FIRMWARE(firmware)); priv->flags |= flag; } /** * fu_firmware_has_flag: * @firmware: a #FuFirmware * @flag: the firmware flag * * Finds if the firmware has a specific firmware flag. * * Returns: %TRUE if the flag is set * * Since: 1.5.0 **/ gboolean fu_firmware_has_flag(FuFirmware *firmware, FuFirmwareFlags flag) { FuFirmwarePrivate *priv = GET_PRIVATE(firmware); g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE); return (priv->flags & flag) > 0; } /** * fu_firmware_get_version: * @self: a #FuFirmware * * Gets an optional version that represents the firmware. * * Returns: a string, or %NULL * * Since: 1.3.3 **/ const gchar * fu_firmware_get_version(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); return priv->version; } /** * fu_firmware_set_version: * @self: a #FuFirmware * @version: (nullable): optional string version * * Sets an optional version that represents the firmware. * * Since: 1.3.3 **/ void fu_firmware_set_version(FuFirmware *self, const gchar *version) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); /* not changed */ if (g_strcmp0(priv->version, version) == 0) return; g_free(priv->version); priv->version = g_strdup(version); } /** * fu_firmware_get_version_raw: * @self: a #FuFirmware * * Gets an raw version that represents the firmware. This is most frequently * used when building firmware with `0x123456` in a * `firmware.builder.xml` file to avoid string splitting and sanity checks. * * Returns: an integer, or %G_MAXUINT64 for invalid * * Since: 1.5.7 **/ guint64 fu_firmware_get_version_raw(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT64); return priv->version_raw; } /** * fu_firmware_set_version_raw: * @self: a #FuFirmware * @version_raw: a raw version, or %G_MAXUINT64 for invalid * * Sets an raw version that represents the firmware. * * This is optional, and is typically only used for debugging. * * Since: 1.5.7 **/ void fu_firmware_set_version_raw(FuFirmware *self, guint64 version_raw) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->version_raw = version_raw; } /** * fu_firmware_get_filename: * @self: a #FuFirmware * * Gets an optional filename that represents the image source or destination. * * Returns: a string, or %NULL * * Since: 1.6.0 **/ const gchar * fu_firmware_get_filename(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); return priv->filename; } /** * fu_firmware_set_filename: * @self: a #FuFirmware * @filename: (nullable): a string filename * * Sets an optional filename that represents the image source or destination. * * Since: 1.6.0 **/ void fu_firmware_set_filename(FuFirmware *self, const gchar *filename) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); /* not changed */ if (g_strcmp0(priv->filename, filename) == 0) return; g_free(priv->filename); priv->filename = g_strdup(filename); } /** * fu_firmware_set_id: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. `config` * * Since: 1.6.0 **/ void fu_firmware_set_id(FuFirmware *self, const gchar *id) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); /* not changed */ if (g_strcmp0(priv->id, id) == 0) return; g_free(priv->id); priv->id = g_strdup(id); } /** * fu_firmware_get_id: * @self: a #FuPlugin * * Gets the image ID, typically set at construction. * * Returns: image ID, e.g. `config` * * Since: 1.6.0 **/ const gchar * fu_firmware_get_id(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); return priv->id; } /** * fu_firmware_set_addr: * @self: a #FuPlugin * @addr: integer * * Sets the base address of the image. * * Since: 1.6.0 **/ void fu_firmware_set_addr(FuFirmware *self, guint64 addr) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->addr = addr; } /** * fu_firmware_get_addr: * @self: a #FuPlugin * * Gets the base address of the image. * * Returns: integer * * Since: 1.6.0 **/ guint64 fu_firmware_get_addr(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT64); return priv->addr; } /** * fu_firmware_set_offset: * @self: a #FuPlugin * @offset: integer * * Sets the base offset of the image. * * Since: 1.6.0 **/ void fu_firmware_set_offset(FuFirmware *self, guint64 offset) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->offset = offset; } /** * fu_firmware_get_offset: * @self: a #FuPlugin * * Gets the base offset of the image. * * Returns: integer * * Since: 1.6.0 **/ guint64 fu_firmware_get_offset(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT64); return priv->offset; } /** * fu_firmware_get_parent: * @self: a #FuFirmware * * Gets the parent. * * Returns: (transfer none): the parent firmware, or %NULL if unset * * Since: 1.8.2 **/ FuFirmware * fu_firmware_get_parent(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); return priv->parent; } /** * fu_firmware_set_parent: * @self: a #FuFirmware * @parent: (nullable): another #FuFirmware * * Sets the parent. Only used internally. * * Since: 1.8.2 **/ void fu_firmware_set_parent(FuFirmware *self, FuFirmware *parent) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); if (priv->parent != NULL) g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); if (parent != NULL) g_object_add_weak_pointer(G_OBJECT(parent), (gpointer *)&priv->parent); priv->parent = parent; } /** * fu_firmware_set_size: * @self: a #FuPlugin * @size: integer * * Sets the total size of the image, which should be the same size as the * data from fu_firmware_write(). * * Since: 1.6.0 **/ void fu_firmware_set_size(FuFirmware *self, gsize size) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->size = size; } /** * fu_firmware_get_size: * @self: a #FuPlugin * * Gets the total size of the image, which is typically the same size as the * data from fu_firmware_write(). * * If the size has not been explicitly set, and fu_firmware_set_bytes() has been * used then the size of this is used instead. * * Returns: integer * * Since: 1.6.0 **/ gsize fu_firmware_get_size(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXSIZE); if (priv->size != 0) return priv->size; if (priv->bytes != NULL) return g_bytes_get_size(priv->bytes); return 0; } /** * fu_firmware_set_idx: * @self: a #FuPlugin * @idx: integer * * Sets the index of the image which is used for ordering. * * Since: 1.6.0 **/ void fu_firmware_set_idx(FuFirmware *self, guint64 idx) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->idx = idx; } /** * fu_firmware_get_idx: * @self: a #FuPlugin * * Gets the index of the image which is used for ordering. * * Returns: integer * * Since: 1.6.0 **/ guint64 fu_firmware_get_idx(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT64); return priv->idx; } /** * fu_firmware_set_bytes: * @self: a #FuPlugin * @bytes: data blob * * Sets the contents of the image if not created with fu_firmware_new_from_bytes(). * * Since: 1.6.0 **/ void fu_firmware_set_bytes(FuFirmware *self, GBytes *bytes) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(bytes != NULL); if (priv->bytes != NULL) g_bytes_unref(priv->bytes); priv->bytes = g_bytes_ref(bytes); } /** * fu_firmware_get_bytes: * @self: a #FuPlugin * @error: (nullable): optional return location for an error * * Gets the firmware payload, which does not have any header or footer included. * * If there is more than one potential payload or image section then fu_firmware_add_image() * should be used instead. * * Returns: (transfer full): a #GBytes, or %NULL if the payload has never been set * * Since: 1.6.0 **/ GBytes * fu_firmware_get_bytes(FuFirmware *self, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); if (priv->bytes == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no payload set"); return NULL; } return g_bytes_ref(priv->bytes); } /** * fu_firmware_get_bytes_with_patches: * @self: a #FuPlugin * @error: (nullable): optional return location for an error * * Gets the firmware payload, with any defined patches applied. * * Returns: (transfer full): a #GBytes, or %NULL if the payload has never been set * * Since: 1.7.4 **/ GBytes * fu_firmware_get_bytes_with_patches(FuFirmware *self, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = g_byte_array_new(); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); if (priv->bytes == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no payload set"); return NULL; } /* usual case */ if (priv->patches == NULL) return g_bytes_ref(priv->bytes); /* convert to a mutable buffer, apply each patch, aborting if the offset isn't valid */ fu_byte_array_append_bytes(buf, priv->bytes); for (guint i = 0; i < priv->patches->len; i++) { FuFirmwarePatch *ptch = g_ptr_array_index(priv->patches, i); if (!fu_memcpy_safe(buf->data, buf->len, ptch->offset, /* dst */ g_bytes_get_data(ptch->blob, NULL), g_bytes_get_size(ptch->blob), 0x0, /* src */ g_bytes_get_size(ptch->blob), error)) { g_prefix_error(error, "failed to apply patch @0x%x: ", (guint)ptch->offset); return NULL; } } /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } /** * fu_firmware_set_alignment: * @self: a #FuFirmware * @alignment: integer, or 0 to disable * * Sets the alignment of the firmware. * * This allows a firmware to pad to a power of 2 boundary, where @alignment * is the bit position to align to. * * Since: 1.6.0 **/ void fu_firmware_set_alignment(FuFirmware *self, guint8 alignment) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->alignment = alignment; } /** * fu_firmware_get_alignment: * @self: a #FuFirmware * * Gets the alignment of the firmware. * * This allows a firmware to pad to a power of 2 boundary, where @alignment * is the bit position to align to. * * Returns: integer * * Since: 1.6.0 **/ guint8 fu_firmware_get_alignment(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT8); return priv->alignment; } /** * fu_firmware_get_chunks: * @self: a #FuFirmware * @error: (nullable): optional return location for an error * * Gets the optional image chunks. * * Returns: (transfer container) (element-type FuChunk) (nullable): chunk data, or %NULL * * Since: 1.6.0 **/ GPtrArray * fu_firmware_get_chunks(FuFirmware *self, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* set */ if (priv->chunks != NULL) return g_ptr_array_ref(priv->chunks); /* lets build something plausible */ if (priv->bytes != NULL) { g_autoptr(GPtrArray) chunks = NULL; g_autoptr(FuChunk) chk = NULL; chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); chk = fu_chunk_bytes_new(priv->bytes); fu_chunk_set_idx(chk, priv->idx); fu_chunk_set_address(chk, priv->addr); g_ptr_array_add(chunks, g_steal_pointer(&chk)); return g_steal_pointer(&chunks); } /* nothing to do */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no bytes or chunks found in firmware"); return NULL; } /** * fu_firmware_add_chunk: * @self: a #FuFirmware * @chk: a #FuChunk * * Adds a chunk to the image. * * Since: 1.6.0 **/ void fu_firmware_add_chunk(FuFirmware *self, FuChunk *chk) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(FU_IS_CHUNK(chk)); if (priv->chunks == NULL) priv->chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(priv->chunks, g_object_ref(chk)); } /** * fu_firmware_get_checksum: * @self: a #FuPlugin * @csum_kind: a checksum type, e.g. %G_CHECKSUM_SHA256 * @error: (nullable): optional return location for an error * * Returns a checksum of the payload data. * * Returns: (transfer full): a checksum string, or %NULL if the checksum is not available * * Since: 1.6.0 **/ gchar * fu_firmware_get_checksum(FuFirmware *self, GChecksumType csum_kind, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); g_autoptr(GBytes) blob = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* subclassed */ if (klass->get_checksum != NULL) return klass->get_checksum(self, csum_kind, error); /* internal data */ if (priv->bytes != NULL) return g_compute_checksum_for_bytes(csum_kind, priv->bytes); /* write */ blob = fu_firmware_write(self, error); if (blob == NULL) return NULL; return g_compute_checksum_for_bytes(csum_kind, blob); } /** * fu_firmware_tokenize: * @self: a #FuFirmware * @fw: firmware blob * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Tokenizes a firmware, typically breaking the firmware into records. * * Records can be enumerated using subclass-specific functionality, for example * using fu_srec_firmware_get_records(). * * Returns: %TRUE for success * * Since: 1.3.2 **/ gboolean fu_firmware_tokenize(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(fw != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optionally subclassed */ if (klass->tokenize != NULL) return klass->tokenize(self, fw, flags, error); return TRUE; } /** * fu_firmware_check_compatible: * @self: a #FuFirmware * @other: a #FuFirmware * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Check a new firmware is compatible with the existing firmware. * * Returns: %TRUE for success * * Since: 1.8.4 **/ gboolean fu_firmware_check_compatible(FuFirmware *self, FuFirmware *other, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(other), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optionally subclassed */ if (klass->check_compatible == NULL) return TRUE; return klass->check_compatible(self, other, flags, error); } static gboolean fu_firmware_check_magic_for_offset(FuFirmware *self, GBytes *fw, gsize *offset, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); /* not implemented */ if (klass->check_magic == NULL) return TRUE; /* fuzzing */ if (!fu_firmware_has_flag(self, FU_FIRMWARE_FLAG_ALWAYS_SEARCH) && (flags & FWUPD_INSTALL_FLAG_NO_SEARCH) > 0) { if (!klass->check_magic(self, fw, *offset, error)) { g_prefix_error(error, "not searching magic due to install flags: "); return FALSE; } return TRUE; } /* limit the size of firmware we search */ if (g_bytes_get_size(fw) > FU_FIRMWARE_SEARCH_MAGIC_BUFSZ_MAX) { if (!klass->check_magic(self, fw, *offset, error)) { g_prefix_error(error, "failed to search for magic as firmware size was 0x%x and " "limit was 0x%x: ", (guint)g_bytes_get_size(fw), (guint)FU_FIRMWARE_SEARCH_MAGIC_BUFSZ_MAX); return FALSE; } return TRUE; } /* increment the offset, looking for the magic */ for (gsize offset_tmp = *offset; offset_tmp < g_bytes_get_size(fw); offset_tmp++) { if (klass->check_magic(self, fw, offset_tmp, NULL)) { fu_firmware_set_offset(self, offset_tmp); *offset = offset_tmp; return TRUE; } } /* did not find what we were looking for */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "did not find magic"); return FALSE; } /** * fu_firmware_parse_full: * @self: a #FuFirmware * @fw: firmware blob * @offset: start offset, useful for ignoring a bootloader * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Parses a firmware, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_firmware_parse_full(FuFirmware *self, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(fw != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (fu_firmware_has_flag(self, FU_FIRMWARE_FLAG_DONE_PARSE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware object cannot be reused"); return FALSE; } if (g_bytes_get_size(fw) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid firmware as zero sized"); return FALSE; } /* any FuFirmware subclass that gets past this point might have allocated memory in * ->tokenize() or ->parse() and needs to be destroyed before parsing again */ fu_firmware_add_flag(self, FU_FIRMWARE_FLAG_DONE_PARSE); /* subclassed */ if (klass->tokenize != NULL) { if (!klass->tokenize(self, fw, flags, error)) return FALSE; } if (!fu_firmware_check_magic_for_offset(self, fw, &offset, flags, error)) return FALSE; /* always set by default */ fu_firmware_set_bytes(self, fw); /* handled by the subclass */ if (klass->parse != NULL) return klass->parse(self, fw, offset, flags, error); /* verify alignment */ if (g_bytes_get_size(fw) % (1ull << priv->alignment) != 0) { g_autofree gchar *str = NULL; str = g_format_size_full(1ull << priv->alignment, G_FORMAT_SIZE_IEC_UNITS); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "raw firmware is not aligned to 0x%x (%s)", (guint)(1ull << priv->alignment), str); return FALSE; } /* success */ return TRUE; } /** * fu_firmware_parse: * @self: a #FuFirmware * @fw: firmware blob * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Parses a firmware, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.3.1 **/ gboolean fu_firmware_parse(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { return fu_firmware_parse_full(self, fw, 0x0, flags, error); } /** * fu_firmware_build: * @self: a #FuFirmware * @n: a Xmlb node * @error: (nullable): optional return location for an error * * Builds a firmware from an XML manifest. The manifest would typically have the * following form: * * |[ * * * 1.2.3 * * 7.8.9 * stage1 * 0x01 * stage1.bin * * * stage2 * * * * ape * 0x7 * aGVsbG8gd29ybGQ= * * * ]| * * This would be used in a build-system to merge images from generated files: * `fwupdtool firmware-build fw.builder.xml test.fw` * * Static binary content can be specified in the `/` section and * is encoded as base64 text if not empty. * * Additionally, extra nodes can be included under nested `` objects * which can be parsed by the subclassed objects. You should verify the * subclassed object `FuFirmware->build` vfunc for the specific additional * options supported. * * Plugins should manually g_type_ensure() subclassed image objects if not * constructed as part of the plugin fu_plugin_init() or fu_plugin_setup() * functions. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fu_firmware_build(FuFirmware *self, XbNode *n, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); const gchar *tmp; guint64 tmpval; guint64 version_raw; g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GPtrArray) xb_images = NULL; g_autoptr(XbNode) data = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(XB_IS_NODE(n), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* set attributes */ tmp = xb_node_query_text(n, "version", NULL); if (tmp != NULL) fu_firmware_set_version(self, tmp); version_raw = xb_node_query_text_as_uint(n, "version_raw", NULL); if (version_raw != G_MAXUINT64) fu_firmware_set_version_raw(self, version_raw); tmp = xb_node_query_text(n, "id", NULL); if (tmp != NULL) fu_firmware_set_id(self, tmp); tmpval = xb_node_query_text_as_uint(n, "idx", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_idx(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "addr", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_addr(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "offset", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_offset(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "size", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_size(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "alignment", NULL); if (tmpval != G_MAXUINT64) { if (tmpval > FU_FIRMWARE_ALIGNMENT_2G) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "0x%x invalid, maximum is 0x%x", (guint)tmpval, (guint)FU_FIRMWARE_ALIGNMENT_2G); return FALSE; } fu_firmware_set_alignment(self, (guint8)tmpval); } tmp = xb_node_query_text(n, "filename", NULL); if (tmp != NULL) { g_autoptr(GBytes) blob = NULL; blob = fu_bytes_get_contents(tmp, error); if (blob == NULL) return FALSE; fu_firmware_set_bytes(self, blob); fu_firmware_set_filename(self, tmp); } data = xb_node_query_first(n, "data", NULL); if (data != NULL) { guint64 sz = xb_node_get_attr_as_uint(data, "size"); g_autoptr(GBytes) blob = NULL; /* base64 encoded data */ if (xb_node_get_text(data) != NULL) { gsize bufsz = 0; g_autofree guchar *buf = NULL; buf = g_base64_decode(xb_node_get_text(data), &bufsz); blob = g_bytes_new(buf, bufsz); } else { blob = g_bytes_new(NULL, 0); } /* padding is optional */ if (sz == 0 || sz == G_MAXUINT64) { fu_firmware_set_bytes(self, blob); } else { g_autoptr(GBytes) blob_padded = fu_bytes_pad(blob, (gsize)sz); fu_firmware_set_bytes(self, blob_padded); } } /* optional chunks */ chunks = xb_node_query(n, "chunks/chunk", 0, NULL); if (chunks != NULL) { for (guint i = 0; i < chunks->len; i++) { XbNode *c = g_ptr_array_index(chunks, i); g_autoptr(FuChunk) chk = fu_chunk_bytes_new(NULL); fu_chunk_set_idx(chk, i); if (!fu_chunk_build(chk, c, error)) return FALSE; fu_firmware_add_chunk(self, chk); } } /* parse images */ xb_images = xb_node_query(n, "firmware", 0, NULL); if (xb_images != NULL) { for (guint i = 0; i < xb_images->len; i++) { XbNode *xb_image = g_ptr_array_index(xb_images, i); g_autoptr(FuFirmware) img = NULL; tmp = xb_node_get_attr(xb_image, "gtype"); if (tmp != NULL) { GType gtype = g_type_from_name(tmp); if (gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not registered", tmp); return FALSE; } img = g_object_new(gtype, NULL); } else { img = fu_firmware_new(); } if (!fu_firmware_build(img, xb_image, error)) return FALSE; fu_firmware_add_image(self, img); } } /* subclassed */ if (klass->build != NULL) { if (!klass->build(self, n, error)) return FALSE; } /* success */ return TRUE; } /** * fu_firmware_build_from_xml: * @self: a #FuFirmware * @xml: XML text * @error: (nullable): optional return location for an error * * Builds a firmware from an XML manifest. The manifest would typically have the * following form: * * |[ * * * 1.2.3 * * 7.8.9 * stage1 * 0x01 * stage1.bin * * * stage2 * * * * ape * 0x7 * aGVsbG8gd29ybGQ= * * * ]| * * This would be used in a build-system to merge images from generated files: * `fwupdtool firmware-build fw.builder.xml test.fw` * * Static binary content can be specified in the `/` section and * is encoded as base64 text if not empty. * * Additionally, extra nodes can be included under nested `` objects * which can be parsed by the subclassed objects. You should verify the * subclassed object `FuFirmware->build` vfunc for the specific additional * options supported. * * Plugins should manually g_type_ensure() subclassed image objects if not * constructed as part of the plugin fu_plugin_init() or fu_plugin_setup() * functions. * * Returns: %TRUE for success * * Since: 1.6.0 **/ gboolean fu_firmware_build_from_xml(FuFirmware *self, const gchar *xml, GError **error) { g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; /* parse XML */ if (!xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, error)) { g_prefix_error(error, "could not parse XML: "); return FALSE; } xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; /* create FuFirmware of specific GType */ n = xb_silo_query_first(silo, "firmware", error); if (n == NULL) return FALSE; return fu_firmware_build(self, n, error); } /** * fu_firmware_parse_file: * @self: a #FuFirmware * @file: a file * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Parses a firmware file, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_firmware_parse_file(FuFirmware *self, GFile *file, FwupdInstallFlags flags, GError **error) { gchar *buf = NULL; gsize bufsz = 0; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!g_file_load_contents(file, NULL, &buf, &bufsz, NULL, error)) return FALSE; fw = g_bytes_new_take(buf, bufsz); return fu_firmware_parse(self, fw, flags, error); } /** * fu_firmware_write: * @self: a #FuFirmware * @error: (nullable): optional return location for an error * * Writes a firmware, typically packing the images into a binary blob. * * Returns: (transfer full): a data blob * * Since: 1.3.1 **/ GBytes * fu_firmware_write(FuFirmware *self, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* subclassed */ if (klass->write != NULL) return klass->write(self, error); /* just add default blob */ return fu_firmware_get_bytes_with_patches(self, error); } /** * fu_firmware_add_patch: * @self: a #FuFirmware * @offset: an address smaller than fu_firmware_get_size() * @blob: (not nullable): bytes to replace * * Adds a byte patch at a specific offset. If a patch already exists at the specified address then * it is replaced. * * If the @address is larger than the size of the image then an error is returned. * * Since: 1.7.4 **/ void fu_firmware_add_patch(FuFirmware *self, gsize offset, GBytes *blob) { FuFirmwarePrivate *priv = GET_PRIVATE(self); FuFirmwarePatch *ptch; g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(blob != NULL); /* ensure exists */ if (priv->patches == NULL) { priv->patches = g_ptr_array_new_with_free_func((GDestroyNotify)fu_firmware_patch_free); } /* find existing of exact same size */ for (guint i = 0; i < priv->patches->len; i++) { ptch = g_ptr_array_index(priv->patches, i); if (ptch->offset == offset && g_bytes_get_size(ptch->blob) == g_bytes_get_size(blob)) { g_bytes_unref(ptch->blob); ptch->blob = g_bytes_ref(blob); return; } } /* add new */ ptch = g_new0(FuFirmwarePatch, 1); ptch->offset = offset; ptch->blob = g_bytes_ref(blob); g_ptr_array_add(priv->patches, ptch); } /** * fu_firmware_write_chunk: * @self: a #FuFirmware * @address: an address smaller than fu_firmware_get_addr() * @chunk_sz_max: the size of the new chunk * @error: (nullable): optional return location for an error * * Gets a block of data from the image. If the contents of the image is * smaller than the requested chunk size then the #GBytes will be smaller * than @chunk_sz_max. Use fu_bytes_pad() if padding is required. * * If the @address is larger than the size of the image then an error is returned. * * Returns: (transfer full): a #GBytes, or %NULL * * Since: 1.6.0 **/ GBytes * fu_firmware_write_chunk(FuFirmware *self, guint64 address, guint64 chunk_sz_max, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); gsize chunk_left; guint64 offset; g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* check address requested is larger than base address */ if (address < priv->addr) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "requested address 0x%x less than base address 0x%x", (guint)address, (guint)priv->addr); return NULL; } /* offset into data */ offset = address - priv->addr; if (offset > g_bytes_get_size(priv->bytes)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "offset 0x%x larger than data size 0x%x", (guint)offset, (guint)g_bytes_get_size(priv->bytes)); return NULL; } /* if we have less data than requested */ chunk_left = g_bytes_get_size(priv->bytes) - offset; if (chunk_sz_max > chunk_left) { return fu_bytes_new_offset(priv->bytes, offset, chunk_left, error); } /* check chunk */ return fu_bytes_new_offset(priv->bytes, offset, chunk_sz_max, error); } /** * fu_firmware_write_file: * @self: a #FuFirmware * @file: a file * @error: (nullable): optional return location for an error * * Writes a firmware, typically packing the images into a binary blob. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_firmware_write_file(FuFirmware *self, GFile *file, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(GFile) parent = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); blob = fu_firmware_write(self, error); if (blob == NULL) return FALSE; parent = g_file_get_parent(file); if (!g_file_query_exists(parent, NULL)) { if (!g_file_make_directory_with_parents(parent, NULL, error)) return FALSE; } return g_file_replace_contents(file, g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, error); } /** * fu_firmware_add_image: * @self: a #FuPlugin * @img: a child firmware image * * Adds an image to the firmware. * * If %FU_FIRMWARE_FLAG_DEDUPE_ID is set, an image with the same ID is already * present it is replaced. * * Since: 1.3.1 **/ void fu_firmware_add_image(FuFirmware *self, FuFirmware *img) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(FU_IS_FIRMWARE(img)); /* dedupe */ for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img_tmp = g_ptr_array_index(priv->images, i); if (priv->flags & FU_FIRMWARE_FLAG_DEDUPE_ID) { if (g_strcmp0(fu_firmware_get_id(img_tmp), fu_firmware_get_id(img)) == 0) { g_ptr_array_remove_index(priv->images, i); break; } } if (priv->flags & FU_FIRMWARE_FLAG_DEDUPE_IDX) { if (fu_firmware_get_idx(img_tmp) == fu_firmware_get_idx(img)) { g_ptr_array_remove_index(priv->images, i); break; } } } g_ptr_array_add(priv->images, g_object_ref(img)); /* set the other way around */ fu_firmware_set_parent(img, self); } /** * fu_firmware_remove_image: * @self: a #FuPlugin * @img: a child firmware image * @error: (nullable): optional return location for an error * * Remove an image from the firmware. * * Returns: %TRUE if the image was removed * * Since: 1.5.0 **/ gboolean fu_firmware_remove_image(FuFirmware *self, FuFirmware *img, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(img), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (g_ptr_array_remove(priv->images, img)) return TRUE; /* did not exist */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "image %s not found in firmware", fu_firmware_get_id(img)); return FALSE; } /** * fu_firmware_remove_image_by_idx: * @self: a #FuPlugin * @idx: index * @error: (nullable): optional return location for an error * * Removes the first image from the firmware matching the index. * * Returns: %TRUE if an image was removed * * Since: 1.5.0 **/ gboolean fu_firmware_remove_image_by_idx(FuFirmware *self, guint64 idx, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(FuFirmware) img = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); img = fu_firmware_get_image_by_idx(self, idx, error); if (img == NULL) return FALSE; g_ptr_array_remove(priv->images, img); return TRUE; } /** * fu_firmware_remove_image_by_id: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. `config` * @error: (nullable): optional return location for an error * * Removes the first image from the firmware matching the ID. * * Returns: %TRUE if an image was removed * * Since: 1.5.0 **/ gboolean fu_firmware_remove_image_by_id(FuFirmware *self, const gchar *id, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(FuFirmware) img = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); img = fu_firmware_get_image_by_id(self, id, error); if (img == NULL) return FALSE; g_ptr_array_remove(priv->images, img); return TRUE; } /** * fu_firmware_get_images: * @self: a #FuFirmware * * Returns all the images in the firmware. * * Returns: (transfer container) (element-type FuFirmware): images * * Since: 1.3.1 **/ GPtrArray * fu_firmware_get_images(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(GPtrArray) imgs = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); imgs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); g_ptr_array_add(imgs, g_object_ref(img)); } return g_steal_pointer(&imgs); } /** * fu_firmware_get_image_by_id: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. `config` * @error: (nullable): optional return location for an error * * Gets the firmware image using the image ID. * * Returns: (transfer full): a #FuFirmware, or %NULL if the image is not found * * Since: 1.3.1 **/ FuFirmware * fu_firmware_get_image_by_id(FuFirmware *self, const gchar *id, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); if (g_strcmp0(fu_firmware_get_id(img), id) == 0) return g_object_ref(img); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image id %s found in firmware", id); return NULL; } /** * fu_firmware_get_image_by_id_bytes: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. `config` * @error: (nullable): optional return location for an error * * Gets the firmware image bytes using the image ID. * * Returns: (transfer full): a #GBytes of a #FuFirmware, or %NULL if the image is not found * * Since: 1.3.1 **/ GBytes * fu_firmware_get_image_by_id_bytes(FuFirmware *self, const gchar *id, GError **error) { g_autoptr(FuFirmware) img = fu_firmware_get_image_by_id(self, id, error); if (img == NULL) return NULL; return fu_firmware_write(img, error); } /** * fu_firmware_get_image_by_idx: * @self: a #FuPlugin * @idx: image index * @error: (nullable): optional return location for an error * * Gets the firmware image using the image index. * * Returns: (transfer full): a #FuFirmware, or %NULL if the image is not found * * Since: 1.3.1 **/ FuFirmware * fu_firmware_get_image_by_idx(FuFirmware *self, guint64 idx, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); if (fu_firmware_get_idx(img) == idx) return g_object_ref(img); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image idx %" G_GUINT64_FORMAT " found in firmware", idx); return NULL; } /** * fu_firmware_get_image_by_checksum: * @self: a #FuPlugin * @checksum: checksum string of any format * @error: (nullable): optional return location for an error * * Gets the firmware image using the image checksum. The checksum type is guessed * based on the length of the input string. * * Returns: (transfer full): a #FuFirmware, or %NULL if the image is not found * * Since: 1.5.5 **/ FuFirmware * fu_firmware_get_image_by_checksum(FuFirmware *self, const gchar *checksum, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); GChecksumType csum_kind; g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(checksum != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); csum_kind = fwupd_checksum_guess_kind(checksum); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); g_autofree gchar *checksum_tmp = NULL; /* if this expensive then the subclassed FuFirmware can * cache the result as required */ checksum_tmp = fu_firmware_get_checksum(img, csum_kind, error); if (checksum_tmp == NULL) return NULL; if (g_strcmp0(checksum_tmp, checksum) == 0) return g_object_ref(img); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image with checksum %s found in firmware", checksum); return NULL; } /** * fu_firmware_get_image_by_idx_bytes: * @self: a #FuPlugin * @idx: image index * @error: (nullable): optional return location for an error * * Gets the firmware image bytes using the image index. * * Returns: (transfer full): a #GBytes of a #FuFirmware, or %NULL if the image is not found * * Since: 1.3.1 **/ GBytes * fu_firmware_get_image_by_idx_bytes(FuFirmware *self, guint64 idx, GError **error) { g_autoptr(FuFirmware) img = fu_firmware_get_image_by_idx(self, idx, error); if (img == NULL) return NULL; return fu_firmware_write(img, error); } /** * fu_firmware_export: * @self: a #FuFirmware * @flags: firmware export flags, e.g. %FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG * @bn: a Xmlb builder node * * This allows us to build an XML object for the nested firmware. * * Since: 1.6.0 **/ void fu_firmware_export(FuFirmware *self, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); FuFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *gtypestr = G_OBJECT_TYPE_NAME(self); /* object */ if (g_strcmp0(gtypestr, "FuFirmware") != 0) xb_builder_node_set_attr(bn, "gtype", gtypestr); /* subclassed type */ if (priv->flags != FU_FIRMWARE_FLAG_NONE) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { guint64 flag = (guint64)1 << i; if (flag == FU_FIRMWARE_FLAG_DONE_PARSE) continue; if ((priv->flags & flag) == 0) continue; g_string_append_printf(tmp, "%s|", fu_firmware_flag_to_string(flag)); } if (tmp->len > 0) { g_string_truncate(tmp, tmp->len - 1); fu_xmlb_builder_insert_kv(bn, "flags", tmp->str); } } fu_xmlb_builder_insert_kv(bn, "id", priv->id); fu_xmlb_builder_insert_kx(bn, "idx", priv->idx); fu_xmlb_builder_insert_kv(bn, "version", priv->version); fu_xmlb_builder_insert_kx(bn, "version_raw", priv->version_raw); fu_xmlb_builder_insert_kx(bn, "addr", priv->addr); fu_xmlb_builder_insert_kx(bn, "offset", priv->offset); fu_xmlb_builder_insert_kx(bn, "alignment", priv->alignment); fu_xmlb_builder_insert_kx(bn, "size", priv->size); fu_xmlb_builder_insert_kv(bn, "filename", priv->filename); if (priv->bytes != NULL) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(priv->bytes, &bufsz); g_autofree gchar *datastr = NULL; g_autofree gchar *dataszstr = g_strdup_printf("0x%x", (guint)bufsz); if (flags & FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA) { datastr = fu_strsafe((const gchar *)buf, MIN(bufsz, 16)); } else { #if GLIB_CHECK_VERSION(2, 61, 0) datastr = g_base64_encode(buf, bufsz); #else /* older GLib versions can't cope with buf=NULL */ if (buf == NULL || bufsz == 0) { datastr = g_strdup(""); } else { datastr = g_base64_encode(buf, bufsz); } #endif } xb_builder_node_insert_text(bn, "data", datastr, "size", dataszstr, NULL); } fu_xmlb_builder_insert_kx(bn, "alignment", priv->alignment); /* chunks */ if (priv->chunks != NULL && priv->chunks->len > 0) { g_autoptr(XbBuilderNode) bp = xb_builder_node_insert(bn, "chunks", NULL); for (guint i = 0; i < priv->chunks->len; i++) { FuChunk *chk = g_ptr_array_index(priv->chunks, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bp, "chunk", NULL); fu_chunk_export(chk, flags, bc); } } /* vfunc */ if (klass->export != NULL) klass->export(self, flags, bn); /* children */ if (priv->images->len > 0) { for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "firmware", NULL); fu_firmware_export(img, flags, bc); } } } /** * fu_firmware_export_to_xml: * @self: a #FuFirmware * @flags: firmware export flags, e.g. %FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG * @error: (nullable): optional return location for an error * * This allows us to build an XML object for the nested firmware. * * Returns: a string value, or %NULL for invalid. * * Since: 1.6.0 **/ gchar * fu_firmware_export_to_xml(FuFirmware *self, FuFirmwareExportFlags flags, GError **error) { g_autoptr(XbBuilderNode) bn = xb_builder_node_new("firmware"); fu_firmware_export(self, flags, bn); return xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | #if LIBXMLB_CHECK_VERSION(0, 2, 2) XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | #endif XB_NODE_EXPORT_FLAG_FORMAT_INDENT, error); } /** * fu_firmware_to_string: * @self: a #FuFirmware * * This allows us to easily print the object. * * Returns: a string value, or %NULL for invalid. * * Since: 1.3.1 **/ gchar * fu_firmware_to_string(FuFirmware *self) { g_autoptr(XbBuilderNode) bn = xb_builder_node_new("firmware"); fu_firmware_export(self, FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG | FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA, bn); return xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | #if LIBXMLB_CHECK_VERSION(0, 2, 2) XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | #endif XB_NODE_EXPORT_FLAG_FORMAT_INDENT, NULL); } static void fu_firmware_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuFirmware *self = FU_FIRMWARE(object); FuFirmwarePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_PARENT: g_value_set_object(value, priv->parent); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_firmware_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuFirmware *self = FU_FIRMWARE(object); switch (prop_id) { case PROP_PARENT: fu_firmware_set_parent(self, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_firmware_init(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); priv->images = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } static void fu_firmware_finalize(GObject *object) { FuFirmware *self = FU_FIRMWARE(object); FuFirmwarePrivate *priv = GET_PRIVATE(self); g_free(priv->version); g_free(priv->id); g_free(priv->filename); if (priv->bytes != NULL) g_bytes_unref(priv->bytes); if (priv->chunks != NULL) g_ptr_array_unref(priv->chunks); if (priv->patches != NULL) g_ptr_array_unref(priv->patches); if (priv->parent != NULL) g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); g_ptr_array_unref(priv->images); G_OBJECT_CLASS(fu_firmware_parent_class)->finalize(object); } static void fu_firmware_class_init(FuFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_firmware_finalize; object_class->get_property = fu_firmware_get_property; object_class->set_property = fu_firmware_set_property; /** * FuFirmware:parent: * * The firmware parent. * * Since: 1.8.2 */ pspec = g_param_spec_object("parent", NULL, NULL, FU_TYPE_FIRMWARE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PARENT, pspec); } /** * fu_firmware_new: * * Creates an empty firmware object. * * Returns: a #FuFirmware * * Since: 1.3.1 **/ FuFirmware * fu_firmware_new(void) { FuFirmware *self = g_object_new(FU_TYPE_FIRMWARE, NULL); return FU_FIRMWARE(self); } /** * fu_firmware_new_from_bytes: * @fw: firmware blob image * * Creates a firmware object with the provided image set as default. * * Returns: a #FuFirmware * * Since: 1.3.1 **/ FuFirmware * fu_firmware_new_from_bytes(GBytes *fw) { FuFirmware *self = fu_firmware_new(); fu_firmware_set_bytes(self, fw); return self; } /** * fu_firmware_new_from_gtypes: * @fw: firmware blob * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM * @error: (nullable): optional return location for an error * @...: an array of #GTypes, ending with %G_TYPE_INVALID * * Tries to parse the firmware with each #GType in order. * * Returns: (transfer full) (nullable): a #FuFirmware, or %NULL * * Since: 1.5.6 **/ FuFirmware * fu_firmware_new_from_gtypes(GBytes *fw, FwupdInstallFlags flags, GError **error, ...) { va_list args; g_autoptr(GArray) gtypes = g_array_new(FALSE, FALSE, sizeof(GType)); g_autoptr(GError) error_all = NULL; g_return_val_if_fail(fw != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* create array of GTypes */ va_start(args, error); while (TRUE) { GType gtype = va_arg(args, GType); if (gtype == G_TYPE_INVALID) break; g_array_append_val(gtypes, gtype); } va_end(args); /* invalid */ if (gtypes->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no GTypes specified"); return NULL; } /* try each GType in turn */ for (guint i = 0; i < gtypes->len; i++) { GType gtype = g_array_index(gtypes, GType, i); g_autoptr(FuFirmware) firmware = g_object_new(gtype, NULL); g_autoptr(GError) error_local = NULL; if (!fu_firmware_parse(firmware, fw, flags, &error_local)) { if (error_all == NULL) { g_propagate_error(&error_all, g_steal_pointer(&error_local)); } else { g_prefix_error(&error_all, "%s: ", error_local->message); } continue; } return g_steal_pointer(&firmware); } /* failed */ g_propagate_error(error, g_steal_pointer(&error_all)); return NULL; }