Coverage Report

Created: 2026-05-30 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-dfu-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2019 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuFirmware"
8
9
#include "config.h"
10
11
#include <string.h>
12
13
#include "fu-byte-array.h"
14
#include "fu-common.h"
15
#include "fu-crc.h"
16
#include "fu-dfu-firmware-private.h"
17
#include "fu-dfu-firmware-struct.h"
18
#include "fu-input-stream.h"
19
20
/**
21
 * FuDfuFirmware:
22
 *
23
 * A DFU firmware image.
24
 *
25
 * See also: [class@FuFirmware]
26
 */
27
28
typedef struct {
29
  guint16 vid;
30
  guint16 pid;
31
  guint16 release;
32
  guint16 dfu_version;
33
  guint8 footer_len;
34
} FuDfuFirmwarePrivate;
35
36
2.59k
G_DEFINE_TYPE_WITH_PRIVATE(FuDfuFirmware, fu_dfu_firmware, FU_TYPE_FIRMWARE)
37
2.59k
#define GET_PRIVATE(o) (fu_dfu_firmware_get_instance_private(o))
38
39
static void
40
fu_dfu_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
41
0
{
42
0
  FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware);
43
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
44
0
  fu_xmlb_builder_insert_kx(bn, "vendor", priv->vid);
45
0
  fu_xmlb_builder_insert_kx(bn, "product", priv->pid);
46
0
  fu_xmlb_builder_insert_kx(bn, "release", priv->release);
47
0
  fu_xmlb_builder_insert_kx(bn, "dfu_version", priv->dfu_version);
48
0
}
49
50
/* private */
51
guint8
52
fu_dfu_firmware_get_footer_len(FuDfuFirmware *self)
53
861
{
54
861
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
55
861
  g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0);
56
861
  return priv->footer_len;
57
861
}
58
59
/**
60
 * fu_dfu_firmware_get_vid:
61
 * @self: a #FuDfuFirmware
62
 *
63
 * Gets the vendor ID, or 0xffff for no restriction.
64
 *
65
 * Returns: integer
66
 *
67
 * Since: 1.3.3
68
 **/
69
guint16
70
fu_dfu_firmware_get_vid(FuDfuFirmware *self)
71
0
{
72
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
73
0
  g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0);
74
0
  return priv->vid;
75
0
}
76
77
/**
78
 * fu_dfu_firmware_get_pid:
79
 * @self: a #FuDfuFirmware
80
 *
81
 * Gets the product ID, or 0xffff for no restriction.
82
 *
83
 * Returns: integer
84
 *
85
 * Since: 1.3.3
86
 **/
87
guint16
88
fu_dfu_firmware_get_pid(FuDfuFirmware *self)
89
0
{
90
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
91
0
  g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0);
92
0
  return priv->pid;
93
0
}
94
95
/**
96
 * fu_dfu_firmware_get_release:
97
 * @self: a #FuDfuFirmware
98
 *
99
 * Gets the device ID, or 0xffff for no restriction.
100
 *
101
 * Returns: integer
102
 *
103
 * Since: 1.3.3
104
 **/
105
guint16
106
fu_dfu_firmware_get_release(FuDfuFirmware *self)
107
0
{
108
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
109
0
  g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0);
110
0
  return priv->release;
111
0
}
112
113
/**
114
 * fu_dfu_firmware_get_version:
115
 * @self: a #FuDfuFirmware
116
 *
117
 * Gets the file format version with is 0x0100 by default.
118
 *
119
 * Returns: integer
120
 *
121
 * Since: 1.3.3
122
 **/
123
guint16
124
fu_dfu_firmware_get_version(FuDfuFirmware *self)
125
0
{
126
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
127
0
  g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0);
128
0
  return priv->dfu_version;
129
0
}
130
131
/**
132
 * fu_dfu_firmware_set_vid:
133
 * @self: a #FuDfuFirmware
134
 * @vid: vendor ID, or 0xffff if the firmware should match any vendor
135
 *
136
 * Sets the vendor ID.
137
 *
138
 * Since: 1.3.3
139
 **/
