Coverage Report

Created: 2026-10-28 07:47

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
174k
#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
67.0k
G_DEFINE_TYPE_WITH_PRIVATE(FuCoswidFirmware, fu_coswid_firmware, FU_TYPE_FIRMWARE)
40
247k
#define GET_PRIVATE(o) (fu_coswid_firmware_get_instance_private(o))
41
42
73.0k
#define FU_COSWID_CBOR_MAX_DEPTH  10
43
73.0k
#define FU_COSWID_CBOR_MAX_ITEMS  100
44
73.0k
#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.03k
{
71
5.03k
  g_free(entity->name);
72
5.03k
  g_free(entity->regid);
73
5.03k
  g_free(entity);
74
5.03k
}
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
11.0k
{
81
11.0k
  g_free(link->href);
82
11.0k
  g_free(link);
83
11.0k
}
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
993
{
90
993
  if (hash->value != NULL)
91
760
    g_bytes_unref(hash->value);
92
993
  g_free(hash);
93
993
}
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
5.35k
{
100
5.35k
  FuCoswidFirmwarePayload *payload = g_new0(FuCoswidFirmwarePayload, 1);
101
5.35k
  payload->hashes =
102
5.35k
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_hash_free);
103
5.35k
  return payload;
104
5.35k
}
105
106
static void
107
fu_coswid_firmware_payload_free(FuCoswidFirmwarePayload *payload)
108
5.35k
{
109
5.35k
  g_ptr_array_unref(payload->hashes);
110
5.35k
  g_free(payload->name);
111
5.35k
  g_free(payload);
112
5.35k
}
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
4.16k
{
120
4.16k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
121
4.16k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
122
123
15.3k
  for (guint i = 0; i < fu_cbor_item_map_length(item); i++) {
124
12.1k
    FuCoswidTag tag_id = 0;
125
12.1k
    FuCborItem *item_key = NULL;
126
12.1k
    FuCborItem *item_value = NULL;
127
128
12.1k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
129
12.1k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
130
411
      g_prefix_error(error, "failed to parse meta tag %u: ", i);
131
411
      return FALSE;
132
411
    }
133
11.7k
    if (tag_id == FU_COSWID_TAG_SUMMARY) {
134
424
      g_free(priv->summary);
135
424
      priv->summary = fu_coswid_read_string(item_value, error);
136
424
      if (priv->summary == NULL) {
137
230
        g_prefix_error_literal(error, "failed to parse summary: ");
138
230
        return FALSE;
139
230
      }
140
11.3k
    } else if (tag_id == FU_COSWID_TAG_COLLOQUIAL_VERSION) {
141
301
      g_free(priv->colloquial_version);
142
301
      priv->colloquial_version = fu_coswid_read_string(item_value, error);
143
301
      if (priv->colloquial_version == NULL) {
144
105
        g_prefix_error_literal(error,
145
105
                   "failed to parse colloquial-version: ");
146
105
        return FALSE;
147
105
      }
148
11.0k
    } else if (tag_id == FU_COSWID_TAG_PERSISTENT_ID) {
149
414
      g_free(priv->persistent_id);
150
414
      priv->persistent_id = fu_coswid_read_string(item_value, error);
151
414
      if (priv->persistent_id == NULL) {
152
230
        g_prefix_error_literal(error, "failed to parse persistent-id: ");
153
230
        return FALSE;
154
230
      }
155
10.6k
    } else {
156
10.6k
      g_debug("unhandled tag %s from %s",
157
10.6k
        fu_coswid_tag_to_string(tag_id),
158
10.6k
        fu_coswid_tag_to_string(FU_COSWID_TAG_SOFTWARE_META));
159
10.6k
    }
160
11.7k
  }
161
162
  /* success */
163
3.18k
  return TRUE;
164
4.16k
}
165
166
/* @userdata: a #FuCoswidFirmware */
167
static gboolean
168
fu_coswid_firmware_parse_evidence(FuCborItem *item, gpointer user_data, GError **error)
169
2.52k
{
170
2.52k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
171
2.52k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
172
173
12.3k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
174
10.3k
    FuCoswidTag tag_id = 0;
175
10.3k
    FuCborItem *item_key = NULL;
176
10.3k
    FuCborItem *item_value = NULL;
177
178
10.3k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
179
10.3k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
180
471
      g_prefix_error(error, "failed to parse evidence tag %u: ", (guint)i);
181
471
      return FALSE;
182
471
    }
183
9.89k
    if (tag_id == FU_COSWID_TAG_DEVICE_ID) {
184
525
      g_free(priv->device_id);
185
525
      priv->device_id = fu_coswid_read_string(item_value, error);
186
525
      if (priv->device_id == NULL) {
187
104
        g_prefix_error_literal(error, "failed to parse device-id: ");
188
104
        return FALSE;
189
104
      }
190
9.36k
    } else {
191
9.36k
      g_debug("unhandled tag %s from %s",
192
9.36k
        fu_coswid_tag_to_string(tag_id),
193
9.36k
        fu_coswid_tag_to_string(FU_COSWID_TAG_SOFTWARE_META));
194
9.36k
    }
195
9.89k
  }
196
197
  /* success */
