Coverage Report

Created: 2026-05-30 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-coswid-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2022 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
232k
#define G_LOG_DOMAIN "FuFirmware"
8
9
#include "config.h"
10
11
#include "fu-bytes.h"
12
#include "fu-cbor-common.h"
13
#include "fu-common.h"
14
#include "fu-coswid-common.h"
15
#include "fu-coswid-firmware.h"
16
#include "fu-coswid-struct.h"
17
#include "fu-input-stream.h"
18
19
/**
20
 * FuCoswidFirmware:
21
 *
22
 * A coSWID SWID section.
23
 *
24
 * See also: [class@FuUswidFirmware]
25
 */
26
27
typedef struct {
28
  gchar *product;
29
  gchar *summary;
30
  gchar *colloquial_version;
31
  gchar *persistent_id;
32
  gchar *device_id;
33
  FuCoswidVersionScheme version_scheme;
34
  GPtrArray *links;    /* of FuCoswidFirmwareLink */
35
  GPtrArray *entities; /* of FuCoswidFirmwareEntity */
36
  GPtrArray *payloads; /* of FuCoswidFirmwarePayload */
37
} FuCoswidFirmwarePrivate;
38
39
449k
G_DEFINE_TYPE_WITH_PRIVATE(FuCoswidFirmware, fu_coswid_firmware, FU_TYPE_FIRMWARE)
40
449k
#define GET_PRIVATE(o) (fu_coswid_firmware_get_instance_private(o))
41
42
110k
#define FU_COSWID_CBOR_MAX_DEPTH  10
43
110k
#define FU_COSWID_CBOR_MAX_ITEMS  100
44
110k
#define FU_COSWID_CBOR_MAX_LENGTH 10240
45
46
typedef struct {
47
  gchar *name;
48
  gchar *regid;
49
  guint8 roles; /* bitfield of FuCoswidEntityRole */
50
} FuCoswidFirmwareEntity;
51
52
typedef struct {
53
  gchar *href;
54
  FuCoswidLinkRel rel;
55
} FuCoswidFirmwareLink;
56
57
typedef struct {
58
  GBytes *value;
59
  FuCoswidHashAlg alg_id;
60
} FuCoswidFirmwareHash;
61
62
typedef struct {
63
  gchar *name;
64
  guint64 size;
65
  GPtrArray *hashes; /* of FuCoswidFirmwareHash */
66
} FuCoswidFirmwarePayload;
67
68
static void
69
fu_coswid_firmware_entity_free(FuCoswidFirmwareEntity *entity)
70
5.24k
{
71
5.24k
  g_free(entity->name);
72
5.24k
  g_free(entity->regid);
73
5.24k
  g_free(entity);
74
5.24k
}
75
76
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwareEntity, fu_coswid_firmware_entity_free)
77
78
static void
79
fu_coswid_firmware_link_free(FuCoswidFirmwareLink *link)
80
66.6k
{
81
66.6k
  g_free(link->href);
82
66.6k
  g_free(link);
83
66.6k
}
84
85
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwareLink, fu_coswid_firmware_link_free)
86
87
static void
88
fu_coswid_firmware_hash_free(FuCoswidFirmwareHash *hash)
89
906
{
90
906
  if (hash->value != NULL)
91
689
    g_bytes_unref(hash->value);
92
906
  g_free(hash);
93
906
}
94
95
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwareHash, fu_coswid_firmware_hash_free)
96
97
static FuCoswidFirmwarePayload *
98
fu_coswid_firmware_payload_new(void)
99
4.04k
{
100
4.04k
  FuCoswidFirmwarePayload *payload = g_new0(FuCoswidFirmwarePayload, 1);
101
4.04k
  payload->hashes =
102
4.04k
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_hash_free);
103
4.04k
  return payload;
104
4.04k
}
105
106
static void
107
fu_coswid_firmware_payload_free(FuCoswidFirmwarePayload *payload)
108
4.04k
{
109
4.04k
  g_ptr_array_unref(payload->hashes);
110
4.04k
  g_free(payload->name);
111
4.04k
  g_free(payload);
112
4.04k
}
113
114
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwarePayload, fu_coswid_firmware_payload_free)
115
116
/* @userdata: a #FuCoswidFirmware */
117
static gboolean
118
fu_coswid_firmware_parse_meta(FuCborItem *item, gpointer user_data, GError **error)
119
2.91k
{
120
2.91k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
121
2.91k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
122
123
15.0k
  for (guint i = 0; i < fu_cbor_item_map_length(item); i++) {
124
12.6k
    FuCoswidTag tag_id = 0;
125
12.6k
    FuCborItem *item_key = NULL;
126
12.6k
    FuCborItem *item_value = NULL;
127
128
12.6k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
129
12.6k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
130
246
      g_prefix_error(error, "failed to parse meta tag %u: ", (guint)i);
131
246
      return FALSE;
132
246
    }
133
12.4k
    if (tag_id == FU_COSWID_TAG_SUMMARY) {
134
323
      g_free(priv->summary);
135
323
      priv->summary = fu_coswid_read_string(item_value, error);
136
323
      if (priv->summary == NULL) {
137
106
        g_prefix_error_literal(error, "failed to parse summary: ");
138
106
        return FALSE;
139
106
      }
140
12.1k
    } else if (tag_id == FU_COSWID_TAG_COLLOQUIAL_VERSION) {
141
349
      g_free(priv->colloquial_version);
142
349
      priv->colloquial_version = fu_coswid_read_string(item_value, error);
143
349
      if (priv->colloquial_version == NULL) {
144
60
        g_prefix_error_literal(error,
145
60
                   "failed to parse colloquial-version: ");
146
60
        return FALSE;
147
60
      }
148
11.7k
    } else if (tag_id == FU_COSWID_TAG_PERSISTENT_ID) {
149
270
      g_free(priv->persistent_id);
150
270
      priv->persistent_id = fu_coswid_read_string(item_value, error);
151
270
      if (priv->persistent_id == NULL) {
152
105
        g_prefix_error_literal(error, "failed to parse persistent-id: ");
153
105
        return FALSE;
154
105
      }
155
11.5k
    } else {
156
11.5k
      g_debug("unhandled tag %s from %s",
157
11.5k
        fu_coswid_tag_to_string(tag_id),
158
11.5k
        fu_coswid_tag_to_string(FU_COSWID_TAG_SOFTWARE_META));
159
11.5k
    }
160
12.4k
  }
161
162
  /* success */
163
2.40k
  return TRUE;
164
2.91k
}
165
166
/* @userdata: a #FuCoswidFirmware */
167
static gboolean
168
fu_coswid_firmware_parse_evidence(FuCborItem *item, gpointer user_data, GError **error)
169
2.30k
{
170
2.30k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
171
2.30k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
172
173
13.7k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
174
11.9k
    FuCoswidTag tag_id = 0;
175
11.9k
    FuCborItem *item_key = NULL;
176
11.9k
    FuCborItem *item_value = NULL;
177
178
11.9k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
179
11.9k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
180
441
      g_prefix_error(error, "failed to parse evidence tag %u: ", (guint)i);
181
441
      return FALSE;
182
441
    }
183
11.4k
    if (tag_id == FU_COSWID_TAG_DEVICE_ID) {
184
212
      g_free(priv->device_id);
185
212
      priv->device_id = fu_coswid_read_string(item_value, error);
186
212
      if (priv->device_id == NULL) {
187
56
        g_prefix_error_literal(error, "failed to parse device-id: ");
188
56
        return FALSE;
189
56
      }
190
11.2k
    } else {
191
11.2k
      g_debug("unhandled tag %s from %s",
192
11.2k
        fu_coswid_tag_to_string(tag_id),
193
11.2k
        fu_coswid_tag_to_string(FU_COSWID_TAG_SOFTWARE_META));
194
11.2k
    }
195
11.4k
  }
196
197
  /* success */
198
1.80k
  return TRUE;
