mirror of
				https://git.proxmox.com/git/fwupd
				synced 2025-11-04 02:01:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			358 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			358 lines
		
	
	
		
			9.1 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:
 | 
						|
 *
 | 
						|
 * 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), FU_DFU_FIRMARE_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));
 | 
						|
}
 |