glib-aux: add nm_g_variant_cmp()

There is g_variant_equal(), which can handle all variant types (however
that is not a compare function).

There is g_variant_compare(), which is a compare function but only works for
basic types.

Add nm_g_variant_cmp() which works with all variant types.

This is based on nm_property_compare(), with some differences:

- nm_property_compare() tries (wrongly) to accept string dictionaries in
  any order. That functionality seems wrong, and nm_g_variant_cmp()
  doesn't do that.

- nm_property_compare() does possibly not support all variant types.
  This can be a problem, if we call the function on untrusted data
  (and it can be hard to validate first, whether the function can
  be called with a particular variant). Instead, nm_g_variant_cmp()
  should work with all variants.

The unit tests are copied from "src/libnm-core-impl/tests/test-compare.c"
with some adjustments (because nm_property_compare() is not the same as
nm_g_variant_cmp()).

Note that the code is actually unused. It was written as replacement for
nm_property_compare(), but turns out not to be used there. For now,
leave it, because it might still be useful to have in the toolbox and it
exists (including tests).
This commit is contained in:
Thomas Haller 2023-12-12 14:15:11 +01:00
parent e415e4fc18
commit 9db8cdb64d
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
3 changed files with 372 additions and 0 deletions

View file

@ -623,6 +623,149 @@ nm_g_variant_maybe_singleton_i(gint32 value)
/*****************************************************************************/
static int
_variant_type_cmp(const GVariantType *type1, const GVariantType *type2)
{
const char *string1;
const char *string2;
gsize size;
NM_CMP_SELF(type1, type2);
size = g_variant_type_get_string_length(type1);
NM_CMP_DIRECT(size, g_variant_type_get_string_length(type2));
string1 = g_variant_type_peek_string(type1);
string2 = g_variant_type_peek_string(type2);
NM_CMP_DIRECT_MEMCMP(string1, string2, size);
return 0;
}
int
nm_g_variant_type_cmp(const GVariantType *type1, const GVariantType *type2)
{
int r;
r = _variant_type_cmp(type1, type2);
nm_assert((!!g_variant_type_equal(type1, type2)) == (r == 0));
return r;
}
/*****************************************************************************/
typedef enum {
VARIANT_CMP_TYPE_VARIANT,
VARIANT_CMP_TYPE_STRDICT,
VARIANT_CMP_TYPE_VARDICT,
} VariantCmpType;
static int
_variant_cmp_array(GVariant *value1, GVariant *value2, VariantCmpType type)
{
gsize len;
gsize i;
len = g_variant_n_children(value1);
NM_CMP_DIRECT(len, g_variant_n_children(value2));
for (i = 0; i < len; i++) {
gs_unref_variant GVariant *child1 = g_variant_get_child_value(value1, i);
gs_unref_variant GVariant *child2 = g_variant_get_child_value(value2, i);
const char *key1;
const char *key2;
const char *val1_str;
const char *val2_str;
nm_assert(child1);
nm_assert(child2);
switch (type) {
case VARIANT_CMP_TYPE_VARIANT:
NM_CMP_RETURN(nm_g_variant_cmp(child1, child2));
break;
case VARIANT_CMP_TYPE_STRDICT:
g_variant_get(child1, "{&s&s}", &key1, &val1_str);
g_variant_get(child2, "{&s&s}", &key2, &val2_str);
NM_CMP_DIRECT_STRCMP(key1, key2);
NM_CMP_DIRECT_STRCMP(val1_str, val2_str);
break;
case VARIANT_CMP_TYPE_VARDICT:
{
gs_unref_variant GVariant *val1_var = NULL;
gs_unref_variant GVariant *val2_var = NULL;
g_variant_get(child1, "{&sv}", &key1, &val1_var);
g_variant_get(child2, "{&sv}", &key2, &val2_var);
NM_CMP_DIRECT_STRCMP(key1, key2);
NM_CMP_RETURN(nm_g_variant_cmp(val1_var, val2_var));
break;
}
}
}
return 0;
}
static int
_variant_cmp_generic(GVariant *value1, GVariant *value2)
{
gs_free char *str1 = NULL;
gs_free char *str2 = NULL;
/* This is like g_variant_equal(), which also resorts to pretty-printing
* the variants for comparison.
*
* Note that the variant types are already checked and equal. We thus don't
* need to include the type annotation. */
str1 = g_variant_print(value1, FALSE);
str2 = g_variant_print(value2, FALSE);
NM_CMP_DIRECT_STRCMP(str1, str2);
return 0;
}
static int
_variant_cmp(GVariant *value1, GVariant *value2)
{
const GVariantType *type;
NM_CMP_SELF(value1, value2);
type = g_variant_get_type(value1);
NM_CMP_RETURN(nm_g_variant_type_cmp(type, g_variant_get_type(value2)));
if (g_variant_type_is_basic(type))
NM_CMP_RETURN(g_variant_compare(value1, value2));
else if (g_variant_type_is_subtype_of(type, G_VARIANT_TYPE("a{ss}")))
NM_CMP_RETURN(_variant_cmp_array(value1, value2, VARIANT_CMP_TYPE_STRDICT));
else if (g_variant_type_is_subtype_of(type, G_VARIANT_TYPE("a{sv}")))
NM_CMP_RETURN(_variant_cmp_array(value1, value2, VARIANT_CMP_TYPE_VARDICT));
else if (g_variant_type_is_array(type) || g_variant_type_is_tuple(type))
NM_CMP_RETURN(_variant_cmp_array(value1, value2, VARIANT_CMP_TYPE_VARIANT));
else
NM_CMP_RETURN(_variant_cmp_generic(value1, value2));
return 0;
}
int
nm_g_variant_cmp(GVariant *value1, GVariant *value2)
{
int r;
r = _variant_cmp(value1, value2);
nm_assert((!!nm_g_variant_equal(value1, value2)) == (r == 0));
return r;
}
/*****************************************************************************/
GHashTable *
nm_strdict_clone(GHashTable *src)
{

View file

@ -1418,6 +1418,10 @@ nm_g_variant_builder_add_sv_str(GVariantBuilder *builder, const char *key, const
nm_g_variant_builder_add_sv(builder, key, g_variant_new_string(str));
}
int nm_g_variant_type_cmp(const GVariantType *type1, const GVariantType *type2);
int nm_g_variant_cmp(GVariant *value1, GVariant *value2);
static inline void
nm_g_source_destroy_and_unref(GSource *source)
{

View file

@ -2630,6 +2630,224 @@ test_uid_to_name(void)
/*****************************************************************************/
static void
compare_ints(void)
{
GVariant *value1, *value2;
value1 = g_variant_new_int32(5);
value2 = g_variant_new_int32(5);
g_assert(nm_g_variant_cmp(value1, value2) == 0);
g_variant_unref(value2);
value2 = g_variant_new_int32(10);
g_assert(nm_g_variant_cmp(value1, value2) < 0);
g_variant_unref(value2);
value2 = g_variant_new_int32(-1);
g_assert(nm_g_variant_cmp(value1, value2) > 0);
g_variant_unref(value1);
g_variant_unref(value2);
}
static void
compare_strings(void)
{
GVariant *value1, *value2;
const char *str1 = "hello";
const char *str2 = "world";
value1 = g_variant_new_string(str1);
value2 = g_variant_new_string(str1);
g_assert(nm_g_variant_cmp(value1, value2) == 0);
g_variant_unref(value2);
value2 = g_variant_new_string(str2);
g_assert(nm_g_variant_cmp(value1, value2) < 0);
g_assert(nm_g_variant_cmp(value2, value1) > 0);
g_variant_unref(value1);
g_variant_unref(value2);
}
static void
compare_strv(void)
{
GVariant *value1, *value2;
const char *const strv1[] = {"foo", "bar", "baz", NULL};
const char *const strv2[] = {"foo", "bar", "bar", NULL};
const char *const strv3[] = {"foo", "bar", NULL};
const char *const strv4[] = {"foo", "bar", "baz", "bam", NULL};
value1 = g_variant_new_strv(strv1, -1);
value2 = g_variant_new_strv(strv1, -1);
g_assert(nm_g_variant_cmp(value1, value2) == 0);
g_variant_unref(value2);
value2 = g_variant_new_strv(strv2, -1);
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_variant_unref(value2);
value2 = g_variant_new_strv(strv3, -1);
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_variant_unref(value2);
value2 = g_variant_new_strv(strv4, -1);
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_variant_unref(value1);
g_variant_unref(value2);
}
static void
compare_arrays(void)
{
GVariant *value1, *value2;
guint32 array[] = {0, 1, 2, 3, 4};
value1 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
array,
G_N_ELEMENTS(array),
sizeof(guint32));
value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
array,
G_N_ELEMENTS(array),
sizeof(guint32));
g_assert(nm_g_variant_cmp(value1, value2) == 0);
g_variant_unref(value2);
value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
array + 1,
G_N_ELEMENTS(array) - 1,
sizeof(guint32));
g_assert(nm_g_variant_cmp(value1, value2) != 0);
array[0] = 7;
g_variant_unref(value2);
value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
array,
G_N_ELEMENTS(array),
sizeof(guint32));
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_variant_unref(value1);
g_variant_unref(value2);
}
static void
compare_str_hash(void)
{
GVariant *value1, *value2;
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
g_variant_builder_add(&builder, "{ss}", "key1", "hello");
g_variant_builder_add(&builder, "{ss}", "key2", "world");
g_variant_builder_add(&builder, "{ss}", "key3", "!");
value1 = g_variant_builder_end(&builder);
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
g_variant_builder_add(&builder, "{ss}", "key3", "!");
g_variant_builder_add(&builder, "{ss}", "key2", "world");
g_variant_builder_add(&builder, "{ss}", "key1", "hello");
value2 = g_variant_builder_end(&builder);
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_variant_unref(value1);
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
g_variant_builder_add(&builder, "{sv}", "key1", g_variant_new_string("hello"));
g_variant_builder_add(&builder, "{sv}", "key2", g_variant_new_string("world"));
g_variant_builder_add(&builder, "{sv}", "key3", g_variant_new_string("!"));
value1 = g_variant_builder_end(&builder);
g_variant_unref(value2);
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
g_variant_builder_add(&builder, "{sv}", "key1", g_variant_new_string("hello"));
g_variant_builder_add(&builder, "{sv}", "key2", g_variant_new_string("world"));
g_variant_builder_add(&builder, "{sv}", "key3", g_variant_new_string("!"));
value2 = g_variant_builder_end(&builder);
g_assert(nm_g_variant_cmp(value1, value2) == 0);
g_variant_unref(value2);
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
g_variant_builder_add(&builder, "{ss}", "key1", "hello");
g_variant_builder_add(&builder, "{ss}", "key3", "!");
value2 = g_variant_builder_end(&builder);
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_assert(nm_g_variant_cmp(value2, value1) != 0);
g_variant_unref(value2);
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
g_variant_builder_add(&builder, "{ss}", "key1", "hello");
g_variant_builder_add(&builder, "{ss}", "key2", "moon");
g_variant_builder_add(&builder, "{ss}", "key3", "!");
value2 = g_variant_builder_end(&builder);
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_variant_unref(value1);
g_variant_unref(value2);
}
static void
compare_ip6_addresses(void)
{
GVariant *value1, *value2;
struct in6_addr addr1;
struct in6_addr addr2;
struct in6_addr addr3;
guint32 prefix1 = 64;
guint32 prefix2 = 64;
guint32 prefix3 = 0;
inet_pton(AF_INET6, "1:2:3:4:5:6:7:8", &addr1);
inet_pton(AF_INET6, "ffff:2:3:4:5:6:7:8", &addr2);
inet_pton(AF_INET6, "::", &addr3);
value1 = g_variant_new(
"(@ayu@ay)",
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr1.s6_addr, 16, 1),
prefix1,
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
value2 = g_variant_new(
"(@ayu@ay)",
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr1.s6_addr, 16, 1),
prefix1,
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
g_assert(nm_g_variant_cmp(value1, value2) == 0);
g_variant_unref(value2);
value2 = g_variant_new(
"(@ayu@ay)",
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr2.s6_addr, 16, 1),
prefix2,
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_variant_unref(value2);
value2 = g_variant_new(
"(@ayu@ay)",
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1),
prefix3,
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
g_assert(nm_g_variant_cmp(value1, value2) != 0);
g_variant_unref(value1);
g_variant_unref(value2);
}
/*****************************************************************************/
NMTST_DEFINE();
int
@ -2683,5 +2901,12 @@ main(int argc, char **argv)
g_test_add_func("/general/test_nm_random", test_nm_random);
g_test_add_func("/general/test_uid_to_name", test_uid_to_name);
g_test_add_func("/libnm/compare/ints", compare_ints);
g_test_add_func("/libnm/compare/strings", compare_strings);
g_test_add_func("/libnm/compare/strv", compare_strv);
g_test_add_func("/libnm/compare/arrays", compare_arrays);
g_test_add_func("/libnm/compare/str_hash", compare_str_hash);
g_test_add_func("/libnm/compare/ip6_addresses", compare_ip6_addresses);
return g_test_run();
}