199
2.30k
}
200
201
/* @userdata: a #FuCoswidFirmware */
202
static gboolean
203
fu_coswid_firmware_parse_link(FuCborItem *item, gpointer user_data, GError **error)
204
66.6k
{
205
66.6k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
206
66.6k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
207
66.6k
  g_autoptr(FuCoswidFirmwareLink) link = g_new0(FuCoswidFirmwareLink, 1);
208
209
129k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
210
63.9k
    FuCoswidTag tag_id = 0;
211
63.9k
    FuCborItem *item_key = NULL;
212
63.9k
    FuCborItem *item_value = NULL;
213
214
63.9k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
215
63.9k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
216
489
      g_prefix_error(error, "failed to parse link tag %u: ", (guint)i);
217
489
      return FALSE;
218
489
    }
219
63.4k
    if (tag_id == FU_COSWID_TAG_HREF) {
220
416
      g_free(link->href);
221
416
      link->href = fu_coswid_read_string(item_value, error);
222
416
      if (link->href == NULL) {
223
106
        g_prefix_error_literal(error, "failed to parse link href: ");
224
106
        return FALSE;
225
106
      }
226
62.9k
    } else if (tag_id == FU_COSWID_TAG_REL) {
227
11.8k
      gint64 tmp = 0;
228
11.8k
      if (!fu_cbor_item_get_integer(item_value, &tmp, error)) {
229
56
        g_prefix_error_literal(error, "failed to parse link rel: ");
230
56
        return FALSE;
231
56
      }
232
11.8k
      link->rel = tmp;
233
51.1k
    } else {
234
51.1k
      g_debug("unhandled tag %s from %s",
235
51.1k
        fu_coswid_tag_to_string(tag_id),
236
51.1k
        fu_coswid_tag_to_string(FU_COSWID_TAG_LINK));
237
51.1k
    }
238
63.4k
  }
239
240
  /* success */
241
66.0k
  g_ptr_array_add(priv->links, g_steal_pointer(&link));
242
66.0k
  return TRUE;
243
66.6k
}
244
245
/* @userdata: a #FuCoswidFirmwarePayload */
246
static gboolean
247
fu_coswid_firmware_parse_hash(FuCborItem *item, gpointer user_data, GError **error)
248
906
{
249
906
  FuCoswidFirmwarePayload *payload = (FuCoswidFirmwarePayload *)user_data;
250
906
  gint64 alg_id8 = 0;
251
906
  g_autoptr(FuCoswidFirmwareHash) hash = g_new0(FuCoswidFirmwareHash, 1);
252
906
  FuCborItem *hash_item_alg_id;
253
906
  FuCborItem *hash_item_value;
254
255
  /* sanity check */
256
906
  if (fu_cbor_item_get_kind(item) != FU_CBOR_ITEM_KIND_ARRAY) {
257
56
    g_set_error_literal(error,
258
56
            FWUPD_ERROR,
259
56
            FWUPD_ERROR_INVALID_DATA,
260
56
            "hash item is not an array");
261
56
    return FALSE;
262
56
  }
263
850
  if (fu_cbor_item_array_length(item) != 2) {
264
52
    g_set_error_literal(error,
265
52
            FWUPD_ERROR,
266
52
            FWUPD_ERROR_INVALID_DATA,
267
52
            "hash array has invalid size");
268
52
    return FALSE;
269
52
  }
270
798
  hash_item_alg_id = fu_cbor_item_array_index(item, 0);
271
798
  hash_item_value = fu_cbor_item_array_index(item, 1);
272
798
  if (hash_item_alg_id == NULL || hash_item_value == NULL) {
273
0
    g_set_error_literal(error,
274
0
            FWUPD_ERROR,
275
0
            FWUPD_ERROR_INVALID_DATA,
276
0
            "invalid hash item");
277
0
    return FALSE;
278
0
  }
279
798
  if (!fu_cbor_item_get_integer(hash_item_alg_id, &alg_id8, error)) {
280
44
    g_prefix_error_literal(error, "failed to parse hash alg-id: ");
281
44
    return FALSE;
282
44
  }
283
284
  /* success */
285
754
  hash->alg_id = alg_id8;
286
754
  hash->value = fu_cbor_item_get_bytes(hash_item_value, error);
287
754
  if (hash->value == NULL) {
288
65
    g_prefix_error_literal(error, "failed to parse hash value: ");
289
65
    return FALSE;
290
65
  }
291
689
  g_ptr_array_add(payload->hashes, g_steal_pointer(&hash));
292
689
  return TRUE;
293
754
}
294
295
/* @userdata: a #FuCoswidFirmwarePayload */
296
static gboolean
297
fu_coswid_firmware_parse_hash_array(FuCborItem *item, gpointer user_data, GError **error)
298
327
{
299
705
  for (guint j = 0; j < fu_cbor_item_array_length(item); j++) {
300
495
    FuCborItem *value = fu_cbor_item_array_index(item, j);
301
495
    if (!fu_coswid_firmware_parse_hash(value, user_data, error))
302
117
      return FALSE;
303
495
  }
304
210
  return TRUE;
305
327
}
306
307
/* @userdata: a #FuCoswidFirmware */
308
static gboolean
309
fu_coswid_firmware_parse_file(FuCborItem *item, gpointer user_data, GError **error)
310
4.04k
{
311
4.04k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
312
4.04k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
313
4.04k
  g_autoptr(FuCoswidFirmwarePayload) payload = fu_coswid_firmware_payload_new();
314
315
19.1k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
316
16.0k
    FuCoswidTag tag_id = 0;
317
16.0k
    FuCborItem *item_key = NULL;
318
16.0k
    FuCborItem *item_value = NULL;
319
320
16.0k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
321
16.0k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
322
87
      g_prefix_error(error, "failed to parse file tag %u: ", (guint)i);
323
87
      return FALSE;
324
87
    }
325
16.0k
    if (tag_id == FU_COSWID_TAG_FS_NAME) {
326
221
      g_free(payload->name);
327
221
      payload->name = fu_coswid_read_string(item_value, error);
328
221
      if (payload->name == NULL) {
329
64
        g_prefix_error_literal(error, "failed to parse payload name: ");
330
64
        return FALSE;
331
64
      }
332
15.7k
    } else if (tag_id == FU_COSWID_TAG_SIZE) {
333
2.37k
      gint64 tmp;
334
2.37k
      if (!fu_cbor_item_get_integer(item_value, &tmp, error))
335
59
        return FALSE;
336
2.31k
      if (tmp < 0) {
337
465
        g_set_error_literal(error,
338
465
                FWUPD_ERROR,
339
465
                FWUPD_ERROR_INVALID_DATA,
340
465
                "payload size cannot be negative");
341
465
        return FALSE;
342
465
      }
343
1.85k
      payload->size = (guint64)tmp;
344
13.4k
    } else if (tag_id == FU_COSWID_TAG_HASH) {
345
847
      if (fu_cbor_item_get_kind(item_value) == FU_CBOR_ITEM_KIND_ARRAY &&
346
781
          fu_cbor_item_array_length(item_value) >= 1) {
347
738
        FuCborItem *value = fu_cbor_item_array_index(item_value, 0);
348
        /* we can't use fu_coswid_parse_one_or_many() here as
349
         * the hash is an array, not a map -- for some reason */
350
738
        if (fu_cbor_item_get_kind(value) == FU_CBOR_ITEM_KIND_ARRAY) {
351
327
          if (!fu_coswid_firmware_parse_hash_array(item_value,
352
327
                     payload,
353
327
                     error))
354
117
            return FALSE;
355
411
        } else {
356
411
          if (!fu_coswid_firmware_parse_hash(item_value,
357
411
                     payload,
358
411
                     error))
359
100
            return FALSE;
360
411
        }
361
738
      } else {
362
109
        g_set_error_literal(error,
363
109
                FWUPD_ERROR,
364
109
                FWUPD_ERROR_INVALID_DATA,
365
109
                "hashes neither an array or array of array");
366
109
        return FALSE;
367
109
      }
368
12.5k
    } else {
369
12.5k
      g_debug("unhandled tag %s from %s",
370
12.5k
        fu_coswid_tag_to_string(tag_id),
371
12.5k
        fu_coswid_tag_to_string(FU_COSWID_TAG_FILE));
372
12.5k
    }
