Coverage Report

Created: 2026-01-09 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-device-event.c
Line
Count
Source
1
/*
2
 * Copyright 2024 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuDeviceEvent"
8
9
#include "config.h"
10
11
#include "fu-device-event-private.h"
12
#include "fu-mem.h"
13
#include "fu-string.h"
14
15
/**
16
 * FuDeviceEvent:
17
 *
18
 * A device event, used to enumulate hardware.
19
 *
20
 * See also: [class@FuDevice]
21
 */
22
23
typedef struct {
24
  GType gtype;
25
  GRefString *key;
26
  gpointer data;
27
  GDestroyNotify data_destroy;
28
} FuDeviceEventBlob;
29
30
struct _FuDeviceEvent {
31
  GObject parent_instance;
32
  gchar *id;
33
  gchar *id_uncompressed;
34
  GPtrArray *values; /* element-type FuDeviceEventBlob */
35
};
36
37
static void
38
fu_device_event_codec_iface_init(FwupdCodecInterface *iface);
39
40
0
G_DEFINE_TYPE_WITH_CODE(FuDeviceEvent,
41
0
      fu_device_event,
42
0
      G_TYPE_OBJECT,
43
0
      G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC, fu_device_event_codec_iface_init))
44
0
45
0
/*
46
0
 * NOTE: We use an event *counter* that gets the next event in the emulation, and this ID is only
47
0
 * used as a sanity check in case we have to skip an entry.
48
0
 */
49
0
#define FU_DEVICE_EVENT_KEY_HASH_PREFIX_SIZE 8
50
51
static void
52
fu_device_event_blob_free(FuDeviceEventBlob *blob)
53
0
{
54
0
  g_ref_string_release(blob->key);
55
0
  if (blob->data_destroy != NULL)
56
0
    blob->data_destroy(blob->data);
57
0
  g_free(blob);
58
0
}
59
60
static FuDeviceEventBlob *
61
fu_device_event_blob_new_internal(GType gtype,
62
          GRefString *key,
63
          gpointer data,
64
          GDestroyNotify data_destroy)
65
0
{
66
0
  FuDeviceEventBlob *blob = g_new0(FuDeviceEventBlob, 1);
67
0
  blob->key = g_ref_string_acquire(key);
68
0
  blob->gtype = gtype;
69
0
  blob->data = data;
70
0
  blob->data_destroy = data_destroy;
71
0
  return blob;
72
0
}
73
74
static FuDeviceEventBlob *
75
fu_device_event_blob_new(GType gtype, const gchar *key, gpointer data, GDestroyNotify data_destroy)
76
0
{
77
0
  g_autoptr(GRefString) key_ref = NULL;
78
0
  const gchar *known_keys[] = {
79
0
      "Data",
80
0
      "DataOut",
81
0
      "Error",
82
0
      "ErrorMsg",
83
0
      "Rc",
84
0
      NULL,
85
0
  };
86
0
  key_ref = g_strv_contains(known_keys, key) ? g_ref_string_new_intern(key)
87
0
               : g_ref_string_new(key);
88
0
  return fu_device_event_blob_new_internal(gtype, key_ref, data, data_destroy);
89
0
}
90
91
/**
92
 * fu_device_event_build_id:
93
 * @id: a string
94
 *
95
 * Return the hash of the event ID.
96
 *
97
 * Returns: string hash prefix
98
 *
99
 * Since: 2.0.3
100
 **/
101
gchar *
102
fu_device_event_build_id(const gchar *id)
103
0
{
104
0
  guint8 buf[20] = {0};
105
0
  gsize bufsz = sizeof(buf);
106
0
  g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA1);
107
0
  g_autoptr(GString) id_hash = g_string_sized_new(FU_DEVICE_EVENT_KEY_HASH_PREFIX_SIZE + 1);
108
109
0
  g_return_val_if_fail(id != NULL, NULL);
