Coverage Report

Created: 2026-07-16 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupd/fwupd-common.c
Line
Count
Source
1
/*
2
 * Copyright 2017 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include "fwupd-common-private.h"
10
#include "fwupd-error.h"
11
12
#ifdef HAVE_GIO_UNIX
13
#include <errno.h>
14
#include <fcntl.h>
15
#include <glib/gstdio.h>
16
#include <unistd.h>
17
#endif
18
19
#ifdef HAVE_MEMFD_CREATE
20
#include <sys/mman.h>
21
#endif
22
23
#include <string.h>
24
25
/**
26
 * fwupd_checksum_guess_kind:
27
 * @checksum: (nullable): a checksum
28
 *
29
 * Guesses the checksum kind based on the length of the hash.
30
 *
31
 * Returns: a checksum type, e.g. %G_CHECKSUM_SHA1
32
 *
33
 * Since: 0.9.3
34
 **/
35
GChecksumType
36
fwupd_checksum_guess_kind(const gchar *checksum)
37
0
{
38
0
  guint len;
39
0
  g_return_val_if_fail(checksum != NULL, G_CHECKSUM_SHA1);
40
0
  len = strlen(checksum);
41
0
  if (len == 32)
42
0
    return G_CHECKSUM_MD5;
43
0
  if (len == 40)
44
0
    return G_CHECKSUM_SHA1;
45
0
  if (len == 64)
46
0
    return G_CHECKSUM_SHA256;
47
0
  if (len == 96)
48
0
    return G_CHECKSUM_SHA384;
49
0
  if (len == 128)
50
0
    return G_CHECKSUM_SHA512;
51
0
  return G_CHECKSUM_SHA1;
52
0
}
53
54
/**
55
 * fwupd_checksum_type_to_string_display:
56
 * @checksum_type: a #GChecksumType, e.g. %G_CHECKSUM_SHA1
57
 *
58
 * Formats a checksum type for display.
59
 *
60
 * Returns: text, or %NULL for invalid
61
 *
62
 * Since: 1.9.6
63
 **/
64
const gchar *
65
fwupd_checksum_type_to_string_display(GChecksumType checksum_type)
66
0
{
67
0
  if (checksum_type == G_CHECKSUM_MD5)
68
0
    return "MD5";
69
0
  if (checksum_type == G_CHECKSUM_SHA1)
70
0
    return "SHA1";
71
0
  if (checksum_type == G_CHECKSUM_SHA256)
72
0
    return "SHA256";
73
0
  if (checksum_type == G_CHECKSUM_SHA384)
74
0
    return "SHA384";
75
0
  if (checksum_type == G_CHECKSUM_SHA512)
76
0
    return "SHA512";
77
0
  return NULL;
78
0
}
79
80
/**
81
 * fwupd_checksum_format_for_display:
82
 * @checksum: (nullable): a checksum
83
 *
84
 * Formats a checksum for display.
85
 *
86
 * Returns: text, or %NULL for invalid
87
 *
88
 * Since: 0.9.3
89
 **/
90
gchar *
91
fwupd_checksum_format_for_display(const gchar *checksum)
92
0
{
93
0
  GChecksumType kind = fwupd_checksum_guess_kind(checksum);
94
0
  return g_strdup_printf("%s(%s)", fwupd_checksum_type_to_string_display(kind), checksum);
95
0
}
96
97
/**
98
 * fwupd_checksum_get_by_kind:
99
 * @checksums: (element-type utf8): checksums
100
 * @kind: a checksum type, e.g. %G_CHECKSUM_SHA512
101
 *
102
 * Gets a specific checksum kind.
103
 *
104
 * Returns: a checksum from the array, or %NULL if not found
105
 *
106
 * Since: 0.9.4
107
 **/
108
const gchar *
109
fwupd_checksum_get_by_kind(GPtrArray *checksums, GChecksumType kind)
110
0
{
111
0
  g_return_val_if_fail(checksums != NULL, NULL);
112
0
  for (guint i = 0; i < checksums->len; i++) {
113
0
    const gchar *checksum = g_ptr_array_index(checksums, i);
114
0
    if (fwupd_checksum_guess_kind(checksum) == kind)
115
0
      return checksum;
116
0
  }
117
0
  return NULL;
118
0
}
119
120
/**
121
 * fwupd_checksum_get_best:
122
 * @checksums: (element-type utf8): checksums
123
 *
124
 * Gets the best possible checksum kind.
125
 *
126
 * Returns: a checksum from the array, or %NULL if nothing was suitable
127
 *
128
 * Since: 0.9.4
129
 **/
