fwupd/libfwupdplugin/fu-dfuse-firmware.c
Mario Limonciello 1e17457b16 Allow building the documentation with gi-docgen and gtk-doc
Until gi-docgen is declared stable support either of them.
This effectively means that hand builds and CI builds will use
gi-docgen, but distro builds use gtk-doc-tools.
2021-06-09 22:21:53 +01:00

356 lines
9.3 KiB
C

/*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuFirmware"
#include "config.h"
#include <string.h>
#include "fu-chunk-private.h"
#include "fu-common.h"
#include "fu-dfu-firmware-private.h"
#include "fu-dfuse-firmware.h"
/**
* FuDfuseFirmware:
*
* An object that represents a DfuSe firmware image.
*
* See also: [class@FuDfuFirmware]
*/
G_DEFINE_TYPE (FuDfuseFirmware, fu_dfuse_firmware, FU_TYPE_DFU_FIRMWARE)
/* firmware: LE */
typedef struct __attribute__((packed)) {
guint8 sig[5];
guint8 ver;
guint32 image_size;
guint8 targets;
} DfuSeHdr;
/* image: LE */
typedef struct __attribute__((packed)) {
guint8 sig[6];
guint8 alt_setting;
guint32 target_named;
gchar target_name[255];
guint32 target_size;
guint32 chunks;
} DfuSeImageHdr;
/* element: LE */
typedef struct __attribute__((packed)) {
guint32 address;
guint32 size;
} DfuSeElementHdr;
G_STATIC_ASSERT(sizeof(DfuSeHdr) == 11);
G_STATIC_ASSERT(sizeof(DfuSeImageHdr) == 274);
G_STATIC_ASSERT(sizeof(DfuSeElementHdr) == 8);
static FuChunk *
fu_firmware_image_chunk_parse (FuDfuseFirmware *self,
GBytes *bytes,
gsize *offset,
GError **error)
{
DfuSeElementHdr hdr = { 0x0 };
gsize bufsz = 0;
gsize ftrlen = fu_dfu_firmware_get_footer_len (FU_DFU_FIRMWARE (self));
const guint8 *buf = g_bytes_get_data (bytes, &bufsz);
g_autoptr(FuChunk) chk = NULL;
g_autoptr(GBytes) blob = NULL;
/* check size */
if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */
buf, bufsz - ftrlen, *offset, /* src */
sizeof(hdr), error))
return NULL;
/* create new chunk */
*offset += sizeof(hdr);
blob = fu_common_bytes_new_offset (bytes, *offset,
GUINT32_FROM_LE (hdr.size),
error);
if (blob == NULL)
return NULL;
chk = fu_chunk_bytes_new (blob);
fu_chunk_set_address (chk, GUINT32_FROM_LE (hdr.address));
*offset += fu_chunk_get_data_sz (chk);
/* success */
return g_steal_pointer (&chk);
}
static FuFirmware *
fu_dfuse_firmware_image_parse (FuDfuseFirmware *self,
GBytes *bytes,
gsize *offset,
GError **error)
{
DfuSeImageHdr hdr = { 0x0 };
gsize bufsz = 0;
const guint8 *buf = g_bytes_get_data (bytes, &bufsz);
g_autoptr(FuFirmware) image = fu_firmware_new ();
/* verify image signature */
if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */
buf, bufsz, *offset, /* src */
sizeof(hdr), error))
return NULL;
if (memcmp (hdr.sig, "Target", sizeof(hdr.sig)) != 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"invalid DfuSe target signature");
return NULL;
}
/* set properties */
fu_firmware_set_idx (image, hdr.alt_setting);
if (GUINT32_FROM_LE (hdr.target_named) == 0x01) {
g_autofree gchar *img_id = NULL;
img_id = g_strndup (hdr.target_name, sizeof(hdr.target_name));
fu_firmware_set_id (image, img_id);
}
/* no chunks */
if (hdr.chunks == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"DfuSe image has no chunks");
return NULL;
}
/* parse chunks */
*offset += sizeof(hdr);
for (guint j = 0; j < GUINT32_FROM_LE (hdr.chunks); j++) {
g_autoptr(FuChunk) chk = NULL;
chk = fu_firmware_image_chunk_parse (self,
bytes,
offset,
error);
if (chk == NULL)
return NULL;
fu_firmware_add_chunk (image, chk);
}
/* success */
return g_steal_pointer (&image);
}
static gboolean
fu_dfuse_firmware_parse (FuFirmware *firmware,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
FuDfuFirmware *dfu_firmware = FU_DFU_FIRMWARE (firmware);
DfuSeHdr hdr = { 0x0 };
gsize bufsz = 0;
gsize offset = 0;
const guint8 *buf;
/* DFU footer first */
if (!fu_dfu_firmware_parse_footer (dfu_firmware, fw, flags, error))
return FALSE;
/* check the prefix */
buf = (const guint8 *) g_bytes_get_data (fw, &bufsz);
if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */
buf, bufsz, offset, /* src */
sizeof(hdr), error))
return FALSE;
if (memcmp (hdr.sig, "DfuSe", sizeof(hdr.sig)) != 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"invalid DfuSe prefix");
return FALSE;
}
/* check the version */
if (hdr.ver != 0x01) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"invalid DfuSe version, got %02x",
hdr.ver);
return FALSE;
}
/* check image size */
if (GUINT32_FROM_LE (hdr.image_size) !=
bufsz - fu_dfu_firmware_get_footer_len (dfu_firmware)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"invalid DfuSe image size, "
"got %" G_GUINT32_FORMAT ", "
"expected %" G_GSIZE_FORMAT,
GUINT32_FROM_LE (hdr.image_size),
bufsz - fu_dfu_firmware_get_footer_len (dfu_firmware));
return FALSE;
}
/* parse the image targets */
offset += sizeof(hdr);
for (guint i = 0; i < hdr.targets; i++) {
g_autoptr(FuFirmware) image = NULL;
image = fu_dfuse_firmware_image_parse (FU_DFUSE_FIRMWARE (firmware),
fw, &offset,
error);
if (image == NULL)
return FALSE;
fu_firmware_add_image (firmware, image);
}
return TRUE;
}
static GBytes *
fu_firmware_chunk_write (FuChunk *chk)
{
DfuSeElementHdr hdr = { 0x0 };
const guint8 *data = fu_chunk_get_data (chk);
gsize length = fu_chunk_get_data_sz (chk);
g_autoptr(GByteArray) buf = NULL;
buf = g_byte_array_sized_new (sizeof(DfuSeElementHdr) + length);
hdr.address = GUINT32_TO_LE (fu_chunk_get_address (chk));
hdr.size = GUINT32_TO_LE (length);
g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr));
g_byte_array_append (buf, data, length);
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
}
static GBytes *
fu_dfuse_firmware_write_image (FuFirmware *image, GError **error)
{
DfuSeImageHdr hdr = { 0x0 };
gsize totalsz = 0;
g_autoptr(GByteArray) buf = NULL;
g_autoptr(GPtrArray) blobs = NULL;
g_autoptr(GPtrArray) chunks = NULL;
/* get total size */
blobs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
chunks = fu_firmware_get_chunks (image, error);
if (chunks == NULL)
return NULL;
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index (chunks, i);
GBytes *bytes = fu_firmware_chunk_write (chk);
g_ptr_array_add (blobs, bytes);
totalsz += g_bytes_get_size (bytes);
}
/* mutable output buffer */
buf = g_byte_array_sized_new (sizeof(DfuSeImageHdr) + totalsz);
/* add prefix */
memcpy (hdr.sig, "Target", 6);
hdr.alt_setting = fu_firmware_get_idx (image);
if (fu_firmware_get_id (image) != NULL) {
hdr.target_named = GUINT32_TO_LE (0x01);
g_strlcpy ((gchar *) &hdr.target_name,
fu_firmware_get_id (image),
sizeof(hdr.target_name));
}
hdr.target_size = GUINT32_TO_LE (totalsz);
hdr.chunks = GUINT32_TO_LE (chunks->len);
g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr));
/* copy data */
for (guint i = 0; i < blobs->len; i++) {
GBytes *blob = g_ptr_array_index (blobs, i);
fu_byte_array_append_bytes (buf, blob);
}
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
}
static GBytes *
fu_dfuse_firmware_write (FuFirmware *firmware, GError **error)
{
DfuSeHdr hdr = { 0x0 };
gsize totalsz = 0;
g_autoptr(GByteArray) buf = NULL;
g_autoptr(GBytes) blob_noftr = NULL;
g_autoptr(GPtrArray) blobs = NULL;
g_autoptr(GPtrArray) images = NULL;
/* create mutable output buffer */
blobs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
images = fu_firmware_get_images (FU_FIRMWARE (firmware));
for (guint i = 0; i < images->len; i++) {
FuFirmware *img = g_ptr_array_index (images, i);
g_autoptr(GBytes) blob = NULL;
blob = fu_dfuse_firmware_write_image (img, error);
if (blob == NULL)
return NULL;
totalsz += g_bytes_get_size (blob);
g_ptr_array_add (blobs, g_steal_pointer (&blob));
}
buf = g_byte_array_sized_new (sizeof(DfuSeHdr) + totalsz);
/* DfuSe header */
memcpy (hdr.sig, "DfuSe", 5);
hdr.ver = 0x01;
hdr.image_size = GUINT32_TO_LE (sizeof(hdr) + totalsz);
if (images->len > G_MAXUINT8) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"too many (%u) images to write DfuSe file",
images->len);
return NULL;
}
hdr.targets = (guint8) images->len;
g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr));
/* copy images */
for (guint i = 0; i < blobs->len; i++) {
GBytes *blob = g_ptr_array_index (blobs, i);
fu_byte_array_append_bytes (buf, blob);
}
/* return blob */
blob_noftr = g_byte_array_free_to_bytes (g_steal_pointer (&buf));
return fu_dfu_firmware_append_footer (FU_DFU_FIRMWARE (firmware),
blob_noftr, error);
}
static void
fu_dfuse_firmware_init (FuDfuseFirmware *self)
{
fu_dfu_firmware_set_version (FU_DFU_FIRMWARE (self), DFU_VERSION_DFUSE);
}
static void
fu_dfuse_firmware_class_init (FuDfuseFirmwareClass *klass)
{
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
klass_firmware->parse = fu_dfuse_firmware_parse;
klass_firmware->write = fu_dfuse_firmware_write;
}
/**
* fu_dfuse_firmware_new:
*
* Creates a new #FuFirmware of sub type DfuSe
*
* Since: 1.5.6
**/
FuFirmware *
fu_dfuse_firmware_new (void)
{
return FU_FIRMWARE (g_object_new (FU_TYPE_DFUSE_FIRMWARE, NULL));
}