Coverage Report

Created: 2026-07-16 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-version-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
0
#define G_LOG_DOMAIN "FuCommon"
8
9
#include "config.h"
10
11
#include <string.h>
12
13
#include "fwupd-enums.h"
14
#include "fwupd-error.h"
15
16
#include "fu-string.h"
17
#include "fu-version-common.h"
18
19
/*
20
 * nocheck:magic-inlines=81
21
 */
22
23
278
#define FU_COMMON_VERSION_DECODE_BCD(val) (((((val) >> 4) & 0x0f) * 10) + ((val) & 0x0f))
24
25
static gchar *
26
fu_version_ensure_semver_internal(const gchar *version);
27
28
/**
29
 * fu_version_from_uint64:
30
 * @val: a raw version number
31
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_QUAD
32
 *
33
 * Returns a dotted decimal version string from a 64 bit number.
34
 *
35
 * Returns: a version number, e.g. `1.2.3.4`, or %NULL if not supported
36
 *
37
 * Since: 1.8.2
38
 **/
39
gchar *
40
fu_version_from_uint64(guint64 val, FwupdVersionFormat kind)
41
787
{
42
787
  if (kind == FWUPD_VERSION_FORMAT_QUAD) {
43
    /* AABB.CCDD.EEFF.GGHH */
44
787
    return g_strdup_printf("%" G_GUINT64_FORMAT "."
45
787
               "%" G_GUINT64_FORMAT "."
46
787
               "%" G_GUINT64_FORMAT "."
47
787
               "%" G_GUINT64_FORMAT "",
48
787
               (val >> 48) & 0xffff,
49
787
               (val >> 32) & 0xffff,
50
787
               (val >> 16) & 0xffff,
51
787
               val & 0xffff);
52
787
  }
53
0
  if (kind == FWUPD_VERSION_FORMAT_PAIR) {
54
    /* AABBCCDD.EEFFGGHH */
55
0
    return g_strdup_printf("%" G_GUINT64_FORMAT ".%" G_GUINT64_FORMAT "",
56
0
               (val >> 32) & 0xffffffff,
57
0
               val & 0xffffffff);
58
0
  }
59
0
  if (kind == FWUPD_VERSION_FORMAT_COMPAL_BIOS) {
60
    /* AA.BB */
61
0
    return g_strdup_printf("%02x.%02x",
62
0
               (guint)((val >> 8) & 0xff),
63
0
               (guint)(val & 0xff));
64
0
  }
65
0
  if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) {
66
    /* AABBCCDD */
67
0
    return g_strdup_printf("%" G_GUINT64_FORMAT, val);
68
0
  }
69
0
  if (kind == FWUPD_VERSION_FORMAT_HEX) {
70
    /* 0xAABBCCDDEEFFGGHH */
71
0
    return g_strdup_printf("0x%08x%08x",
72
0
               (guint32)(val >> 32),
73
0
               (guint32)(val & 0xffffffff));
74
0
  }
75
0
  g_critical("failed to convert version format %s: %" G_GUINT64_FORMAT "",
76
0
       fwupd_version_format_to_string(kind),
77
0
       val);
78
0
  return NULL;
79
0
}
80
81
/**
82
 * fu_version_from_uint32:
83
 * @val: a uint32le version number
84
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
85
 *
86
 * Returns a dotted decimal version string from a 32 bit number.
87
 *
88
 * Returns: a version number, e.g. `1.0.3`, or %NULL if not supported
89
 *
90
 * Since: 1.8.2
91
 **/
92
gchar *
93
fu_version_from_uint32(guint32 val, FwupdVersionFormat kind)
94
857
{
95
857
  if (kind == FWUPD_VERSION_FORMAT_QUAD) {
96
    /* AA.BB.CC.DD */
97
429
    return g_strdup_printf("%u.%u.%u.%u",
98
429
               (val >> 24) & 0xff,
99
429
               (val >> 16) & 0xff,
100
429
               (val >> 8) & 0xff,
101
429
               val & 0xff);
102
429
  }
103
428
  if (kind == FWUPD_VERSION_FORMAT_TRIPLET) {
104
    /* AA.BB.CCDD */
105
316
    return g_strdup_printf("%u.%u.%u",
106
316
               (val >> 24) & 0xff,
107
316
               (val >> 16) & 0xff,
108
316
               val & 0xffff);
109
316
  }
110
112
  if (kind == FWUPD_VERSION_FORMAT_PAIR) {
111
    /* AABB.CCDD */
112
0
    return g_strdup_printf("%u.%u", (val >> 16) & 0xffff, val & 0xffff);
113
0
  }
114
112
  if (kind == FWUPD_VERSION_FORMAT_COMPAL_BIOS) {
115
    /* AA.BB */
116
0
    return g_strdup_printf("%02x.%02x", (val >> 8) & 0xff, val & 0xff);
117
0
  }
118
112
  if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) {
119
    /* AABBCCDD */
120
0
    return g_strdup_printf("%" G_GUINT32_FORMAT, val);
121
0
  }