198
1.95k
  return TRUE;
199
2.52k
}
200
201
/* @userdata: a #FuCoswidFirmware */
202
static gboolean
203
fu_coswid_firmware_parse_link(FuCborItem *item, gpointer user_data, GError **error)
204
11.0k
{
205
11.0k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
206
11.0k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
207
11.0k
  g_autoptr(FuCoswidFirmwareLink) link = g_new0(FuCoswidFirmwareLink, 1);
208
209
30.7k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
210
20.9k
    FuCoswidTag tag_id = 0;
211
20.9k
    FuCborItem *item_key = NULL;
212
20.9k
    FuCborItem *item_value = NULL;
213
214
20.9k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
215
20.9k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
216
589
      g_prefix_error(error, "failed to parse link tag %u: ", (guint)i);
217
589
      return FALSE;
218
589
    }
219
20.3k
    if (tag_id == FU_COSWID_TAG_HREF) {
220
681
      g_free(link->href);
221
681
      link->href = fu_coswid_read_string(item_value, error);
222
681
      if (link->href == NULL) {
223
238
        g_prefix_error_literal(error, "failed to parse link href: ");
224
238
        return FALSE;
225
238
      }
226
19.6k
    } else if (tag_id == FU_COSWID_TAG_REL) {
227
966
      gint64 tmp = 0;
228
966
      if (!fu_cbor_item_get_integer(item_value, &tmp, error)) {
229
398
        g_prefix_error_literal(error, "failed to parse link rel: ");
230
398
        return FALSE;
231
398
      }
232
568
      link->rel = tmp;
233
18.6k
    } else {
234
18.6k
      g_debug("unhandled tag %s from %s",
235
18.6k
        fu_coswid_tag_to_string(tag_id),
236
18.6k
        fu_coswid_tag_to_string(FU_COSWID_TAG_LINK));
237
18.6k
    }
238
20.3k
  }
239
240
  /* success */
241
9.82k
  g_ptr_array_add(priv->links, g_steal_pointer(&link));
242
9.82k
  return TRUE;
243
11.0k
}
244
245
/* @userdata: a #FuCoswidFirmwarePayload */
246
static gboolean
247
fu_coswid_firmware_parse_hash(FuCborItem *item, gpointer user_data, GError **error)
248
993
{
249
993
  FuCoswidFirmwarePayload *payload = (FuCoswidFirmwarePayload *)user_data;
250
993
  gint64 alg_id8 = 0;
251
993
  g_autoptr(FuCoswidFirmwareHash) hash = g_new0(FuCoswidFirmwareHash, 1);
252
993
  FuCborItem *hash_item_alg_id;
253
993
  FuCborItem *hash_item_value;
254
255
  /* sanity check */
256
993
  if (fu_cbor_item_get_kind(item) != FU_CBOR_ITEM_KIND_ARRAY) {
257
54
    g_set_error_literal(error,
258
54
            FWUPD_ERROR,
259
54
            FWUPD_ERROR_INVALID_DATA,
260
54
            "hash item is not an array");
261
54
    return FALSE;
262
54
  }
263
939
  if (fu_cbor_item_array_length(item) != 2) {
264
69
    g_set_error_literal(error,
265
69
            FWUPD_ERROR,
266
69
            FWUPD_ERROR_INVALID_DATA,
267
69
            "hash array has invalid size");
268
69
    return FALSE;
269
69
  }
270
870
  hash_item_alg_id = fu_cbor_item_array_index(item, 0);
271
870
  hash_item_value = fu_cbor_item_array_index(item, 1);
272
870
  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
870
  if (!fu_cbor_item_get_integer(hash_item_alg_id, &alg_id8, error)) {
280
54
    g_prefix_error_literal(error, "failed to parse hash alg-id: ");
281
54
    return FALSE;
282
54
  }
283
284
  /* success */
285
816
  hash->alg_id = alg_id8;
286
816
  hash->value = fu_cbor_item_get_bytes(hash_item_value, error);
287
816
  if (hash->value == NULL) {
288
56
    g_prefix_error_literal(error, "failed to parse hash value: ");
289
56
    return FALSE;
290
56
  }
291
760
  g_ptr_array_add(payload->hashes, g_steal_pointer(&hash));
292
760
  return TRUE;
293
816
}
294
295
/* @userdata: a #FuCoswidFirmwarePayload */
296
static gboolean
297
fu_coswid_firmware_parse_hash_array(FuCborItem *item, gpointer user_data, GError **error)
298
319
{
299
829
  for (guint j = 0; j < fu_cbor_item_array_length(item); j++) {
300
637
    FuCborItem *value = fu_cbor_item_array_index(item, j);
301
637
    if (!fu_coswid_firmware_parse_hash(value, user_data, error))
302
127
      return FALSE;
303
637
  }
304
192
  return TRUE;
305
319
}
306
307
/* @userdata: a #FuCoswidFirmware */
308
static gboolean
309
fu_coswid_firmware_parse_file(FuCborItem *item, gpointer user_data, GError **error)
310
5.35k
{
311
5.35k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
312
5.35k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
313
5.35k
  g_autoptr(FuCoswidFirmwarePayload) payload = fu_coswid_firmware_payload_new();
314
315
14.8k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
316
10.6k
    FuCoswidTag tag_id = 0;
317
10.6k
    FuCborItem *item_key = NULL;
318
10.6k
    FuCborItem *item_value = NULL;
319
320
10.6k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
321
10.6k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
322
212
      g_prefix_error(error, "failed to parse file tag %u: ", (guint)i);
323
212
      return FALSE;
324
212
    }
