Coverage Report

Created: 2025-07-01 07:09

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