<commit_before>/*
 * Copyright (C) 2017-2018 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#define G_LOG_DOMAIN				"FuCommon"

#include <config.h>

#include <string.h>

#include "fwupd-enums.h"
#include "fwupd-error.h"

#include "fu-common-version.h"

#define FU_COMMON_VERSION_DECODE_BCD(val)	((((val) >> 4) & 0x0f) * 10 + ((val) & 0x0f))

/**
 * fu_common_version_from_uint64:
 * @val: A raw version number
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_QUAD
 *
 * Returns a dotted decimal version string from a 64 bit number.
 *
 * Returns: A version number, e.g. "1.2.3.4", or %NULL if not supported
 *
 * Since: 1.3.6
 **/
gchar *
fu_common_version_from_uint64 (guint64 val, FwupdVersionFormat kind)
{
	if (kind == FWUPD_VERSION_FORMAT_QUAD) {
		/* AABB.CCDD.EEFF.GGHH */
		return g_strdup_printf ("%" G_GUINT64_FORMAT "."
					"%" G_GUINT64_FORMAT "."
					"%" G_GUINT64_FORMAT "."
					"%" G_GUINT64_FORMAT "",
					(val >> 48) & 0xffff,
					(val >> 32) & 0xffff,
					(val >> 16) & 0xffff,
					val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_PAIR) {
		/* AABBCCDD.EEFFGGHH */
		return g_strdup_printf ("%" G_GUINT64_FORMAT ".%" G_GUINT64_FORMAT "",
					(val >> 32) & 0xffffffff,
					val & 0xffffffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_NUMBER ||
	    kind == FWUPD_VERSION_FORMAT_PLAIN) {
		/* AABBCCDD */
		return g_strdup_printf ("%" G_GUINT64_FORMAT, val);
	}
	if (kind == FWUPD_VERSION_FORMAT_HEX) {
		/* 0xAABBCCDDEEFFGGHH */
		return g_strdup_printf ("0x%08x%08x", (guint32) (val >> 32), (guint32) (val & 0xffffffff));
	}
	g_critical ("failed to convert version format %s: %" G_GUINT64_FORMAT "",
		    fwupd_version_format_to_string (kind), val);
	return NULL;
}

/**
 * fu_common_version_from_uint32:
 * @val: A uint32le version number
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
 *
 * Returns a dotted decimal version string from a 32 bit number.
 *
 * Returns: A version number, e.g. "1.0.3", or %NULL if not supported
 *
 * Since: 1.2.0
 **/
gchar *
fu_common_version_from_uint32 (guint32 val, FwupdVersionFormat kind)
{
	if (kind == FWUPD_VERSION_FORMAT_QUAD) {
		/* AA.BB.CC.DD */
		return g_strdup_printf ("%u.%u.%u.%u",
					(val >> 24) & 0xff,
					(val >> 16) & 0xff,
					(val >> 8) & 0xff,
					val & 0xff);
	}
	if (kind == FWUPD_VERSION_FORMAT_TRIPLET) {
		/* AA.BB.CCDD */
		return g_strdup_printf ("%u.%u.%u",
					(val >> 24) & 0xff,
					(val >> 16) & 0xff,
					val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_PAIR) {
		/* AABB.CCDD */
		return g_strdup_printf ("%u.%u",
					(val >> 16) & 0xffff,
					val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_NUMBER ||
	    kind == FWUPD_VERSION_FORMAT_PLAIN) {
		/* AABBCCDD */
		return g_strdup_printf ("%" G_GUINT32_FORMAT, val);
	}
	if (kind == FWUPD_VERSION_FORMAT_BCD) {
		/* AA.BB.CC.DD, but BCD */
		return g_strdup_printf ("%u.%u.%u.%u",
					FU_COMMON_VERSION_DECODE_BCD(val >> 24),
					FU_COMMON_VERSION_DECODE_BCD(val >> 16),
					FU_COMMON_VERSION_DECODE_BCD(val >> 8),
					FU_COMMON_VERSION_DECODE_BCD(val));
	}
	if (kind == FWUPD_VERSION_FORMAT_INTEL_ME) {
		/* aaa+11.bbbbb.cccccccc.dddddddddddddddd */
		return g_strdup_printf ("%u.%u.%u.%u",
					((val >> 29) & 0x07) + 0x0b,
					 (val >> 24) & 0x1f,
					 (val >> 16) & 0xff,
					  val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_INTEL_ME2) {
		/* A.B.CC.DDDD */
		return g_strdup_printf ("%u.%u.%u.%u",
					(val >> 28) & 0x0f,
					(val >> 24) & 0x0f,
					(val >> 16) & 0xff,
					val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_SURFACE_LEGACY) {
		/* 10b.12b.10b */
		return g_strdup_printf ("%u.%u.%u",
					(val >> 22) & 0x3ff,
					(val >> 10) & 0xfff,
					val & 0x3ff);
	}
	if (kind == FWUPD_VERSION_FORMAT_SURFACE) {
		/* 8b.16b.8b */
		return g_strdup_printf ("%u.%u.%u",
					(val >> 24) & 0xff,
					(val >> 8) & 0xffff,
					val & 0xff);
	}
	if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS) {
		/* BB.CC.DD */
		return g_strdup_printf ("%u.%u.%u",
					(val >> 16) & 0xff,
					(val >> 8) & 0xff,
					val & 0xff);
	}
	if (kind == FWUPD_VERSION_FORMAT_HEX) {
		/* 0xAABBCCDD */
		return g_strdup_printf ("0x%08x", val);
	}
	g_critical ("failed to convert version format %s: %u",
		    fwupd_version_format_to_string (kind), val);
	return NULL;
}

/**
 * fu_common_version_from_uint16:
 * @val: A uint16le version number
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
 *
 * Returns a dotted decimal version string from a 16 bit number.
 *
 * Returns: A version number, e.g. "1.3", or %NULL if not supported
 *
 * Since: 1.2.0
 **/
gchar *
fu_common_version_from_uint16 (guint16 val, FwupdVersionFormat kind)
{
	if (kind == FWUPD_VERSION_FORMAT_BCD) {
		return g_strdup_printf ("%i.%i",
					FU_COMMON_VERSION_DECODE_BCD(val >> 8),
					FU_COMMON_VERSION_DECODE_BCD(val));
	}
	if (kind == FWUPD_VERSION_FORMAT_PAIR) {
		return g_strdup_printf ("%u.%u",
					(guint) (val >> 8) & 0xff,
					(guint) val & 0xff);
	}
	if (kind == FWUPD_VERSION_FORMAT_NUMBER ||
	    kind == FWUPD_VERSION_FORMAT_PLAIN) {
		return g_strdup_printf ("%" G_GUINT16_FORMAT, val);
	}
	if (kind == FWUPD_VERSION_FORMAT_HEX) {
		/* 0xAABB */
		return g_strdup_printf ("0x%04x", val);
	}
	g_critical ("failed to convert version format %s: %u",
		    fwupd_version_format_to_string (kind), val);
	return NULL;
}

static gint
fu_common_vercmp_char (gchar chr1, gchar chr2)
{
	if (chr1 == chr2)
		return 0;
	if (chr1 == '~')
		return -1;
	if (chr2 == '~')
		return 1;
	return chr1 < chr2 ? -1 : 1;
}

static gint
fu_common_vercmp_chunk (const gchar *str1, const gchar *str2)
{
	guint i;

	/* trivial */
	if (g_strcmp0 (str1, str2) == 0)
		return 0;
	if (str1 == NULL)
		return 1;
	if (str2 == NULL)
		return -1;

	/* check each char of the chunk */
	for (i = 0; str1[i] != '\0' && str2[i] != '\0'; i++) {
		gint rc = fu_common_vercmp_char (str1[i], str2[i]);
		if (rc != 0)
			return rc;
	}
	return fu_common_vercmp_char (str1[i], str2[i]);
}

static gboolean
_g_ascii_is_digits (const gchar *str)
{
	g_return_val_if_fail (str != NULL, FALSE);
	for (gsize i = 0; str[i] != '\0'; i++) {
		if (!g_ascii_isdigit (str[i]))
			return FALSE;
	}
	return TRUE;
}

static gboolean
fu_common_version_is_valid_semver_char (gchar c)
{
	if (g_ascii_isdigit (c))
		return TRUE;
	if (c == '.')
		return TRUE;
	return FALSE;
}

/**
 * fu_common_version_ensure_semver:
 * @version: A version number, e.g. ` V1.2.3 `
 *
 * Builds a semver from the possibly crazy version number.
 *
 * Returns: A version number, e.g. "1.2.3"
 *
 * Since: 1.2.9
 */
gchar *
fu_common_version_ensure_semver (const gchar *version)
{
	GString *version_safe = g_string_new (NULL);
	for (guint i = 0; version[i] != '\0'; i++) {
		if (fu_common_version_is_valid_semver_char (version[i]))
			g_string_append_c (version_safe, version[i]);
	}
	return g_string_free (version_safe, FALSE);
}

/**
 * fu_common_version_parse:
 * @version: A version number
 *
 * Returns a dotted decimal version string from a version string. The supported
 * formats are:
 *
 * - Dotted decimal, e.g. "1.2.3"
 * - Base 16, a hex number *with* a 0x prefix, e.g. "0x10203"
 * - Base 10, a string containing just [0-9], e.g. "66051"
 * - Date in YYYYMMDD format, e.g. 20150915
 *
 * Anything with a '.' or that doesn't match [0-9] or 0x[a-f,0-9] is considered
 * a string and returned without modification.
 *
 * Returns: A version number, e.g. "1.0.3"
 *
 * Since: 1.2.0
 */
gchar *
fu_common_version_parse (const gchar *version)
{
	return fu_common_version_parse_from_format (version, FWUPD_VERSION_FORMAT_TRIPLET);
}

/**
 * fu_common_version_parse_from_format
 * @version: A version number
 * @fmt: A FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
 *
 * Returns a dotted decimal version string from a version string using fmt.
 * The supported formats are:
 *
 * - Dotted decimal, e.g. "1.2.3"
 * - Base 16, a hex number *with* a 0x prefix, e.g. "0x10203"
 * - Base 10, a string containing just [0-9], e.g. "66051"
 * - Date in YYYYMMDD format, e.g. 20150915
 *
 * Anything with a '.' or that doesn't match [0-9] or 0x[a-f,0-9] is considered
 * a string and returned without modification.
 *
 * Returns: A version number, e.g. "1.0.3"
 *
 * Since: 1.3.3
 */
gchar *
fu_common_version_parse_from_format (const gchar *version, FwupdVersionFormat fmt)
{
	const gchar *version_noprefix = version;
	gchar *endptr = NULL;
	guint64 tmp;
	guint base;

	/* already dotted decimal */
	if (g_strstr_len (version, -1, ".") != NULL)
		return g_strdup (version);

	/* is a date */
	if (g_str_has_prefix (version, "20") &&
	    strlen (version) == 8)
		return g_strdup (version);

	/* convert 0x prefixed strings to dotted decimal */
	if (g_str_has_prefix (version, "0x")) {
		version_noprefix += 2;
		base = 16;
	} else {
		/* for non-numeric content, just return the string */
		if (!_g_ascii_is_digits (version))
			return g_strdup (version);
		base = 10;
	}

	/* convert */
	tmp = g_ascii_strtoull (version_noprefix, &endptr, base);
	if (endptr != NULL && endptr[0] != '\0')
		return g_strdup (version);
	if (tmp == 0)
		return g_strdup (version);
	return fu_common_version_from_uint32 ((guint32) tmp, fmt);
}

/**
 * fu_common_version_guess_format:
 * @version: A version number, e.g. "1.2.3"
 *
 * Guesses the version format from the version number. This is only a heuristic
 * and plugins and components should explicitly set the version format whenever
 * possible.
 *
 * If the version format cannot be guessed with any degree of accuracy, the
 * %FWUPD_VERSION_FORMAT_UNKNOWN constant is returned.
 *
 * Returns: A #FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_QUAD
 *
 * Since: 1.2.0
 */
FwupdVersionFormat
fu_common_version_guess_format (const gchar *version)
{
	guint sz;
	g_auto(GStrv) split = NULL;

	/* nothing to use */
	if (version == NULL || version[0] == '\0')
		return FWUPD_VERSION_FORMAT_UNKNOWN;

	/* no dots, assume just text */
	split = g_strsplit (version, ".", -1);
	sz = g_strv_length (split);
	if (sz == 1) {
		if (g_str_has_prefix (version, "0x"))
			version += 2;
		if (_g_ascii_is_digits (version))
			return FWUPD_VERSION_FORMAT_NUMBER;
		return FWUPD_VERSION_FORMAT_PLAIN;
	}

	/* check for only-digit semver version */
	for (guint i = 0; split[i] != NULL; i++) {
		/* check sections are plain numbers */
		if (!_g_ascii_is_digits (split[i]))
			return FWUPD_VERSION_FORMAT_PLAIN;
	}

	/* the most common formats */
	if (sz == 2)
		return FWUPD_VERSION_FORMAT_PAIR;
	if (sz == 3)
		return FWUPD_VERSION_FORMAT_TRIPLET;
	if (sz == 4)
		return FWUPD_VERSION_FORMAT_QUAD;

	/* unknown! */
	return FWUPD_VERSION_FORMAT_UNKNOWN;
}

static FwupdVersionFormat
fu_common_version_convert_base (FwupdVersionFormat fmt)
{
	if (fmt == FWUPD_VERSION_FORMAT_INTEL_ME ||<commit_msg>trivial: improve version format guessing for hex<commit_after>/*
 * Copyright (C) 2017-2018 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#define G_LOG_DOMAIN				"FuCommon"

#include <config.h>

#include <string.h>

#include "fwupd-enums.h"
#include "fwupd-error.h"

#include "fu-common-version.h"

#define FU_COMMON_VERSION_DECODE_BCD(val)	((((val) >> 4) & 0x0f) * 10 + ((val) & 0x0f))

/**
 * fu_common_version_from_uint64:
 * @val: A raw version number
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_QUAD
 *
 * Returns a dotted decimal version string from a 64 bit number.
 *
 * Returns: A version number, e.g. "1.2.3.4", or %NULL if not supported
 *
 * Since: 1.3.6
 **/
gchar *
fu_common_version_from_uint64 (guint64 val, FwupdVersionFormat kind)
{
	if (kind == FWUPD_VERSION_FORMAT_QUAD) {
		/* AABB.CCDD.EEFF.GGHH */
		return g_strdup_printf ("%" G_GUINT64_FORMAT "."
					"%" G_GUINT64_FORMAT "."
					"%" G_GUINT64_FORMAT "."
					"%" G_GUINT64_FORMAT "",
					(val >> 48) & 0xffff,
					(val >> 32) & 0xffff,
					(val >> 16) & 0xffff,
					val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_PAIR) {
		/* AABBCCDD.EEFFGGHH */
		return g_strdup_printf ("%" G_GUINT64_FORMAT ".%" G_GUINT64_FORMAT "",
					(val >> 32) & 0xffffffff,
					val & 0xffffffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_NUMBER ||
	    kind == FWUPD_VERSION_FORMAT_PLAIN) {
		/* AABBCCDD */
		return g_strdup_printf ("%" G_GUINT64_FORMAT, val);
	}
	if (kind == FWUPD_VERSION_FORMAT_HEX) {
		/* 0xAABBCCDDEEFFGGHH */
		return g_strdup_printf ("0x%08x%08x", (guint32) (val >> 32), (guint32) (val & 0xffffffff));
	}
	g_critical ("failed to convert version format %s: %" G_GUINT64_FORMAT "",
		    fwupd_version_format_to_string (kind), val);
	return NULL;
}

/**
 * fu_common_version_from_uint32:
 * @val: A uint32le version number
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
 *
 * Returns a dotted decimal version string from a 32 bit number.
 *
 * Returns: A version number, e.g. "1.0.3", or %NULL if not supported
 *
 * Since: 1.2.0
 **/
gchar *
fu_common_version_from_uint32 (guint32 val, FwupdVersionFormat kind)
{
	if (kind == FWUPD_VERSION_FORMAT_QUAD) {
		/* AA.BB.CC.DD */
		return g_strdup_printf ("%u.%u.%u.%u",
					(val >> 24) & 0xff,
					(val >> 16) & 0xff,
					(val >> 8) & 0xff,
					val & 0xff);
	}
	if (kind == FWUPD_VERSION_FORMAT_TRIPLET) {
		/* AA.BB.CCDD */
		return g_strdup_printf ("%u.%u.%u",
					(val >> 24) & 0xff,
					(val >> 16) & 0xff,
					val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_PAIR) {
		/* AABB.CCDD */
		return g_strdup_printf ("%u.%u",
					(val >> 16) & 0xffff,
					val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_NUMBER ||
	    kind == FWUPD_VERSION_FORMAT_PLAIN) {
		/* AABBCCDD */
		return g_strdup_printf ("%" G_GUINT32_FORMAT, val);
	}
	if (kind == FWUPD_VERSION_FORMAT_BCD) {
		/* AA.BB.CC.DD, but BCD */
		return g_strdup_printf ("%u.%u.%u.%u",
					FU_COMMON_VERSION_DECODE_BCD(val >> 24),
					FU_COMMON_VERSION_DECODE_BCD(val >> 16),
					FU_COMMON_VERSION_DECODE_BCD(val >> 8),
					FU_COMMON_VERSION_DECODE_BCD(val));
	}
	if (kind == FWUPD_VERSION_FORMAT_INTEL_ME) {
		/* aaa+11.bbbbb.cccccccc.dddddddddddddddd */
		return g_strdup_printf ("%u.%u.%u.%u",
					((val >> 29) & 0x07) + 0x0b,
					 (val >> 24) & 0x1f,
					 (val >> 16) & 0xff,
					  val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_INTEL_ME2) {
		/* A.B.CC.DDDD */
		return g_strdup_printf ("%u.%u.%u.%u",
					(val >> 28) & 0x0f,
					(val >> 24) & 0x0f,
					(val >> 16) & 0xff,
					val & 0xffff);
	}
	if (kind == FWUPD_VERSION_FORMAT_SURFACE_LEGACY) {
		/* 10b.12b.10b */
		return g_strdup_printf ("%u.%u.%u",
					(val >> 22) & 0x3ff,
					(val >> 10) & 0xfff,
					val & 0x3ff);
	}
	if (kind == FWUPD_VERSION_FORMAT_SURFACE) {
		/* 8b.16b.8b */
		return g_strdup_printf ("%u.%u.%u",
					(val >> 24) & 0xff,
					(val >> 8) & 0xffff,
					val & 0xff);
	}
	if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS) {
		/* BB.CC.DD */
		return g_strdup_printf ("%u.%u.%u",
					(val >> 16) & 0xff,
					(val >> 8) & 0xff,
					val & 0xff);
	}
	if (kind == FWUPD_VERSION_FORMAT_HEX) {
		/* 0xAABBCCDD */
		return g_strdup_printf ("0x%08x", val);
	}
	g_critical ("failed to convert version format %s: %u",
		    fwupd_version_format_to_string (kind), val);
	return NULL;
}

/**
 * fu_common_version_from_uint16:
 * @val: A uint16le version number
 * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
 *
 * Returns a dotted decimal version string from a 16 bit number.
 *
 * Returns: A version number, e.g. "1.3", or %NULL if not supported
 *
 * Since: 1.2.0
 **/
gchar *
fu_common_version_from_uint16 (guint16 val, FwupdVersionFormat kind)
{
	if (kind == FWUPD_VERSION_FORMAT_BCD) {
		return g_strdup_printf ("%i.%i",
					FU_COMMON_VERSION_DECODE_BCD(val >> 8),
					FU_COMMON_VERSION_DECODE_BCD(val));
	}
	if (kind == FWUPD_VERSION_FORMAT_PAIR) {
		return g_strdup_printf ("%u.%u",
					(guint) (val >> 8) & 0xff,
					(guint) val & 0xff);
	}
	if (kind == FWUPD_VERSION_FORMAT_NUMBER ||
	    kind == FWUPD_VERSION_FORMAT_PLAIN) {
		return g_strdup_printf ("%" G_GUINT16_FORMAT, val);
	}
	if (kind == FWUPD_VERSION_FORMAT_HEX) {
		/* 0xAABB */
		return g_strdup_printf ("0x%04x", val);
	}
	g_critical ("failed to convert version format %s: %u",
		    fwupd_version_format_to_string (kind), val);
	return NULL;
}

static gint
fu_common_vercmp_char (gchar chr1, gchar chr2)
{
	if (chr1 == chr2)
		return 0;
	if (chr1 == '~')
		return -1;
	if (chr2 == '~')
		return 1;
	return chr1 < chr2 ? -1 : 1;
}

static gint
fu_common_vercmp_chunk (const gchar *str1, const gchar *str2)
{
	guint i;

	/* trivial */
	if (g_strcmp0 (str1, str2) == 0)
		return 0;
	if (str1 == NULL)
		return 1;
	if (str2 == NULL)
		return -1;

	/* check each char of the chunk */
	for (i = 0; str1[i] != '\0' && str2[i] != '\0'; i++) {
		gint rc = fu_common_vercmp_char (str1[i], str2[i]);
		if (rc != 0)
			return rc;
	}
	return fu_common_vercmp_char (str1[i], str2[i]);
}

static gboolean
_g_ascii_is_digits (const gchar *str)
{
	g_return_val_if_fail (str != NULL, FALSE);
	for (gsize i = 0; str[i] != '\0'; i++) {
		if (!g_ascii_isdigit (str[i]))
			return FALSE;
	}
	return TRUE;
}

static gboolean
fu_common_version_is_valid_semver_char (gchar c)
{
	if (g_ascii_isdigit (c))
		return TRUE;
	if (c == '.')
		return TRUE;
	return FALSE;
}

/**
 * fu_common_version_ensure_semver:
 * @version: A version number, e.g. ` V1.2.3 `
 *
 * Builds a semver from the possibly crazy version number.
 *
 * Returns: A version number, e.g. "1.2.3"
 *
 * Since: 1.2.9
 */
gchar *
fu_common_version_ensure_semver (const gchar *version)
{
	GString *version_safe = g_string_new (NULL);
	for (guint i = 0; version[i] != '\0'; i++) {
		if (fu_common_version_is_valid_semver_char (version[i]))
			g_string_append_c (version_safe, version[i]);
	}
	return g_string_free (version_safe, FALSE);
}

/**
 * fu_common_version_parse:
 * @version: A version number
 *
 * Returns a dotted decimal version string from a version string. The supported
 * formats are:
 *
 * - Dotted decimal, e.g. "1.2.3"
 * - Base 16, a hex number *with* a 0x prefix, e.g. "0x10203"
 * - Base 10, a string containing just [0-9], e.g. "66051"
 * - Date in YYYYMMDD format, e.g. 20150915
 *
 * Anything with a '.' or that doesn't match [0-9] or 0x[a-f,0-9] is considered
 * a string and returned without modification.
 *
 * Returns: A version number, e.g. "1.0.3"
 *
 * Since: 1.2.0
 */
gchar *
fu_common_version_parse (const gchar *version)
{
	return fu_common_version_parse_from_format (version, FWUPD_VERSION_FORMAT_TRIPLET);
}

/**
 * fu_common_version_parse_from_format
 * @version: A version number
 * @fmt: A FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_TRIPLET
 *
 * Returns a dotted decimal version string from a version string using fmt.
 * The supported formats are:
 *
 * - Dotted decimal, e.g. "1.2.3"
 * - Base 16, a hex number *with* a 0x prefix, e.g. "0x10203"
 * - Base 10, a string containing just [0-9], e.g. "66051"
 * - Date in YYYYMMDD format, e.g. 20150915
 *
 * Anything with a '.' or that doesn't match [0-9] or 0x[a-f,0-9] is considered
 * a string and returned without modification.
 *
 * Returns: A version number, e.g. "1.0.3"
 *
 * Since: 1.3.3
 */
gchar *
fu_common_version_parse_from_format (const gchar *version, FwupdVersionFormat fmt)
{
	const gchar *version_noprefix = version;
	gchar *endptr = NULL;
	guint64 tmp;
	guint base;

	/* already dotted decimal */
	if (g_strstr_len (version, -1, ".") != NULL)
		return g_strdup (version);

	/* is a date */
	if (g_str_has_prefix (version, "20") &&
	    strlen (version) == 8)
		return g_strdup (version);

	/* convert 0x prefixed strings to dotted decimal */
	if (g_str_has_prefix (version, "0x")) {
		version_noprefix += 2;
		base = 16;
	} else {
		/* for non-numeric content, just return the string */
		if (!_g_ascii_is_digits (version))
			return g_strdup (version);
		base = 10;
	}

	/* convert */
	tmp = g_ascii_strtoull (version_noprefix, &endptr, base);
	if (endptr != NULL && endptr[0] != '\0')
		return g_strdup (version);
	if (tmp == 0)
		return g_strdup (version);
	return fu_common_version_from_uint32 ((guint32) tmp, fmt);
}

/**
 * fu_common_version_guess_format:
 * @version: A version number, e.g. "1.2.3"
 *
 * Guesses the version format from the version number. This is only a heuristic
 * and plugins and components should explicitly set the version format whenever
 * possible.
 *
 * If the version format cannot be guessed with any degree of accuracy, the
 * %FWUPD_VERSION_FORMAT_UNKNOWN constant is returned.
 *
 * Returns: A #FwupdVersionFormat, e.g. %FWUPD_VERSION_FORMAT_QUAD
 *
 * Since: 1.2.0
 */
FwupdVersionFormat
fu_common_version_guess_format (const gchar *version)
{
	guint sz;
	g_auto(GStrv) split = NULL;

	/* nothing to use */
	if (version == NULL || version[0] == '\0')
		return FWUPD_VERSION_FORMAT_UNKNOWN;

	/* no dots, assume just text */
	split = g_strsplit (version, ".", -1);
	sz = g_strv_length (split);
	if (sz == 1) {
		if (g_str_has_prefix (version, "0x") || _g_ascii_is_digits (version))
			return FWUPD_VERSION_FORMAT_NUMBER;
		return FWUPD_VERSION_FORMAT_PLAIN;
	}

	/* check for only-digit semver version */
	for (guint i = 0; split[i] != NULL; i++) {
		/* check sections are plain numbers */
		if (!_g_ascii_is_digits (split[i]))
			return FWUPD_VERSION_FORMAT_PLAIN;
	}

	/* the most common formats */
	if (sz == 2)
		return FWUPD_VERSION_FORMAT_PAIR;
	if (sz == 3)
		return FWUPD_VERSION_FORMAT_TRIPLET;
	if (sz == 4)
		return FWUPD_VERSION_FORMAT_QUAD;

	/* unknown! */
	return FWUPD_VERSION_FORMAT_UNKNOWN;
}

static FwupdVersionFormat
fu_common_version_convert_base (FwupdVersionFormat fmt)
{
	if (fmt == FWUPD_VERSION_FORMAT_INTEL_ME ||
{'fraction_non_alphanumeric': 0.14724949064641601, 'fraction_numerical': 0.034311909612891274, 'mean_word_length': 3.5669274688094736, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 129, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 10, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5880736', 'n_tokens_mistral': 9858, 'n_tokens_neox': 8673, 'n_words': 2103}