325
10.3k
    if (tag_id == FU_COSWID_TAG_FS_NAME) {
326
227
      g_free(payload->name);
327
227
      payload->name = fu_coswid_read_string(item_value, error);
328
227
      if (payload->name == NULL) {
329
68
        g_prefix_error_literal(error, "failed to parse payload name: ");
330
68
        return FALSE;
331
68
      }
332
10.1k
    } else if (tag_id == FU_COSWID_TAG_SIZE) {
333
1.84k
      gint64 tmp;
334
1.84k
      if (!fu_cbor_item_get_integer(item_value, &tmp, error))
335
55
        return FALSE;
336
1.78k
      if (tmp < 0) {
337
402
        g_set_error_literal(error,
338
402
                FWUPD_ERROR,
339
402
                FWUPD_ERROR_INVALID_DATA,
340
402
                "payload size cannot be negative");
341
402
        return FALSE;
342
402
      }
343
1.38k
      payload->size = (guint64)tmp;
344
8.32k
    } else if (tag_id == FU_COSWID_TAG_HASH) {
345
790
      if (fu_cbor_item_get_kind(item_value) == FU_CBOR_ITEM_KIND_ARRAY &&
346
730
          fu_cbor_item_array_length(item_value) >= 1) {
347
675
        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
675
        if (fu_cbor_item_get_kind(value) == FU_CBOR_ITEM_KIND_ARRAY) {
351
319
          if (!fu_coswid_firmware_parse_hash_array(item_value,
352
319
                     payload,
353
319
                     error))
354
127
            return FALSE;
355
356
        } else {
356
356
          if (!fu_coswid_firmware_parse_hash(item_value,
357
356
                     payload,
358
356
                     error))
359
106
            return FALSE;
360
356
        }
361
675
      } else {
362
115
        g_set_error_literal(error,
363
115
                FWUPD_ERROR,
364
115
                FWUPD_ERROR_INVALID_DATA,
365
115
                "hashes neither an array or array of array");
366
115
        return FALSE;
367
115
      }
368
7.53k
    } else {
369
7.53k
      g_debug("unhandled tag %s from %s",
370
7.53k
        fu_coswid_tag_to_string(tag_id),
371
7.53k
        fu_coswid_tag_to_string(FU_COSWID_TAG_FILE));
372
7.53k
    }
373
10.3k
  }
374
375
  /* success */
376
4.26k
  g_ptr_array_add(priv->payloads, g_steal_pointer(&payload));
377
4.26k
  return TRUE;
378
5.35k
}
379
380
/* @userdata: a #FuCoswidFirmware */
381
static gboolean
382
fu_coswid_firmware_parse_path_elements(FuCborItem *item, gpointer user_data, GError **error)
383
1.41k
{
384
1.41k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
385
8.92k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
386
7.66k
    FuCoswidTag tag_id = 0;
387
7.66k
    FuCborItem *item_key = NULL;
388
7.66k
    FuCborItem *item_value = NULL;
389
390
7.66k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
391
7.66k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
392
69
      g_prefix_error(error, "failed to parse elements tag %u: ", (guint)i);
393
69
      return FALSE;
394
69
    }
395
7.59k
    if (tag_id == FU_COSWID_TAG_FILE) {
396
534
      if (!fu_coswid_parse_one_or_many(item_value,
397
534
               fu_coswid_firmware_parse_file,
398
534
               self, /* user_data */
399
534
               error))
400
80
        return FALSE;
401
7.05k
    } else {
402
7.05k
      g_debug("unhandled tag %s from %s",
403
7.05k
        fu_coswid_tag_to_string(tag_id),
404
7.05k
        fu_coswid_tag_to_string(FU_COSWID_TAG_PATH_ELEMENTS));
405
7.05k
    }
406
7.59k
  }
407
408
  /* success */
409
1.26k
  return TRUE;
410
1.41k
}
411
412
/* @userdata: a #FuCoswidFirmware */
413
static gboolean
414
fu_coswid_firmware_parse_directory(FuCborItem *item, gpointer user_data, GError **error)
415
1.85k
{
416
1.85k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
417
9.80k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
418
8.20k
    FuCoswidTag tag_id = 0;
419
8.20k
    FuCborItem *item_key = NULL;
420
8.20k
    FuCborItem *item_value = NULL;
421
422
8.20k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
423
8.20k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
424
84
      g_prefix_error(error, "failed to parse directory tag %u: ", (guint)i);
425
84
      return FALSE;
426
84
    }