373
16.0k
  }
374
375
  /* success */
376
3.04k
  g_ptr_array_add(priv->payloads, g_steal_pointer(&payload));
377
3.04k
  return TRUE;
378
4.04k
}
379
380
/* @userdata: a #FuCoswidFirmware */
381
static gboolean
382
fu_coswid_firmware_parse_path_elements(FuCborItem *item, gpointer user_data, GError **error)
383
1.65k
{
384
1.65k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
385
10.1k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
386
8.65k
    FuCoswidTag tag_id = 0;
387
8.65k
    FuCborItem *item_key = NULL;
388
8.65k
    FuCborItem *item_value = NULL;
389
390
8.65k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
391
8.65k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
392
82
      g_prefix_error(error, "failed to parse elements tag %u: ", (guint)i);
393
82
      return FALSE;
394
82
    }
395
8.57k
    if (tag_id == FU_COSWID_TAG_FILE) {
396
774
      if (!fu_coswid_parse_one_or_many(item_value,
397
774
               fu_coswid_firmware_parse_file,
398
774
               self, /* user_data */
399
774
               error))
400
71
        return FALSE;
401
7.80k
    } else {
402
7.80k
      g_debug("unhandled tag %s from %s",
403
7.80k
        fu_coswid_tag_to_string(tag_id),
404
7.80k
        fu_coswid_tag_to_string(FU_COSWID_TAG_PATH_ELEMENTS));
405
7.80k
    }
406
8.57k
  }
407
408
  /* success */
409
1.50k
  return TRUE;
410
1.65k
}
411
412
/* @userdata: a #FuCoswidFirmware */
413
static gboolean
414
fu_coswid_firmware_parse_directory(FuCborItem *item, gpointer user_data, GError **error)
415
1.79k
{
416
1.79k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
417
11.3k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
418
9.78k
    FuCoswidTag tag_id = 0;
419
9.78k
    FuCborItem *item_key = NULL;
420
9.78k
    FuCborItem *item_value = NULL;
421
422
9.78k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
423
9.78k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
424
95
      g_prefix_error(error, "failed to parse directory tag %u: ", (guint)i);
425
95
      return FALSE;
426
95
    }
427
9.69k
    if (tag_id == FU_COSWID_TAG_PATH_ELEMENTS) {
428
986
      if (!fu_coswid_parse_one_or_many(item_value,
429
986
               fu_coswid_firmware_parse_path_elements,
430
986
               self, /* user_data */
431
986
               error))
432
177
        return FALSE;
433
8.70k
    } else {
434
8.70k
      g_debug("unhandled tag %s from %s",
435
8.70k
        fu_coswid_tag_to_string(tag_id),
436
8.70k
        fu_coswid_tag_to_string(FU_COSWID_TAG_DIRECTORY));
437
8.70k
    }
438
9.69k
  }
439
440
  /* success */
441
1.52k
  return TRUE;
442
1.79k
}
443
444
/* @userdata: a #FuCoswidFirmware */
445
static gboolean
446
fu_coswid_firmware_parse_payload(FuCborItem *item, gpointer user_data, GError **error)
447
5.18k
{
448
5.18k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
449
26.7k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
450
23.4k
    FuCoswidTag tag_id = 0;
451
23.4k
    FuCborItem *item_key = NULL;
452
23.4k
    FuCborItem *item_value = NULL;
453
454
23.4k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
455
23.4k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
456
247
      g_prefix_error(error, "failed to parse payload tag %u: ", (guint)i);
457
247
      return FALSE;
458
247
    }
459
23.2k
    if (tag_id == FU_COSWID_TAG_FILE) {
460
2.59k
      if (!fu_coswid_parse_one_or_many(item_value,
461
2.59k
               fu_coswid_firmware_parse_file,
462
2.59k
               self, /* user_data */
463
2.59k
               error))
464
1.14k
        return FALSE;
465
20.6k
    } else if (tag_id == FU_COSWID_TAG_DIRECTORY) {
466
1.58k
      if (!fu_coswid_parse_one_or_many(item_value,
467
1.58k
               fu_coswid_firmware_parse_directory,
468
1.58k
               self, /* user_data */
469
1.58k
               error))
470
454
        return FALSE;
471
19.0k
    } else {
472
19.0k
      g_debug("unhandled tag %s from %s",
473
19.0k
        fu_coswid_tag_to_string(tag_id),
474
19.0k
        fu_coswid_tag_to_string(FU_COSWID_TAG_PAYLOAD));
475
19.0k
    }
476
23.2k
  }
477
478
  /* success */
479
3.33k
  return TRUE;
480
5.18k
}
481
482
static gboolean
483
fu_coswid_firmware_parse_entity_name(FuCoswidFirmwareEntity *entity,
484
             FuCborItem *item,
485
             GError **error)
486
6.40k
{
487
  /* we might be calling this twice... */
488
6.40k
  g_free(entity->name);
489
490
6.40k
  entity->name = fu_coswid_read_string(item, error);
491
6.40k
  if (entity->name == NULL) {
492
56
    g_prefix_error_literal(error, "failed to parse entity name: ");
493
56
    return FALSE;
494
56
  }
495
496
  /* success */
497
6.35k
  return TRUE;
498
6.40k
}
499
500
static gboolean
501
fu_coswid_firmware_parse_entity_regid(FuCoswidFirmwareEntity *entity,
502
              FuCborItem *item,
503
              GError **error)
504
482
{
505
  /* we might be calling this twice... */
506
482
  g_free(entity->regid);
507
508
482
  entity->regid = fu_coswid_read_string(item, error);
509
482
  if (entity->regid == NULL) {
510
103
    g_prefix_error_literal(error, "failed to parse entity regid: ");
511
103
    return FALSE;
512
103
  }
513
514
  /* success */
515
379
  return TRUE;
516
482
}
517
518
static gboolean
519
fu_coswid_firmware_parse_entity_role(FuCoswidFirmwareEntity *entity,
520
             FuCborItem *item,
521
             GError **error)
522
3.91k
{
523
3.91k
  if (fu_cbor_item_get_kind(item) == FU_CBOR_ITEM_KIND_INTEGER) {
524
2.70k
    gint64 tmp = 0;
525
2.70k
    if (!fu_cbor_item_get_integer(item, &tmp, error)) {
526
0
      g_prefix_error_literal(error, "failed to parse entity role: ");
527
0
      return FALSE;
528
0
    }
529
2.70k
    if (tmp <= FU_COSWID_ENTITY_ROLE_UNKNOWN || tmp >= FU_COSWID_ENTITY_ROLE_LAST) {
530
637
      g_set_error(error,
531
637
            FWUPD_ERROR,
532
637
            FWUPD_ERROR_INVALID_DATA,
533
637
            "invalid entity role 0x%x",
534
637
            (guint)tmp);
535
637
      return FALSE;
536
637
    }
537
2.06k
    FU_BIT_SET(entity->roles, tmp);
538
539
2.06k
  } else if (fu_cbor_item_get_kind(item) == FU_CBOR_ITEM_KIND_ARRAY) {
540
2.48k
    for (guint j = 0; j < fu_cbor_item_array_length(item); j++) {
541
2.20k
      gint64 tmp = 0;
542
2.20k
      FuCborItem *value = fu_cbor_item_array_index(item, j);
543
2.20k
      if (!fu_cbor_item_get_integer(value, &tmp, error)) {
544
74
        g_prefix_error_literal(error, "failed to parse entity role: ");
545
74
        return FALSE;
546
74
      }
547
2.13k
      if (tmp <= FU_COSWID_ENTITY_ROLE_UNKNOWN ||
548
1.71k
          tmp >= FU_COSWID_ENTITY_ROLE_LAST) {
549
792
        g_set_error(error,
550
792
              FWUPD_ERROR,
551
792
              FWUPD_ERROR_INVALID_DATA,
552
792
              "invalid entity role 0x%x",
553
792
              (guint)tmp);
554
792
        return FALSE;
555
792
      }
556
1.33k
      FU_BIT_SET(entity->roles, tmp);
557
1.33k
    }
558
1.14k
  } else {
559
61
    g_set_error_literal(error,
560
61
            FWUPD_ERROR,
561
61
            FWUPD_ERROR_INVALID_DATA,
562
61
            "entity role item is not an uint or array");
563
61
    return FALSE;
564
61
  }
565
566
  /* success */
567
2.34k
  return TRUE;
568
3.91k
}
569
570
/* @userdata: a #FuCoswidFirmware */
571
static gboolean
572
fu_coswid_firmware_parse_entity(FuCborItem *item, gpointer user_data, GError **error)
573
5.24k
{
574
5.24k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
575
5.24k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
576
5.24k
  g_autoptr(FuCoswidFirmwareEntity) entity = g_new0(FuCoswidFirmwareEntity, 1);
577
578
57.0k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
579
53.9k
    FuCoswidTag tag_id = 0;
580
53.9k
    FuCborItem *item_key = NULL;
581
53.9k
    FuCborItem *item_value = NULL;
582
583
53.9k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
584
53.9k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
585
412
      g_prefix_error(error, "failed to parse entity tag %u: ", (guint)i);
586
412
      return FALSE;
587
412
    }