130
const gchar *
131
fwupd_checksum_get_best(GPtrArray *checksums)
132
0
{
133
0
  GChecksumType checksum_types[] = {
134
0
      G_CHECKSUM_SHA512,
135
0
      G_CHECKSUM_SHA256,
136
0
      G_CHECKSUM_SHA384,
137
0
      G_CHECKSUM_SHA1,
138
0
  };
139
0
  g_return_val_if_fail(checksums != NULL, NULL);
140
0
  for (guint i = 0; i < G_N_ELEMENTS(checksum_types); i++) {
141
0
    for (guint j = 0; j < checksums->len; j++) {
142
0
      const gchar *checksum = g_ptr_array_index(checksums, j);
143
0
      if (fwupd_checksum_guess_kind(checksum) == checksum_types[i])
144
0
        return checksum;
145
0
    }
146
0
  }
147
0
  return NULL;
148
0
}
149
150
#define FWUPD_GUID_NAMESPACE_DEFAULT   "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
151
#define FWUPD_GUID_NAMESPACE_MICROSOFT "70ffd812-4c7f-4c7d-0000-000000000000"
152
153
typedef struct __attribute__((packed)) { /* nocheck:blocked */
154
  guint32 a;
155
  guint16 b;
156
  guint16 c;
157
  guint16 d;
158
  guint8 e[6];
159
} fwupd_guid_native_t;
160
161
/**
162
 * fwupd_guid_to_string:
163
 * @guid: a #fwupd_guid_t to read
164
 * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_MIXED_ENDIAN
165
 *
166
 * Returns a text GUID of mixed or BE endian for a packed buffer.
167
 *
168
 * Returns: a new GUID string
169
 *
170
 * Since: 1.2.5
171
 **/
172
gchar *
173
fwupd_guid_to_string(const fwupd_guid_t *guid, FwupdGuidFlags flags)
174
96.8k
{
175
96.8k
  fwupd_guid_native_t gnat;
176
177
96.8k
  g_return_val_if_fail(guid != NULL, NULL);
178
179
  /* copy to avoid issues with aligning */
180
96.8k
  memcpy(&gnat, guid, sizeof(gnat)); /* nocheck:blocked */
181
182
  /* mixed is bizaar, but specified as the DCE encoding */
183
96.8k
  if (flags & FWUPD_GUID_FLAG_MIXED_ENDIAN) {
184
78.8k
    return g_strdup_printf("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
185
78.8k
               (guint)GUINT32_FROM_LE(gnat.a), /* nocheck:blocked */
186
78.8k
               (guint)GUINT16_FROM_LE(gnat.b), /* nocheck:blocked */
187
78.8k
               (guint)GUINT16_FROM_LE(gnat.c), /* nocheck:blocked */
188
78.8k
               (guint)GUINT16_FROM_BE(gnat.d), /* nocheck:blocked */
189
78.8k
               gnat.e[0],
190
78.8k
               gnat.e[1],
191
78.8k
               gnat.e[2],
192
78.8k
               gnat.e[3],
193
78.8k
               gnat.e[4],
194
78.8k
               gnat.e[5]);
195
78.8k
  }
196
17.9k
  return g_strdup_printf("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
197
17.9k
             (guint)GUINT32_FROM_BE(gnat.a), /* nocheck:blocked */
198
17.9k
             (guint)GUINT16_FROM_BE(gnat.b), /* nocheck:blocked */
199
17.9k
             (guint)GUINT16_FROM_BE(gnat.c), /* nocheck:blocked */
200
17.9k
             (guint)GUINT16_FROM_BE(gnat.d), /* nocheck:blocked */
201
17.9k
             gnat.e[0],
202
17.9k
             gnat.e[1],
203
17.9k
             gnat.e[2],
204
17.9k
             gnat.e[3],
205
17.9k
             gnat.e[4],
206
17.9k
             gnat.e[5]);
207
96.8k
}
208
209
/**
210
 * fwupd_guid_from_string:
211
 * @guidstr: (not nullable): a GUID, e.g. `00112233-4455-6677-8899-aabbccddeeff`
212
 * @guid: (nullable): a #fwupd_guid_t, or NULL to just check the GUID
213
 * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_MIXED_ENDIAN
214
 * @error: (nullable): optional return location for an error
215
 *
216
 * Converts a string GUID into its binary encoding. All string GUIDs are
217
 * formatted as big endian but on-disk can be encoded in different ways.
218
 *
219
 * Returns: %TRUE for success
220
 *
221
 * Since: 1.2.5
222
 **/
