Coverage Report

Created: 2026-04-09 06:28

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.62k
G_DEFINE_TYPE_WITH_PRIVATE(FuDfuFirmware, fu_dfu_firmware, FU_TYPE_FIRMWARE)
37
2.62k
#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
616
{
54
616
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
55
616
  g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0);
56
616
  return priv->footer_len;
57
616
}
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
640
{
194
640
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
195
640
  g_return_if_fail(FU_IS_DFU_FIRMWARE(self));
196
640
  priv->dfu_version = version;
197
640
}
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
569
{
221
569
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
222
569
  gsize bufsz;
223
569
  const guint8 *buf;
224
569
  g_autoptr(FuStructDfuFtr) st = NULL;
225
569
  g_autoptr(GBytes) fw = NULL;
226
227
569
  fw = fu_input_stream_read_bytes(stream, 0, G_MAXSIZE, NULL, error);
228
569
  if (fw == NULL)
229
0
    return FALSE;
230
569
  buf = g_bytes_get_data(fw, &bufsz);
231
232
  /* parse */
233
569
  st = fu_struct_dfu_ftr_parse_stream(stream, bufsz - FU_STRUCT_DFU_FTR_SIZE, error);
234
569
  if (st == NULL)
235
42
    return FALSE;
236
527
  priv->vid = fu_struct_dfu_ftr_get_vid(st);
237
527
  priv->pid = fu_struct_dfu_ftr_get_pid(st);
238
527
  priv->release = fu_struct_dfu_ftr_get_release(st);
239
527
  priv->dfu_version = fu_struct_dfu_ftr_get_ver(st);
240
527
  priv->footer_len = fu_struct_dfu_ftr_get_len(st);
241
242
  /* verify the checksum */
243
527
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
244
0
    guint32 crc_new = 0;
245
0
    if (!fu_crc32_safe(FU_CRC_KIND_B32_JAMCRC,
246
0
           buf,
247
0
           bufsz,
248
0
           0x0,
249
0
           bufsz - 4,
250
0
           &crc_new,
251
0
           error))
252
0
      return FALSE;
253
0
    if (fu_struct_dfu_ftr_get_crc(st) != crc_new) {
254
0
      g_set_error(error,
255
0
            FWUPD_ERROR,
256
0
            FWUPD_ERROR_INTERNAL,
257
0
            "CRC failed, expected 0x%04x, got 0x%04x",
258
0
            crc_new,
259
0
            fu_struct_dfu_ftr_get_crc(st));
260
0
      return FALSE;
261
0
    }
262
0
  }
263
264
  /* check reported length */
265
527
  if (priv->footer_len > bufsz) {
266
3
    g_set_error(error,
267
3
          FWUPD_ERROR,
268
3
          FWUPD_ERROR_INTERNAL,
269
3
          "reported footer size 0x%04x larger than file 0x%04x",
270
3
          (guint)priv->footer_len,
271
3
          (guint)bufsz);
272
3
    return FALSE;
273
3
  }
274
275
  /* success */
276
524
  return TRUE;
277
527
}
278
279
static gboolean
280
fu_dfu_firmware_parse(FuFirmware *firmware,
281
          GInputStream *stream,
282
          FuFirmwareParseFlags flags,
283
          GError **error)
284
0
{
285
0
  FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware);
286
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
287
0
  gsize streamsz = 0;
288
0
  g_autoptr(GBytes) contents = NULL;
289
290
  /* parse footer */
291
0
  if (!fu_dfu_firmware_parse_footer(self, stream, flags, error))
292
0
    return FALSE;
293
294
  /* trim footer off */
295
0
  if (!fu_input_stream_size(stream, &streamsz, error))
296
0
    return FALSE;
297
0
  contents = fu_input_stream_read_bytes(stream, 0, streamsz - priv->footer_len, NULL, error);
298
0
  if (contents == NULL)
299
0
    return FALSE;
300
0
  fu_firmware_set_bytes(firmware, contents);
301
0
  return TRUE;