588
53.5k
    if (tag_id == FU_COSWID_TAG_ENTITY_NAME) {
589
6.40k
      if (!fu_coswid_firmware_parse_entity_name(entity, item_value, error))
590
56
        return FALSE;
591
47.1k
    } else if (tag_id == FU_COSWID_TAG_REG_ID) {
592
482
      if (!fu_coswid_firmware_parse_entity_regid(entity, item_value, error))
593
103
        return FALSE;
594
46.6k
    } else if (tag_id == FU_COSWID_TAG_ROLE) {
595
3.91k
      if (!fu_coswid_firmware_parse_entity_role(entity, item_value, error))
596
1.56k
        return FALSE;
597
42.7k
    } else {
598
42.7k
      g_debug("unhandled tag %s from %s",
599
42.7k
        fu_coswid_tag_to_string(tag_id),
600
42.7k
        fu_coswid_tag_to_string(FU_COSWID_TAG_ENTITY));
601
42.7k
    }
602
53.5k
  }
603
604
  /* sanity check */
605
3.10k
  if (entity->name == NULL) {
606
1.22k
    g_set_error_literal(error,
607
1.22k
            FWUPD_ERROR,
608
1.22k
            FWUPD_ERROR_INVALID_DATA,
609
1.22k
            "entity does not have a name");
610
1.22k
    return FALSE;
611
1.22k
  }
612
1.88k
  if (entity->roles == 0) {
613
81
    g_set_error_literal(error,
614
81
            FWUPD_ERROR,
615
81
            FWUPD_ERROR_INVALID_DATA,
616
81
            "entity has no roles");
617
81
    return FALSE;
618
81
  }
619
620
  /* success */
621
1.79k
  g_ptr_array_add(priv->entities, g_steal_pointer(&entity));
622
1.79k
  return TRUE;
623
1.88k
}
624
625
static gboolean
626
fu_coswid_firmware_parse(FuFirmware *firmware,
627
       GInputStream *stream,
628
       FuFirmwareParseFlags flags,
629
       GError **error)
630
110k
{
631
110k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware);
632
110k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
633
110k
  gsize offset = 0;
634
110k
  g_autoptr(FuCborItem) item = NULL;
635
636
110k
  item = fu_cbor_parse(stream,
637
110k
           &offset,
638
110k
           FU_COSWID_CBOR_MAX_DEPTH,
639
110k
           FU_COSWID_CBOR_MAX_ITEMS,
640
110k
           FU_COSWID_CBOR_MAX_LENGTH,
641
110k
           error);
642
110k
  if (item == NULL)
643
26.6k
    return FALSE;
644
84.1k
  fu_firmware_set_size(firmware, offset);
645
646
  /* pretty-print the result */
647
84.1k
  if (g_getenv("FWUPD_CBOR_VERBOSE") != NULL) {
648
0
    g_autofree gchar *str = fu_cbor_item_to_string(item);
649
0
    g_debug("%s", str);
650
0
  }
651
652
  /* sanity check */
653
84.1k
  if (fu_cbor_item_get_kind(item) != FU_CBOR_ITEM_KIND_MAP) {
654
1.04k
    g_set_error_literal(error,
655
1.04k
            FWUPD_ERROR,
656
1.04k
            FWUPD_ERROR_INVALID_DATA,
657
1.04k
            "root item is not a map");
658
1.04k
    return FALSE;
659
1.04k
  }
660
661
  /* parse out anything interesting */
662
232k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
663
163k
    FuCoswidTag tag_id = 0;
664
163k
    FuCborItem *item_key = NULL;
665
163k
    FuCborItem *item_value = NULL;
666
667
163k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
668
163k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
669
1.88k
      g_prefix_error(error, "failed to parse root tag %u: ", (guint)i);
670
1.88k
      return FALSE;
671
1.88k
    }
672
673
    /* identity can be specified as a string or in binary */
674
161k
    if (tag_id == FU_COSWID_TAG_TAG_ID) {
675
8.26k
      g_autofree gchar *str = fu_coswid_read_string(item_value, error);
676
8.26k
      if (str == NULL) {
677
2.49k
        g_prefix_error_literal(error, "failed to parse tag-id: ");
678
2.49k
        return FALSE;
679
2.49k
      }
680
5.77k
      fu_firmware_set_id(firmware, str);
681
153k
    } else if (tag_id == FU_COSWID_TAG_SOFTWARE_NAME) {
682
3.44k
      g_free(priv->product);
683
3.44k
      priv->product = fu_coswid_read_string(item_value, error);
684
3.44k
      if (priv->product == NULL) {
685
658
        g_prefix_error_literal(error, "failed to parse product: ");
686
658
        return FALSE;
687
658
      }
688
149k
    } else if (tag_id == FU_COSWID_TAG_SOFTWARE_VERSION) {
689
2.57k
      g_autofree gchar *str = fu_coswid_read_string(item_value, error);
690
2.57k
      if (str == NULL) {
691
407
        g_prefix_error_literal(error, "failed to parse software-version: ");
692
407
        return FALSE;
693
407
      }
694
2.16k
      fu_firmware_set_version(firmware, str);
695
147k
    } else if (tag_id == FU_COSWID_TAG_VERSION_SCHEME) {
696
3.11k
      if (!fu_coswid_read_version_scheme(item_value,
697
3.11k
                 &priv->version_scheme,
698
3.11k
                 error))
699
288
        return FALSE;
700
144k
    } else if (tag_id == FU_COSWID_TAG_SOFTWARE_META) {
701
3.93k
      if (!fu_coswid_parse_one_or_many(item_value,
702
3.93k
               fu_coswid_firmware_parse_meta,
703
3.93k
               self, /* user_data */
704
3.93k
               error))
705
1.01k
        return FALSE;
706
140k
    } else if (tag_id == FU_COSWID_TAG_EVIDENCE) {
707
5.06k
      if (!fu_coswid_parse_one_or_many(item_value,
708
5.06k
               fu_coswid_firmware_parse_evidence,
709
5.06k
               self, /* user_data */
710
5.06k
               error))
711
797
        return FALSE;
712
135k
    } else if (tag_id == FU_COSWID_TAG_LINK) {
713
56.3k
      if (!fu_coswid_parse_one_or_many(item_value,
714
56.3k
               fu_coswid_firmware_parse_link,
715
56.3k
               self, /* user_data */
716
56.3k
               error))
717
907
        return FALSE;
718
78.7k
    } else if (tag_id == FU_COSWID_TAG_PAYLOAD) {
719
4.84k
      if (!fu_coswid_parse_one_or_many(item_value,
720
4.84k
               fu_coswid_firmware_parse_payload,
721
4.84k
               self, /* user_data */
722
4.84k
               error))
723
2.20k
        return FALSE;
724
73.9k
    } else if (tag_id == FU_COSWID_TAG_ENTITY) {
725
5.95k
      if (!fu_coswid_parse_one_or_many(item_value,
726
5.95k
               fu_coswid_firmware_parse_entity,
727
5.95k
               self, /* user_data */
728
5.95k
               error))
729
3.76k
        return FALSE;
730
67.9k
    } else {
731
67.9k
      g_debug("unhandled tag %s from root", fu_coswid_tag_to_string(tag_id));
732
67.9k
    }