223
gboolean
224
fwupd_guid_from_string(const gchar *guidstr,
225
           fwupd_guid_t *guid,
226
           FwupdGuidFlags flags,
227
           GError **error)
228
22.9k
{
229
22.9k
  fwupd_guid_native_t gu = {0x0};
230
22.9k
  gboolean mixed_endian = flags & FWUPD_GUID_FLAG_MIXED_ENDIAN;
231
22.9k
  guint64 tmp;
232
22.9k
  g_auto(GStrv) split = NULL;
233
234
22.9k
  g_return_val_if_fail(guidstr != NULL, FALSE);
235
22.9k
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
236
237
  /* split into sections */
238
22.9k
  if (strlen(guidstr) != 36) {
239
1.50k
    g_set_error_literal(error,
240
1.50k
            FWUPD_ERROR,
241
1.50k
            FWUPD_ERROR_INVALID_DATA,
242
1.50k
            "GUID is not valid format");
243
1.50k
    return FALSE;
244
1.50k
  }
245
21.4k
  split = g_strsplit(guidstr, "-", 5);
246
21.4k
  if (g_strv_length(split) != 5) {
247
36
    g_set_error_literal(error,
248
36
            FWUPD_ERROR,
249
36
            FWUPD_ERROR_INVALID_DATA,
250
36
            "GUID is not valid format, no dashes");
251
36
    return FALSE;
252
36
  }
253
21.4k
  if (strlen(split[0]) != 8 || strlen(split[1]) != 4 || strlen(split[2]) != 4 ||
254
21.3k
      strlen(split[3]) != 4 || strlen(split[4]) != 12) {
255
97
    g_set_error_literal(error,
256
97
            FWUPD_ERROR,
257
97
            FWUPD_ERROR_INVALID_DATA,
258
97
            "GUID is not valid format, not GUID");
259
97
    return FALSE;
260
97
  }
261
262
  /* parse */
263
21.3k
  if (!g_ascii_string_to_unsigned(split[0], 16, 0, 0xffffffff, &tmp, error))
264
24
    return FALSE;
265
21.3k
  gu.a = mixed_endian ? GUINT32_TO_LE(tmp) : GUINT32_TO_BE(tmp); /* nocheck:blocked */
266
21.3k
  if (!g_ascii_string_to_unsigned(split[1], 16, 0, 0xffff, &tmp, error))
267
34
    return FALSE;
268
21.2k
  gu.b = mixed_endian ? GUINT16_TO_LE(tmp) : GUINT16_TO_BE(tmp); /* nocheck:blocked */
269
21.2k
  if (!g_ascii_string_to_unsigned(split[2], 16, 0, 0xffff, &tmp, error))
270
33
    return FALSE;
271
21.2k
  gu.c = mixed_endian ? GUINT16_TO_LE(tmp) : GUINT16_TO_BE(tmp); /* nocheck:blocked */
272
21.2k
  if (!g_ascii_string_to_unsigned(split[3], 16, 0, 0xffff, &tmp, error))
273
25
    return FALSE;
274
21.2k
  gu.d = GUINT16_TO_BE(tmp); /* nocheck:blocked */
275
148k
  for (guint i = 0; i < 6; i++) {
276
127k
    gchar buffer[3] = {0x0};
277
127k
    memcpy(buffer, split[4] + (i * 2), 2); /* nocheck:blocked */
278
127k
    if (!g_ascii_string_to_unsigned(buffer, 16, 0, 0xff, &tmp, error))
279
72
      return FALSE;
280
127k
    gu.e[i] = tmp;
281
127k
  }
282
21.1k
  if (guid != NULL)
283
21.1k
    memcpy(guid, &gu, sizeof(gu)); /* nocheck:blocked */
284
285
  /* success */
286
21.1k
  return TRUE;
287
21.2k
}
288
289
/**
290
 * fwupd_guid_hash_data:
291
 * @data: data to hash
292
 * @datasz: length of @data
293
 * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT
294
 *
295
 * Returns a GUID for some data. This uses a hash and so even small
296
 * differences in the @data will produce radically different return values.
297
 *
298
 * The implementation is taken from RFC4122, Section 4.1.3; specifically
299
 * using a type-5 SHA-1 hash.
300
 *
301
 * Returns: a new GUID, or %NULL for internal error
302
 *
303
 * Since: 1.2.5
304
 **/