302
0
}
303
304
GByteArray *
305
fu_dfu_firmware_append_footer(FuDfuFirmware *self, GBytes *contents, GError **error)
306
156
{
307
156
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
308
156
  g_autoptr(GByteArray) buf = g_byte_array_new();
309
156
  g_autoptr(FuStructDfuFtr) st = fu_struct_dfu_ftr_new();
310
311
  /* add the raw firmware data, the footer-less-CRC, and only then the CRC */
312
156
  fu_byte_array_append_bytes(buf, contents);
313
156
  fu_struct_dfu_ftr_set_release(st, priv->release);
314
156
  fu_struct_dfu_ftr_set_pid(st, priv->pid);
315
156
  fu_struct_dfu_ftr_set_vid(st, priv->vid);
316
156
  fu_struct_dfu_ftr_set_ver(st, priv->dfu_version);
317
156
  g_byte_array_append(buf, st->buf->data, st->buf->len - sizeof(guint32));
318
156
  fu_byte_array_append_uint32(buf,
319
156
            fu_crc32(FU_CRC_KIND_B32_JAMCRC, buf->data, buf->len),
320
156
            G_LITTLE_ENDIAN);
321
156
  return g_steal_pointer(&buf);
322
156
}
323
324
static GByteArray *
325
fu_dfu_firmware_write(FuFirmware *firmware, GError **error)
326
0
{
327
0
  FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware);
328
0
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
329
0
  g_autoptr(GBytes) fw = NULL;
330
331
  /* can only contain one image */
332
0
  if (images->len > 1) {
333
0
    g_set_error_literal(error,
334
0
            FWUPD_ERROR,
335
0
            FWUPD_ERROR_NOT_SUPPORTED,
336
0
            "DFU only supports writing one image");
337
0
    return NULL;
338
0
  }
339
340
  /* add footer */
341
0
  fw = fu_firmware_get_bytes_with_patches(firmware, error);
342
0
  if (fw == NULL)
343
0
    return NULL;
344
0
  return fu_dfu_firmware_append_footer(self, fw, error);
345
0
}
346
347
static gboolean
348
fu_dfu_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
349
0
{
350
0
  FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware);
351
0
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
352
0
  guint64 tmp;
353
354
  /* optional properties */
355
0
  tmp = xb_node_query_text_as_uint(n, "vendor", NULL);
356
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
357
0
    priv->vid = tmp;
358
0
  tmp = xb_node_query_text_as_uint(n, "product", NULL);
359
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
360
0
    priv->pid = tmp;
361
0
  tmp = xb_node_query_text_as_uint(n, "release", NULL);
362
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
363
0
    priv->release = tmp;
364
0
  tmp = xb_node_query_text_as_uint(n, "dfu_version", NULL);
365
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
366
0
    priv->dfu_version = tmp;
367
368
  /* success */
369
0
  return TRUE;
370
0
}
371
372
static void
373
fu_dfu_firmware_init(FuDfuFirmware *self)
374
640
{
375
640
  FuDfuFirmwarePrivate *priv = GET_PRIVATE(self);
376
640
  priv->vid = 0xffff;
377
640
  priv->pid = 0xffff;
378
640
  priv->release = 0xffff;
379
640
  priv->dfu_version = FU_DFU_FIRMARE_VERSION_DFU_1_0;
380
640
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
381
640
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID);
382
640
}
383
384
static void
385
fu_dfu_firmware_class_init(FuDfuFirmwareClass *klass)
386
1
{
387
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
388
1
  firmware_class->validate = fu_dfu_firmware_validate;
389
1
  firmware_class->export = fu_dfu_firmware_export;
390
1
  firmware_class->parse = fu_dfu_firmware_parse;
391
1
  firmware_class->write = fu_dfu_firmware_write;
392
1
  firmware_class->build = fu_dfu_firmware_build;
393
1
}
394
395
/**
396
 * fu_dfu_firmware_new:
397
 *
398
 * Creates a new #FuFirmware of sub type Dfu
399
 *
400
 * Since: 1.3.3
401
 **/
402
FuFirmware *
403
fu_dfu_firmware_new(void)
404
0
{
405
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_DFU_FIRMWARE, NULL));
406
0
}