Coverage Report

Created: 2026-04-28 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-cfu-offer.c
Line
Count
Source
1
/*
2
 * Copyright 2021 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 "fu-cfu-firmware-struct.h"
12
#include "fu-cfu-offer.h"
13
#include "fu-common.h"
14
#include "fu-string.h"
15
#include "fu-version-common.h"
16
17
/**
18
 * FuCfuOffer:
19
 *
20
 * A CFU offer. This is a 16 byte blob which contains enough data for the device to either accept
21
 * or refuse a firmware payload. The offer may be loaded from disk, network, or even constructed
22
 * manually. There is much left to how the specific firmware implements CFU, and it's expected
23
 * that multiple different plugins will use this offer in different ways.
24
 *
25
 * Documented: https://docs.microsoft.com/en-us/windows-hardware/drivers/cfu/cfu-specification
26
 *
27
 * See also: [class@FuFirmware]
28
 */
29
30
typedef struct {
31
  guint8 segment_number;
32
  gboolean force_immediate_reset;
33
  gboolean force_ignore_version;
34
  guint8 component_id;
35
  guint8 token;
36
  guint32 hw_variant;
37
  guint8 protocol_revision;
38
  guint8 bank;
39
  guint8 milestone;
40
  guint16 product_id;
41
} FuCfuOfferPrivate;
42
43
752
G_DEFINE_TYPE_WITH_PRIVATE(FuCfuOffer, fu_cfu_offer, FU_TYPE_FIRMWARE)
44
752
#define GET_PRIVATE(o) (fu_cfu_offer_get_instance_private(o))
45
46
static void
47
fu_cfu_offer_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
48
0
{
49
0
  FuCfuOffer *self = FU_CFU_OFFER(firmware);
50
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
51
0
  fu_xmlb_builder_insert_kx(bn, "segment_number", priv->segment_number);
52
0
  fu_xmlb_builder_insert_kb(bn, "force_immediate_reset", priv->force_immediate_reset);
53
0
  fu_xmlb_builder_insert_kb(bn, "force_ignore_version", priv->force_ignore_version);
54
0
  fu_xmlb_builder_insert_kx(bn, "component_id", priv->component_id);
55
0
  fu_xmlb_builder_insert_kx(bn, "token", priv->token);
56
0
  fu_xmlb_builder_insert_kx(bn, "hw_variant", priv->hw_variant);
57
0
  fu_xmlb_builder_insert_kx(bn, "protocol_revision", priv->protocol_revision);
58
0
  fu_xmlb_builder_insert_kx(bn, "bank", priv->bank);
59
0
  fu_xmlb_builder_insert_kx(bn, "milestone", priv->milestone);
60
0
  fu_xmlb_builder_insert_kx(bn, "product_id", priv->product_id);
61
0
}
62
63
/**
64
 * fu_cfu_offer_get_segment_number:
65
 * @self: a #FuCfuOffer
66
 *
67
 * Gets the part of the firmware that is being transferred.
68
 *
69
 * Returns: integer
70
 *
71
 * Since: 1.7.0
72
 **/
73
guint8
74
fu_cfu_offer_get_segment_number(FuCfuOffer *self)
75
0
{
76
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
77
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0);
78
0
  return priv->segment_number;
79
0
}
80
81
/**
82
 * fu_cfu_offer_get_force_immediate_reset:
83
 * @self: a #FuCfuOffer
84
 *
85
 * Gets if the in-situ firmware should reset into the new firmware immediately, rather than waiting
86
 * for the next time the device is replugged.
87
 *
88
 * Returns: boolean
89
 *
90
 * Since: 1.7.0
91
 **/
92
gboolean
93
fu_cfu_offer_get_force_immediate_reset(FuCfuOffer *self)
94
0
{
95
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
96
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), FALSE);
97
0
  return priv->force_immediate_reset;
98
0
}
99
100
/**
101
 * fu_cfu_offer_get_force_ignore_version:
102
 * @self: a #FuCfuOffer
103
 *
104
 * Gets if the in-situ firmware should ignore version mismatch (e.g. downgrade).
105
 *
106
 * Returns: boolean
107
 *
108
 * Since: 1.7.0
109
 **/