122
112
  if (kind == FWUPD_VERSION_FORMAT_BCD) {
123
    /* AA.BB.CC.DD, but BCD */
124
0
    return g_strdup_printf("%u.%u.%u.%u",
125
0
               FU_COMMON_VERSION_DECODE_BCD(val >> 24),
126
0
               FU_COMMON_VERSION_DECODE_BCD(val >> 16),
127
0
               FU_COMMON_VERSION_DECODE_BCD(val >> 8),
128
0
               FU_COMMON_VERSION_DECODE_BCD(val));
129
0
  }
130
112
  if (kind == FWUPD_VERSION_FORMAT_INTEL_ME) {
131
    /* aaa+11.bbbbb.cccccccc.dddddddddddddddd */
132
0
    return g_strdup_printf("%u.%u.%u.%u",
133
0
               ((val >> 29) & 0x07) + 0x0b,
134
0
               (val >> 24) & 0x1f,
135
0
               (val >> 16) & 0xff,
136
0
               val & 0xffff);
137
0
  }
138
112
  if (kind == FWUPD_VERSION_FORMAT_INTEL_ME2) {
139
    /* A.B.CC.DDDD */
140
0
    return g_strdup_printf("%u.%u.%u.%u",
141
0
               (val >> 28) & 0x0f,
142
0
               (val >> 24) & 0x0f,
143
0
               (val >> 16) & 0xff,
144
0
               val & 0xffff);
145
0
  }
146
112
  if (kind == FWUPD_VERSION_FORMAT_INTEL_CSME19) {
147
    /* aaa+19.bbbbb.cccccccc.dddddddddddddddd */
148
0
    return g_strdup_printf("%u.%u.%u.%u",
149
0
               ((val >> 29) & 0x07) + 19,
150
0
               (val >> 24) & 0x1f,
151
0
               (val >> 16) & 0xff,
152
0
               val & 0xffff);
153
0
  }
154
112
  if (kind == FWUPD_VERSION_FORMAT_SURFACE_LEGACY) {
155
    /* 10b.12b.10b */
156
0
    return g_strdup_printf("%u.%u.%u",
157
0
               (val >> 22) & 0x3ff,
158
0
               (val >> 10) & 0xfff,
159
0
               val & 0x3ff);
160
0
  }
161
112
  if (kind == FWUPD_VERSION_FORMAT_SURFACE) {
162
    /* 8b.16b.8b */
163
70
    return g_strdup_printf("%u.%u.%u",
164
70
               (val >> 24) & 0xff,
165
70
               (val >> 8) & 0xffff,
166
70
               val & 0xff);
167
70
  }
168
42
  if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS) {
169
    /* BB.CC.DD */
170
42
    return g_strdup_printf("%u.%u.%u",
171
42
               (val >> 16) & 0xff,
172
42
               (val >> 8) & 0xff,
173
42
               val & 0xff);
174
42
  }
175
0
  if (kind == FWUPD_VERSION_FORMAT_HEX) {
176
    /* 0xAABBCCDD */
177
0
    return g_strdup_printf("0x%08x", val);
178
0
  }
179
0
  if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS_MSB) {
180
    /* AA.BB.CC */
181
0
    return g_strdup_printf("%u.%u.%u",
182
0
               (val >> 24) & 0xff,
183
0
               (val >> 16) & 0xff,
184
0
               (val >> 8) & 0xff);
185
0
  }
186
0
  g_critical("failed to convert version format %s: %u",
187
0
       fwupd_version_format_to_string(kind),
188
0
       val);
189
0
  return NULL;
190
0
}
191
192
/**
193
 * fu_version_from_uint32_hex:
194
 * @val: a uint32le version number
195
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
196
 *
197
 * Returns a dotted decimal hex string from a 32 bit number.
198
 *
199
 * Returns: a version number, e.g. `1a.0.d3`, or %NULL if not supported
200
 *
201
 * Since: 2.0.0
202
 **/
