Coverage Report

Created: 2026-07-16 07:10

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