fwupd/libfwupdplugin/fu-firmware-image.c
Richard Hughes 41400a8cc6 Allow contructing a firmware with multiple images
At the moment there are commands to convert one file format to another, but not
to 'merge' or alter them. Some firmware files are containers which can store
multiple images, each with optional id, idx and addresses.

This would allow us to, for instance, create a DfuSe file with two different
raw files that are flashed to different addresses on the SPI flash. It would
also allow us to create very small complicated container formats for fuzzing.

This can be used by writing a `firmware.builder.xml` file like:

   <?xml version="1.0" encoding="UTF-8"?>
   <firmware gtype="FuBcm57xxFirmware">
     <version>1.2.3</version>
     <image>
       <version>4.5.6</version>
       <id>header</id>
       <idx>456</idx>
       <addr>0x456</addr>
       <filename>header.bin</filename>
     </image>
     <image>
       <version>7.8.9</version>
       <id>payload</id>
       <idx>789</idx>
       <addr>0x789</addr>
       <data>aGVsbG8=</data>
     </image>
   </firmware>

...and then using something like:

   # fwupdtool firmware-convert firmware.builder.xml firmware.dfu builder dfu
2020-09-21 18:11:13 +01:00

503 lines
12 KiB
C

/*
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuFirmware"
#include "config.h"
#include "fu-common.h"
#include "fu-firmware-image-private.h"
/**
* SECTION:fu-firmware_image
* @short_description: a firmware_image file
*
* An object that represents a firmware_image file.
*/
typedef struct {
gchar *id;
GBytes *bytes;
guint64 addr;
guint64 idx;
gchar *version;
gchar *filename;
} FuFirmwareImagePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (FuFirmwareImage, fu_firmware_image, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (fu_firmware_image_get_instance_private (o))
/**
* fu_firmware_image_get_version:
* @self: A #FuFirmwareImage
*
* Gets an optional version that represents the firmware image.
*
* Returns: a string, or %NULL
*
* Since: 1.3.4
**/
const gchar *
fu_firmware_image_get_version (FuFirmwareImage *self)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
return priv->version;
}
/**
* fu_firmware_image_set_version:
* @self: A #FuFirmwareImage
* @version: (nullable): A string version, or %NULL
*
* Sets an optional version that represents the firmware image.
*
* Since: 1.3.4
**/
void
fu_firmware_image_set_version (FuFirmwareImage *self, const gchar *version)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
g_free (priv->version);
priv->version = g_strdup (version);
}
/**
* fu_firmware_image_get_filename:
* @self: A #FuFirmwareImage
*
* Gets an optional filename that represents the image source or destination.
*
* Returns: a string, or %NULL
*
* Since: 1.5.0
**/
const gchar *
fu_firmware_image_get_filename (FuFirmwareImage *self)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
return priv->filename;
}
/**
* fu_firmware_image_set_filename:
* @self: A #FuFirmwareImage
* @filename: (nullable): A string filename, or %NULL
*
* Sets an optional filename that represents the image source or destination.
*
* Since: 1.5.0
**/
void
fu_firmware_image_set_filename (FuFirmwareImage *self, const gchar *filename)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
g_free (priv->filename);
priv->filename = g_strdup (filename);
}
/**
* fu_firmware_image_set_id:
* @self: a #FuPlugin
* @id: (nullable): image ID, e.g. "config"
*
* Since: 1.3.1
**/
void
fu_firmware_image_set_id (FuFirmwareImage *self, const gchar *id)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
g_free (priv->id);
priv->id = g_strdup (id);
}
/**
* fu_firmware_image_get_id:
* @self: a #FuPlugin
*
* Gets the image ID, typically set at construction.
*
* Returns: image ID, e.g. "config"
*
* Since: 1.3.1
**/
const gchar *
fu_firmware_image_get_id (FuFirmwareImage *self)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
return priv->id;
}
/**
* fu_firmware_image_set_addr:
* @self: a #FuPlugin
* @addr: integer
*
* Sets the base address of the image.
*
* Since: 1.3.1
**/
void
fu_firmware_image_set_addr (FuFirmwareImage *self, guint64 addr)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
priv->addr = addr;
}
/**
* fu_firmware_image_get_addr:
* @self: a #FuPlugin
*
* Gets the base address of the image.
*
* Returns: integer
*
* Since: 1.3.1
**/
guint64
fu_firmware_image_get_addr (FuFirmwareImage *self)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), G_MAXUINT64);
return priv->addr;
}
/**
* fu_firmware_image_set_idx:
* @self: a #FuPlugin
* @idx: integer
*
* Sets the index of the image which is used for ordering.
*
* Since: 1.3.1
**/
void
fu_firmware_image_set_idx (FuFirmwareImage *self, guint64 idx)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
priv->idx = idx;
}
/**
* fu_firmware_image_get_idx:
* @self: a #FuPlugin
*
* Gets the index of the image which is used for ordering.
*
* Returns: integer
*
* Since: 1.3.1
**/
guint64
fu_firmware_image_get_idx (FuFirmwareImage *self)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), G_MAXUINT64);
return priv->idx;
}
/**
* fu_firmware_image_set_bytes:
* @self: a #FuPlugin
* @bytes: A #GBytes
*
* Sets the contents of the image if not created with fu_firmware_image_new().
*
* Since: 1.3.1
**/
void
fu_firmware_image_set_bytes (FuFirmwareImage *self, GBytes *bytes)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
g_return_if_fail (bytes != NULL);
g_return_if_fail (priv->bytes == NULL);
priv->bytes = g_bytes_ref (bytes);
}
/**
* fu_firmware_image_parse:
* @self: A #FuFirmwareImage
* @fw: A #GBytes
* @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE
* @error: A #GError, or %NULL
*
* Parses a firmware image, typically checking image CRCs and/or headers.
*
* Returns: %TRUE for success
*
* Since: 1.5.0
**/
gboolean
fu_firmware_image_parse (FuFirmwareImage *self,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self);
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), FALSE);
g_return_val_if_fail (fw != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* subclassed */
if (klass->parse != NULL)
return klass->parse (self, fw, flags, error);
/* just add entire blob */
fu_firmware_image_set_bytes (self, fw);
return TRUE;
}
/**
* fu_firmware_image_build:
* @self: A #FuFirmwareImage
* @n: A #XbNode
* @error: A #GError, or %NULL
*
* Builds a firmware image from an XML manifest.
*
* Returns: %TRUE for success
*
* Since: 1.5.0
**/
gboolean
fu_firmware_image_build (FuFirmwareImage *self, XbNode *n, GError **error)
{
guint64 tmpval;
const gchar *tmp;
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), FALSE);
g_return_val_if_fail (XB_IS_NODE (n), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
tmp = xb_node_query_text (n, "version", NULL);
if (tmp != NULL)
fu_firmware_image_set_version (self, tmp);
tmp = xb_node_query_text (n, "id", NULL);
if (tmp != NULL)
fu_firmware_image_set_id (self, tmp);
tmpval = xb_node_query_text_as_uint (n, "idx", NULL);
if (tmpval != G_MAXUINT64)
fu_firmware_image_set_idx (self, tmpval);
tmpval = xb_node_query_text_as_uint (n, "addr", NULL);
if (tmpval != G_MAXUINT64)
fu_firmware_image_set_addr (self, tmpval);
tmp = xb_node_query_text (n, "filename", NULL);
if (tmp != NULL) {
g_autoptr(GBytes) blob = NULL;
blob = fu_common_get_contents_bytes (tmp, error);
if (blob == NULL)
return FALSE;
fu_firmware_image_set_bytes (self, blob);
fu_firmware_image_set_filename (self, tmp);
}
tmp = xb_node_query_text (n, "data", NULL);
if (tmp != NULL) {
gsize bufsz = 0;
g_autofree guchar *buf = NULL;
g_autoptr(GBytes) blob = NULL;
buf = g_base64_decode (tmp, &bufsz);
blob = g_bytes_new (buf, bufsz);
fu_firmware_image_set_bytes (self, blob);
}
/* success */
return TRUE;
}
/**
* fu_firmware_image_write:
* @self: a #FuPlugin
* @error: A #GError, or %NULL
*
* Writes the image, which will try to call a superclassed ->write() function.
*
* By default (and in most cases) this just provides the value set by the
* fu_firmware_image_set_bytes() function.
*
* Returns: (transfer full): a #GBytes of the bytes, or %NULL if the bytes is not set
*
* Since: 1.3.3
**/
GBytes *
fu_firmware_image_write (FuFirmwareImage *self, GError **error)
{
FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self);
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* optional vfunc */
if (klass->write != NULL)
return klass->write (self, error);
/* fall back to what was set manually */
if (priv->bytes == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no bytes found in firmware bytes %s", priv->id);
return NULL;
}
return g_bytes_ref (priv->bytes);
}
/**
* fu_firmware_image_write_chunk:
* @self: a #FuFirmwareImage
* @address: an address greater than dfu_element_get_address()
* @chunk_sz_max: the size of the new chunk
* @error: a #GError, or %NULL
*
* 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_common_bytes_pad() if padding is required.
*
* If the @address is larger than the size of the image then an error is returned.
*
* Return value: (transfer full): a #GBytes, or %NULL
*
* Since: 1.3.1
**/
GBytes *
fu_firmware_image_write_chunk (FuFirmwareImage *self,
guint64 address,
guint64 chunk_sz_max,
GError **error)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
gsize chunk_left;
guint64 offset;
/* 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 g_bytes_new_from_bytes (priv->bytes, offset, chunk_left);
/* check chunk */
return g_bytes_new_from_bytes (priv->bytes, offset, chunk_sz_max);
}
void
fu_firmware_image_add_string (FuFirmwareImage *self, guint idt, GString *str)
{
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self);
fu_common_string_append_kv (str, idt, G_OBJECT_TYPE_NAME (self), NULL);
if (priv->id != NULL)
fu_common_string_append_kv (str, idt, "ID", priv->id);
if (priv->idx != 0x0)
fu_common_string_append_kx (str, idt, "Index", priv->idx);
if (priv->addr != 0x0)
fu_common_string_append_kx (str, idt, "Address", priv->addr);
if (priv->version != NULL)
fu_common_string_append_kv (str, idt, "Version", priv->version);
if (priv->filename != NULL)
fu_common_string_append_kv (str, idt, "Filename", priv->filename);
if (priv->bytes != NULL) {
fu_common_string_append_kx (str, idt, "Data",
g_bytes_get_size (priv->bytes));
}
/* vfunc */
if (klass->to_string != NULL)
klass->to_string (self, idt, str);
}
/**
* fu_firmware_image_to_string:
* @self: A #FuFirmwareImage
*
* This allows us to easily print the object.
*
* Returns: a string value, or %NULL for invalid.
*
* Since: 1.3.1
**/
gchar *
fu_firmware_image_to_string (FuFirmwareImage *self)
{
GString *str = g_string_new (NULL);
fu_firmware_image_add_string (self, 0, str);
return g_string_free (str, FALSE);
}
static void
fu_firmware_image_init (FuFirmwareImage *self)
{
}
static void
fu_firmware_image_finalize (GObject *object)
{
FuFirmwareImage *self = FU_FIRMWARE_IMAGE (object);
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
g_free (priv->id);
g_free (priv->version);
g_free (priv->filename);
if (priv->bytes != NULL)
g_bytes_unref (priv->bytes);
G_OBJECT_CLASS (fu_firmware_image_parent_class)->finalize (object);
}
static void
fu_firmware_image_class_init (FuFirmwareImageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fu_firmware_image_finalize;
}
/**
* fu_firmware_image_new:
* @bytes: Optional #GBytes
*
* Creates an empty firmware_image object.
*
* Returns: a #FuFirmwareImage
*
* Since: 1.3.1
**/
FuFirmwareImage *
fu_firmware_image_new (GBytes *bytes)
{
FuFirmwareImage *self = g_object_new (FU_TYPE_FIRMWARE_IMAGE, NULL);
if (bytes != NULL)
fu_firmware_image_set_bytes (self, bytes);
return self;
}