203
gchar *
204
fu_version_from_uint32_hex(guint32 val, FwupdVersionFormat kind)
205
0
{
206
0
  if (kind == FWUPD_VERSION_FORMAT_HEX)
207
0
    return g_strdup_printf("0x%x", val);
208
0
  if (kind == FWUPD_VERSION_FORMAT_NUMBER)
209
0
    return g_strdup_printf("%x", val);
210
0
  if (kind == FWUPD_VERSION_FORMAT_PAIR)
211
0
    return g_strdup_printf("%x.%x", (val >> 16) & 0xffff, val & 0xffff);
212
0
  if (kind == FWUPD_VERSION_FORMAT_TRIPLET) {
213
0
    return g_strdup_printf("%x.%x.%x",
214
0
               (val >> 24) & 0xff,
215
0
               (val >> 16) & 0xff,
216
0
               val & 0xffff);
217
0
  }
218
0
  if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS) {
219
0
    return g_strdup_printf("%x.%x.%x",
220
0
               (val >> 16) & 0xff,
221
0
               (val >> 8) & 0xff,
222
0
               val & 0xff);
223
0
  }
224
0
  if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS_MSB) {
225
0
    return g_strdup_printf("%x.%x.%x",
226
0
               (val >> 24) & 0xff,
227
0
               (val >> 16) & 0xff,
228
0
               (val >> 8) & 0xff);
229
0
  }
230
0
  if (kind == FWUPD_VERSION_FORMAT_QUAD) {
231
0
    return g_strdup_printf("%x.%x.%x.%x",
232
0
               (val >> 24) & 0xff,
233
0
               (val >> 16) & 0xff,
234
0
               (val >> 8) & 0xff,
235
0
               val & 0xff);
236
0
  }
237
238
0
  if (kind == FWUPD_VERSION_FORMAT_BCD) {
239
0
    return g_strdup_printf("%x.%x.%x.%x",
240
0
               FU_COMMON_VERSION_DECODE_BCD(val >> 24),
241
0
               FU_COMMON_VERSION_DECODE_BCD(val >> 16),
242
0
               FU_COMMON_VERSION_DECODE_BCD(val >> 8),
243
0
               FU_COMMON_VERSION_DECODE_BCD(val));
244
0
  }
245
0
  g_critical("failed to convert version format %s: %u",
246
0
       fwupd_version_format_to_string(kind),
247
0
       val);
248
0
  return NULL;
249
0
}
250
251
/**
252
 * fu_version_from_uint24:
253
 * @val: a uint24le version number
254
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
255
 *
256
 * Returns a dotted decimal version string from a 24 bit number.
257
 *
258
 * Returns: a version number, e.g. `1.0.3`, or %NULL if not supported
259
 *
260
 * Since: 1.8.9
261
 **/
262
gchar *
263
fu_version_from_uint24(guint32 val, FwupdVersionFormat kind)
264
0
{
265
0
  if (kind == FWUPD_VERSION_FORMAT_TRIPLET) {
266
    /* BB.CC.DD */
267
0
    return g_strdup_printf("%u.%u.%u",
268
0
               (val >> 24) & 0xff,
269
0
               (val >> 16) & 0xff,
270
0
               val & 0xffff);
271
0
  }
272
0
  if (kind == FWUPD_VERSION_FORMAT_PAIR) {
273
    /* BB.CCDD */
274
0
    return g_strdup_printf("%u.%u", (val >> 16) & 0xff, val & 0xffff);
275
0
  }
276
0
  if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) {
277
    /* BBCCDD */
278
0
    return g_strdup_printf("%" G_GUINT32_FORMAT, val);
279
0
  }
280
0
  if (kind == FWUPD_VERSION_FORMAT_HEX) {
281
    /* 0xBBCCDD */
282
0
    return g_strdup_printf("0x%06x", val);
283
0
  }
284
0
  g_critical("failed to convert version format %s: %u",
285
0
       fwupd_version_format_to_string(kind),
286
0
       val);
287
0
  return NULL;
288
0
}
289
290
/**
291
 * fu_version_from_uint16:
292
 * @val: a uint16le version number
293
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
294
 *
295
 * Returns a dotted decimal version string from a 16 bit number.
296
 *
297
 * Returns: a version number, e.g. `1.3`, or %NULL if not supported
298
 *
299
 * Since: 1.8.2
300
 **/