140
void
141
fu_dfu_firmware_set_vid(FuDfuFirmware *self, guint16 vid)
142
0
{
143
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
144
0
  g_return_if_fail(FU_IS_DFU_FIRMWARE(self));
145
0
  priv->vid = vid;
146
0
}
147
148
/**
149
 * fu_dfu_firmware_set_pid:
150
 * @self: a #FuDfuFirmware
151
 * @pid: product ID, or 0xffff if the firmware should match any product
152
 *
153
 * Sets the product ID.
154
 *
155
 * Since: 1.3.3
156
 **/
157
void
158
fu_dfu_firmware_set_pid(FuDfuFirmware *self, guint16 pid)
159
0
{
160
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
161
0
  g_return_if_fail(FU_IS_DFU_FIRMWARE(self));
162
0
  priv->pid = pid;
163
0
}
164
165
/**
166
 * fu_dfu_firmware_set_release:
167
 * @self: a #FuDfuFirmware
168
 * @release: release, or 0xffff if the firmware should match any release
169
 *
170
 * Sets the release for the dfu firmware.
171
 *
172
 * Since: 1.3.3
173
 **/
174
void
175
fu_dfu_firmware_set_release(FuDfuFirmware *self, guint16 release)
176
0
{
177
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
178
0
  g_return_if_fail(FU_IS_DFU_FIRMWARE(self));
179
0
  priv->release = release;
180
0
}
181
182
/**
183
 * fu_dfu_firmware_set_version:
184
 * @self: a #FuDfuFirmware
185
 * @version: integer
186
 *
187
 * Sets the file format version.
188
 *
189
 * Since: 1.3.3
190
 **/
191
void
192
fu_dfu_firmware_set_version(FuDfuFirmware *self, guint16 version)
193
555
{
194
555
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
195
555
  g_return_if_fail(FU_IS_DFU_FIRMWARE(self));
196
555
  priv->dfu_version = version;
197
555
}
198
199
static gboolean
200
fu_dfu_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error)
201
0
{
202
0
  gsize streamsz = 0;
203
0
  if (!fu_input_stream_size(stream, &streamsz, error))
204
0
    return FALSE;
205
0
  if (streamsz < FU_STRUCT_DFU_FTR_SIZE) {
206
0
    g_set_error_literal(error,
207
0
            FWUPD_ERROR,
208
0
            FWUPD_ERROR_INVALID_FILE,
209
0
            "stream was too small");
210
0
    return FALSE;
211
0
  }
212
0
  return fu_struct_dfu_ftr_validate_stream(stream, streamsz - FU_STRUCT_DFU_FTR_SIZE, error);
213
0
}
214
215
gboolean
216
fu_dfu_firmware_parse_footer(FuDfuFirmware *self,
217
           GInputStream *stream,
218
           FuFirmwareParseFlags flags,
219
           GError **error)