305
gchar *
306
fwupd_guid_hash_data(const guint8 *data, gsize datasz, FwupdGuidFlags flags)
307
0
{
308
0
  gsize digestlen = 20;
309
0
  guint8 hash[20] = {0};
310
0
  fwupd_guid_t uu_new;
311
0
  g_autoptr(GChecksum) csum = NULL;
312
0
  const fwupd_guid_t uu_default = {0x6b,
313
0
           0xa7,
314
0
           0xb8,
315
0
           0x10,
316
0
           0x9d,
317
0
           0xad,
318
0
           0x11,
319
0
           0xd1,
320
0
           0x80,
321
0
           0xb4,
322
0
           0x00,
323
0
           0xc0,
324
0
           0x4f,
325
0
           0xd4,
326
0
           0x30,
327
0
           0xc8};
328
0
  const fwupd_guid_t uu_microso = {0x70, 0xff, 0xd8, 0x12, 0x4c, 0x7f, 0x4c, 0x7d};
329
0
  const fwupd_guid_t *uu_namespace = &uu_default;
330
331
0
  g_return_val_if_fail(data != NULL, NULL);
332
0
  g_return_val_if_fail(datasz != 0, NULL);
333
334
  /* old MS GUID */
335
0
  if (flags & FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT)
336
0
    uu_namespace = &uu_microso;
337
338
  /* hash the namespace and then the string */
339
0
  csum = g_checksum_new(G_CHECKSUM_SHA1);
340
0
  g_checksum_update(csum, (guchar *)uu_namespace, sizeof(*uu_namespace));
341
0
  g_checksum_update(csum, (guchar *)data, (gssize)datasz);
342
0
  g_checksum_get_digest(csum, hash, &digestlen);
343
344
  /* copy most parts of the hash 1:1 */
345
0
  memcpy(uu_new, hash, sizeof(uu_new)); /* nocheck:blocked */
346
347
  /* set specific bits according to Section 4.1.3 */
348
0
  uu_new[6] = (guint8)((uu_new[6] & 0x0f) | (5 << 4));
349
0
  uu_new[8] = (guint8)((uu_new[8] & 0x3f) | 0x80);
350
0
  return fwupd_guid_to_string((const fwupd_guid_t *)&uu_new, flags);
351
0
}
352
353
/**
354
 * fwupd_device_id_is_valid:
355
 * @device_id: string to check, e.g. `d3fae86d95e5d56626129d00e332c4b8dac95442`
356
 *
357
 * Checks the string is a valid non-partial device ID. It is important to note
358
 * that the wildcard ID of `*` is not considered a valid ID in this function and
359
 * the client must check for this manually if this should be allowed.
360
 *
361
 * Returns: %TRUE if @guid was a fwupd device ID, %FALSE otherwise
362
 *
363
 * Since: 1.4.1
364
 **/