301
gchar *
302
fu_version_from_uint16(guint16 val, FwupdVersionFormat kind)
303
139
{
304
139
  if (kind == FWUPD_VERSION_FORMAT_BCD) {
305
139
    return g_strdup_printf("%i.%i",
306
139
               FU_COMMON_VERSION_DECODE_BCD(val >> 8),
307
139
               FU_COMMON_VERSION_DECODE_BCD(val));
308
139
  }
309
0
  if (kind == FWUPD_VERSION_FORMAT_PAIR)
310
0
    return g_strdup_printf("%u.%u", (guint)(val >> 8) & 0xff, (guint)val & 0xff);
311
0
  if (kind == FWUPD_VERSION_FORMAT_COMPAL_BIOS)
312
0
    return g_strdup_printf("%02x.%02x", (guint)(val >> 8) & 0xff, (guint)val & 0xff);
313
0
  if (kind == FWUPD_VERSION_FORMAT_QUAD) {
314
0
    return g_strdup_printf("%u.%u.%u.%u",
315
0
               (guint)(val >> 12) & 0xF,
316
0
               (guint)(val >> 8) & 0xF,
317
0
               (guint)(val >> 4) & 0xF,
318
0
               (guint)val & 0xF);
319
0
  }
320
0
  if (kind == FWUPD_VERSION_FORMAT_TRIPLET) {
321
0
    return g_strdup_printf("%u.%u.%u",
322
0
               (guint)(val >> 12) & 0xF,
323
0
               (guint)(val >> 8) & 0xF,
324
0
               (guint)val & 0xFF);
325
0
  }
326
0
  if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN)
327
0
    return g_strdup_printf("%" G_GUINT16_FORMAT, val);
328
0
  if (kind == FWUPD_VERSION_FORMAT_HEX) {
329
    /* 0xAABB */
330
0
    return g_strdup_printf("0x%04x", val);
331
0
  }
332
0
  g_critical("failed to convert version format %s: %u",
333
0
       fwupd_version_format_to_string(kind),
334
0
       val);
335
0
  return NULL;
336
0
}
337
338
/**
339
 * fu_version_from_uint16_hex:
340
 * @val: a uint16le version number
341
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
342
 *
343
 * Returns a dotted hex version string from a 16 bit number.
344
 *
345
 * Returns: a version number, e.g. `1a.f3`, or %NULL if not supported
346
 *
347
 * Since: 2.0.0
348
 **/