220
491
{
221
491
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
222
491
  gsize bufsz;
223
491
  const guint8 *buf;
224
491
  g_autoptr(FuStructDfuFtr) st = NULL;
225
491
  g_autoptr(GBytes) fw = NULL;
226
227
491
  fw = fu_input_stream_read_bytes(stream, 0, G_MAXSIZE, NULL, error);
228
491
  if (fw == NULL)
229
0
    return FALSE;
230
491
  buf = g_bytes_get_data(fw, &bufsz);
231
232
  /* validate buffer is large enough for footer */
233
491
  if (bufsz < FU_STRUCT_DFU_FTR_SIZE) {
234
5
    g_set_error(error,
235
5
          FWUPD_ERROR,
236
5
          FWUPD_ERROR_INVALID_DATA,
237
5
          "file too small for DFU footer: 0x%x < 0x%x",
238
5
          (guint)bufsz,
239
5
          (guint)FU_STRUCT_DFU_FTR_SIZE);
240
5
    return FALSE;
241
5
  }
242
243
  /* parse */
244
486
  st = fu_struct_dfu_ftr_parse_stream(stream, bufsz - FU_STRUCT_DFU_FTR_SIZE, error);
245
486
  if (st == NULL)
246
36
    return FALSE;
247
450
  priv->vid = fu_struct_dfu_ftr_get_vid(st);
248
450
  priv->pid = fu_struct_dfu_ftr_get_pid(st);
249
450
  priv->release = fu_struct_dfu_ftr_get_release(st);
250
450
  priv->dfu_version = fu_struct_dfu_ftr_get_ver(st);
251
450
  priv->footer_len = fu_struct_dfu_ftr_get_len(st);
252
253
  /* verify the checksum */
254
450
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
255
0
    guint32 crc_new = 0;
256
0
    if (!fu_crc32_safe(FU_CRC_KIND_B32_JAMCRC,
257
0
           buf,
258
0
           bufsz,
259
0
           0x0,
260
0
           bufsz - 4,
261
0
           &crc_new,
262
0
           error))
263
0
      return FALSE;
264
0
    if (fu_struct_dfu_ftr_get_crc(st) != crc_new) {
265
0
      g_set_error(error,
266
0
            FWUPD_ERROR,
267
0
            FWUPD_ERROR_INTERNAL,
268
0
            "CRC failed, expected 0x%04x, got 0x%04x",
269
0
            crc_new,
270
0
            fu_struct_dfu_ftr_get_crc(st));
271
0
      return FALSE;
272
0
    }
273
0
  }
274
275
  /* check reported length */
276
450
  if (priv->footer_len > bufsz) {
277
4
    g_set_error(error,
278
4
          FWUPD_ERROR,
279
4
          FWUPD_ERROR_INTERNAL,
280
4
          "reported footer size 0x%04x larger than file 0x%04x",
281
4
          (guint)priv->footer_len,
282
4
          (guint)bufsz);
283
4
    return FALSE;
284
4
  }
285
286
  /* validate footer length matches the actual footer size */
287
446
  if (priv->footer_len != FU_STRUCT_DFU_FTR_SIZE) {
288
47
    g_set_error(error,
289
47
          FWUPD_ERROR,
290
47
          FWUPD_ERROR_INTERNAL,
291
47
          "reported footer size 0x%02x does not match expected size 0x%02x",
292
47
          (guint)priv->footer_len,
293
47
          (guint)FU_STRUCT_DFU_FTR_SIZE);
294
47
    return FALSE;
295
47
  }
296
297
  /* success */
298
399
  return TRUE;
299
446
}
300
301
static gboolean
302
fu_dfu_firmware_parse(FuFirmware *firmware,
303
          GInputStream *stream,
304
          FuFirmwareParseFlags flags,
305
          GError **error)
306
0
{
307
0
  FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware);
308
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
309
0
  gsize streamsz = 0;
310
0
  g_autoptr(GBytes) contents = NULL;
311
312
  /* parse footer */
313
0
  if (!fu_dfu_firmware_parse_footer(self, stream, flags, error))
314
0
    return FALSE;
315
316
  /* trim footer off */
317
0
  if (!fu_input_stream_size(stream, &streamsz, error))
318
0
    return FALSE;
319
0
  contents = fu_input_stream_read_bytes(stream, 0, streamsz - priv->footer_len, NULL, error);
320
0
  if (contents == NULL)
321
0
    return FALSE;
322
0
  fu_firmware_set_bytes(firmware, contents);
323
0
  return TRUE;