110
111
  /* IMPORTANT: if you're reading this we're not using the SHA1 prefix for any kind of secure
112
   * hash, just because it is a tiny string that takes up less memory than the full ID. */
113
0
  g_checksum_update(csum, (const guchar *)id, strlen(id));
114
0
  g_checksum_get_digest(csum, buf, &bufsz);
115
0
  g_string_append_c(id_hash, '#');
116
0
  for (guint i = 0; i < FU_DEVICE_EVENT_KEY_HASH_PREFIX_SIZE / 2; i++)
117
0
    g_string_append_printf(id_hash, "%02x", buf[i]);
118
0
  return g_string_free(g_steal_pointer(&id_hash), FALSE);
119
0
}
120
121
/**
122
 * fu_device_event_get_id:
123
 * @self: a #FuDeviceEvent
124
 *
125
 * Return the truncated SHA1 of the #FuDeviceEvent key, which is normally set when creating the
126
 * object.
127
 *
128
 * Returns: (nullable): string
129
 *
130
 * Since: 2.0.0
131
 **/
132
const gchar *
133
fu_device_event_get_id(FuDeviceEvent *self)
134
0
{
135
0
  g_return_val_if_fail(FU_IS_DEVICE_EVENT(self), NULL);
136
0
  return self->id;
137
0
}
138
139
/**
140
 * fu_device_event_set_str:
141
 * @self: a #FuDeviceEvent
142
 * @key: (not nullable): a unique key, e.g. `Name`
143
 * @value: (nullable): a string
144
 *
145
 * Sets a string value on the event.
146
 *
147
 * Since: 2.0.0
148
 **/
149
void
150
fu_device_event_set_str(FuDeviceEvent *self, const gchar *key, const gchar *value)
151
0
{
152
0
  g_return_if_fail(FU_IS_DEVICE_EVENT(self));
153
0
  g_return_if_fail(key != NULL);
154
0
  g_ptr_array_add(self->values,
155
0
      fu_device_event_blob_new(G_TYPE_STRING, key, g_strdup(value), g_free));
156
0
}
157
158
/**
159
 * fu_device_event_set_i64:
160
 * @self: a #FuDeviceEvent
161
 * @key: (not nullable): a unique key, e.g. `Name`
162
 * @value: a string
163
 *
164
 * Sets an integer value on the string.
165
 *
166
 * Since: 2.0.0
167
 **/
168
void
169
fu_device_event_set_i64(FuDeviceEvent *self, const gchar *key, gint64 value)
170
0
{
171
0
  g_return_if_fail(FU_IS_DEVICE_EVENT(self));
172
0
  g_return_if_fail(key != NULL);
173
174
0
  g_ptr_array_add(
175
0
      self->values,
176
0
      fu_device_event_blob_new(G_TYPE_INT, key, g_memdup2(&value, sizeof(value)), g_free));
177
0
}
178
179
/**
180
 * fu_device_event_set_bytes:
181
 * @self: a #FuDeviceEvent
182
 * @key: (not nullable): a unique key, e.g. `Name`
183
 * @value: (not nullable): a #GBytes
184
 *
185
 * Sets a blob on the event. Note: blobs are stored internally as BASE-64 strings.
186
 *
187
 * Since: 2.0.0
188
 **/
189
void
190
fu_device_event_set_bytes(FuDeviceEvent *self, const gchar *key, GBytes *value)
191
0
{
192
0
  g_return_if_fail(FU_IS_DEVICE_EVENT(self));
193
0
  g_return_if_fail(key != NULL);
194
0
  g_return_if_fail(value != NULL);
195
0
  g_ptr_array_add(self->values,
196
0
      fu_device_event_blob_new(
197
0
          G_TYPE_STRING,
198
0
          key,
199
0
          g_base64_encode(g_bytes_get_data(value, NULL), g_bytes_get_size(value)),
200
0
          g_free));
201
0
}
202
203
/**
204
 * fu_device_event_set_data:
205
 * @self: a #FuDeviceEvent
206
 * @key: (not nullable): a unique key, e.g. `Name`
207
 * @buf: (nullable): a buffer
208
 * @bufsz: size of @buf
209
 *
210
 * Sets a memory buffer on the event. Note: memory buffers are stored internally as BASE-64 strings.
211
 *
212
 * Since: 2.0.0
213
 **/
