NetworkManager/src/libnm-glib-aux/nm-uuid.c
Beniamino Galvani fcea2f174e libnm-glib-aux: fix comments about UUID generation
Whether the length is supplied explicitly or implicitly (via -1), the
result is the same. Update the comment.
2024-04-05 11:40:44 +02:00

490 lines
16 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libnm-glib-aux/nm-default-glib.h"
#include "nm-uuid.h"
#include "libnm-glib-aux/nm-random-utils.h"
#include "libnm-glib-aux/nm-str-buf.h"
/*****************************************************************************/
const NMUuid nm_uuid_ns_zero =
NM_UUID_INIT(00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00);
/* arbitrarily chosen namespace UUID for some uses of nm_uuid_generate_from_strings_old().
* Try not to re-use this namespace, instead, generate a unique one. */
const NMUuid nm_uuid_ns_1 =
NM_UUID_INIT(b4, 25, e9, fb, 75, 98, 44, b4, 9e, 3b, 5a, 2e, 3a, aa, 49, 05);
char *
nm_uuid_unparse_case(const NMUuid *uuid, char out_str[static 37], gboolean upper_case)
{
char *s;
int i;
nm_assert(uuid);
nm_assert(out_str);
s = out_str;
for (i = 0; i < 16; i++) {
const guint8 c = uuid->uuid[i];
if (NM_IN_SET(i, 4, 6, 8, 10))
*(s++) = '-';
*(s++) = nm_hexchar(c >> 4, upper_case);
*(s++) = nm_hexchar(c, upper_case);
}
*s = '\0';
return out_str;
}
/*****************************************************************************/
gboolean
nm_uuid_parse_full(const char *str, NMUuid *out_uuid, gboolean *out_is_normalized)
{
NMUuid uuid;
guint8 *p;
int i;
gboolean is_normalized = TRUE;
nm_assert(str);
p = uuid.uuid;
for (i = 0; TRUE;) {
int v0;
int v1;
if (NM_IN_SET(i, 8, 13, 18, 23)) {
if (str[i] != '-')
return FALSE;
i++;
continue;
}
if (i == 36) {
if (str[i] != '\0')
return FALSE;
NM_SET_OUT(out_is_normalized, is_normalized);
NM_SET_OUT(out_uuid, uuid);
return TRUE;
}
#define _hexchar(ch, out_is_normalized2) \
({ \
const char _ch = (ch); \
int _result = -1; \
\
if (_ch >= '0') { \
if (_ch <= '9') \
_result = (_ch - '0'); \
else if (_ch >= 'A') { \
if (_ch <= 'F') { \
*(out_is_normalized2) = FALSE; \
_result = ((int) _ch) + (10 - (int) 'A'); \
} else if (_ch >= 'a' && _ch <= 'f') \
_result = ((int) _ch) + (10 - (int) 'a'); \
} \
} \
\
_result; \
})
v0 = _hexchar(str[i++], &is_normalized);
if (v0 < 0)
return FALSE;
v1 = _hexchar(str[i++], &is_normalized);
if (v1 < 0)
return FALSE;
*(p++) = (v0 << 4) + v1;
}
}
/*****************************************************************************/
NMUuid *
nm_uuid_generate_random(NMUuid *out_uuid)
{
nm_assert(out_uuid);
/* https://tools.ietf.org/html/rfc4122#section-4.4 */
/* See also, systemd's id128_make_v4_uuid() */
/* nm_random_get_bytes() is supposed to try hard to give good
* randomness. If it fails, it still makes an effort to fill
* random data into the buffer. There is not much we can do about
* that case, except making sure that it does not happen in the
* first place. */
nm_random_get_bytes(out_uuid, sizeof(*out_uuid));
/* Set the four most significant bits (bits 12 through 15) of the
* time_hi_and_version field to the 4-bit version number from
* Section 4.1.3. */
out_uuid->uuid[6] = (out_uuid->uuid[6] & 0x0Fu) | 0x40u;
/* Set the two most significant bits (bits 6 and 7) of the
* clock_seq_hi_and_reserved to zero and one, respectively. */
out_uuid->uuid[8] = (out_uuid->uuid[8] & 0x3Fu) | 0x80u;
return out_uuid;
}
/*****************************************************************************/
gboolean
nm_uuid_is_normalized_full(const char *str)
{
/* The only reason why this exists is that nm_uuid_is_normalized() is an inline function.
* If you need to forward declare the function, that won't work.
*
* Usually, you wouldn't use this variant! */
return nm_uuid_is_normalized(str);
}
/*****************************************************************************/
/**
* nm_uuid_is_valid_nmlegacy()
* @str: the string to check whether it's a valid UUID.
*
* Note that this does not perform a strict check.
* Instead, it checks a more relaxed format (including
* non valid UUID strings). This is for backward compatibility,
* where older code did not perform a strict check.
*
* Returns: %TRUE, if the string is a valid legacy format.
* This may not be a valid UUID!
*/
gboolean
nm_uuid_is_valid_nmlegacy(const char *str)
{
const char *p = str;
int num_dashes = 0;
if (!p)
return FALSE;
while (*p) {
if (*p == '-')
num_dashes++;
else if (!g_ascii_isxdigit(*p))
return FALSE;
p++;
}
/* While we accept here bogus strings as UUIDs, they must contain only
* hexdigits and '-', and they must be either 36 or 40 chars long. */
if ((num_dashes == 4) && (p - str == 36))
return TRUE;
/* Backwards compat for older configurations */
if ((num_dashes == 0) && (p - str == 40))
return TRUE;
return FALSE;
}
/*****************************************************************************/
gboolean
nm_uuid_is_valid_nm(const char *str,
gboolean *out_normalized,
char *out_normalized_str /* [static 37] */)
{
NMUuid uuid;
gboolean is_normalized;
/* @out_normalized_str is only set, if normalization was necessary
* and possible. The caller cannot request @out_normalized_str, without
* also requesting @out_normalized. Otherwise, they couldn't know whether
* a normalized string was returned. */
nm_assert(!out_normalized_str || out_normalized);
if (!str)
return FALSE;
if (nm_uuid_parse_full(str, &uuid, &is_normalized)) {
if (is_normalized) {
/* @str is already normalized. No need to normalize again, so
* @out_normalized is FALSE. */
NM_SET_OUT(out_normalized, FALSE);
} else {
NM_SET_OUT(out_normalized, TRUE);
if (out_normalized_str) {
/* we need to normalize the UUID */
nm_uuid_unparse(&uuid, out_normalized_str);
}
}
return TRUE;
}
if (nm_uuid_is_valid_nmlegacy(str)) {
/* This is not a valid UUID, but something that we used to
* accept according to nm_uuid_is_valid_nmlegacy().
*
* Normalize it by sha1 hashing the string. Upper case characters
* are made lower case first. */
NM_SET_OUT(out_normalized, TRUE);
if (out_normalized_str) {
char str_lower[40];
int i;
nm_assert(strlen(str) <= G_N_ELEMENTS(str_lower));
/* normalize first to lower-case. */
for (i = 0; str[i]; i++) {
nm_assert(i < G_N_ELEMENTS(str_lower));
str_lower[i] = g_ascii_tolower(str[i]);
}
nm_assert(i <= G_N_ELEMENTS(str_lower));
/* The namespace UUID is chosen randomly. */
nm_uuid_generate_from_string(
&uuid,
str_lower,
i,
NM_UUID_TYPE_VERSION5,
&NM_UUID_INIT(4e, 72, f7, 09, ca, 95, 44, 05, 90, 53, 1f, 43, 29, 4a, 61, 8c));
nm_uuid_unparse(&uuid, out_normalized_str);
}
return TRUE;
}
/* UUID is not valid. */
return FALSE;
}
/*****************************************************************************/
gboolean
nm_uuid_is_null(const NMUuid *uuid)
{
int i;
if (!uuid)
return TRUE;
for (i = 0; i < (int) G_N_ELEMENTS(uuid->uuid); i++) {
if (uuid->uuid[i])
return FALSE;
}
return TRUE;
}
char *
nm_uuid_generate_random_str(char buf[static 37])
{
NMUuid uuid;
nm_assert(buf);
nm_uuid_generate_random(&uuid);
return nm_uuid_unparse(&uuid, buf);
}
/*****************************************************************************/
/**
* nm_uuid_generate_from_string:
* @uuid: the UUID to update inplace. This function cannot
* fail to succeed.
* @s: a string to use as the seed for the UUID
* @slen: if negative, treat @s as zero terminated C string.
* Otherwise, assume the length as given (and allow @s to be
* non-null terminated or contain '\0').
* @uuid_type: a type identifier which UUID format to generate.
* @type_args: additional arguments, depending on the uuid_type
*
* For a given @s, this function will always return the same UUID.
*
* Returns: the input @uuid. This function cannot fail.
**/
NMUuid *
nm_uuid_generate_from_string(NMUuid *uuid,
const char *s,
gssize slen,
NMUuidType uuid_type,
const NMUuid *type_args)
{
nm_auto_free_checksum GChecksum *sum = NULL;
union {
guint8 sha1[NM_UTILS_CHECKSUM_LENGTH_SHA1];
guint8 md5[NM_UTILS_CHECKSUM_LENGTH_MD5];
NMUuid uuid;
} digest;
gsize digest_len;
GChecksumType checksum_type;
G_STATIC_ASSERT_EXPR(sizeof(digest.md5) >= sizeof(digest.uuid));
G_STATIC_ASSERT_EXPR(sizeof(digest.sha1) >= sizeof(digest.uuid));
g_return_val_if_fail(uuid, NULL);
g_return_val_if_fail(slen <= 0 || s, NULL);
if (slen < 0)
slen = s ? strlen(s) : 0;
switch (uuid_type) {
case NM_UUID_TYPE_LEGACY:
nm_assert(!type_args);
type_args = NULL;
checksum_type = G_CHECKSUM_MD5;
break;
case NM_UUID_TYPE_VERSION3:
if (!type_args)
type_args = &nm_uuid_ns_zero;
checksum_type = G_CHECKSUM_MD5;
break;
case NM_UUID_TYPE_VERSION5:
if (!type_args)
type_args = &nm_uuid_ns_zero;
checksum_type = G_CHECKSUM_SHA1;
break;
default:
g_return_val_if_reached(NULL);
}
sum = g_checksum_new(checksum_type);
if (type_args)
g_checksum_update(sum, (guchar *) type_args, sizeof(*type_args));
g_checksum_update(sum, (guchar *) s, slen);
digest_len = sizeof(digest);
g_checksum_get_digest(sum, (guint8 *) &digest, &digest_len);
nm_assert(digest_len >= sizeof(digest.uuid));
nm_assert(digest_len
== ((checksum_type == G_CHECKSUM_MD5 ? NM_UTILS_CHECKSUM_LENGTH_MD5
: NM_UTILS_CHECKSUM_LENGTH_SHA1)));
*uuid = digest.uuid;
if (uuid_type != NM_UUID_TYPE_LEGACY) {
uuid->uuid[6] = (uuid->uuid[6] & 0x0F) | (uuid_type << 4);
uuid->uuid[8] = (uuid->uuid[8] & 0x3F) | 0x80;
}
return uuid;
}
/**
* nm_uuid_generate_from_string_str:
* @s: a string to use as the seed for the UUID
* @slen: if negative, treat @s as zero terminated C string.
* Otherwise, assume the length as given (and allow @s to be
* non-null terminated or contain '\0').
* @uuid_type: a type identifier which UUID format to generate.
* @type_args: additional arguments, depending on the uuid_type
*
* For a given @s, this function will always return the same UUID.
*
* Returns: a newly allocated UUID suitable for use as the #NMSettingConnection
* object's #NMSettingConnection:id: property
**/
char *
nm_uuid_generate_from_string_str(const char *s,
gssize slen,
NMUuidType uuid_type,
const NMUuid *type_args)
{
NMUuid uuid;
const NMUuid *u;
u = nm_uuid_generate_from_string(&uuid, s, slen, uuid_type, type_args);
if (G_UNLIKELY(!u))
return nm_assert_unreachable_val(NULL);
nm_assert(u == &uuid);
return nm_uuid_unparse(&uuid, g_new(char, 37));
}
/**
* nm_uuid_generate_from_strings_strv:
* @uuid_type: the UUID type to use. Prefer version 5 unless you have
* good reasons.
* @type_args: the namespace UUID.
* @strv: (nullable): the strv list to hash. Can be NULL, in which
* case the result is different from an empty array.
* @len: if negative, @strv is a NULL terminated array. Otherwise,
* it is the length of the strv array. In the latter case it may
* also contain NULL strings.
*
* Returns a @uuid_type UUID based on the concatenated C strings.
* It does not simply concatenate them, but also includes the
* terminating '\0' character. For example "a", "b", gives
* "a\0b\0".
* This has the advantage, that the following invocations
* all give different UUIDs: (NULL), (""), ("",""), ("","a"), ("a",""),
* ("aa"), ("aa", ""), ("", "aa"), ...
*/
char *
nm_uuid_generate_from_strings_strv(NMUuidType uuid_type,
const NMUuid *type_args,
const char *const *strv,
gssize len)
{
nm_auto_str_buf NMStrBuf str = NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_232, TRUE);
gsize slen;
const char *s;
if (len > 0) {
gboolean has_nulls = FALSE;
gssize i;
nm_assert(len == 0 || strv);
for (i = 0; i < len; i++) {
if (strv[i])
nm_str_buf_append_len(&str, strv[i], strlen(strv[i]) + 1u);
else
has_nulls = TRUE;
}
if (has_nulls) {
/* We either support a NULL terminated strv array, or a ptr array of fixed
* length (@len argument).
*
* If there are no NULLs within the first @len strings, then the result
* is the same. If there are any NULL strings, we need to encode that
* in a unique way. We do that by appending a bitmap of the elements
* whether they were set, plus one 'n' character (without NUL termination).
* None of the other branches below hashes to that, so this will uniquely
* encoded the NULL strings.
*/
for (i = 0; i < len; i++)
nm_str_buf_append_c(&str, strv[i] ? '1' : '_');
nm_str_buf_append_c(&str, 'n');
}
slen = str.len;
s = nm_str_buf_get_str_unsafe(&str);
} else if (!strv) {
/* NULL is treated differently from an empty strv. We achieve that
* by using a non-empty, non-NUL terminated string (which cannot happen
* in the other cases). */
slen = 1;
s = "x";
} else if (!strv[0] || len == 0) {
slen = 0;
s = "";
} else if (!strv[1]) {
slen = strlen(strv[0]) + 1u;
s = strv[0];
} else {
/* We concatenate the NUL terminated string, including the NUL
* character. This way, ("a","a"), ("aa"), ("aa","") all hash
* differently. */
for (; strv[0]; strv++)
nm_str_buf_append_len(&str, strv[0], strlen(strv[0]) + 1u);
slen = str.len;
s = nm_str_buf_get_str_unsafe(&str);
}
return nm_uuid_generate_from_string_str(s, slen, uuid_type, type_args);
}