427
8.12k
    if (tag_id == FU_COSWID_TAG_PATH_ELEMENTS) {
428
868
      if (!fu_coswid_parse_one_or_many(item_value,
429
868
               fu_coswid_firmware_parse_path_elements,
430
868
               self, /* user_data */
431
868
               error))
432
181
        return FALSE;
433
7.25k
    } else {
434
7.25k
      g_debug("unhandled tag %s from %s",
435
7.25k
        fu_coswid_tag_to_string(tag_id),
436
7.25k
        fu_coswid_tag_to_string(FU_COSWID_TAG_DIRECTORY));
437
7.25k
    }
438
8.12k
  }
439
440
  /* success */
441
1.59k
  return TRUE;
442
1.85k
}
443
444
/* @userdata: a #FuCoswidFirmware */
445
static gboolean
446
fu_coswid_firmware_parse_payload(FuCborItem *item, gpointer user_data, GError **error)
447
6.77k
{
448
6.77k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
449
31.1k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
450
26.7k
    FuCoswidTag tag_id = 0;
451
26.7k
    FuCborItem *item_key = NULL;
452
26.7k
    FuCborItem *item_value = NULL;
453
454
26.7k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
455
26.7k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
456
650
      g_prefix_error(error, "failed to parse payload tag %u: ", (guint)i);
457
650
      return FALSE;
458
650
    }
459
26.0k
    if (tag_id == FU_COSWID_TAG_FILE) {
460
3.23k
      if (!fu_coswid_parse_one_or_many(item_value,
461
3.23k
               fu_coswid_firmware_parse_file,
462
3.23k
               self, /* user_data */
463
3.23k
               error))
464
1.25k
        return FALSE;
465
22.8k
    } else if (tag_id == FU_COSWID_TAG_DIRECTORY) {
466
1.92k
      if (!fu_coswid_parse_one_or_many(item_value,
467
1.92k
               fu_coswid_firmware_parse_directory,
468
1.92k
               self, /* user_data */
469
1.92k
               error))
470
482
        return FALSE;
471
20.9k
    } else {
472
20.9k
      g_debug("unhandled tag %s from %s",
473
20.9k
        fu_coswid_tag_to_string(tag_id),
474
20.9k
        fu_coswid_tag_to_string(FU_COSWID_TAG_PAYLOAD));
475
20.9k
    }
476
26.0k
  }
477
478
  /* success */
479
4.38k
  return TRUE;
480
6.77k
}
481
482
static gboolean
483
fu_coswid_firmware_parse_entity_name(FuCoswidFirmwareEntity *entity,
484
             FuCborItem *item,
485
             GError **error)
486
692
{
487
  /* we might be calling this twice... */
488
692
  g_free(entity->name);
489
490
692
  entity->name = fu_coswid_read_string(item, error);
491
692
  if (entity->name == NULL) {
492
134
    g_prefix_error_literal(error, "failed to parse entity name: ");
493
134
    return FALSE;
494
134
  }
495
496
  /* success */
497
558
  return TRUE;
498
692
}
499
500
static gboolean
501
fu_coswid_firmware_parse_entity_regid(FuCoswidFirmwareEntity *entity,
502
              FuCborItem *item,
503
              GError **error)
504
489
{
505
  /* we might be calling this twice... */
506
489
  g_free(entity->regid);
507
508
489
  entity->regid = fu_coswid_read_string(item, error);
509
489
  if (entity->regid == NULL) {
510
273
    g_prefix_error_literal(error, "failed to parse entity regid: ");
511
273
    return FALSE;
512
273
  }
513
514
  /* success */
515
216
  return TRUE;
516
489
}
517
518
static gboolean
519
fu_coswid_firmware_parse_entity_role(FuCoswidFirmwareEntity *entity,
520
             FuCborItem *item,
521
             GError **error)
522
3.24k
{
523
3.24k
  if (fu_cbor_item_get_kind(item) == FU_CBOR_ITEM_KIND_INTEGER) {
524
1.51k
    gint64 tmp = 0;
525
1.51k
    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
1.51k
    if (tmp <= FU_COSWID_ENTITY_ROLE_UNKNOWN || tmp >= FU_COSWID_ENTITY_ROLE_LAST) {
530
1.03k
      g_set_error(error,
531
1.03k
            FWUPD_ERROR,
532
1.03k
            FWUPD_ERROR_INVALID_DATA,
533
1.03k
            "invalid entity role 0x%x",
534
1.03k
            (guint)tmp);
535
1.03k
      return FALSE;
536
1.03k
    }
537
479
    FU_BIT_SET(entity->roles, tmp);
538
539
1.73k
  } else if (fu_cbor_item_get_kind(item) == FU_CBOR_ITEM_KIND_ARRAY) {
540
3.18k
    for (guint j = 0; j < fu_cbor_item_array_length(item); j++) {
541
2.70k
      gint64 tmp = 0;
542
2.70k
      FuCborItem *value = fu_cbor_item_array_index(item, j);
543
2.70k
      if (!fu_cbor_item_get_integer(value, &tmp, error)) {
544
136
        g_prefix_error_literal(error, "failed to parse entity role: ");
545
136
        return FALSE;
546
136
      }
547
2.57k
      if (tmp <= FU_COSWID_ENTITY_ROLE_UNKNOWN ||
548
2.15k
          tmp >= FU_COSWID_ENTITY_ROLE_LAST) {
549
804
        g_set_error(error,
550
804
              FWUPD_ERROR,
551
804
              FWUPD_ERROR_INVALID_DATA,
552
804
              "invalid entity role 0x%x",
553
804
              (guint)tmp);
554
804
        return FALSE;
555
804
      }
556
1.76k
      FU_BIT_SET(entity->roles, tmp);
557
1.76k
    }
558
1.41k
  } else {
559
318
    g_set_error_literal(error,
560
318
            FWUPD_ERROR,
561
318
            FWUPD_ERROR_INVALID_DATA,
562
318
            "entity role item is not an uint or array");
563
318
    return FALSE;
564
318
  }
565
566
  /* success */
567
954
  return TRUE;
568
3.24k
}
569
570
/* @userdata: a #FuCoswidFirmware */
571
static gboolean
572
fu_coswid_firmware_parse_entity(FuCborItem *item, gpointer user_data, GError **error)
573
5.03k
{
574
5.03k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(user_data);
575
5.03k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
576
5.03k
  g_autoptr(FuCoswidFirmwareEntity) entity = g_new0(FuCoswidFirmwareEntity, 1);
577
578
21.0k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
579
19.2k
    FuCoswidTag tag_id = 0;
580
19.2k
    FuCborItem *item_key = NULL;
581
19.2k
    FuCborItem *item_value = NULL;
582
583
19.2k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
584
19.2k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
585
471
      g_prefix_error(error, "failed to parse entity tag %u: ", (guint)i);
586
471
      return FALSE;
587
471
    }