365
gboolean
366
fwupd_device_id_is_valid(const gchar *device_id)
367
0
{
368
0
  if (device_id == NULL)
369
0
    return FALSE;
370
0
  if (strlen(device_id) != 40)
371
0
    return FALSE;
372
0
  for (guint i = 0; device_id[i] != '\0'; i++) {
373
0
    gchar tmp = device_id[i];
374
    /* isalnum isn't case specific */
375
0
    if ((tmp < 'a' || tmp > 'f') && (tmp < '0' || tmp > '9'))
376
0
      return FALSE;
377
0
  }
378
0
  return TRUE;
379
0
}
380
381
/**
382
 * fwupd_guid_is_valid:
383
 * @guid: string to check, e.g. `00112233-4455-6677-8899-aabbccddeeff`
384
 *
385
 * Checks the string is a valid GUID.
386
 *
387
 * Returns: %TRUE if @guid was a valid GUID, %FALSE otherwise
388
 *
389
 * Since: 1.2.5
390
 **/
391
gboolean
392
fwupd_guid_is_valid(const gchar *guid)
393
0
{
394
0
  const gchar zeroguid[] = {"00000000-0000-0000-0000-000000000000"};
395
396
  /* sanity check */
397
0
  if (guid == NULL)
398
0
    return FALSE;
399
400
  /* check for dashes and hexdigits in the right place */
401
0
  for (guint i = 0; i < sizeof(zeroguid) - 1; i++) {
402
0
    if (guid[i] == '\0')
403
0
      return FALSE;
404
0
    if (zeroguid[i] == '-') {
405
0
      if (guid[i] != '-')
406
0
        return FALSE;
407
0
      continue;
408
0
    }
409
0
    if (!g_ascii_isxdigit(guid[i]))
410
0
      return FALSE;
411
0
  }
412
413
  /* longer than required */
414
0
  if (guid[sizeof(zeroguid) - 1] != '\0')
415
0
    return FALSE;
416
417
  /* not valid */
418
0
  return g_strcmp0(guid, zeroguid) != 0;
419
0
}
420
421
/**
422
 * fwupd_guid_hash_string:
423
 * @str: (nullable): a source string to use as a key
424
 *
425
 * Returns a GUID for a given string. This uses a hash and so even small
426
 * differences in the @str will produce radically different return values.
427
 *
428
 * The default implementation is taken from RFC4122, Section 4.1.3; specifically
429
 * using a type-5 SHA-1 hash with a DNS namespace.
430
 * The same result can be obtained with this simple python program:
431
 *
432
 *    #!/usr/bin/python
433
 *    import uuid
434
 *    print uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
435
 *
436
 * Returns: a new GUID, or %NULL if the string was invalid
437
 *
438
 * Since: 1.2.5
439
 **/
440
gchar *
441
fwupd_guid_hash_string(const gchar *str)
442
0
{
443
0
  if (str == NULL || str[0] == '\0')
444
0
    return NULL;
445
0
  return fwupd_guid_hash_data((const guint8 *)str, strlen(str), FWUPD_GUID_FLAG_NONE);
446
0
}
447
448
#ifdef HAVE_GIO_UNIX
449
/**
450
 * fwupd_unix_input_stream_from_bytes: (skip):
451
 **/
452
GUnixInputStream *
453
fwupd_unix_input_stream_from_bytes(GBytes *bytes, GError **error)
454
{
455
  g_autofd gint fd = -1;
456
  gssize rc;
457
#ifndef HAVE_MEMFD_CREATE
458
  gchar tmp_file[] = "/tmp/fwupd.XXXXXX";
459
#endif
460
461
#ifdef HAVE_MEMFD_CREATE
462
  fd = memfd_create("fwupd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
463
#else
464
  /* emulate in-memory file by an unlinked temporary file */
465
  fd = g_mkstemp(tmp_file);
466
  if (fd != -1) {
467
    rc = g_unlink(tmp_file);
468
    if (rc != 0) {
469
      g_set_error_literal(error,
470
              FWUPD_ERROR,
471
              FWUPD_ERROR_INVALID_FILE,
472
              "failed to unlink temporary file");
473
      return NULL;
474
    }
475
  }
476
#endif
477
478
  if (fd < 0) {
479
    g_set_error_literal(error,
480
            FWUPD_ERROR,
481
            FWUPD_ERROR_INVALID_FILE,
482
            "failed to create memfd");
483
    return NULL;
484
  }
485
  rc = write(fd, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes));
486
  if (rc < 0) {
487
    g_set_error(error,
488
          FWUPD_ERROR,
489
          FWUPD_ERROR_INVALID_FILE,
490
          "failed to write %" G_GSSIZE_FORMAT,
491
          rc);
492
    return NULL;
493
  }
494
  if (lseek(fd, 0, SEEK_SET) < 0) {
495
    g_set_error(error,
496
          FWUPD_ERROR,
497
          FWUPD_ERROR_INVALID_FILE,
498
          "failed to seek: %s",
499
          fwupd_strerror(errno));
500
    return NULL;
501
  }
502
#ifdef HAVE_MEMFD_CREATE
503
  if (fcntl(fd, F_ADD_SEALS, F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL) < 0) {
504
    g_set_error(error,
505
          FWUPD_ERROR,
506
          FWUPD_ERROR_INVALID_FILE,
507
          "failed to seal memfd: %s",
508
          fwupd_strerror(errno));
509
    return NULL;
510
  }
511
#endif
512
  return G_UNIX_INPUT_STREAM(g_unix_input_stream_new(g_steal_fd(&fd), TRUE));
513
}
514
515
/**
516
 * fwupd_unix_input_stream_from_fn: (skip):
517
 **/