733
161k
  }
734
735
  /* device not supported */
736
68.7k
  if (fu_firmware_get_id(firmware) == NULL && fu_firmware_get_version(firmware) == NULL &&
737
63.0k
      priv->product == NULL && priv->entities->len == 0 && priv->links->len == 0) {
738
5.32k
    g_set_error_literal(error,
739
5.32k
            FWUPD_ERROR,
740
5.32k
            FWUPD_ERROR_NOT_SUPPORTED,
741
5.32k
            "not enough SBOM data");
742
5.32k
    return FALSE;
743
5.32k
  }
744
745
  /* success */
746
63.4k
  return TRUE;
747
68.7k
}
748
749
static gchar *
750
fu_coswid_firmware_get_checksum(FuFirmware *firmware, GChecksumType csum_kind, GError **error)
751
0
{
752
0
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware);
753
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
754
0
  FuCoswidHashAlg alg_id = FU_COSWID_HASH_ALG_UNKNOWN;
755
0
  struct {
756
0
    GChecksumType kind;
757
0
    FuCoswidHashAlg alg_id;
758
0
  } csum_kinds[] = {{G_CHECKSUM_SHA256, FU_COSWID_HASH_ALG_SHA256},
759
0
        {G_CHECKSUM_SHA384, FU_COSWID_HASH_ALG_SHA384},
760
0
        {G_CHECKSUM_SHA512, FU_COSWID_HASH_ALG_SHA512},
761
0
        {0, FU_COSWID_HASH_ALG_UNKNOWN}};
762
763
  /* convert to FuCoswidHashAlg */
764
0
  for (guint i = 0; csum_kinds[i].kind != 0; i++) {
765
0
    if (csum_kinds[i].kind == csum_kind) {
766
0
      alg_id = csum_kinds[i].alg_id;
767
0
      break;
768
0
    }
769
0
  }
770
0
  if (alg_id == FU_COSWID_HASH_ALG_UNKNOWN) {
771
0
    g_set_error(error,
772
0
          FWUPD_ERROR,
773
0
          FWUPD_ERROR_NOT_SUPPORTED,
774
0
          "cannot convert %s",
775
0
          fwupd_checksum_type_to_string_display(csum_kind));
776
0
    return NULL;
777
0
  }
778
779
  /* find the correct hash kind */
780
0
  for (guint i = 0; i < priv->payloads->len; i++) {
781
0
    FuCoswidFirmwarePayload *payload = g_ptr_array_index(priv->payloads, i);
782
0
    for (guint j = 0; j < payload->hashes->len; j++) {
783
0
      FuCoswidFirmwareHash *hash = g_ptr_array_index(payload->hashes, j);
784
0
      if (hash->alg_id == alg_id)
785
0
        return fu_bytes_to_string(hash->value);
786
0
    }
787
0
  }
788
0
  g_set_error(error,
789
0
        FWUPD_ERROR,
790
0
        FWUPD_ERROR_NOT_SUPPORTED,
791
0
        "no hash kind %s",
792
0
        fwupd_checksum_type_to_string_display(csum_kind));
793
0
  return NULL;
794
0
}
795
796
/**
797
 * fu_coswid_firmware_get_product:
798
 * @self: a #FuCoswidFirmware
799
 *
800
 * Gets the product name.
801
 *
802
 * Returns: string, or %NULL for unset
803
 *
804
 * Since: 2.0.12
805
 **/
806
const gchar *
807
fu_coswid_firmware_get_product(FuCoswidFirmware *self)
808
0
{
809
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
810
0
  g_return_val_if_fail(FU_IS_COSWID_FIRMWARE(self), NULL);
811
0
  return priv->product;
812
0
}
813
814
/**
815
 * fu_coswid_firmware_get_persistent_id:
816
 * @self: a #FuCoswidFirmware
817
 *
818
 * Gets the persistent ID.
819
 *
820
 * Returns: string, or %NULL for unset
821
 *
822
 * Since: 2.0.17
823
 **/
824
const gchar *
825
fu_coswid_firmware_get_persistent_id(FuCoswidFirmware *self)
826
0
{
827
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
828
0
  g_return_val_if_fail(FU_IS_COSWID_FIRMWARE(self), NULL);
829
0
  return priv->persistent_id;
830
0
}
831
832
/**
833
 * fu_coswid_firmware_get_device_id:
834
 * @self: a #FuCoswidFirmware
835
 *
836
 * Gets the device ID.
837
 *
838
 * Returns: string, or %NULL for unset
839
 *
840
 * Since: 2.0.17
841
 **/
842
const gchar *
843
fu_coswid_firmware_get_device_id(FuCoswidFirmware *self)
844
0
{
845
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
846
0
  g_return_val_if_fail(FU_IS_COSWID_FIRMWARE(self), NULL);
847
0
  return priv->device_id;
848
0
}
849
850
static gboolean
851
fu_coswid_firmware_write_hash(FuCborItem *root, FuCoswidFirmwareHash *hash, GError **error)
852
270
{
853
270
  g_autoptr(FuCborItem) item_hash = fu_cbor_item_new_array();
854
270
  g_autoptr(FuCborItem) item_hash_alg_id = fu_cbor_item_new_integer(hash->alg_id);
855
270
  g_autoptr(FuCborItem) item_hash_value = fu_cbor_item_new_bytes(hash->value);
856
270
  if (!fu_cbor_item_array_append(item_hash, item_hash_alg_id, error))
857
0
    return FALSE;
858
270
  if (!fu_cbor_item_array_append(item_hash, item_hash_value, error))
859
0
    return FALSE;
860
270
  if (!fu_cbor_item_array_append(root, item_hash, error))
861
0
    return FALSE;
862
863
  /* success */
864
270
  return TRUE;
865
270
}
866
867
static gboolean
868
fu_coswid_firmware_write_payload(FuCborItem *root, FuCoswidFirmwarePayload *payload, GError **error)
869
753
{
870
753
  g_autoptr(FuCborItem) item_payload = fu_cbor_item_new_map();
871
753
  g_autoptr(FuCborItem) item_file = fu_cbor_item_new_map();
872
753
  if (payload->name != NULL)
873
21
    fu_coswid_write_tag_string(item_file, FU_COSWID_TAG_FS_NAME, payload->name);
874
753
  if (payload->size != 0)
875
205
    fu_coswid_write_tag_integer(item_file, FU_COSWID_TAG_SIZE, payload->size);
876
753
  if (payload->hashes->len > 0) {
877
100
    g_autoptr(FuCborItem) item_hashes = fu_cbor_item_new_array();
878
370
    for (guint j = 0; j < payload->hashes->len; j++) {
879
270
      FuCoswidFirmwareHash *hash = g_ptr_array_index(payload->hashes, j);
880
270
      if (!fu_coswid_firmware_write_hash(item_hashes, hash, error)) {
881
0
        g_prefix_error_literal(error, "failed to add payload: ");
882
0
        return FALSE;
883
0
      }
884
270
    }
885
100
    fu_coswid_write_tag_item(item_file, FU_COSWID_TAG_HASH, item_hashes);
886
100
  }
887
753
  fu_coswid_write_tag_item(item_payload, FU_COSWID_TAG_FILE, item_file);
888
753
  return fu_cbor_item_array_append(root, item_payload, error);
889
753
}
890
891
static gboolean
892
fu_coswid_firmware_write_entity(FuCborItem *root, FuCoswidFirmwareEntity *entity, GError **error)
893
846
{
894
846
  g_autoptr(FuCborItem) item_entity = fu_cbor_item_new_map();
895
846
  g_autoptr(FuCborItem) item_roles = fu_cbor_item_new_array();
896
846
  if (entity->name != NULL)
897
846
    fu_coswid_write_tag_string(item_entity, FU_COSWID_TAG_ENTITY_NAME, entity->name);
898
846
  if (entity->regid != NULL)
899
83
    fu_coswid_write_tag_string(item_entity, FU_COSWID_TAG_REG_ID, entity->regid);
900
6.76k
  for (guint j = 0; j < FU_COSWID_ENTITY_ROLE_LAST; j++) {
901
5.92k
    if (FU_BIT_IS_SET(entity->roles, j)) {
902
892
      g_autoptr(FuCborItem) item_role = fu_cbor_item_new_integer(j);
903
892
      if (!fu_cbor_item_array_append(item_roles, item_role, error))
904
0
        return FALSE;
905
892
    }
906
5.92k
  }
907
846
  fu_coswid_write_tag_item(item_entity, FU_COSWID_TAG_ROLE, item_roles);
908
846
  return fu_cbor_item_array_append(root, item_entity, error);
909
846
}
910
911
static gboolean
912
fu_coswid_firmware_write_evidence(FuCborItem *root, const gchar *device_id, GError **error)
913
1
{
914
1
  g_autoptr(FuCborItem) item_entity = fu_cbor_item_new_map();
915
1
  fu_coswid_write_tag_string(item_entity, FU_COSWID_TAG_DEVICE_ID, device_id);
916
1
  return fu_cbor_item_array_append(root, item_entity, error);
917
1
}
918
919
static GByteArray *
920
fu_coswid_firmware_write(FuFirmware *firmware, GError **error)
921
20.5k
{
922
20.5k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware);
923
20.5k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
924
20.5k
  g_autoptr(FuCborItem) root = fu_cbor_item_new_map();