324
0
}
325
326
GByteArray *
327
fu_dfu_firmware_append_footer(FuDfuFirmware *self, GBytes *contents, GError **error)
328
128
{
329
128
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
330
128
  g_autoptr(GByteArray) buf = g_byte_array_new();
331
128
  g_autoptr(FuStructDfuFtr) st = fu_struct_dfu_ftr_new();
332
333
  /* add the raw firmware data, the footer-less-CRC, and only then the CRC */
334
128
  fu_byte_array_append_bytes(buf, contents);
335
128
  fu_struct_dfu_ftr_set_release(st, priv->release);
336
128
  fu_struct_dfu_ftr_set_pid(st, priv->pid);
337
128
  fu_struct_dfu_ftr_set_vid(st, priv->vid);
338
128
  fu_struct_dfu_ftr_set_ver(st, priv->dfu_version);
339
128
  g_byte_array_append(buf, st->buf->data, st->buf->len - sizeof(guint32));
340
128
  fu_byte_array_append_uint32(buf,
341
128
            fu_crc32(FU_CRC_KIND_B32_JAMCRC, buf->data, buf->len),
342
128
            G_LITTLE_ENDIAN);
343
128
  return g_steal_pointer(&buf);
344
128
}
345
346
static GByteArray *
347
fu_dfu_firmware_write(FuFirmware *firmware, GError **error)
348
0
{
349
0
  FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware);
350
0
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
351
0
  g_autoptr(GBytes) fw = NULL;
352
353
  /* can only contain one image */
354
0
  if (images->len > 1) {
355
0
    g_set_error_literal(error,
356
0
            FWUPD_ERROR,
357
0
            FWUPD_ERROR_NOT_SUPPORTED,
358
0
            "DFU only supports writing one image");
359
0
    return NULL;
360
0
  }
361
362
  /* add footer */
363
0
  fw = fu_firmware_get_bytes_with_patches(firmware, error);
364
0
  if (fw == NULL)
365
0
    return NULL;
366
0
  return fu_dfu_firmware_append_footer(self, fw, error);
367
0
}
368
369
static gboolean
370
fu_dfu_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
371
0
{
372
0
  FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware);
373
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
374
0
  guint64 tmp;
375
376
  /* optional properties */
377
0
  tmp = xb_node_query_text_as_uint(n, "vendor", NULL);
378
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
379
0
    priv->vid = tmp;
380
0
  tmp = xb_node_query_text_as_uint(n, "product", NULL);
381
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
382
0
    priv->pid = tmp;
383
0
  tmp = xb_node_query_text_as_uint(n, "release", NULL);
384
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
385
0
    priv->release = tmp;
386
0
  tmp = xb_node_query_text_as_uint(n, "dfu_version", NULL);
387
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
388
0
    priv->dfu_version = tmp;
389
390
  /* success */
391
0
  return TRUE;
392
0
}
393
394
static void
395
fu_dfu_firmware_init(FuDfuFirmware *self)
396
555
{
397
555
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
398
555
  priv->vid = 0xffff;
399
555
  priv->pid = 0xffff;
400
555
  priv->release = 0xffff;
401
555
  priv->dfu_version = FU_DFU_FIRMARE_VERSION_DFU_1_0;
402
555
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
403
555
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID);
404
555
  fu_firmware_set_size_max(FU_FIRMWARE(self), 128 * FU_MB);
405
555
}
406
407
static void
408
fu_dfu_firmware_class_init(FuDfuFirmwareClass *klass)
409
1
{
410
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
411
1
  firmware_class->validate = fu_dfu_firmware_validate;
412
1
  firmware_class->export = fu_dfu_firmware_export;
413
1
  firmware_class->parse = fu_dfu_firmware_parse;
414
1
  firmware_class->write = fu_dfu_firmware_write;
415
1
  firmware_class->build = fu_dfu_firmware_build;
416
1
}
417
418
/**
419
 * fu_dfu_firmware_new:
420
 *
421
 * Creates a new #FuFirmware of sub type Dfu
422
 *
423
 * Since: 1.3.3
424
 **/
425
FuFirmware *
426
fu_dfu_firmware_new(void)
427
0
{
428
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_DFU_FIRMWARE, NULL));
429
0
}