214
void
215
fu_device_event_set_data(FuDeviceEvent *self, const gchar *key, const guint8 *buf, gsize bufsz)
216
0
{
217
0
  g_return_if_fail(FU_IS_DEVICE_EVENT(self));
218
0
  g_return_if_fail(key != NULL);
219
0
  g_ptr_array_add(
220
0
      self->values,
221
0
      fu_device_event_blob_new(G_TYPE_STRING, key, g_base64_encode(buf, bufsz), g_free));
222
0
}
223
224
/**
225
 * fu_device_event_set_error:
226
 * @self: a #FuDeviceEvent
227
 * @error: (not nullable): a #GError with domain #FwupdError
228
 *
229
 * Sets an error on the event.
230
 *
231
 * Since: 2.0.6
232
 **/
233
void
234
fu_device_event_set_error(FuDeviceEvent *self, const GError *error)
235
0
{
236
0
  g_return_if_fail(FU_IS_DEVICE_EVENT(self));
237
0
  g_return_if_fail(error != NULL);
238
0
  g_return_if_fail(error->domain == FWUPD_ERROR);
239
0
  fu_device_event_set_i64(self, "Error", error->code);
240
0
  fu_device_event_set_str(self, "ErrorMsg", error->message);
241
0
}
242
243
/**
244
 * fu_device_event_check_error:
245
 * @self: a #FuDeviceEvent
246
 * @error: (nullable): optional return location for an error
247
 *
248
 * Sets an error from the event if possible.
249
 *
250
 * Returns: %FALSE if @error was set
251
 *
252
 * Since: 2.0.6
253
 **/
254
gboolean
255
fu_device_event_check_error(FuDeviceEvent *self, GError **error)
256
0
{
257
0
  gint64 code;
258
0
  const gchar *message;
259
260
0
  g_return_val_if_fail(FU_IS_DEVICE_EVENT(self), FALSE);
261
262
  /* nothing to do */
263
0
  if (error == NULL)
264
0
    return TRUE;
265
266
  /* anything set */
267
0
  code = fu_device_event_get_i64(self, "Error", NULL);
268
0
  if (code == G_MAXINT64)
269
0
    return TRUE;
270
0
  message = fu_device_event_get_str(self, "ErrorMsg", NULL);
271
0
  if (message == NULL)
272
0
    message = fwupd_error_to_string(code);
273
274
  /* success, in a way */
275
0
  g_set_error_literal(error, FWUPD_ERROR, code, message);
276
0
  return FALSE;
277
0
}
278
279
static gpointer
280
fu_device_event_lookup(FuDeviceEvent *self, const gchar *key, GType gtype, GError **error)
281
0
{
282
0
  FuDeviceEventBlob *blob = NULL;
283
284
0
  for (guint i = 0; i < self->values->len; i++) {
285
0
    FuDeviceEventBlob *blob_tmp = g_ptr_array_index(self->values, i);
286
0
    if (g_strcmp0(blob_tmp->key, key) == 0) {
287
0
      blob = blob_tmp;
288
0
      break;
289
0
    }
290
0
  }
291
0
  if (blob == NULL) {
292
0
    g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no event for key %s", key);
293
0
    return NULL;
294
0
  }
295
0
  if (blob->gtype != gtype) {
296
0
    g_set_error(error,
297
0
          FWUPD_ERROR,
298
0
          FWUPD_ERROR_INVALID_DATA,
299
0
          "invalid event type for key %s",
300
0
          key);
301
0
    return NULL;
302
0
  }
303
0
  return blob->data;
304
0
}
305
306
/**
307
 * fu_device_event_get_str:
308
 * @self: a #FuDeviceEvent
309
 * @key: (not nullable): a unique key, e.g. `Name`
310
 * @error: (nullable): optional return location for an error
311
 *
312
 * Gets a string value from the event.
313
 *
314
 * Returns: (nullable): string, or %NULL on error
315
 *
316
 * Since: 2.0.0
317
 **/