925
20.5k
  g_autoptr(FuCborItem) item_meta = fu_cbor_item_new_map();
926
927
  /* preallocate the map structure */
928
20.5k
  fu_coswid_write_tag_string(root, FU_COSWID_TAG_LANG, "en-US");
929
20.5k
  if (fu_firmware_get_id(firmware) != NULL) {
930
3.01k
    fwupd_guid_t uuid = {0};
931
3.01k
    if (fwupd_guid_from_string(fu_firmware_get_id(firmware),
932
3.01k
             &uuid,
933
3.01k
             FWUPD_GUID_FLAG_NONE,
934
3.01k
             NULL)) {
935
1.28k
      fu_coswid_write_tag_bytestring(root,
936
1.28k
                   FU_COSWID_TAG_TAG_ID,
937
1.28k
                   (const guint8 *)&uuid,
938
1.28k
                   sizeof(uuid));
939
1.73k
    } else {
940
1.73k
      fu_coswid_write_tag_string(root,
941
1.73k
               FU_COSWID_TAG_TAG_ID,
942
1.73k
               fu_firmware_get_id(firmware));
943
1.73k
    }
944
3.01k
  }
945
20.5k
  fu_coswid_write_tag_bool(root, FU_COSWID_TAG_CORPUS, TRUE);
946
20.5k
  if (priv->product != NULL)
947
617
    fu_coswid_write_tag_string(root, FU_COSWID_TAG_SOFTWARE_NAME, priv->product);
948
20.5k
  if (fu_firmware_get_version(firmware) != NULL) {
949
529
    fu_coswid_write_tag_string(root,
950
529
             FU_COSWID_TAG_SOFTWARE_VERSION,
951
529
             fu_firmware_get_version(firmware));
952
529
  }
953
20.5k
  if (priv->version_scheme != FU_COSWID_VERSION_SCHEME_UNKNOWN)
954
20.5k
    fu_coswid_write_tag_integer(root,
955
20.5k
              FU_COSWID_TAG_VERSION_SCHEME,
956
20.5k
              priv->version_scheme);
957
20.5k
  fu_coswid_write_tag_item(root, FU_COSWID_TAG_SOFTWARE_META, item_meta);
958
20.5k
  fu_coswid_write_tag_string(item_meta, FU_COSWID_TAG_GENERATOR, PACKAGE_NAME);
959
20.5k
  if (priv->summary != NULL)
960
26
    fu_coswid_write_tag_string(item_meta, FU_COSWID_TAG_SUMMARY, priv->summary);
961
20.5k
  if (priv->colloquial_version != NULL) {
962
29
    fu_coswid_write_tag_string(item_meta,
963
29
             FU_COSWID_TAG_COLLOQUIAL_VERSION,
964
29
             priv->colloquial_version);
965
29
  }
966
20.5k
  if (priv->persistent_id != NULL) {
967
22
    fu_coswid_write_tag_string(item_meta,
968
22
             FU_COSWID_TAG_PERSISTENT_ID,
969
22
             priv->persistent_id);
970
22
  }
971
972
  /* add evidence */
973
20.5k
  if (priv->device_id != NULL) {
974
1
    g_autoptr(FuCborItem) items = fu_cbor_item_new_array();
975
1
    if (!fu_coswid_firmware_write_evidence(items, priv->device_id, error))
976
0
      return NULL;
977
1
    fu_coswid_write_tag_item(root, FU_COSWID_TAG_EVIDENCE, items);
978
1
  }
979
980
  /* add entities */
981
20.5k
  if (priv->entities->len > 0) {
982
795
    g_autoptr(FuCborItem) item_entities = fu_cbor_item_new_array();
983
1.64k
    for (guint i = 0; i < priv->entities->len; i++) {
984
846
      FuCoswidFirmwareEntity *entity = g_ptr_array_index(priv->entities, i);
985
846
      if (!fu_coswid_firmware_write_entity(item_entities, entity, error))
986
0
        return NULL;
987
846
    }
988
795
    fu_coswid_write_tag_item(root, FU_COSWID_TAG_ENTITY, item_entities);
989
795
  }
990
991
  /* add links */
992
20.5k
  if (priv->links->len > 0) {
993
16.0k
    g_autoptr(FuCborItem) item_links = fu_cbor_item_new_array();
994
36.3k
    for (guint i = 0; i < priv->links->len; i++) {
995
20.3k
      FuCoswidFirmwareLink *link = g_ptr_array_index(priv->links, i);
996
20.3k
      g_autoptr(FuCborItem) item_link = fu_cbor_item_new_map();
997
20.3k
      if (link->href != NULL) {
998
109
        fu_coswid_write_tag_string(item_link,
999
109
                 FU_COSWID_TAG_HREF,
1000
109
                 link->href);
1001
109
      }
1002
20.3k
      fu_coswid_write_tag_integer(item_link, FU_COSWID_TAG_REL, link->rel);
1003
20.3k
      if (!fu_cbor_item_array_append(item_links, item_link, error))
1004
0
        return NULL;
1005
20.3k
    }
1006
16.0k
    fu_coswid_write_tag_item(root, FU_COSWID_TAG_LINK, item_links);
1007
16.0k
  }
1008
1009
  /* add payloads */
1010
20.5k
  if (priv->payloads->len > 0) {
1011
147
    g_autoptr(FuCborItem) item_payloads = fu_cbor_item_new_array();
1012
900
    for (guint i = 0; i < priv->payloads->len; i++) {
1013
753
      FuCoswidFirmwarePayload *payload = g_ptr_array_index(priv->payloads, i);
1014
753
      if (!fu_coswid_firmware_write_payload(item_payloads, payload, error))
1015
0
        return NULL;
1016
753
    }
1017
147
    fu_coswid_write_tag_item(root, FU_COSWID_TAG_PAYLOAD, item_payloads);
1018
147
  }
1019
1020
  /* serialize */
