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
This commit is contained in:
Richard Hughes 2020-09-21 13:43:15 +01:00
parent f17db477eb
commit 41400a8cc6
11 changed files with 265 additions and 15 deletions

View File

@ -259,6 +259,63 @@ fu_firmware_image_parse (FuFirmwareImage *self,
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

View File

@ -8,6 +8,7 @@
#include <glib-object.h>
#include <fwupd.h>
#include <xmlb.h>
#define FU_TYPE_FIRMWARE_IMAGE (fu_firmware_image_get_type ())
G_DECLARE_DERIVABLE_TYPE (FuFirmwareImage, fu_firmware_image, FU, FIRMWARE_IMAGE, GObject)
@ -56,6 +57,9 @@ gboolean fu_firmware_image_parse (FuFirmwareImage *self,
GBytes *fw,
FwupdInstallFlags flags,
GError **error);
gboolean fu_firmware_image_build (FuFirmwareImage *self,
XbNode *n,
GError **error);
GBytes *fu_firmware_image_write (FuFirmwareImage *self,
GError **error);
GBytes *fu_firmware_image_write_chunk (FuFirmwareImage *self,

View File

@ -239,6 +239,56 @@ fu_firmware_parse (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError
return fu_firmware_parse_full (self, fw, 0x0, 0x0, flags, error);
}
/**
* fu_firmware_build:
* @self: A #FuFirmware
* @n: A #XbNode
* @error: A #GError, or %NULL
*
* Builds a firmware from an XML manifest.
*
* 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;
g_autoptr(GPtrArray) xb_images = 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);
/* parse images */
xb_images = xb_node_query (n, "image", 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(FuFirmwareImage) img = fu_firmware_image_new (NULL);
if (!fu_firmware_image_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_parse_file:
* @self: A #FuFirmware

View File

@ -32,8 +32,11 @@ struct _FuFirmwareClass
GBytes *fw,
FwupdInstallFlags flags,
GError **error);
gboolean (*build) (FuFirmware *self,
XbNode *n,
GError **error);
/*< private >*/
gpointer padding[28];
gpointer padding[27];
};
/**
@ -67,6 +70,9 @@ gboolean fu_firmware_tokenize (FuFirmware *self,
GBytes *fw,
FwupdInstallFlags flags,
GError **error);
gboolean fu_firmware_build (FuFirmware *self,
XbNode *n,
GError **error);
gboolean fu_firmware_parse (FuFirmware *self,
GBytes *fw,
FwupdInstallFlags flags,

View File

@ -1539,6 +1539,75 @@ fu_firmware_srec_tokenization_func (void)
g_assert_cmpint (rcd->buf->data[0], ==, 0x50);
}
static void
fu_firmware_build_func (void)
{
gboolean ret;
g_autofree gchar *str = NULL;
g_autoptr(FuFirmware) firmware = fu_firmware_new ();
g_autoptr(FuFirmwareImage) img = NULL;
g_autoptr(GBytes) blob = NULL;
g_autoptr(GBytes) blob2 = NULL;
g_autoptr(GError) error = NULL;
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;
const gchar *buf =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<firmware>\n"
" <version>1.2.3</version>\n"
" <image>\n"
" <version>4.5.6</version>\n"
" <id>header</id>\n"
" <idx>456</idx>\n"
" <addr>0x456</addr>\n"
" <data>aGVsbG8=</data>\n"
" </image>\n"
" <image>\n"
" <version>7.8.9</version>\n"
" <id>header</id>\n"
" <idx>789</idx>\n"
" <addr>0x789</addr>\n"
" </image>\n"
"</firmware>\n";
blob = g_bytes_new_static (buf, strlen (buf));
g_assert_no_error (error);
g_assert_nonnull (blob);
/* parse XML */
ret = xb_builder_source_load_bytes (source, blob, XB_BUILDER_SOURCE_FLAG_NONE, &error);
g_assert_no_error (error);
g_assert (ret);
xb_builder_import_source (builder, source);
silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (silo);
n = xb_silo_query_first (silo, "firmware", &error);
g_assert_no_error (error);
g_assert_nonnull (n);
/* build object */
ret = fu_firmware_build (firmware, n, &error);
g_assert_no_error (error);
g_assert (ret);
g_assert_cmpstr (fu_firmware_get_version (firmware), ==, "1.2.3");
/* verify image */
img = fu_firmware_get_image_by_id (firmware, "header", &error);
g_assert_no_error (error);
g_assert_nonnull (img);
g_assert_cmpstr (fu_firmware_image_get_version (img), ==, "4.5.6");
g_assert_cmpint (fu_firmware_image_get_idx (img), ==, 456);
g_assert_cmpint (fu_firmware_image_get_addr (img), ==, 0x456);
blob2 = fu_firmware_image_write (img, &error);
g_assert_no_error (error);
g_assert_nonnull (blob2);
g_assert_cmpint (g_bytes_get_size (blob2), ==, 5);
str = g_strndup (g_bytes_get_data (blob2, NULL), g_bytes_get_size (blob2));
g_assert_cmpstr (str, ==, "hello");
}
static void
fu_firmware_dfu_func (void)
{
@ -1976,6 +2045,7 @@ main (int argc, char **argv)
g_test_add_func ("/fwupd/smbios3", fu_smbios3_func);
g_test_add_func ("/fwupd/firmware", fu_firmware_func);
g_test_add_func ("/fwupd/firmware{dedupe}", fu_firmware_dedupe_func);
g_test_add_func ("/fwupd/firmware{build}", fu_firmware_build_func);
g_test_add_func ("/fwupd/firmware{ihex}", fu_firmware_ihex_func);
g_test_add_func ("/fwupd/firmware{ihex-offset}", fu_firmware_ihex_offset_func);
g_test_add_func ("/fwupd/firmware{ihex-signed}", fu_firmware_ihex_signed_func);

View File

@ -618,9 +618,11 @@ LIBFWUPDPLUGIN_1.5.0 {
fu_device_report_metadata_post;
fu_device_report_metadata_pre;
fu_firmware_add_flag;
fu_firmware_build;
fu_firmware_flag_from_string;
fu_firmware_flag_to_string;
fu_firmware_has_flag;
fu_firmware_image_build;
fu_firmware_image_get_filename;
fu_firmware_image_parse;
fu_firmware_image_set_filename;

View File

@ -24,6 +24,7 @@ dfu = static_library(
dependencies : [
giounix,
libm,
libxmlb,
gusb,
gudev,
],

View File

@ -76,6 +76,7 @@ if get_option('tests')
],
dependencies : [
gio,
libxmlb,
],
link_with : [
fwupd,

View File

@ -48,6 +48,7 @@ if get_option('tests')
],
dependencies : [
gio,
libxmlb,
],
link_with : [
fwupd,

View File

@ -1639,12 +1639,16 @@ fu_util_get_firmware_types (FuUtilPrivate *priv, gchar **values, GError **error)
}
static gchar *
fu_util_prompt_for_firmware_type (FuUtilPrivate *priv, GError **error)
fu_util_prompt_for_firmware_type (FuUtilPrivate *priv, gboolean add_builder, GError **error)
{
g_autoptr(GPtrArray) firmware_types = NULL;
guint idx;
firmware_types = fu_engine_get_firmware_gtype_ids (priv->engine);
/* add fake entry */
if (add_builder)
g_ptr_array_add (firmware_types, g_strdup ("builder"));
/* TRANSLATORS: get interactive prompt */
g_print ("%s\n", _("Choose a firmware type:"));
/* TRANSLATORS: this is to abort the interactive prompt */
@ -1697,7 +1701,7 @@ fu_util_firmware_parse (FuUtilPrivate *priv, gchar **values, GError **error)
/* find the GType to use */
if (firmware_type == NULL)
firmware_type = fu_util_prompt_for_firmware_type (priv, error);
firmware_type = fu_util_prompt_for_firmware_type (priv, TRUE, error);
if (firmware_type == NULL)
return FALSE;
gtype = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type);
@ -1716,6 +1720,53 @@ fu_util_firmware_parse (FuUtilPrivate *priv, gchar **values, GError **error)
return TRUE;
}
static FuFirmware *
fu_util_firmware_builder_new (FuUtilPrivate *priv, GBytes *fw, GError **error)
{
const gchar *tmp;
g_autoptr(FuFirmware) firmware = NULL;
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_bytes (source, fw,
XB_BUILDER_SOURCE_FLAG_NONE,
error)) {
g_prefix_error (error, "could not parse XML: ");
return NULL;
}
xb_builder_import_source (builder, source);
silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
if (silo == NULL)
return NULL;
/* create FuFirmware of specific GType */
n = xb_silo_query_first (silo, "firmware", error);
if (n == NULL)
return FALSE;
tmp = xb_node_get_attr (n, "gtype");
if (tmp != NULL) {
GType gtype = fu_engine_get_firmware_gtype_by_id (priv->engine, tmp);
if (gtype == G_TYPE_INVALID) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"GType %s not supported", tmp);
return NULL;
}
firmware = g_object_new (gtype, NULL);
} else {
firmware = fu_firmware_new ();
}
if (!fu_firmware_build (firmware, n, error))
return NULL;
/* success */
return g_steal_pointer (&firmware);
}
static gboolean
fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error)
{
@ -1756,20 +1807,29 @@ fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error)
/* find the GType to use */
if (firmware_type_src == NULL)
firmware_type_src = fu_util_prompt_for_firmware_type (priv, error);
firmware_type_src = fu_util_prompt_for_firmware_type (priv, TRUE, error);
if (firmware_type_src == NULL)
return FALSE;
if (firmware_type_dst == NULL)
firmware_type_dst = fu_util_prompt_for_firmware_type (priv, error);
firmware_type_dst = fu_util_prompt_for_firmware_type (priv, FALSE, error);
if (firmware_type_dst == NULL)
return FALSE;
gtype_src = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_src);
if (gtype_src == G_TYPE_INVALID) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"GType %s not supported", firmware_type_src);
return FALSE;
if (g_strcmp0 (firmware_type_src, "builder") == 0) {
firmware_src = fu_util_firmware_builder_new (priv, blob_src, error);
if (firmware_src == NULL)
return FALSE;
} else {
gtype_src = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_src);
if (gtype_src == G_TYPE_INVALID) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"GType %s not supported", firmware_type_src);
return FALSE;
}
firmware_src = g_object_new (gtype_src, NULL);
if (!fu_firmware_parse (firmware_src, blob_src, priv->flags, error))
return FALSE;
}
gtype_dst = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_dst);
if (gtype_dst == G_TYPE_INVALID) {
@ -1779,9 +1839,6 @@ fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error)
"GType %s not supported", firmware_type_dst);
return FALSE;
}
firmware_src = g_object_new (gtype_src, NULL);
if (!fu_firmware_parse (firmware_src, blob_src, priv->flags, error))
return FALSE;
str_src = fu_firmware_to_string (firmware_src);
g_print ("%s", str_src);

View File

@ -345,6 +345,7 @@ if get_option('tests')
fwupdplugin_incdir,
],
dependencies : [
libxmlb,
gio,
],
link_with : [