349
gchar *
350
fu_version_from_uint16_hex(guint16 val, FwupdVersionFormat kind)
351
693
{
352
693
  if (kind == FWUPD_VERSION_FORMAT_NUMBER)
353
0
    return g_strdup_printf("%x", val);
354
693
  if (kind == FWUPD_VERSION_FORMAT_HEX)
355
0
    return g_strdup_printf("0x%x", val);
356
693
  if (kind == FWUPD_VERSION_FORMAT_PAIR)
357
693
    return g_strdup_printf("%x.%x", (guint)(val >> 8) & 0xff, (guint)val & 0xff);
358
0
  if (kind == FWUPD_VERSION_FORMAT_BCD) {
359
0
    return g_strdup_printf("%x.%x",
360
0
               (guint)FU_COMMON_VERSION_DECODE_BCD(val >> 8),
361
0
               (guint)FU_COMMON_VERSION_DECODE_BCD(val));
362
0
  }
363
0
  if (kind == FWUPD_VERSION_FORMAT_TRIPLET) {
364
0
    return g_strdup_printf("%x.%x.%x",
365
0
               (guint)(val >> 12) & 0xF,
366
0
               (guint)(val >> 8) & 0xF,
367
0
               (guint)val & 0xFF);
368
0
  }
369
0
  g_critical("failed to convert version format %s: %u",
370
0
       fwupd_version_format_to_string(kind),
371
0
       val);
372
0
  return NULL;
373
0
}
374
375
static gint
376
fu_version_compare_char(gchar chr1, gchar chr2)
377
0
{
378
0
  if (chr1 == chr2)
379
0
    return 0;
380
0
  if (chr1 == '~')
381
0
    return -1;
382
0
  if (chr2 == '~')
383
0
    return 1;
384
0
  return chr1 < chr2 ? -1 : 1;
385
0
}
386
387
static gint
388
fu_version_compare_chunk(const gchar *str1, const gchar *str2)
389
0
{
390
0
  guint i;
391
392
  /* trivial */
393
0
  if (g_strcmp0(str1, str2) == 0)
394
0
    return 0;
395
0
  if (str1 == NULL)
396
0
    return 1;
397
0
  if (str2 == NULL)
398
0
    return -1;
399
400
  /* check each char of the chunk */
401
0
  for (i = 0; str1[i] != '\0' && str2[i] != '\0'; i++) {
402
0
    gint rc = fu_version_compare_char(str1[i], str2[i]);
403
0
    if (rc != 0)
404
0
      return rc;
405
0
  }
406
0
  return fu_version_compare_char(str1[i], str2[i]);
407
0
}
408
409
static gboolean
410
_g_ascii_is_digits(const gchar *str)
411
0
{
412
0
  g_return_val_if_fail(str != NULL, FALSE);
413
0
  for (gsize i = 0; str[i] != '\0'; i++) {
414
0
    if (!g_ascii_isdigit(str[i]))
415
0
      return FALSE;
416
0
  }
417
0
  return TRUE;
418
0
}
419
420
static gboolean
421
_g_ascii_is_xdigits(const gchar *str)
422
0
{
423
0
  g_return_val_if_fail(str != NULL, FALSE);
424
0
  for (gsize i = 0; str[i] != '\0'; i++) {
425
0
    if (!g_ascii_isxdigit(str[i]))
426
0
      return FALSE;
427
0
  }
428
0
  return TRUE;
429
0
}
430
431
static guint
432
fu_version_format_number_sections(FwupdVersionFormat fmt)
433
0
{
434
0
  if (fmt == FWUPD_VERSION_FORMAT_PLAIN || fmt == FWUPD_VERSION_FORMAT_NUMBER ||
435
0
      fmt == FWUPD_VERSION_FORMAT_HEX)
436
0
    return 1;
437
0
  if (fmt == FWUPD_VERSION_FORMAT_PAIR || fmt == FWUPD_VERSION_FORMAT_COMPAL_BIOS ||
438
0
      fmt == FWUPD_VERSION_FORMAT_BCD)
439
0
    return 2;
440
0
  if (fmt == FWUPD_VERSION_FORMAT_TRIPLET || fmt == FWUPD_VERSION_FORMAT_SURFACE_LEGACY ||
441
0
      fmt == FWUPD_VERSION_FORMAT_SURFACE || fmt == FWUPD_VERSION_FORMAT_DELL_BIOS ||
442
0
      fmt == FWUPD_VERSION_FORMAT_DELL_BIOS_MSB)
443
0
    return 3;
444
0
  if (fmt == FWUPD_VERSION_FORMAT_QUAD || fmt == FWUPD_VERSION_FORMAT_INTEL_ME ||
445
0
      fmt == FWUPD_VERSION_FORMAT_INTEL_ME2 || fmt == FWUPD_VERSION_FORMAT_INTEL_CSME19)
446
0
    return 4;
447
0
  return 0;
448
0
}
449
450
/**
451
 * fu_version_ensure_semver:
452
 * @version: (nullable): a version number, e.g. ` V1.2.3 `
453
 * @fmt: a version format, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
454
 *
455
 * Builds a semver from the possibly crazy version number. Depending on the @semver value
456
 * the string will be split and a string in the correct format will be returned.
457
 *
458
 * Returns: a version number, e.g. `1.2.3`, or %NULL if the version was not valid
459
 *
460
 * Since: 1.8.2
461
 */
462
gchar *
463
fu_version_ensure_semver(const gchar *version, FwupdVersionFormat fmt)
464
0
{
465
0
  guint sections_actual;
466
0
  guint sections_expected = fu_version_format_number_sections(fmt);
467
0
  g_autofree gchar *tmp = NULL;
468
0
  g_auto(GStrv) split = NULL;
469
0
  g_autoptr(GString) str = g_string_new(NULL);
470
471
  /* split into all sections */
472
0
  tmp = fu_version_ensure_semver_internal(version);
473
0
  if (tmp == NULL)
474
0
    return NULL;
475
0
  if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN)
476
0
    return g_steal_pointer(&tmp);
477
0
  split = g_strsplit(tmp, ".", -1);
478
0
  sections_actual = g_strv_length(split);
479
480
  /* add zero sections as required */
481
0
  if (sections_actual < sections_expected) {
482
0
    for (guint i = 0; i < sections_expected - sections_actual; i++) {
483
0
      if (str->len > 0)
484
0
        g_string_append(str, ".");
485
0
      g_string_append(str, "0");
486
0
    }
487
0
  }
488
489
  /* only add enough sections for the format */