1021
20.5k
  return fu_cbor_item_write(root, error);
1022
20.5k
}
1023
1024
static gboolean
1025
fu_coswid_firmware_build_entity(FuCoswidFirmware *self, XbNode *n, GError **error)
1026
0
{
1027
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1028
0
  const gchar *tmp;
1029
0
  FuCoswidEntityRole role;
1030
0
  g_autoptr(GPtrArray) roles = NULL;
1031
0
  g_autoptr(FuCoswidFirmwareEntity) entity = g_new0(FuCoswidFirmwareEntity, 1);
1032
1033
  /* these are required */
1034
0
  tmp = xb_node_query_text(n, "name", error);
1035
0
  if (tmp == NULL) {
1036
0
    fwupd_error_convert(error);
1037
0
    return FALSE;
1038
0
  }
1039
0
  entity->name = g_strdup(tmp);
1040
0
  tmp = xb_node_query_text(n, "regid", error);
1041
0
  if (tmp == NULL) {
1042
0
    fwupd_error_convert(error);
1043
0
    return FALSE;
1044
0
  }
1045
0
  entity->regid = g_strdup(tmp);
1046
1047
  /* optional */
1048
0
  roles = xb_node_query(n, "role", 0, NULL);
1049
0
  if (roles != NULL) {
1050
0
    for (guint i = 0; i < roles->len; i++) {
1051
0
      XbNode *c = g_ptr_array_index(roles, i);
1052
0
      tmp = xb_node_get_text(c);
1053
0
      role = fu_coswid_entity_role_from_string(tmp);
1054
0
      if (role == FU_COSWID_ENTITY_ROLE_UNKNOWN ||
1055
0
          role >= FU_COSWID_ENTITY_ROLE_LAST) {
1056
0
        g_set_error(error,
1057
0
              FWUPD_ERROR,
1058
0
              FWUPD_ERROR_INVALID_DATA,
1059
0
              "failed to parse entity role %s",
1060
0
              tmp);
1061
0
        return FALSE;
1062
0
      }
1063
0
      FU_BIT_SET(entity->roles, role);
1064
0
    }
1065
0
  }
1066
1067
  /* success */
1068
0
  g_ptr_array_add(priv->entities, g_steal_pointer(&entity));
1069
0
  return TRUE;
1070
0
}
1071
1072
static gboolean
1073
fu_coswid_firmware_build_link(FuCoswidFirmware *self, XbNode *n, GError **error)
1074
0
{
1075
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1076
0
  const gchar *tmp;
1077
0
  g_autoptr(FuCoswidFirmwareLink) link = g_new0(FuCoswidFirmwareLink, 1);
1078
1079
  /* required */
1080
0
  tmp = xb_node_query_text(n, "href", error);
1081
0
  if (tmp == NULL) {
1082
0
    fwupd_error_convert(error);
1083
0
    return FALSE;
1084
0
  }
1085
0
  link->href = g_strdup(tmp);
1086
1087
  /* optional */
1088
0
  tmp = xb_node_query_text(n, "rel", NULL);
1089
0
  if (tmp != NULL) {
1090
0
    link->rel = fu_coswid_link_rel_from_string(tmp);
1091
0
    if (link->rel == FU_COSWID_LINK_REL_UNKNOWN) {
1092
0
      g_set_error(error,
1093
0
            FWUPD_ERROR,
1094
0
            FWUPD_ERROR_INVALID_DATA,
1095
0
            "failed to parse link rel %s",
1096
0
            tmp);
1097
0
      return FALSE;
1098
0
    }
1099
0
  }
1100
1101
  /* success */
1102
0
  g_ptr_array_add(priv->links, g_steal_pointer(&link));
1103
0
  return TRUE;
1104
0
}
1105
1106
static gboolean
1107
fu_coswid_firmware_build_hash(FuCoswidFirmware *self,
1108
            XbNode *n,
1109
            FuCoswidFirmwarePayload *payload,
1110
            GError **error)
1111
0
{
1112
0
  const gchar *tmp;
1113
0
  g_autoptr(FuCoswidFirmwareHash) hash = g_new0(FuCoswidFirmwareHash, 1);
1114
1115
  /* required */
1116
0
  tmp = xb_node_query_text(n, "value", error);
1117
0
  if (tmp == NULL) {
1118
0
    fwupd_error_convert(error);
1119
0
    return FALSE;
1120
0
  }
1121
0
  hash->value = fu_bytes_from_string(tmp, error);
1122
0
  if (hash->value == NULL)
1123
0
    return FALSE;
1124
1125
  /* optional */
1126
0
  tmp = xb_node_query_text(n, "alg_id", NULL);
1127
0
  if (tmp != NULL) {
1128
0
    hash->alg_id = fu_coswid_hash_alg_from_string(tmp);
1129
0
    if (hash->alg_id == FU_COSWID_HASH_ALG_UNKNOWN) {
1130
0
      g_set_error(error,
1131
0
            FWUPD_ERROR,
1132
0
            FWUPD_ERROR_INVALID_DATA,
1133
0
            "failed to parse alg_id %s",
1134
0
            tmp);
1135
0
      return FALSE;
1136
0
    }
1137
0
  }
1138
1139
  /* success */
1140
0
  g_ptr_array_add(payload->hashes, g_steal_pointer(&hash));
1141
0
  return TRUE;
1142
0
}
1143
1144
static gboolean
1145
fu_coswid_firmware_build_payload(FuCoswidFirmware *self, XbNode *n, GError **error)
1146
0
{
1147
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1148
0
  const gchar *tmp;
1149
0
  guint64 tmp64;
1150
0
  g_autoptr(FuCoswidFirmwarePayload) payload = fu_coswid_firmware_payload_new();
1151
0
  g_autoptr(GPtrArray) hashes = NULL;
1152
1153
  /* required */
1154
0
  tmp = xb_node_query_text(n, "name", NULL);
1155
0
  if (tmp != NULL)
1156
0
    payload->name = g_strdup(tmp);
1157
0
  tmp64 = xb_node_query_text_as_uint(n, "size", NULL);
1158
0
  if (tmp64 != G_MAXUINT64)
1159
0
    payload->size = tmp64;
1160
1161
  /* multiple hashes allowed */
1162
0
  hashes = xb_node_query(n, "hash", 0, NULL);
1163
0
  if (hashes != NULL) {
1164
0
    for (guint i = 0; i < hashes->len; i++) {
1165
0
      XbNode *c = g_ptr_array_index(hashes, i);
1166
0
      if (!fu_coswid_firmware_build_hash(self, c, payload, error))
1167
0
        return FALSE;
1168
0
    }
1169
0
  }
1170
1171
  /* success */
1172
0
  g_ptr_array_add(priv->payloads, g_steal_pointer(&payload));
1173
0
  return TRUE;
1174
0
}
1175
1176
static gboolean
1177
fu_coswid_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
1178
0
{
1179
0
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware);
1180
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1181
0
  const gchar *tmp;
1182
0
  g_autoptr(GPtrArray) links = NULL;
1183
0
  g_autoptr(GPtrArray) payloads = NULL;
1184
0
  g_autoptr(GPtrArray) entities = NULL;
1185
1186
  /* simple properties */
1187
0
  tmp = xb_node_query_text(n, "product", NULL);
1188
0
  if (tmp != NULL)
1189
0
    priv->product = g_strdup(tmp);
1190
0
  tmp = xb_node_query_text(n, "summary", NULL);
1191
0
  if (tmp != NULL)
1192
0
    priv->summary = g_strdup(tmp);
1193
0
  tmp = xb_node_query_text(n, "colloquial_version", NULL);
1194
0
  if (tmp != NULL)
1195
0
    priv->colloquial_version = g_strdup(tmp);
1196
0
  tmp = xb_node_query_text(n, "persistent_id", NULL);
1197
0
  if (tmp != NULL)
1198
0
    priv->persistent_id = g_strdup(tmp);
1199
0
  tmp = xb_node_query_text(n, "device_id", NULL);