588
18.7k
    if (tag_id == FU_COSWID_TAG_ENTITY_NAME) {
589
692
      if (!fu_coswid_firmware_parse_entity_name(entity, item_value, error))
590
134
        return FALSE;
591
18.0k
    } else if (tag_id == FU_COSWID_TAG_REG_ID) {
592
489
      if (!fu_coswid_firmware_parse_entity_regid(entity, item_value, error))
593
273
        return FALSE;
594
17.5k
    } else if (tag_id == FU_COSWID_TAG_ROLE) {
595
3.24k
      if (!fu_coswid_firmware_parse_entity_role(entity, item_value, error))
596
2.29k
        return FALSE;
597
14.3k
    } else {
598
14.3k
      g_debug("unhandled tag %s from %s",
599
14.3k
        fu_coswid_tag_to_string(tag_id),
600
14.3k
        fu_coswid_tag_to_string(FU_COSWID_TAG_ENTITY));
601
14.3k
    }
602
18.7k
  }
603
604
  /* sanity check */
605
1.86k
  if (entity->name == NULL) {
606
1.36k
    g_set_error_literal(error,
607
1.36k
            FWUPD_ERROR,
608
1.36k
            FWUPD_ERROR_INVALID_DATA,
609
1.36k
            "entity does not have a name");
610
1.36k
    return FALSE;
611
1.36k
  }
612
497
  if (entity->roles == 0) {
613
127
    g_set_error_literal(error,
614
127
            FWUPD_ERROR,
615
127
            FWUPD_ERROR_INVALID_DATA,
616
127
            "entity has no roles");
617
127
    return FALSE;
618
127
  }
619
620
  /* success */
621
370
  g_ptr_array_add(priv->entities, g_steal_pointer(&entity));
622
370
  return TRUE;
623
497
}
624
625
static gboolean
626
fu_coswid_firmware_parse(FuFirmware *firmware,
627
       FuInputStream *stream,
628
       FuFirmwareParseFlags flags,
629
       GError **error)
630
73.0k
{
631
73.0k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware);
632
73.0k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
633
73.0k
  gsize offset = 0;
634
73.0k
  g_autoptr(FuCborItem) item = NULL;
635
636
73.0k
  item = fu_cbor_parse(stream,
637
73.0k
           &offset,
638
73.0k
           FU_COSWID_CBOR_MAX_DEPTH,
639
73.0k
           FU_COSWID_CBOR_MAX_ITEMS,
640
73.0k
           FU_COSWID_CBOR_MAX_LENGTH,
641
73.0k
           error);
642
73.0k
  if (item == NULL)
643
33.4k
    return FALSE;
644
39.5k
  fu_firmware_set_size(firmware, offset);
645
646
  /* pretty-print the result */
647
39.5k
  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
39.5k
  if (fu_cbor_item_get_kind(item) != FU_CBOR_ITEM_KIND_MAP) {
654
2.62k
    g_set_error_literal(error,
655
2.62k
            FWUPD_ERROR,
656
2.62k
            FWUPD_ERROR_INVALID_DATA,
657
2.62k
            "root item is not a map");
658
2.62k
    return FALSE;
659
2.62k
  }
660
661
  /* parse out anything interesting */
