From 9db8cdb64d568574356ae8082f53b300af76225e Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 12 Dec 2023 14:15:11 +0100 Subject: [PATCH] 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). --- src/libnm-glib-aux/nm-shared-utils.c | 143 +++++++++++ src/libnm-glib-aux/nm-shared-utils.h | 4 + .../tests/test-shared-general.c | 225 ++++++++++++++++++ 3 files changed, 372 insertions(+) diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c index f8620279b3..7d623bd9d3 100644 --- a/src/libnm-glib-aux/nm-shared-utils.c +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -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) { diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index b0ce25a568..ea38e083cd 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -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) { diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c index e65c0efc1d..b19ac1ce52 100644 --- a/src/libnm-glib-aux/tests/test-shared-general.c +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -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(); }