518
GUnixInputStream *
519
fwupd_unix_input_stream_from_fn(const gchar *fn, GError **error)
520
{
521
  g_autofd gint fd = open(fn, O_RDONLY);
522
  if (fd < 0) {
523
    g_set_error(error,
524
          FWUPD_ERROR,
525
          FWUPD_ERROR_INVALID_FILE,
526
          "failed to open %s: %s",
527
          fn,
528
          fwupd_strerror(errno));
529
    return NULL;
530
  }
531
  return G_UNIX_INPUT_STREAM(g_unix_input_stream_new(g_steal_fd(&fd), TRUE));
532
}
533
534
/**
535
 * fwupd_unix_output_stream_from_fn: (skip):
536
 **/
537
GUnixOutputStream *
538
fwupd_unix_output_stream_from_fn(const gchar *fn, GError **error)
539
{
540
  g_autofd gint fd = g_open(fn, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
541
  if (fd < 0) {
542
    g_set_error(error,
543
          FWUPD_ERROR,
544
          FWUPD_ERROR_INVALID_FILE,
545
          "failed to open %s: %s",
546
          fn,
547
          fwupd_strerror(errno));
548
    return NULL;
549
  }
550
  return G_UNIX_OUTPUT_STREAM(g_unix_output_stream_new(g_steal_fd(&fd), TRUE));
551
}
552
#endif
553
554
/**
555
 * fwupd_percentage_is_valid:
556
 * @value: maybe a percentage value
557
 *
558
 * Calculates is a percentage value is valid, i.e. `>= 0.0` and `<= 100.0`.
559
 *
560
 * Returns: %TRUE for success
561
 *
562
 * Since: 2.1.3
563
 **/
564
gboolean
565
fwupd_percentage_is_valid(gdouble value)
566
0
{
567
0
  return value >= 0.0 && value <= 100.0;
568
0
}
569
570
0
#define FWUPD_PERCENTAGE_CHANGE_EPSILON (0.01)
571
572
/**
573
 * fwupd_percentage_delta_notify:
574
 * @value_old: a percentage value
575
 * @value_new: another percentage value
576
 *
577
 * Calculates if a percentage change is significant, and worth notifying the client about.
578
 *
579
 * Returns: %TRUE for success
580
 *
581
 * Since: 2.1.3
582
 **/
583
gboolean
584
fwupd_percentage_delta_notify(gdouble value_old, gdouble value_new)
585
0
{
586
0
  if (value_old - value_new > FWUPD_PERCENTAGE_CHANGE_EPSILON)
587
0
    return TRUE;
588
0
  if (value_new - value_old > FWUPD_PERCENTAGE_CHANGE_EPSILON)
589
0
    return TRUE;
590
0
  if (value_old < 100.0 && value_new >= 100.0)
591
0
    return TRUE;
592
0
  if (fwupd_percentage_is_valid(value_old) && !fwupd_percentage_is_valid(value_new))
593
0
    return TRUE;
594
0
  if (fwupd_percentage_is_valid(value_new) && !fwupd_percentage_is_valid(value_old))
595
0
    return TRUE;
596
0
  return FALSE;
597
0
}