662
146k
  for (gsize i = 0; i < fu_cbor_item_map_length(item); i++) {
663
128k
    FuCoswidTag tag_id = 0;
664
128k
    FuCborItem *item_key = NULL;
665
128k
    FuCborItem *item_value = NULL;
666
667
128k
    fu_cbor_item_map_index(item, i, &item_key, &item_value);
668
128k
    if (!fu_coswid_read_tag(item_key, &tag_id, error)) {
669
1.79k
      g_prefix_error(error, "failed to parse root tag %u: ", (guint)i);
670
1.79k
      return FALSE;
671
1.79k
    }
672
673
    /* identity can be specified as a string or in binary */
674
126k
    if (tag_id == FU_COSWID_TAG_TAG_ID) {
675
7.66k
      g_autofree gchar *str = fu_coswid_read_string(item_value, error);
676
7.66k
      if (str == NULL) {
677
3.19k
        g_prefix_error_literal(error, "failed to parse tag-id: ");
678
3.19k
        return FALSE;
679
3.19k
      }
680
4.47k
      fu_firmware_set_id(firmware, str);
681
118k
    } else if (tag_id == FU_COSWID_TAG_SOFTWARE_NAME) {
682
4.67k
      g_free(priv->product);
683
4.67k
      priv->product = fu_coswid_read_string(item_value, error);
684
4.67k
      if (priv->product == NULL) {
685
1.08k
        g_prefix_error_literal(error, "failed to parse product: ");
686
1.08k
        return FALSE;
687
1.08k
      }
688
114k
    } else if (tag_id == FU_COSWID_TAG_SOFTWARE_VERSION) {
689
2.54k
      g_autofree gchar *str = fu_coswid_read_string(item_value, error);
690
2.54k
      if (str == NULL) {
691
581
        g_prefix_error_literal(error, "failed to parse software-version: ");
692
581
        return FALSE;
693
581
      }
694
1.96k
      fu_firmware_set_version(firmware, str);
695
111k
    } else if (tag_id == FU_COSWID_TAG_VERSION_SCHEME) {
696
2.59k
      if (!fu_coswid_read_version_scheme(item_value,
697
2.59k
                 &priv->version_scheme,
698
2.59k
                 error))
699
408
        return FALSE;
700
109k
    } else if (tag_id == FU_COSWID_TAG_SOFTWARE_META) {
701
4.22k
      if (!fu_coswid_parse_one_or_many(item_value,
702
4.22k
               fu_coswid_firmware_parse_meta,
703
4.22k
               self, /* user_data */
704
4.22k
               error))
705
1.42k
        return FALSE;
706
104k
    } else if (tag_id == FU_COSWID_TAG_EVIDENCE) {
707
6.76k
      if (!fu_coswid_parse_one_or_many(item_value,
708
6.76k
               fu_coswid_firmware_parse_evidence,
709
6.76k
               self, /* user_data */
710
6.76k
               error))
711
1.19k
        return FALSE;
712
98.0k
    } else if (tag_id == FU_COSWID_TAG_LINK) {
713
7.23k
      if (!fu_coswid_parse_one_or_many(item_value,
714
7.23k
               fu_coswid_firmware_parse_link,
715
7.23k
               self, /* user_data */
716
7.23k
               error))
717
1.55k
        return FALSE;
718
90.7k
    } else if (tag_id == FU_COSWID_TAG_PAYLOAD) {
719
6.16k
      if (!fu_coswid_parse_one_or_many(item_value,
720
6.16k
               fu_coswid_firmware_parse_payload,
721
6.16k
               self, /* user_data */
722
6.16k
               error))
723
2.74k
        return FALSE;
724
84.6k
    } else if (tag_id == FU_COSWID_TAG_ENTITY) {
725
5.55k
      if (!fu_coswid_parse_one_or_many(item_value,
726
5.55k
               fu_coswid_firmware_parse_entity,
727
5.55k
               self, /* user_data */
728
5.55k
               error))
729
4.86k
        return FALSE;
730
79.0k
    } else {
731
79.0k
      g_debug("unhandled tag %s from root", fu_coswid_tag_to_string(tag_id));
732
79.0k
    }
733
126k
  }
734
735
  /* device not supported */
736
18.1k
  if (fu_firmware_get_id(firmware) == NULL && fu_firmware_get_version(firmware) == NULL &&
737
14.6k
      priv->product == NULL && priv->entities->len == 0 && priv->links->len == 0) {
738
6.76k
    g_set_error_literal(error,
739
6.76k
            FWUPD_ERROR,
740
6.76k
            FWUPD_ERROR_NOT_SUPPORTED,
741
6.76k
            "not enough SBOM data");
742
6.76k
    return FALSE;
743
6.76k
  }
744
745
  /* success */
746
11.3k
  return TRUE;