490
0
  for (guint i = 0; i < sections_actual && i < sections_expected; i++) {
491
0
    if (str->len > 0)
492
0
      g_string_append(str, ".");
493
0
    g_string_append(str, split[i]);
494
0
  }
495
496
  /* success */
497
0
  return g_string_free(g_steal_pointer(&str), FALSE);
498
0
}
499
500
static gchar *
501
fu_version_ensure_semver_internal(const gchar *version)
502
0
{
503
0
  gboolean dot_valid = FALSE;
504
0
  guint digit_cnt = 0;
505
0
  g_autoptr(GString) version_safe = g_string_new(NULL);
506
507
  /* invalid */
508
0
  if (version == NULL)
509
0
    return NULL;
510
511
  /* hex prefix */
512
0
  if (g_str_has_prefix(version, "0x")) {
513
0
    return fu_version_parse_from_format(version, FWUPD_VERSION_FORMAT_TRIPLET);
514
0
  }
515
516
  /* make sane */
517
0
  for (guint i = 0; version[i] != '\0'; i++) {
518
0
    if (g_ascii_isdigit(version[i])) {
519
0
      g_string_append_c(version_safe, version[i]);
520
0
      digit_cnt++;
521
0
      dot_valid = TRUE;
522
0
      continue;
523
0
    }
524
0
    if (version[i] == '-' || version[i] == '~') {
525
0
      g_string_append_c(version_safe, '.');
526
0
      dot_valid = FALSE;
527
0
      continue;
528
0
    }
529
0
    if (version[i] == '.' && dot_valid && version[i + 1] != '\0') {
530
0
      g_string_append_c(version_safe, version[i]);
531
0
      dot_valid = FALSE;
532
0
      continue;
533
0
    }
534
0
  }
535
536
  /* remove any trailing dot */
537
0
  if (version_safe->len > 0 && version_safe->str[version_safe->len - 1] == '.')
538
0
    g_string_truncate(version_safe, version_safe->len - 1);
539
540
  /* found no digits */
541
0
  if (digit_cnt == 0)
542
0
    return NULL;
543
0
  return g_string_free(g_steal_pointer(&version_safe), FALSE);
544
0
}
545
546
/**
547
 * fu_version_parse_from_format
548
 * @version: (nullable): a version number
549
 * @fmt: a version format, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
550
 *
551
 * Returns a dotted decimal version string from a version string using @fmt.
552
 * The supported formats are:
553
 *
554
 * - Dotted decimal, e.g. `1.2.3`
555
 * - Base 16, a hex number *with* a 0x prefix, e.g. `0x10203`
556
 * - Base 10, a string containing just [0-9], e.g. `66051`
557
 * - Date in YYYYMMDD format, e.g. `20150915`
558
 *
559
 * Anything with a `.` or that doesn't match `[0-9]` or `0x[a-f,0-9]` is considered
560
 * a string and returned without modification.
561
 *
562
 * Returns: a version number, e.g. `1.0.3`, or %NULL on error
563
 *
564
 * Since: 1.8.2
565
 */
566
gchar *
567
fu_version_parse_from_format(const gchar *version, FwupdVersionFormat fmt)
568
0
{
569
0
  guint64 tmp = 0;
570
571
  /* sanity check */
572
0
  if (version == NULL)
573
0
    return NULL;
574
575
  /* already dotted decimal */
576
0
  if (g_strstr_len(version, -1, ".") != NULL)
577
0
    return g_strdup(version);
578
579
  /* is a date */
580
0
  if (g_str_has_prefix(version, "20") && strlen(version) == 8)
581
0
    return g_strdup(version);
582
583
  /* convert 0x prefixed strings to dotted decimal */
584
0
  if (!fu_strtoull(version, &tmp, 0, G_MAXUINT32, FU_INTEGER_BASE_AUTO, NULL))
585
0
    return g_strdup(version);
586
0
  if (tmp == 0)
587
0
    return g_strdup(version);
588
0
  return fu_version_from_uint32((guint32)tmp, fmt);
589
0
}
590
591
/**
592
 * fu_version_guess_format:
593
 * @version: (nullable): a version number, e.g. `1.2.3`
594
 *
595
 * Guesses the version format from the version number. This is only a heuristic
596
 * and plugins and components should explicitly set the version format whenever
597
 * possible.
598
 *
599
 * If the version format cannot be guessed with any degree of accuracy, the
600
 * %FWUPD_VERSION_FORMAT_UNKNOWN constant is returned.
601
 *
602
 * Returns: a version format, e.g. %FWUPD_VERSION_FORMAT_QUAD
603
 *
604
 * Since: 1.8.2
605
 */