110
gboolean
111
fu_cfu_offer_get_force_ignore_version(FuCfuOffer *self)
112
0
{
113
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
114
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), FALSE);
115
0
  return priv->force_ignore_version;
116
0
}
117
118
/**
119
 * fu_cfu_offer_get_component_id:
120
 * @self: a #FuCfuOffer
121
 *
122
 * Gets the component in the device to apply the firmware update.
123
 *
124
 * Returns: integer
125
 *
126
 * Since: 1.7.0
127
 **/
128
guint8
129
fu_cfu_offer_get_component_id(FuCfuOffer *self)
130
0
{
131
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
132
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0);
133
0
  return priv->component_id;
134
0
}
135
136
/**
137
 * fu_cfu_offer_get_token:
138
 * @self: a #FuCfuOffer
139
 *
140
 * Gets the token to identify the user specific software making the offer.
141
 *
142
 * Returns: integer
143
 *
144
 * Since: 1.7.0
145
 **/
146
guint8
147
fu_cfu_offer_get_token(FuCfuOffer *self)
148
0
{
149
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
150
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0);
151
0
  return priv->token;
152
0
}
153
154
/**
155
 * fu_cfu_offer_get_hw_variant:
156
 * @self: a #FuCfuOffer
157
 *
158
 * Gets the hardware variant bitmask corresponding with compatible firmware.
159
 *
160
 * Returns: integer
161
 *
162
 * Since: 1.7.0
163
 **/
164
guint32
165
fu_cfu_offer_get_hw_variant(FuCfuOffer *self)
166
0
{
167
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
168
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0);
169
0
  return priv->hw_variant;
170
0
}
171
172
/**
173
 * fu_cfu_offer_get_protocol_revision:
174
 * @self: a #FuCfuOffer
175
 *
176
 * Gets the CFU protocol version.
177
 *
178
 * Returns: integer
179
 *
180
 * Since: 1.7.0
181
 **/
182
guint8
183
fu_cfu_offer_get_protocol_revision(FuCfuOffer *self)
184
0
{
185
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
186
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0);
187
0
  return priv->protocol_revision;
188
0
}
189
190
/**
191
 * fu_cfu_offer_get_bank:
192
 * @self: a #FuCfuOffer
193
 *
194
 * Gets the bank register, used if multiple banks are supported.
195
 *
196
 * Returns: integer
197
 *
198
 * Since: 1.7.0
199
 **/
200
guint8
201
fu_cfu_offer_get_bank(FuCfuOffer *self)
202
0
{
203
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
204
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0);
205
0
  return priv->bank;
206
0
}
207
208
/**
209
 * fu_cfu_offer_get_milestone:
210
 * @self: a #FuCfuOffer
211
 *
212
 * Gets the milestone, which can be used as a version for example EV1, EVT etc.
213
 *
214
 * Returns: integer
215
 *
216
 * Since: 1.7.0
217
 **/
218
guint8
219
fu_cfu_offer_get_milestone(FuCfuOffer *self)
220
0
{
221
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
222
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0);
223
0
  return priv->milestone;
224
0
}
225
226
/**
227
 * fu_cfu_offer_get_product_id:
228
 * @self: a #FuCfuOffer
229
 *
230
 * Gets the product ID for this CFU image.
231
 *
232
 * Returns: integer
233
 *
234
 * Since: 1.7.0
235
 **/
236
guint16
237
fu_cfu_offer_get_product_id(FuCfuOffer *self)
238
0
{
239
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
240
0
  g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0);
241
0
  return priv->product_id;
242
0
}
243
244
/**
245
 * fu_cfu_offer_set_segment_number:
246
 * @self: a #FuCfuOffer
247
 * @segment_number: integer
248
 *
249
 * Sets the part of the firmware that is being transferred.
250
 *
251
 * Since: 1.7.0
252
 **/
