blob: fe86e41adb2573f171165c51a9eaab0394582b88 [file] [log] [blame]
Richard Hughes98972f42021-01-29 11:35:02 +00001/*
2 * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1+
5 */
6
7#define G_LOG_DOMAIN "FuFirmware"
8
9#include "config.h"
10
11#include <string.h>
12
13#include "fu-chunk-private.h"
14#include "fu-common.h"
15#include "fu-dfu-firmware-private.h"
16#include "fu-dfuse-firmware.h"
17
18/**
19 * SECTION:fu-dfuse-firmware
20 * @short_description: DfuSe firmware image
21 *
22 * An object that represents a DfuSe firmware image.
23 *
24 * See also: #FuDfuFirmware
25 */
26
27G_DEFINE_TYPE (FuDfuseFirmware, fu_dfuse_firmware, FU_TYPE_DFU_FIRMWARE)
28
29/* firmware: LE */
30typedef struct __attribute__((packed)) {
31 guint8 sig[5];
32 guint8 ver;
33 guint32 image_size;
34 guint8 targets;
35} DfuSeHdr;
36
37/* image: LE */
38typedef struct __attribute__((packed)) {
39 guint8 sig[6];
40 guint8 alt_setting;
41 guint32 target_named;
42 gchar target_name[255];
43 guint32 target_size;
44 guint32 chunks;
45} DfuSeImageHdr;
46
47/* element: LE */
48typedef struct __attribute__((packed)) {
49 guint32 address;
50 guint32 size;
51} DfuSeElementHdr;
52
53G_STATIC_ASSERT(sizeof(DfuSeHdr) == 11);
54G_STATIC_ASSERT(sizeof(DfuSeImageHdr) == 274);
55G_STATIC_ASSERT(sizeof(DfuSeElementHdr) == 8);
56
57static FuChunk *
Richard Hughes7a233302021-02-11 08:11:40 +000058fu_firmware_image_chunk_parse (FuDfuseFirmware *self,
59 GBytes *bytes,
60 gsize *offset,
61 GError **error)
Richard Hughes98972f42021-01-29 11:35:02 +000062{
63 DfuSeElementHdr hdr = { 0x0 };
64 gsize bufsz = 0;
Richard Hughes7a233302021-02-11 08:11:40 +000065 gsize ftrlen = fu_dfu_firmware_get_footer_len (FU_DFU_FIRMWARE (self));
Richard Hughes98972f42021-01-29 11:35:02 +000066 const guint8 *buf = g_bytes_get_data (bytes, &bufsz);
67 g_autoptr(FuChunk) chk = NULL;
68 g_autoptr(GBytes) blob = NULL;
69
70 /* check size */
71 if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */
Richard Hughes7a233302021-02-11 08:11:40 +000072 buf, bufsz - ftrlen, *offset, /* src */
Richard Hughes98972f42021-01-29 11:35:02 +000073 sizeof(hdr), error))
74 return NULL;
75
76 /* create new chunk */
77 *offset += sizeof(hdr);
78 blob = fu_common_bytes_new_offset (bytes, *offset,
79 GUINT32_FROM_LE (hdr.size),
80 error);
81 if (blob == NULL)
82 return NULL;
83 chk = fu_chunk_bytes_new (blob);
84 fu_chunk_set_address (chk, GUINT32_FROM_LE (hdr.address));
85 *offset += fu_chunk_get_data_sz (chk);
86
87 /* success */
88 return g_steal_pointer (&chk);
89}
90
Richard Hughes1981c632021-03-09 09:44:12 +000091static FuFirmware *
Richard Hughes7a233302021-02-11 08:11:40 +000092fu_dfuse_firmware_image_parse (FuDfuseFirmware *self,
93 GBytes *bytes,
94 gsize *offset,
95 GError **error)
Richard Hughes98972f42021-01-29 11:35:02 +000096{
97 DfuSeImageHdr hdr = { 0x0 };
98 gsize bufsz = 0;
99 const guint8 *buf = g_bytes_get_data (bytes, &bufsz);
Richard Hughes1981c632021-03-09 09:44:12 +0000100 g_autoptr(FuFirmware) image = fu_firmware_new ();
Richard Hughes98972f42021-01-29 11:35:02 +0000101
102 /* verify image signature */
103 if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */
104 buf, bufsz, *offset, /* src */
105 sizeof(hdr), error))
106 return NULL;
107 if (memcmp (hdr.sig, "Target", sizeof(hdr.sig)) != 0) {
108 g_set_error_literal (error,
109 FWUPD_ERROR,
110 FWUPD_ERROR_INVALID_FILE,
111 "invalid DfuSe target signature");
112 return NULL;
113 }
114
115 /* set properties */
Richard Hughes1981c632021-03-09 09:44:12 +0000116 fu_firmware_set_idx (image, hdr.alt_setting);
Richard Hughesabdc82b2021-02-08 17:40:11 +0000117 if (GUINT32_FROM_LE (hdr.target_named) == 0x01) {
118 g_autofree gchar *img_id = NULL;
119 img_id = g_strndup (hdr.target_name, sizeof(hdr.target_name));
Richard Hughes1981c632021-03-09 09:44:12 +0000120 fu_firmware_set_id (image, img_id);
Richard Hughesabdc82b2021-02-08 17:40:11 +0000121 }
Richard Hughes98972f42021-01-29 11:35:02 +0000122
Richard Hughes9bb5d272021-02-11 12:52:47 +0000123 /* no chunks */
124 if (hdr.chunks == 0) {
125 g_set_error_literal (error,
126 FWUPD_ERROR,
127 FWUPD_ERROR_INVALID_FILE,
128 "DfuSe image has no chunks");
129 return NULL;
130 }
131
Richard Hughes98972f42021-01-29 11:35:02 +0000132 /* parse chunks */
133 *offset += sizeof(hdr);
134 for (guint j = 0; j < GUINT32_FROM_LE (hdr.chunks); j++) {
135 g_autoptr(FuChunk) chk = NULL;
Richard Hughes7a233302021-02-11 08:11:40 +0000136 chk = fu_firmware_image_chunk_parse (self,
137 bytes,
138 offset,
139 error);
Richard Hughes98972f42021-01-29 11:35:02 +0000140 if (chk == NULL)
141 return NULL;
Richard Hughes1981c632021-03-09 09:44:12 +0000142 fu_firmware_add_chunk (image, chk);
Richard Hughes98972f42021-01-29 11:35:02 +0000143 }
144
145 /* success */
146 return g_steal_pointer (&image);
147}
148
149static gboolean
150fu_dfuse_firmware_parse (FuFirmware *firmware,
151 GBytes *fw,
152 guint64 addr_start,
153 guint64 addr_end,
154 FwupdInstallFlags flags,
155 GError **error)
156{
157 FuDfuFirmware *dfu_firmware = FU_DFU_FIRMWARE (firmware);
158 DfuSeHdr hdr = { 0x0 };
159 gsize bufsz = 0;
160 gsize offset = 0;
161 const guint8 *buf;
162
163 /* DFU footer first */
164 if (!fu_dfu_firmware_parse_footer (dfu_firmware, fw, flags, error))
165 return FALSE;
166
167 /* check the prefix */
168 buf = (const guint8 *) g_bytes_get_data (fw, &bufsz);
169 if (!fu_memcpy_safe ((guint8 *) &hdr, sizeof(hdr), 0x0, /* dst */
170 buf, bufsz, offset, /* src */
171 sizeof(hdr), error))
172 return FALSE;
173 if (memcmp (hdr.sig, "DfuSe", sizeof(hdr.sig)) != 0) {
174 g_set_error_literal (error,
175 FWUPD_ERROR,
176 FWUPD_ERROR_INTERNAL,
177 "invalid DfuSe prefix");
178 return FALSE;
179 }
180
181 /* check the version */
182 if (hdr.ver != 0x01) {
183 g_set_error (error,
184 FWUPD_ERROR,
185 FWUPD_ERROR_INTERNAL,
186 "invalid DfuSe version, got %02x",
187 hdr.ver);
188 return FALSE;
189 }
190
191 /* check image size */
192 if (GUINT32_FROM_LE (hdr.image_size) !=
193 bufsz - fu_dfu_firmware_get_footer_len (dfu_firmware)) {
194 g_set_error (error,
195 FWUPD_ERROR,
196 FWUPD_ERROR_INTERNAL,
197 "invalid DfuSe image size, "
198 "got %" G_GUINT32_FORMAT ", "
199 "expected %" G_GSIZE_FORMAT,
200 GUINT32_FROM_LE (hdr.image_size),
201 bufsz - fu_dfu_firmware_get_footer_len (dfu_firmware));
202 return FALSE;
203 }
204
205 /* parse the image targets */
206 offset += sizeof(hdr);
207 for (guint i = 0; i < hdr.targets; i++) {
Richard Hughes1981c632021-03-09 09:44:12 +0000208 g_autoptr(FuFirmware) image = NULL;
Richard Hughes7a233302021-02-11 08:11:40 +0000209 image = fu_dfuse_firmware_image_parse (FU_DFUSE_FIRMWARE (firmware),
210 fw, &offset,
211 error);
Richard Hughes98972f42021-01-29 11:35:02 +0000212 if (image == NULL)
213 return FALSE;
214 fu_firmware_add_image (firmware, image);
215 }
216 return TRUE;
217}
218
219static GBytes *
Richard Hughes1981c632021-03-09 09:44:12 +0000220fu_firmware_chunk_write (FuChunk *chk)
Richard Hughes98972f42021-01-29 11:35:02 +0000221{
222 DfuSeElementHdr hdr = { 0x0 };
223 const guint8 *data = fu_chunk_get_data (chk);
224 gsize length = fu_chunk_get_data_sz (chk);
225 g_autoptr(GByteArray) buf = NULL;
226
227 buf = g_byte_array_sized_new (sizeof(DfuSeElementHdr) + length);
228 hdr.address = GUINT32_TO_LE (fu_chunk_get_address (chk));
229 hdr.size = GUINT32_TO_LE (length);
230 g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr));
231 g_byte_array_append (buf, data, length);
232 return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
233}
234
235static GBytes *
Richard Hughes1981c632021-03-09 09:44:12 +0000236fu_dfuse_firmware_write_image (FuFirmware *image, GError **error)
Richard Hughes98972f42021-01-29 11:35:02 +0000237{
238 DfuSeImageHdr hdr = { 0x0 };
239 gsize totalsz = 0;
240 g_autoptr(GByteArray) buf = NULL;
241 g_autoptr(GPtrArray) blobs = NULL;
242 g_autoptr(GPtrArray) chunks = NULL;
243
244 /* get total size */
245 blobs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
Richard Hughes1981c632021-03-09 09:44:12 +0000246 chunks = fu_firmware_get_chunks (image, error);
Richard Hughes87a80912021-02-11 12:54:15 +0000247 if (chunks == NULL)
248 return NULL;
Richard Hughes98972f42021-01-29 11:35:02 +0000249 for (guint i = 0; i < chunks->len; i++) {
250 FuChunk *chk = g_ptr_array_index (chunks, i);
Richard Hughes1981c632021-03-09 09:44:12 +0000251 GBytes *bytes = fu_firmware_chunk_write (chk);
Richard Hughes98972f42021-01-29 11:35:02 +0000252 g_ptr_array_add (blobs, bytes);
253 totalsz += g_bytes_get_size (bytes);
254 }
255
256 /* mutable output buffer */
257 buf = g_byte_array_sized_new (sizeof(DfuSeImageHdr) + totalsz);
258
259 /* add prefix */
260 memcpy (hdr.sig, "Target", 6);
Richard Hughes1981c632021-03-09 09:44:12 +0000261 hdr.alt_setting = fu_firmware_get_idx (image);
262 if (fu_firmware_get_id (image) != NULL) {
Richard Hughes98972f42021-01-29 11:35:02 +0000263 hdr.target_named = GUINT32_TO_LE (0x01);
264 g_strlcpy ((gchar *) &hdr.target_name,
Richard Hughes1981c632021-03-09 09:44:12 +0000265 fu_firmware_get_id (image),
Richard Hughes98972f42021-01-29 11:35:02 +0000266 sizeof(hdr.target_name));
267 }
268 hdr.target_size = GUINT32_TO_LE (totalsz);
269 hdr.chunks = GUINT32_TO_LE (chunks->len);
270 g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr));
271
272 /* copy data */
273 for (guint i = 0; i < blobs->len; i++) {
274 GBytes *blob = g_ptr_array_index (blobs, i);
275 g_byte_array_append (buf,
276 g_bytes_get_data (blob, NULL),
277 g_bytes_get_size (blob));
278 }
279 return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
280}
281
282static GBytes *
283fu_dfuse_firmware_write (FuFirmware *firmware, GError **error)
284{
285 DfuSeHdr hdr = { 0x0 };
286 gsize totalsz = 0;
287 g_autoptr(GByteArray) buf = NULL;
288 g_autoptr(GBytes) blob_noftr = NULL;
289 g_autoptr(GPtrArray) blobs = NULL;
290 g_autoptr(GPtrArray) images = NULL;
291
292 /* create mutable output buffer */
293 blobs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
294 images = fu_firmware_get_images (FU_FIRMWARE (firmware));
295 for (guint i = 0; i < images->len; i++) {
Richard Hughes1981c632021-03-09 09:44:12 +0000296 FuFirmware *img = g_ptr_array_index (images, i);
Richard Hughes87a80912021-02-11 12:54:15 +0000297 g_autoptr(GBytes) blob = NULL;
Richard Hughes1981c632021-03-09 09:44:12 +0000298 blob = fu_dfuse_firmware_write_image (img, error);
Richard Hughes87a80912021-02-11 12:54:15 +0000299 if (blob == NULL)
300 return NULL;
Richard Hughes98972f42021-01-29 11:35:02 +0000301 totalsz += g_bytes_get_size (blob);
Richard Hughes87a80912021-02-11 12:54:15 +0000302 g_ptr_array_add (blobs, g_steal_pointer (&blob));
Richard Hughes98972f42021-01-29 11:35:02 +0000303 }
304 buf = g_byte_array_sized_new (sizeof(DfuSeHdr) + totalsz);
305
306 /* DfuSe header */
307 memcpy (hdr.sig, "DfuSe", 5);
308 hdr.ver = 0x01;
309 hdr.image_size = GUINT32_TO_LE (sizeof(hdr) + totalsz);
310 if (images->len > G_MAXUINT8) {
311 g_set_error (error,
312 FWUPD_ERROR,
313 FWUPD_ERROR_INTERNAL,
314 "too many (%u) images to write DfuSe file",
315 images->len);
316 return NULL;
317 }
318 hdr.targets = (guint8) images->len;
319 g_byte_array_append (buf, (const guint8 *) &hdr, sizeof(hdr));
320
321 /* copy images */
322 for (guint i = 0; i < blobs->len; i++) {
323 GBytes *blob = g_ptr_array_index (blobs, i);
324 g_byte_array_append (buf,
325 g_bytes_get_data (blob, NULL),
326 g_bytes_get_size (blob));
327 }
328
329 /* return blob */
330 blob_noftr = g_byte_array_free_to_bytes (g_steal_pointer (&buf));
331 return fu_dfu_firmware_append_footer (FU_DFU_FIRMWARE (firmware),
332 blob_noftr, error);
333}
334
335static void
336fu_dfuse_firmware_init (FuDfuseFirmware *self)
337{
338 fu_dfu_firmware_set_version (FU_DFU_FIRMWARE (self), DFU_VERSION_DFUSE);
339}
340
341static void
342fu_dfuse_firmware_class_init (FuDfuseFirmwareClass *klass)
343{
344 FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
345 klass_firmware->parse = fu_dfuse_firmware_parse;
346 klass_firmware->write = fu_dfuse_firmware_write;
347}
348
349/**
350 * fu_dfuse_firmware_new:
351 *
352 * Creates a new #FuFirmware of sub type Dfuse
353 *
354 * Since: 1.5.6
355 **/
356FuFirmware *
357fu_dfuse_firmware_new (void)
358{
359 return FU_FIRMWARE (g_object_new (FU_TYPE_DFUSE_FIRMWARE, NULL));
360}