606
FwupdVersionFormat
607
fu_version_guess_format(const gchar *version)
608
0
{
609
0
  guint sz;
610
0
  g_auto(GStrv) split = NULL;
611
612
  /* nothing to use */
613
0
  if (version == NULL || version[0] == '\0')
614
0
    return FWUPD_VERSION_FORMAT_UNKNOWN;
615
616
  /* no dots, assume just text */
617
0
  split = g_strsplit(version, ".", -1);
618
0
  sz = g_strv_length(split);
619
0
  if (sz == 1) {
620
0
    if (g_str_has_prefix(version, "0x") || _g_ascii_is_digits(version))
621
0
      return FWUPD_VERSION_FORMAT_NUMBER;
622
0
    if (_g_ascii_is_xdigits(version))
623
0
      return FWUPD_VERSION_FORMAT_HEX;
624
0
    return FWUPD_VERSION_FORMAT_PLAIN;
625
0
  }
626
627
  /* check for only-digit semver version */
628
0
  for (guint i = 0; split[i] != NULL; i++) {
629
    /* check sections are plain numbers */
630
0
    if (!_g_ascii_is_digits(split[i]))
631
0
      return FWUPD_VERSION_FORMAT_PLAIN;
632
0
  }
633
634
  /* the most common formats */
635
0
  if (sz == 2)
636
0
    return FWUPD_VERSION_FORMAT_PAIR;
637
0
  if (sz == 3)
638
0
    return FWUPD_VERSION_FORMAT_TRIPLET;
639
0
  if (sz == 4)
640
0
    return FWUPD_VERSION_FORMAT_QUAD;
641
642
  /* unknown! */
643
0
  return FWUPD_VERSION_FORMAT_UNKNOWN;
644
0
}
645
646
static FwupdVersionFormat
647
fu_version_format_convert_base(FwupdVersionFormat fmt)
648
0
{
649
0
  if (fmt == FWUPD_VERSION_FORMAT_INTEL_ME || fmt == FWUPD_VERSION_FORMAT_INTEL_ME2 ||
650
0
      fmt == FWUPD_VERSION_FORMAT_INTEL_CSME19)
651
0
    return FWUPD_VERSION_FORMAT_QUAD;
652
0
  if (fmt == FWUPD_VERSION_FORMAT_DELL_BIOS || fmt == FWUPD_VERSION_FORMAT_DELL_BIOS_MSB ||
653
0
      fmt == FWUPD_VERSION_FORMAT_SURFACE || fmt == FWUPD_VERSION_FORMAT_SURFACE_LEGACY)
654
0
    return FWUPD_VERSION_FORMAT_TRIPLET;
655
0
  if (fmt == FWUPD_VERSION_FORMAT_BCD)
656
0
    return FWUPD_VERSION_FORMAT_PAIR;
657
0
  if (fmt == FWUPD_VERSION_FORMAT_HEX)
658
0
    return FWUPD_VERSION_FORMAT_NUMBER;
659
0
  return fmt;
660
0
}
661
662
/**
663
 * fu_version_verify_format:
664
 * @version: (not nullable): a string, e.g. `0x1234`
665
 * @fmt: a version format
666
 * @error: (nullable): optional return location for an error
667
 *
668
 * Verifies if a version matches the input format.
669
 *
670
 * Returns: TRUE or FALSE
671
 *
672
 * Since: 1.8.2
673
 **/
674
gboolean
675
fu_version_verify_format(const gchar *version, FwupdVersionFormat fmt, GError **error)
676
0
{
677
0
  FwupdVersionFormat fmt_guess;
678
679
0
  g_return_val_if_fail(version != NULL, FALSE);
680
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
681
682
  /* don't touch */
683
0
  if (fmt == FWUPD_VERSION_FORMAT_PLAIN)
684
0
    return TRUE;
685
686
  /* nothing we can check for */
687
0
  if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN)
688
0
    return TRUE;
689
690
  /* hex */
691
0
  if (fmt == FWUPD_VERSION_FORMAT_HEX) {
692
0
    if (_g_ascii_is_xdigits(version))
693
0
      return TRUE;
694
0
  }
695
696
  /* check the base format */
697
0
  fmt_guess = fu_version_guess_format(version);