253
void
254
fu_cfu_offer_set_segment_number(FuCfuOffer *self, guint8 segment_number)
255
0
{
256
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
257
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
258
0
  priv->segment_number = segment_number;
259
0
}
260
261
/**
262
 * fu_cfu_offer_set_force_immediate_reset:
263
 * @self: a #FuCfuOffer
264
 * @force_immediate_reset: boolean
265
 *
266
 * Sets if the in-situ firmware should reset into the new firmware immediately, rather than waiting
267
 * for the next time the device is replugged.
268
 *
269
 * Since: 1.7.0
270
 **/
271
void
272
fu_cfu_offer_set_force_immediate_reset(FuCfuOffer *self, gboolean force_immediate_reset)
273
0
{
274
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
275
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
276
0
  priv->force_immediate_reset = force_immediate_reset;
277
0
}
278
279
/**
280
 * fu_cfu_offer_set_force_ignore_version:
281
 * @self: a #FuCfuOffer
282
 * @force_ignore_version: boolean
283
 *
284
 * Sets if the in-situ firmware should ignore version mismatch (e.g. downgrade).
285
 *
286
 * Since: 1.7.0
287
 **/
288
void
289
fu_cfu_offer_set_force_ignore_version(FuCfuOffer *self, gboolean force_ignore_version)
290
0
{
291
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
292
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
293
0
  priv->force_ignore_version = force_ignore_version;
294
0
}
295
296
/**
297
 * fu_cfu_offer_set_component_id:
298
 * @self: a #FuCfuOffer
299
 * @component_id: integer
300
 *
301
 * Sets the component in the device to apply the firmware update.
302
 *
303
 * Since: 1.7.0
304
 **/
305
void
306
fu_cfu_offer_set_component_id(FuCfuOffer *self, guint8 component_id)
307
0
{
308
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
309
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
310
0
  priv->component_id = component_id;
311
0
}
312
313
/**
314
 * fu_cfu_offer_set_token:
315
 * @self: a #FuCfuOffer
316
 * @token: integer
317
 *
318
 * Sets the token to identify the user specific software making the offer.
319
 *
320
 * Since: 1.7.0
321
 **/
322
void
323
fu_cfu_offer_set_token(FuCfuOffer *self, guint8 token)
324
0
{
325
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
326
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
327
0
  priv->token = token;
328
0
}
329
330
/**
331
 * fu_cfu_offer_set_hw_variant:
332
 * @self: a #FuCfuOffer
333
 * @hw_variant: integer
334
 *
335
 * Sets the hardware variant bitmask corresponding with compatible firmware.
336
 *
337
 * Since: 1.7.0
338
 **/
339
void
340
fu_cfu_offer_set_hw_variant(FuCfuOffer *self, guint32 hw_variant)
341
0
{
342
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
343
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
344
0
  priv->hw_variant = hw_variant;
345
0
}
346
347
/**
348
 * fu_cfu_offer_set_protocol_revision:
349
 * @self: a #FuCfuOffer
350
 * @protocol_revision: integer
351
 *
352
 * Sets the CFU protocol version.
353
 *
354
 * Since: 1.7.0
355
 **/
356
void
357
fu_cfu_offer_set_protocol_revision(FuCfuOffer *self, guint8 protocol_revision)
358
0
{
359
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
360
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
361
0
  g_return_if_fail(protocol_revision <= 0b1111);
362
0
  priv->protocol_revision = protocol_revision;
363
0
}
364
365
/**
366
 * fu_cfu_offer_set_bank:
367
 * @self: a #FuCfuOffer
368
 * @bank: integer
369
 *
370
 * Sets bank register, used if multiple banks are supported.
371
 *
372
 * Since: 1.7.0
373
 **/
374
void
375
fu_cfu_offer_set_bank(FuCfuOffer *self, guint8 bank)
376
0
{
377
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
378
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
379
0
  g_return_if_fail(bank <= 0b11);
380
0
  priv->bank = bank;
381
0
}
382
383
/**
384
 * fu_cfu_offer_set_milestone:
385
 * @self: a #FuCfuOffer
386
 * @milestone: integer
387
 *
388
 * Sets the milestone, which can be used as a version for example EV1, EVT etc.
389
 *
390
 * Since: 1.7.0
391
 **/