318
const gchar *
319
fu_device_event_get_str(FuDeviceEvent *self, const gchar *key, GError **error)
320
0
{
321
0
  g_return_val_if_fail(FU_IS_DEVICE_EVENT(self), NULL);
322
0
  g_return_val_if_fail(key != NULL, NULL);
323
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
324
0
  return (const gchar *)fu_device_event_lookup(self, key, G_TYPE_STRING, error);
325
0
}
326
327
/**
328
 * fu_device_event_get_i64:
329
 * @self: a #FuDeviceEvent
330
 * @key: (not nullable): a unique key, e.g. `Name`
331
 * @error: (nullable): optional return location for an error
332
 *
333
 * Gets an integer value from the event.
334
 *
335
 * Returns: integer, or %G_MAXINT64 on error
336
 *
337
 * Since: 2.0.0
338
 **/
339
gint64
340
fu_device_event_get_i64(FuDeviceEvent *self, const gchar *key, GError **error)
341
0
{
342
0
  gint64 *val;
343
0
  g_return_val_if_fail(FU_IS_DEVICE_EVENT(self), G_MAXINT64);
344
0
  g_return_val_if_fail(key != NULL, G_MAXINT64);
345
0
  g_return_val_if_fail(error == NULL || *error == NULL, G_MAXINT64);
346
0
  val = fu_device_event_lookup(self, key, G_TYPE_INT, error);
347
0
  if (val == NULL)
348
0
    return G_MAXINT64;
349
0
  return *val;
350
0
}
351
352
/**
353
 * fu_device_event_get_bytes:
354
 * @self: a #FuDeviceEvent
355
 * @key: (not nullable): a unique key, e.g. `Name`
356
 * @error: (nullable): optional return location for an error
357
 *
358
 * Gets a memory blob from the event.
359
 *
360
 * Returns: (transfer full) (nullable): byes data, or %NULL on error
361
 *
362
 * Since: 2.0.0
363
 **/
364
GBytes *
365
fu_device_event_get_bytes(FuDeviceEvent *self, const gchar *key, GError **error)
366
0
{
367
0
  const gchar *blobstr;
368
0
  gsize bufsz = 0;
369
0
  g_autofree guchar *buf = NULL;
370
371
0
  g_return_val_if_fail(FU_IS_DEVICE_EVENT(self), NULL);
372
0
  g_return_val_if_fail(key != NULL, NULL);
373
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
374
375
0
  blobstr = fu_device_event_lookup(self, key, G_TYPE_STRING, error);
376
0
  if (blobstr == NULL)
377
0
    return NULL;
378
0
  if (blobstr[0] == '\0')
379
0
    return g_bytes_new(NULL, 0);
380
0
  buf = g_base64_decode(blobstr, &bufsz);
381
0
  return g_bytes_new_take(g_steal_pointer(&buf), bufsz);
382
0
}
383
384
/**
385
 * fu_device_event_copy_data:
386
 * @self: a #FuDeviceEvent
387
 * @key: (not nullable): a unique key, e.g. `Name`
388
 * @buf: (nullable): a buffer
389
 * @bufsz: size of @buf
390
 * @actual_length: (out) (optional): the actual number of bytes sent, or %NULL
391
 * @error: (nullable): optional return location for an error
392
 *
393
 * Copies memory from the event.
394
 *
395
 * Returns: %TRUE if the buffer was copied
396
 *
397
 * Since: 2.0.0
398
 **/