698
0
  if (fmt == FWUPD_VERSION_FORMAT_BCD &&
699
0
      (fmt_guess == FWUPD_VERSION_FORMAT_PAIR || fmt_guess == FWUPD_VERSION_FORMAT_QUAD))
700
0
    return TRUE;
701
0
  if (fmt_guess == FWUPD_VERSION_FORMAT_PAIR && fmt == FWUPD_VERSION_FORMAT_COMPAL_BIOS)
702
0
    return TRUE;
703
0
  if (fmt_guess != fu_version_format_convert_base(fmt)) {
704
0
    g_set_error(error,
705
0
          FWUPD_ERROR,
706
0
          FWUPD_ERROR_INVALID_DATA,
707
0
          "%s is not a valid %s (guessed %s)",
708
0
          version,
709
0
          fwupd_version_format_to_string(fmt),
710
0
          fwupd_version_format_to_string(fmt_guess));
711
0
    return FALSE;
712
0
  }
713
0
  return TRUE;
714
0
}
715
716
static gint
717
fu_version_compare_safe(const gchar *version_a, const gchar *version_b)
718
0
{
719
0
  guint longest_split;
720
0
  g_auto(GStrv) split_a = NULL;
721
0
  g_auto(GStrv) split_b = NULL;
722
723
  /* sanity check */
724
0
  if (version_a == NULL || version_b == NULL)
725
0
    return G_MAXINT;
726
727
  /* optimization */
728
0
  if (g_strcmp0(version_a, version_b) == 0)
729
0
    return 0;
730
731
  /* split into sections, and try to parse */
732
0
  split_a = g_strsplit(version_a, ".", -1);
733
0
  split_b = g_strsplit(version_b, ".", -1);
734
0
  longest_split = MAX(g_strv_length(split_a), g_strv_length(split_b));
735
0
  for (guint i = 0; i < longest_split; i++) {
736
0
    gchar *endptr_a = NULL;
737
0
    gchar *endptr_b = NULL;
738
0
    gint64 ver_a;
739
0
    gint64 ver_b;
740
741
    /* we lost or gained a dot */
742
0
    if (split_a[i] == NULL)
743
0
      return -1;
744
0
    if (split_b[i] == NULL)
745
0
      return 1;
746
747
    /* compare integers */
748
0
    ver_a = g_ascii_strtoll(split_a[i], &endptr_a, 10); /* nocheck:blocked */
749
0
    ver_b = g_ascii_strtoll(split_b[i], &endptr_b, 10); /* nocheck:blocked */
750
0
    if (ver_a < ver_b)
751
0
      return -1;
752
0
    if (ver_a > ver_b)
753
0
      return 1;
754
755
    /* compare strings */
756
0
    if ((endptr_a != NULL && endptr_a[0] != '\0') ||
757
0
        (endptr_b != NULL && endptr_b[0] != '\0')) {
758
0
      gint rc = fu_version_compare_chunk(endptr_a, endptr_b);
759
0
      if (rc < 0)
760
0
        return -1;
761
0
      if (rc > 0)
762
0
        return 1;
763
0
    }
764
0
  }
765
766
  /* we really shouldn't get here */
767
0
  return 0;
768
0
}
769
770
/**
771
 * fu_version_compare:
772
 * @version_a: (nullable): the semver release version, e.g. `1.2.3`
773
 * @version_b: (nullable): the semver release version, e.g. `1.2.3.1`
774
 * @fmt: a version format, e.g. %FWUPD_VERSION_FORMAT_PLAIN
775
 *
776
 * Compares version numbers for sorting taking into account the version format
777
 * if required.
778
 *
779
 * Returns: -1 if a < b, +1 if a > b, 0 if they are equal, and %G_MAXINT on error
780
 *
781
 * Since: 1.8.2
782
 */
783
gint
784
fu_version_compare(const gchar *version_a, const gchar *version_b, FwupdVersionFormat fmt)
785
0
{
786
0
  if (fmt == FWUPD_VERSION_FORMAT_PLAIN)
787
0
    return g_strcmp0(version_a, version_b);
788
0
  if (fmt == FWUPD_VERSION_FORMAT_HEX) {
789
0
    g_autofree gchar *hex_a = NULL;
790
0
    g_autofree gchar *hex_b = NULL;
791
0
    hex_a = fu_version_parse_from_format(version_a, fmt);
792
0
    hex_b = fu_version_parse_from_format(version_b, fmt);
793
0
    return fu_version_compare_safe(hex_a, hex_b);
794
0
  }
795
0
  return fu_version_compare_safe(version_a, version_b);
796
0
}