392
void
393
fu_cfu_offer_set_milestone(FuCfuOffer *self, guint8 milestone)
394
0
{
395
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
396
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
397
0
  g_return_if_fail(milestone <= 0b111);
398
0
  priv->milestone = milestone;
399
0
}
400
401
/**
402
 * fu_cfu_offer_set_product_id:
403
 * @self: a #FuCfuOffer
404
 * @product_id: integer
405
 *
406
 * Sets the product ID for this CFU image.
407
 *
408
 * Since: 1.7.0
409
 **/
410
void
411
fu_cfu_offer_set_product_id(FuCfuOffer *self, guint16 product_id)
412
0
{
413
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
414
0
  g_return_if_fail(FU_IS_CFU_OFFER(self));
415
0
  priv->product_id = product_id;
416
0
}
417
418
static gboolean
419
fu_cfu_offer_parse(FuFirmware *firmware,
420
       GInputStream *stream,
421
       FuFirmwareParseFlags flags,
422
       GError **error)
423
82
{
424
82
  FuCfuOffer *self = FU_CFU_OFFER(firmware);
425
82
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
426
82
  guint8 flags1;
427
82
  guint8 flags2;
428
82
  guint8 flags3;
429
82
  g_autoptr(FuStructCfuOffer) st = NULL;
430
431
  /* parse */
432
82
  st = fu_struct_cfu_offer_parse_stream(stream, 0x0, error);
433
82
  if (st == NULL)
434
7
    return FALSE;
435
75
  priv->segment_number = fu_struct_cfu_offer_get_segment_number(st);
436
75
  priv->component_id = fu_struct_cfu_offer_get_component_id(st);
437
75
  priv->token = fu_struct_cfu_offer_get_token(st);
438
75
  priv->hw_variant = fu_struct_cfu_offer_get_compat_variant_mask(st);
439
75
  priv->product_id = fu_struct_cfu_offer_get_product_id(st);
440
441
  /* AA.BBCC.DD */
442
75
  fu_firmware_set_version_raw(firmware, fu_struct_cfu_offer_get_version(st));
443
444
  /* component info */
445
75
  flags1 = fu_struct_cfu_offer_get_flags1(st);
446
75
  priv->force_ignore_version = (flags1 & 0b10000000) > 0;
447
75
  priv->force_immediate_reset = (flags1 & 0b01000000) > 0;
448
449
  /* product info */
450
75
  flags2 = fu_struct_cfu_offer_get_flags2(st);
451
75
  priv->protocol_revision = (flags2 >> 4) & 0b1111;
452
75
  priv->bank = (flags2 >> 2) & 0b11;
453
75
  flags3 = fu_struct_cfu_offer_get_flags3(st);
454
75
  priv->milestone = (flags3 >> 5) & 0b111;
455
456
  /* success */
457
75
  return TRUE;
458
82
}
459
460
static GByteArray *
461
fu_cfu_offer_write(FuFirmware *firmware, GError **error)
462
32
{
463
32
  FuCfuOffer *self = FU_CFU_OFFER(firmware);
464
32
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
465
32
  g_autoptr(FuStructCfuOffer) st = fu_struct_cfu_offer_new();
466
467
  /* component info */
468
32
  fu_struct_cfu_offer_set_segment_number(st, priv->segment_number);
469
32
  fu_struct_cfu_offer_set_flags1(st,
470
32
               priv->force_ignore_version << 7 |
471
32
             (priv->force_immediate_reset << 6));
472
32
  fu_struct_cfu_offer_set_component_id(st, priv->component_id);
473
32
  fu_struct_cfu_offer_set_token(st, priv->token);
474
475
  /* version */
476
32
  fu_struct_cfu_offer_set_version(st, fu_firmware_get_version_raw(firmware));
477
32
  fu_struct_cfu_offer_set_compat_variant_mask(st, priv->hw_variant);
478
479
  /* product info */
480
32
  fu_struct_cfu_offer_set_flags2(st, (priv->protocol_revision << 4) | (priv->bank << 2));
481
32
  fu_struct_cfu_offer_set_flags3(st, priv->milestone << 5);
482
32
  fu_struct_cfu_offer_set_product_id(st, priv->product_id);
483
484
  /* success */
485
32
  return g_steal_pointer(&st->buf);
486
32
}
487
488
static gboolean
489
fu_cfu_offer_build(FuFirmware *firmware, XbNode *n, GError **error)
490
0
{
491
0
  FuCfuOffer *self = FU_CFU_OFFER(firmware);
492
0
  FuCfuOfferPrivate *priv = GET_PRIVATE(self);
493
0
  guint64 tmp;
494
0
  const gchar *tmpb;
495
496
  /* optional properties */
497
0
  tmp = xb_node_query_text_as_uint(n, "segment_number", NULL);
498
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
499
0
    priv->segment_number = tmp;
500
0
  tmpb = xb_node_query_text(n, "force_immediate_reset", NULL);
501
0
  if (tmpb != NULL) {
502
0
    if (!fu_strtobool(tmpb, &priv->force_immediate_reset, error))
503
0
      return FALSE;
504
0
  }
505
0
  tmpb = xb_node_query_text(n, "force_ignore_version", NULL);
506
0
  if (tmpb != NULL) {
507
0
    if (!fu_strtobool(tmpb, &priv->force_ignore_version, error))
508
0
      return FALSE;
509
0
  }
510
0
  tmp = xb_node_query_text_as_uint(n, "component_id", NULL);
511
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
512
0
    priv->component_id = tmp;
513
0
  tmp = xb_node_query_text_as_uint(n, "token", NULL);
514
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
515
0
    priv->token = tmp;
516
0
  tmp = xb_node_query_text_as_uint(n, "hw_variant", NULL);
517
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32)
518
0
    priv->hw_variant = tmp;