399
gboolean
400
fu_device_event_copy_data(FuDeviceEvent *self,
401
        const gchar *key,
402
        guint8 *buf,
403
        gsize bufsz,
404
        gsize *actual_length,
405
        GError **error)
406
0
{
407
0
  const gchar *blobstr;
408
0
  gsize bufsz_src = 0;
409
0
  g_autofree guchar *buf_src = NULL;
410
411
0
  g_return_val_if_fail(FU_IS_DEVICE_EVENT(self), FALSE);
412
0
  g_return_val_if_fail(key != NULL, FALSE);
413
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
414
415
0
  blobstr = fu_device_event_lookup(self, key, G_TYPE_STRING, error);
416
0
  if (blobstr == NULL)
417
0
    return FALSE;
418
0
  buf_src = g_base64_decode(blobstr, &bufsz_src);
419
0
  if (actual_length != NULL)
420
0
    *actual_length = bufsz_src;
421
0
  if (buf != NULL)
422
0
    return fu_memcpy_safe(buf, bufsz, 0x0, buf_src, bufsz_src, 0x0, bufsz_src, error);
423
0
  return TRUE;
424
0
}
425
426
static void
427
fu_device_event_add_json(FwupdCodec *codec, FwupdJsonObject *json_obj, FwupdCodecFlags flags)
428
0
{
429
0
  FuDeviceEvent *self = FU_DEVICE_EVENT(codec);
430
431
0
  if (self->id_uncompressed != NULL && (flags & FWUPD_CODEC_FLAG_COMPRESSED) == 0) {
432
0
    fwupd_json_object_add_string(json_obj, "Id", self->id_uncompressed);
433
0
  } else if (self->id != NULL) {
434
0
    fwupd_json_object_add_string(json_obj, "Id", self->id);
435
0
  }
436
437
0
  for (guint i = 0; i < self->values->len; i++) {
438
0
    FuDeviceEventBlob *blob = g_ptr_array_index(self->values, i);
439
0
    if (blob->gtype == G_TYPE_INT) {
440
0
      fwupd_json_object_add_integer(json_obj, blob->key, *((gint64 *)blob->data));
441
0
    } else if (blob->gtype == G_TYPE_BYTES || blob->gtype == G_TYPE_STRING) {
442
0
      fwupd_json_object_add_string(json_obj,
443
0
                 blob->key,
444
0
                 (const gchar *)blob->data);
445
0
    } else {
446
0
      g_warning("invalid GType %s, ignoring", g_type_name(blob->gtype));
447
0
    }
448
0
  }
449
0
}
450
451
static void
452
fu_device_event_set_id(FuDeviceEvent *self, const gchar *id)
453
0
{
454
0
  g_return_if_fail(FU_IS_DEVICE_EVENT(self));
455
0
  g_return_if_fail(id != NULL);
456
457
0
  g_clear_pointer(&self->id, g_free);
458
0
  g_clear_pointer(&self->id_uncompressed, g_free);
459
460
  /* already a truncated SHA1 hash? */
461
0
  if (g_str_has_prefix(id, "#")) {
462
0
    self->id = g_strdup(id);
463
0
  } else {
464
0
    self->id_uncompressed = g_strdup(id);
465
0
    self->id = fu_device_event_build_id(id);
466
0
  }
467
0
}
468
469
static gboolean
470
fu_device_event_from_json(FwupdCodec *codec, FwupdJsonObject *json_obj, GError **error)
471
0
{
472
0
  FuDeviceEvent *self = FU_DEVICE_EVENT(codec);
473
0
  for (guint i = 0; i < fwupd_json_object_get_size(json_obj); i++) {
474
0
    GRefString *key = fwupd_json_object_get_key_for_index(json_obj, i, NULL);
475
0
    g_autoptr(FwupdJsonNode) json_node = NULL;
476
477
0
    json_node = fwupd_json_object_get_node_for_index(json_obj, i, error);
478
0
    if (json_node == NULL)
479
0
      return FALSE;
480
0
    if (fwupd_json_node_get_kind(json_node) == FWUPD_JSON_NODE_KIND_STRING) {
481
0
      GRefString *str = fwupd_json_node_get_string(json_node, NULL);
482
0
      if (g_strcmp0(key, "Id") == 0) {
483
0
        fu_device_event_set_id(self, str);
484
0
      } else if (str != NULL) {
485
0
        g_ptr_array_add(self->values,
486
0
            fu_device_event_blob_new_internal(
487
0
                G_TYPE_STRING,
488
0
                key,
489
0
                g_ref_string_acquire(str),
490
0
                (GDestroyNotify)g_ref_string_release));
491
0
      } else {
492
0
        g_ptr_array_add(self->values,
493
0
            fu_device_event_blob_new_internal(G_TYPE_STRING,
494
0
                      key,
495
0
                      NULL,
496
0
                      NULL));
497
0
      }
498
0
    } else if (fwupd_json_node_get_kind(json_node) == FWUPD_JSON_NODE_KIND_RAW) {
499
0
      GRefString *str;
500
0
      gint64 value = 0;
501
502
0
      str = fwupd_json_node_get_raw(json_node, error);
503
0
      if (str == NULL)
504
0
        return FALSE;
505
0
      if (!fu_strtoll(str,
506
0
          &value,
507
0
          G_MININT64,
508
0
          G_MAXINT64,
509
0
          FU_INTEGER_BASE_AUTO,
510
0
          error))
511
0
        return FALSE;
512
0
      g_ptr_array_add(
513
0
          self->values,
514
0
          fu_device_event_blob_new_internal(G_TYPE_INT,
515
0
                    key,
516
0
                    g_memdup2(&value, sizeof(value)),
517
0
                    g_free));
518
0
    }
519
0
  }
520
521
  /* we do not need this again, so avoid keeping all the tree data in memory */
522
0
  fwupd_json_object_clear(json_obj);
523
524
  /* success */
525
0
  return TRUE;
526
0
}
527
528
static void
529
fu_device_event_init(FuDeviceEvent *self)
530
0
{
531
0
  self->values = g_ptr_array_new_with_free_func((GDestroyNotify)fu_device_event_blob_free);
532
0
}
533
534
static void
535
fu_device_event_finalize(GObject *object)
536
0
{
537
0
  FuDeviceEvent *self = FU_DEVICE_EVENT(object);
538
0
  g_free(self->id);
539
0
  g_free(self->id_uncompressed);
540
0
  g_ptr_array_unref(self->values);
541
0
  G_OBJECT_CLASS(fu_device_event_parent_class)->finalize(object);
542
0
}
543
544
static void
545
fu_device_event_class_init(FuDeviceEventClass *klass)
546
0
{
547
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
548
0
  object_class->finalize = fu_device_event_finalize;
549
0
}
550
551
static void
552
fu_device_event_codec_iface_init(FwupdCodecInterface *iface)
553
0
{
554
0
  iface->add_json = fu_device_event_add_json;
555
0
  iface->from_json = fu_device_event_from_json;
556
0
}
557
558
/**
559
 * fu_device_event_new:
560
 * @id: a cache key, which is converted to a truncated SHA1 hash if required
561
 *
562
 * Return value: (transfer full): a new #FuDeviceEvent object.
563
 *
564
 * Since: 2.0.0
565
 **/
566
FuDeviceEvent *
567
fu_device_event_new(const gchar *id)
568
0
{
569
0
  FuDeviceEvent *self = g_object_new(FU_TYPE_DEVICE_EVENT, NULL);
570
0
  if (id != NULL)
571
0
    fu_device_event_set_id(self, id);
572
0
  return FU_DEVICE_EVENT(self);
573
0
}