747
18.1k
}
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
349
{
853
349
  g_autoptr(FuCborItem) item_hash = fu_cbor_item_new_array();
854
349
  g_autoptr(FuCborItem) item_hash_alg_id = fu_cbor_item_new_integer(hash->alg_id);
855
349
  g_autoptr(FuCborItem) item_hash_value = fu_cbor_item_new_bytes(hash->value);
856
349
  if (!fu_cbor_item_array_append(item_hash, item_hash_alg_id, error))
857
0
    return FALSE;
858
349
  if (!fu_cbor_item_array_append(item_hash, item_hash_value, error))
859
0
    return FALSE;
860
349
  if (!fu_cbor_item_array_append(root, item_hash, error))
861
0
    return FALSE;
862
863
  /* success */
864
349
  return TRUE;
865
349
}
866
867
static gboolean
868
fu_coswid_firmware_write_payload(FuCborItem *root, FuCoswidFirmwarePayload *payload, GError **error)
869
2.32k
{
870
2.32k
  g_autoptr(FuCborItem) item_payload = fu_cbor_item_new_map();
871
2.32k
  g_autoptr(FuCborItem) item_file = fu_cbor_item_new_map();
872
2.32k
  if (payload->name != NULL)
873
19
    fu_coswid_write_tag_string(item_file, FU_COSWID_TAG_FS_NAME, payload->name);
874
2.32k
  if (payload->size != 0)
875
195
    fu_coswid_write_tag_integer(item_file, FU_COSWID_TAG_SIZE, payload->size);
876
2.32k
  if (payload->hashes->len > 0) {
877
83
    g_autoptr(FuCborItem) item_hashes = fu_cbor_item_new_array();
878
432
    for (guint j = 0; j < payload->hashes->len; j++) {
879
349
      FuCoswidFirmwareHash *hash = g_ptr_array_index(payload->hashes, j);
880
349
      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
349
    }
885
83
    fu_coswid_write_tag_item(item_file, FU_COSWID_TAG_HASH, item_hashes);
886
83
  }
887
2.32k
  fu_coswid_write_tag_item(item_payload, FU_COSWID_TAG_FILE, item_file);
888
2.32k
  return fu_cbor_item_array_append(root, item_payload, error);
889
2.32k
}
890
891
static gboolean
892
fu_coswid_firmware_write_entity(FuCborItem *root, FuCoswidFirmwareEntity *entity, GError **error)
893
284
{
894
284
  g_autoptr(FuCborItem) item_entity = fu_cbor_item_new_map();
895
284
  g_autoptr(FuCborItem) item_roles = fu_cbor_item_new_array();
896
284
  if (entity->name != NULL)
897
284
    fu_coswid_write_tag_string(item_entity, FU_COSWID_TAG_ENTITY_NAME, entity->name);
898
284
  if (entity->regid != NULL)
899
31
    fu_coswid_write_tag_string(item_entity, FU_COSWID_TAG_REG_ID, entity->regid);
900
2.27k
  for (guint j = 0; j < FU_COSWID_ENTITY_ROLE_LAST; j++) {
901
1.98k
    if (FU_BIT_IS_SET(entity->roles, j)) {
902
624
      g_autoptr(FuCborItem) item_role = fu_cbor_item_new_integer(j);
903
624
      if (!fu_cbor_item_array_append(item_roles, item_role, error))
904
0
        return FALSE;
905
624
    }
906
1.98k
  }
907
284
  fu_coswid_write_tag_item(item_entity, FU_COSWID_TAG_ROLE, item_roles);
908
284
  return fu_cbor_item_array_append(root, item_entity, error);
909
284
}
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
762
{
922
762
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware);
923
762
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
924
762
  g_autoptr(FuCborItem) root = fu_cbor_item_new_map();
925
762
  g_autoptr(FuCborItem) item_meta = fu_cbor_item_new_map();
926
927
  /* preallocate the map structure */
928
762
  fu_coswid_write_tag_string(root, FU_COSWID_TAG_LANG, "en-US");
929
762
  if (fu_firmware_get_id(firmware) != NULL) {
930
215
    fwupd_guid_t uuid = {0};
931
215
    if (fwupd_guid_from_string(fu_firmware_get_id(firmware),
932
215
             &uuid,
933
215
             FWUPD_GUID_FLAG_NONE,
934
215
             NULL)) {
935
48
      fu_coswid_write_tag_bytestring(root,
936
48
                   FU_COSWID_TAG_TAG_ID,
937
48
                   (const guint8 *)&uuid,
938
48
                   sizeof(uuid));
939
167
    } else {
940
167
      fu_coswid_write_tag_string(root,
941
167
               FU_COSWID_TAG_TAG_ID,
942
167
               fu_firmware_get_id(firmware));
943
167
    }
944
215
  }
945
762
  fu_coswid_write_tag_bool(root, FU_COSWID_TAG_CORPUS, TRUE);
946
762
  if (priv->product != NULL)
947
15
    fu_coswid_write_tag_string(root, FU_COSWID_TAG_SOFTWARE_NAME, priv->product);
948
762
  if (fu_firmware_get_version(firmware) != NULL) {
949
115
    fu_coswid_write_tag_string(root,
950
115
             FU_COSWID_TAG_SOFTWARE_VERSION,
951
115
             fu_firmware_get_version(firmware));
952
115
  }
953
762
  if (priv->version_scheme != FU_COSWID_VERSION_SCHEME_UNKNOWN)
954
754
    fu_coswid_write_tag_integer(root,
955
754
              FU_COSWID_TAG_VERSION_SCHEME,
956
754
              priv->version_scheme);
957
762
  fu_coswid_write_tag_item(root, FU_COSWID_TAG_SOFTWARE_META, item_meta);
958
762
  fu_coswid_write_tag_string(item_meta, FU_COSWID_TAG_GENERATOR, PACKAGE_NAME);
959
762
  if (priv->summary != NULL)