519
0
  tmp = xb_node_query_text_as_uint(n, "protocol_revision", NULL);
520
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
521
0
    priv->protocol_revision = tmp;
522
0
  tmp = xb_node_query_text_as_uint(n, "bank", NULL);
523
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
524
0
    priv->bank = tmp;
525
0
  tmp = xb_node_query_text_as_uint(n, "milestone", NULL);
526
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
527
0
    priv->milestone = tmp;
528
0
  tmp = xb_node_query_text_as_uint(n, "product_id", NULL);
529
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16)
530
0
    priv->product_id = tmp;
531
532
  /* success */
533
0
  return TRUE;
534
0
}
535
536
static gchar *
537
fu_cfu_offer_convert_version(FuFirmware *firmware, guint64 version_raw)
538
75
{
539
75
  return fu_version_from_uint32(version_raw, fu_firmware_get_version_format(firmware));
540
75
}
541
542
static void
543
fu_cfu_offer_init(FuCfuOffer *self)
544
96
{
545
96
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID);
546
96
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION);
547
96
  fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_SURFACE);
548
96
  fu_firmware_set_size_max(FU_FIRMWARE(self), 1 * FU_MB);
549
96
}
550
551
static void
552
fu_cfu_offer_class_init(FuCfuOfferClass *klass)
553
1
{
554
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
555
1
  firmware_class->convert_version = fu_cfu_offer_convert_version;
556
1
  firmware_class->export = fu_cfu_offer_export;
557
1
  firmware_class->parse = fu_cfu_offer_parse;
558
1
  firmware_class->write = fu_cfu_offer_write;
559
1
  firmware_class->build = fu_cfu_offer_build;
560
1
}
561
562
/**
563
 * fu_cfu_offer_new:
564
 *
565
 * Creates a new #FuFirmware for a CFU offer
566
 *
567
 * Since: 1.7.0
568
 **/
569
FuFirmware *
570
fu_cfu_offer_new(void)
571
96
{
572
96
  return FU_FIRMWARE(g_object_new(FU_TYPE_CFU_OFFER, NULL));
573
96
}