1200
0
  if (tmp != NULL)
1201
0
    priv->device_id = g_strdup(tmp);
1202
1203
0
  tmp = xb_node_query_text(n, "version_scheme", NULL);
1204
0
  if (tmp != NULL) {
1205
0
    priv->version_scheme = fu_coswid_version_scheme_from_string(tmp);
1206
0
    if (priv->version_scheme == FU_COSWID_VERSION_SCHEME_UNKNOWN) {
1207
0
      g_set_error(error,
1208
0
            FWUPD_ERROR,
1209
0
            FWUPD_ERROR_INVALID_DATA,
1210
0
            "failed to parse version_scheme %s",
1211
0
            tmp);
1212
0
      return FALSE;
1213
0
    }
1214
0
  }
1215
1216
  /* multiple links allowed */
1217
0
  links = xb_node_query(n, "link", 0, NULL);
1218
0
  if (links != NULL) {
1219
0
    for (guint i = 0; i < links->len; i++) {
1220
0
      XbNode *c = g_ptr_array_index(links, i);
1221
0
      if (!fu_coswid_firmware_build_link(self, c, error))
1222
0
        return FALSE;
1223
0
    }
1224
0
  }
1225
1226
  /* multiple payloads allowed */
1227
0
  payloads = xb_node_query(n, "payload", 0, NULL);
1228
0
  if (payloads != NULL) {
1229
0
    for (guint i = 0; i < payloads->len; i++) {
1230
0
      XbNode *c = g_ptr_array_index(payloads, i);
1231
0
      if (!fu_coswid_firmware_build_payload(self, c, error))
1232
0
        return FALSE;
1233
0
    }
1234
0
  }
1235
1236
  /* multiple entities allowed */
1237
0
  entities = xb_node_query(n, "entity", 0, NULL);
1238
0
  if (entities != NULL) {
1239
0
    for (guint i = 0; i < entities->len; i++) {
1240
0
      XbNode *c = g_ptr_array_index(entities, i);
1241
0
      if (!fu_coswid_firmware_build_entity(self, c, error))
1242
0
        return FALSE;
1243
0
    }
1244
0
  }
1245
1246
  /* success */
1247
0
  return TRUE;
1248
0
}
1249
1250
static void
1251
fu_coswid_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
1252
0
{
1253
0
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware);
1254
0
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1255
0
  if (priv->version_scheme != FU_COSWID_VERSION_SCHEME_UNKNOWN) {
1256
0
    fu_xmlb_builder_insert_kv(bn,
1257
0
            "version_scheme",
1258
0
            fu_coswid_version_scheme_to_string(priv->version_scheme));
1259
0
  }
1260
0
  fu_xmlb_builder_insert_kv(bn, "product", priv->product);
1261
0
  fu_xmlb_builder_insert_kv(bn, "summary", priv->summary);
1262
0
  fu_xmlb_builder_insert_kv(bn, "colloquial_version", priv->colloquial_version);
1263
0
  fu_xmlb_builder_insert_kv(bn, "persistent_id", priv->persistent_id);
1264
0
  fu_xmlb_builder_insert_kv(bn, "device_id", priv->device_id);
1265
0
  for (guint i = 0; i < priv->links->len; i++) {
1266
0
    FuCoswidFirmwareLink *link = g_ptr_array_index(priv->links, i);
1267
0
    g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "link", NULL);
1268
0
    fu_xmlb_builder_insert_kv(bc, "href", link->href);
1269
0
    if (link->rel != FU_COSWID_LINK_REL_UNKNOWN) {
1270
0
      fu_xmlb_builder_insert_kv(bc,
1271
0
              "rel",
1272
0
              fu_coswid_link_rel_to_string(link->rel));
1273
0
    }
1274
0
  }
1275
0
  for (guint i = 0; i < priv->payloads->len; i++) {
1276
0
    FuCoswidFirmwarePayload *payload = g_ptr_array_index(priv->payloads, i);
1277
0
    g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "payload", NULL);
1278
0
    fu_xmlb_builder_insert_kv(bc, "name", payload->name);
1279
0
    fu_xmlb_builder_insert_kx(bc, "size", payload->size);
1280
0
    for (guint j = 0; j < payload->hashes->len; j++) {
1281
0
      FuCoswidFirmwareHash *hash = g_ptr_array_index(payload->hashes, j);
1282
0
      g_autoptr(XbBuilderNode) bh = xb_builder_node_insert(bc, "hash", NULL);
1283
0
      g_autofree gchar *value = fu_bytes_to_string(hash->value);
1284
0
      fu_xmlb_builder_insert_kv(bh,
1285
0
              "alg_id",
1286
0
              fu_coswid_hash_alg_to_string(hash->alg_id));
1287
0
      fu_xmlb_builder_insert_kv(bh, "value", value);
1288
0
    }
1289
0
  }
1290
0
  for (guint i = 0; i < priv->entities->len; i++) {
1291
0
    FuCoswidFirmwareEntity *entity = g_ptr_array_index(priv->entities, i);
1292
0
    g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "entity", NULL);
1293
0
    fu_xmlb_builder_insert_kv(bc, "name", entity->name);
1294
0
    fu_xmlb_builder_insert_kv(bc, "regid", entity->regid);
1295
0
    for (guint j = 0; j < FU_COSWID_ENTITY_ROLE_LAST; j++) {
1296
0
      if (FU_BIT_IS_SET(entity->roles, j)) {
1297
0
        fu_xmlb_builder_insert_kv(bc,
1298
0
                "role",
1299
0
                fu_coswid_entity_role_to_string(j));
1300
0
      }
1301
0
    }
1302
0
  }
1303
0
}
1304
1305
static void
1306
fu_coswid_firmware_init(FuCoswidFirmware *self)
1307
110k
{
1308
110k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1309
110k
  priv->version_scheme = FU_COSWID_VERSION_SCHEME_SEMVER;
1310
110k
  priv->links = g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_link_free);
1311
110k
  priv->payloads =
1312
110k
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_payload_free);
1313
110k
  priv->entities =
1314
110k
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_entity_free);
1315
110k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_ALLOW_LINEAR);
1316
110k
  fu_firmware_set_size_max(FU_FIRMWARE(self), 1 * FU_MB);
1317
110k
}
1318
1319
static void
1320
fu_coswid_firmware_finalize(GObject *object)
1321
110k
{
1322
110k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(object);
1323
110k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1324
1325
110k
  g_free(priv->product);
1326
110k
  g_free(priv->summary);
1327
110k
  g_free(priv->colloquial_version);
1328
110k
  g_free(priv->persistent_id);
1329
110k
  g_free(priv->device_id);
1330
110k
  g_ptr_array_unref(priv->links);
1331
110k
  g_ptr_array_unref(priv->payloads);
1332
110k
  g_ptr_array_unref(priv->entities);
1333
1334
110k
  G_OBJECT_CLASS(fu_coswid_firmware_parent_class)->finalize(object);
1335
110k
}
1336
1337
static void
1338
fu_coswid_firmware_class_init(FuCoswidFirmwareClass *klass)
1339
5
{
1340
5
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
1341
5
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
1342
5
  object_class->finalize = fu_coswid_firmware_finalize;
1343
5
  firmware_class->parse = fu_coswid_firmware_parse;
1344
5
  firmware_class->write = fu_coswid_firmware_write;
1345
5
  firmware_class->build = fu_coswid_firmware_build;
1346
5
  firmware_class->export = fu_coswid_firmware_export;
1347
5
  firmware_class->get_checksum = fu_coswid_firmware_get_checksum;
1348
5
}
1349
1350
/**
1351
 * fu_coswid_firmware_new:
1352
 *
1353
 * Creates a new #FuFirmware of sub type coSWID
1354
 *
1355
 * Since: 1.8.0
1356
 **/
1357
FuFirmware *
1358
fu_coswid_firmware_new(void)
1359
0
{
1360
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_COSWID_FIRMWARE, NULL));
1361
0
}