960
1
    fu_coswid_write_tag_string(item_meta, FU_COSWID_TAG_SUMMARY, priv->summary);
961
762
  if (priv->colloquial_version != NULL) {
962
1
    fu_coswid_write_tag_string(item_meta,
963
1
             FU_COSWID_TAG_COLLOQUIAL_VERSION,
964
1
             priv->colloquial_version);
965
1
  }
966
762
  if (priv->persistent_id != NULL) {
967
1
    fu_coswid_write_tag_string(item_meta,
968
1
             FU_COSWID_TAG_PERSISTENT_ID,
969
1
             priv->persistent_id);
970
1
  }
971
972
  /* add evidence */
973
762
  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
762
  if (priv->entities->len > 0) {
982
33
    g_autoptr(FuCborItem) item_entities = fu_cbor_item_new_array();
983
317
    for (guint i = 0; i < priv->entities->len; i++) {
984
284
      FuCoswidFirmwareEntity *entity = g_ptr_array_index(priv->entities, i);
985
284
      if (!fu_coswid_firmware_write_entity(item_entities, entity, error))
986
0
        return NULL;
987
284
    }
988
33
    fu_coswid_write_tag_item(root, FU_COSWID_TAG_ENTITY, item_entities);
989
33
  }
990
991
  /* add links */
992
762
  if (priv->links->len > 0) {
993
401
    g_autoptr(FuCborItem) item_links = fu_cbor_item_new_array();
994
4.68k
    for (guint i = 0; i < priv->links->len; i++) {
995
4.28k
      FuCoswidFirmwareLink *link = g_ptr_array_index(priv->links, i);
996
4.28k
      g_autoptr(FuCborItem) item_link = fu_cbor_item_new_map();
997
4.28k
      if (link->href != NULL) {
998
82
        fu_coswid_write_tag_string(item_link,
999
82
                 FU_COSWID_TAG_HREF,
1000
82
                 link->href);
1001
82
      }
1002
4.28k
      fu_coswid_write_tag_integer(item_link, FU_COSWID_TAG_REL, link->rel);
1003
4.28k
      if (!fu_cbor_item_array_append(item_links, item_link, error))
1004
0
        return NULL;
1005
4.28k
    }
1006
401
    fu_coswid_write_tag_item(root, FU_COSWID_TAG_LINK, item_links);
1007
401
  }
1008
1009
  /* add payloads */
1010
762
  if (priv->payloads->len > 0) {
1011
163
    g_autoptr(FuCborItem) item_payloads = fu_cbor_item_new_array();
1012
2.49k
    for (guint i = 0; i < priv->payloads->len; i++) {
1013
2.32k
      FuCoswidFirmwarePayload *payload = g_ptr_array_index(priv->payloads, i);
1014
2.32k
      if (!fu_coswid_firmware_write_payload(item_payloads, payload, error))
1015
0
        return NULL;
1016
2.32k
    }
1017
163
    fu_coswid_write_tag_item(root, FU_COSWID_TAG_PAYLOAD, item_payloads);
1018
163
  }
1019
1020
  /* serialize */
1021
762
  return fu_cbor_item_write(root, error);
1022
762
}
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
73.0k
{
1308
73.0k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1309
73.0k
  priv->version_scheme = FU_COSWID_VERSION_SCHEME_SEMVER;
1310
73.0k
  priv->links = g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_link_free);
1311
73.0k
  priv->payloads =
1312
73.0k
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_payload_free);
1313
73.0k
  priv->entities =
1314
73.0k
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_entity_free);
1315
73.0k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_ALLOW_LINEAR);
1316
73.0k
}
1317
1318
static void
1319
fu_coswid_firmware_finalize(GObject *object)
1320
73.0k
{
1321
73.0k
  FuCoswidFirmware *self = FU_COSWID_FIRMWARE(object);
1322
73.0k
  FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self);
1323
1324
73.0k
  g_free(priv->product);
1325
73.0k
  g_free(priv->summary);
1326
73.0k
  g_free(priv->colloquial_version);
1327
73.0k
  g_free(priv->persistent_id);
1328
73.0k
  g_free(priv->device_id);
1329
73.0k
  g_ptr_array_unref(priv->links);
1330
73.0k
  g_ptr_array_unref(priv->payloads);
1331
73.0k
  g_ptr_array_unref(priv->entities);
1332
1333
73.0k
  G_OBJECT_CLASS(fu_coswid_firmware_parent_class)->finalize(object);
1334
73.0k
}
1335
1336
static void
1337
fu_coswid_firmware_class_init(FuCoswidFirmwareClass *klass)
1338
5
{
1339
5
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
1340
5
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
1341
5
  object_class->finalize = fu_coswid_firmware_finalize;
1342
5
  firmware_class->parse = fu_coswid_firmware_parse;
1343
5
  firmware_class->write = fu_coswid_firmware_write;
1344
5
  firmware_class->build = fu_coswid_firmware_build;
1345
5
  firmware_class->export = fu_coswid_firmware_export;
1346
5
  firmware_class->get_checksum = fu_coswid_firmware_get_checksum;
1347
5
  fu_firmware_set_size_max(firmware_class, 1 * FU_MB);
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
}