diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c
index d7a0d2fc11..ac8bdf2d2d 100644
--- a/libnm-core/nm-setting-connection.c
+++ b/libnm-core/nm-setting-connection.c
@@ -1432,13 +1432,32 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class)
/**
* NMSettingConnection:stable-id:
*
- * This token to generate stable IDs for the connection. If unset,
- * the UUID will be used instead.
+ * Token to generate stable IDs for the connection.
*
- * The stable-id is used instead of the connection UUID for generating
- * IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy.
- * It is also used to seed the generated cloned MAC address for
- * ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable.
+ * The stable-id is used for generating IPv6 stable private addresses
+ * with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the
+ * generated cloned MAC address for ethernet.cloned-mac-address=stable
+ * and wifi.cloned-mac-address=stable. Note that also the interface name
+ * of the activating connection and a per-host secret key is included
+ * into the address generation so that the same stable-id on different
+ * hosts/devices yields different addresses.
+ *
+ * If the value is unset, an ID unique for the connection is used.
+ * Specifing a stable-id allows multiple connections to generate the
+ * same addresses. Another use is to generate IDs at runtime via
+ * dynamic substitutions.
+ *
+ * The '$' character is treated special to perform dynamic substitutions
+ * at runtime. Currently supported are "${CONNECTION}", "${BOOT}", "${RANDOM}".
+ * These effectively create unique IDs per-connection, per-boot, or every time.
+ * Any unrecognized patterns following '$' are treated verbatim, however
+ * are reserved for future use. You are thus advised to avoid '$' or
+ * escape it as "$$".
+ * For example, set it to "${CONNECTION}/${BOOT}" to create a unique id for
+ * this connection that changes with every reboot.
+ *
+ * Note that two connections only use the same effective id if
+ * their stable-id is also identical before performing dynamic substitutions.
*
* Since: 1.4
**/
diff --git a/libnm-core/nm-setting-ip6-config.c b/libnm-core/nm-setting-ip6-config.c
index 4ef3333dc3..429f27c95c 100644
--- a/libnm-core/nm-setting-ip6-config.c
+++ b/libnm-core/nm-setting-ip6-config.c
@@ -717,8 +717,8 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class)
* when the interface hardware is replaced.
*
* The value of "stable-privacy" enables use of cryptographically
- * secure hash of a secret host-specific key along with the connection
- * identification and the network address as specified by RFC7217.
+ * secure hash of a secret host-specific key along with the connection's
+ * stable-id and the network address as specified by RFC7217.
* This makes it impossible to use the address track host's presence,
* and makes the address stable when the network interface hardware is
* replaced.
diff --git a/libnm-core/nm-setting-wired.c b/libnm-core/nm-setting-wired.c
index fcf7e95204..fcd8e8370e 100644
--- a/libnm-core/nm-setting-wired.c
+++ b/libnm-core/nm-setting-wired.c
@@ -1150,8 +1150,8 @@ nm_setting_wired_class_init (NMSettingWiredClass *setting_wired_class)
* "preserve" means not to touch the MAC address on activation.
* "permanent" means to use the permanent hardware address of the device.
* "random" creates a random MAC address on each connect.
- * "stable" creates a hashed MAC address based on connection.stable-id (or
- * the connection's UUID) and a machine dependent key.
+ * "stable" creates a hashed MAC address based on connection.stable-id and a
+ * machine dependent key.
*
* If unspecified, the value can be overwritten via global defaults, see manual
* of NetworkManager.conf. If still unspecified, it defaults to "preserve"
diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c
index d7a347f504..1d129f6634 100644
--- a/libnm-core/nm-setting-wireless.c
+++ b/libnm-core/nm-setting-wireless.c
@@ -1348,8 +1348,8 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *setting_wireless_class)
* "preserve" means not to touch the MAC address on activation.
* "permanent" means to use the permanent hardware address of the device.
* "random" creates a random MAC address on each connect.
- * "stable" creates a hashed MAC address based on connection.stable-id (or
- * the connection's UUID) and a machine dependent key.
+ * "stable" creates a hashed MAC address based on connection.stable-id and a
+ * machine dependent key.
*
* If unspecified, the value can be overwritten via global defaults, see manual
* of NetworkManager.conf. If still unspecified, it defaults to "preserve"
diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml
index ae14b05e34..acedc58ddf 100644
--- a/man/NetworkManager.conf.xml
+++ b/man/NetworkManager.conf.xml
@@ -600,6 +600,9 @@ ipv6.ip6-privacy=0
connection.lldp
+
+ connection.stable-id
+
ethernet.cloned-mac-address
If left unspecified, it defaults to "preserve".
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index b6b464caa9..256651cf66 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -261,6 +261,9 @@ typedef struct _NMDevicePrivate {
bool firmware_missing:1;
bool nm_plugin_missing:1;
bool hw_addr_perm_fake:1; /* whether the permanent HW address could not be read and is a fake */
+
+ NMUtilsStableType current_stable_id_type:3;
+
GHashTable * available_connections;
char * hw_addr;
char * hw_addr_perm;
@@ -310,6 +313,8 @@ typedef struct _NMDevicePrivate {
guint32 dhcp_timeout;
char * dhcp_anycast_address;
+ char * current_stable_id;
+
/* Proxy Configuration */
NMProxyConfig *proxy_config;
NMPacrunnerManager *pacrunner_manager;
@@ -663,25 +668,76 @@ _add_capabilities (NMDevice *self, NMDeviceCapabilities capabilities)
/*****************************************************************************/
static const char *
-_get_stable_id (NMConnection *connection, NMUtilsStableType *out_stable_type)
+_get_stable_id (NMDevice *self,
+ NMConnection *connection,
+ NMUtilsStableType *out_stable_type)
{
- NMSettingConnection *s_con;
- const char *stable_id;
+ NMDevicePrivate *priv;
+ nm_assert (NM_IS_DEVICE (self));
nm_assert (NM_IS_CONNECTION (connection));
nm_assert (out_stable_type);
- s_con = nm_connection_get_setting_connection (connection);
- g_return_val_if_fail (s_con, NULL);
+ priv = NM_DEVICE_GET_PRIVATE (self);
- stable_id = nm_setting_connection_get_stable_id (s_con);
- if (!stable_id) {
- *out_stable_type = NM_UTILS_STABLE_TYPE_UUID;
- return nm_connection_get_uuid (connection);
+ /* we cache the generated stable ID for the time of an activation.
+ *
+ * The reason is, that we don't want the stable-id to change as long
+ * as the device is active.
+ *
+ * Especially with ${RANDOM} stable-id we want to generate *one* configuration
+ * for each activation. */
+ if (G_UNLIKELY (!priv->current_stable_id)) {
+ gs_free char *default_id = NULL;
+ gs_free char *generated = NULL;
+ NMUtilsStableType stable_type;
+ NMSettingConnection *s_con;
+ const char *stable_id;
+ const char *uuid;
+
+ s_con = nm_connection_get_setting_connection (connection);
+
+ stable_id = nm_setting_connection_get_stable_id (s_con);
+
+ if (!stable_id) {
+ default_id = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+ "connection.stable-id",
+ self);
+ stable_id = default_id;
+ }
+
+ uuid = nm_connection_get_uuid (connection);
+
+ stable_type = nm_utils_stable_id_parse (stable_id,
+ uuid,
+ NULL,
+ &generated);
+
+ /* current_stable_id_type is a bitfield! */
+ nm_assert (stable_type <= (NMUtilsStableType) 0x2);
+ nm_assert (stable_type + (NMUtilsStableType) 1 > (NMUtilsStableType) 0);
+ priv->current_stable_id_type = stable_type;
+
+ if (stable_type == NM_UTILS_STABLE_TYPE_UUID)
+ priv->current_stable_id = g_strdup (uuid);
+ else if (stable_type == NM_UTILS_STABLE_TYPE_STABLE_ID)
+ priv->current_stable_id = g_strdup (stable_id);
+ else if (stable_type == NM_UTILS_STABLE_TYPE_GENERATED)
+ priv->current_stable_id = nm_str_realloc (nm_utils_stable_id_generated_complete (generated));
+ else {
+ nm_assert (stable_type == NM_UTILS_STABLE_TYPE_RANDOM);
+ priv->current_stable_id = nm_str_realloc (nm_utils_stable_id_random ());
+ }
+ _LOGT (LOGD_DEVICE,
+ "stable-id: type=%d, \"%s\""
+ "%s%s%s",
+ (int) priv->current_stable_id_type,
+ priv->current_stable_id,
+ NM_PRINT_FMT_QUOTED (stable_type == NM_UTILS_STABLE_TYPE_GENERATED, " from \"", generated, "\"", ""));
}
- *out_stable_type = NM_UTILS_STABLE_TYPE_STABLE_ID;
- return stable_id;
+ *out_stable_type = priv->current_stable_id_type;
+ return priv->current_stable_id;
}
/*****************************************************************************/
@@ -6438,7 +6494,7 @@ check_and_add_ipv6ll_addr (NMDevice *self)
NMUtilsStableType stable_type;
const char *stable_id;
- stable_id = _get_stable_id (connection, &stable_type);
+ stable_id = _get_stable_id (self, connection, &stable_type);
if ( !stable_id
|| !nm_utils_ipv6_addr_set_stable_privacy (stable_type,
&lladdr,
@@ -6843,7 +6899,7 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
g_assert (s_ip6);
- stable_id = _get_stable_id (connection, &stable_type);
+ stable_id = _get_stable_id (self, connection, &stable_type);
if (stable_id) {
priv->ndisc = nm_lndp_ndisc_new (NM_PLATFORM_GET,
nm_device_get_ip_ifindex (self),
@@ -11375,7 +11431,7 @@ nm_device_spawn_iface_helper (NMDevice *self)
g_ptr_array_add (argv, g_strdup ("--uuid"));
g_ptr_array_add (argv, g_strdup (nm_connection_get_uuid (connection)));
- stable_id = _get_stable_id (connection, &stable_type);
+ stable_id = _get_stable_id (self, connection, &stable_type);
if (stable_id && stable_type != NM_UTILS_STABLE_TYPE_UUID) {
g_ptr_array_add (argv, g_strdup ("--stable-id"));
g_ptr_array_add (argv, g_strdup_printf ("%d %s", (int) stable_type, stable_id));
@@ -11670,6 +11726,11 @@ _set_state_full (NMDevice *self,
if (state >= NM_DEVICE_STATE_DISCONNECTED && old_state < NM_DEVICE_STATE_DISCONNECTED)
nm_device_recheck_available_connections (self);
+ if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_DEACTIVATING) {
+ if (nm_clear_g_free (&priv->current_stable_id))
+ _LOGT (LOGD_DEVICE, "stable-id: clear");
+ }
+
/* Handle the new state here; but anything that could trigger
* another state change should be done below.
*/
@@ -12544,7 +12605,7 @@ nm_device_hw_addr_set_cloned (NMDevice *self, NMConnection *connection, gboolean
return TRUE;
}
- stable_id = _get_stable_id (connection, &stable_type);
+ stable_id = _get_stable_id (self, connection, &stable_type);
if (stable_id) {
hw_addr_generated = nm_utils_hw_addr_gen_stable_eth (stable_type, stable_id,
nm_device_get_ip_iface (self),
@@ -12933,6 +12994,7 @@ finalize (GObject *object)
g_free (priv->type_desc);
g_free (priv->type_description);
g_free (priv->dhcp_anycast_address);
+ g_free (priv->current_stable_id);
g_hash_table_unref (priv->ip6_saved_properties);
g_hash_table_unref (priv->available_connections);
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index 00f3104966..a50bbe4385 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -1176,7 +1176,7 @@ nm_ndisc_class_init (NMNDiscClass *klass)
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_STABLE_TYPE] =
g_param_spec_int (NM_NDISC_STABLE_TYPE, "", "",
- NM_UTILS_STABLE_TYPE_UUID, NM_UTILS_STABLE_TYPE_STABLE_ID, NM_UTILS_STABLE_TYPE_UUID,
+ NM_UTILS_STABLE_TYPE_UUID, NM_UTILS_STABLE_TYPE_RANDOM, NM_UTILS_STABLE_TYPE_UUID,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index 4654b950d6..219f309beb 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -3267,6 +3267,186 @@ nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid, char *buf)
/*****************************************************************************/
+char *
+nm_utils_stable_id_random (void)
+{
+ char buf[15];
+
+ if (nm_utils_read_urandom (buf, sizeof (buf)) < 0)
+ g_return_val_if_reached (nm_utils_uuid_generate ());
+ return g_base64_encode ((guchar *) buf, sizeof (buf));
+}
+
+char *
+nm_utils_stable_id_generated_complete (const char *stable_id_generated)
+{
+ guint8 buf[20];
+ GChecksum *sum;
+ gsize buf_size;
+ char *base64;
+
+ /* for NM_UTILS_STABLE_TYPE_GENERATED we genererate a possibly long string
+ * by doing text-substitutions in nm_utils_stable_id_parse().
+ *
+ * Let's shorten the (possibly) long stable_id to something more compact. */
+
+ g_return_val_if_fail (stable_id_generated, NULL);
+
+ sum = g_checksum_new (G_CHECKSUM_SHA1);
+ nm_assert (sum);
+
+ g_checksum_update (sum, (guchar *) stable_id_generated, strlen (stable_id_generated));
+
+ buf_size = sizeof (buf);
+ g_checksum_get_digest (sum, buf, &buf_size);
+ nm_assert (buf_size == sizeof (buf));
+
+ g_checksum_free (sum);
+
+ /* we don't care to use the sha1 sum in common hex representation.
+ * Use instead base64, it's 27 chars (stripping the padding) vs.
+ * 40. */
+
+ base64 = g_base64_encode ((guchar *) buf, sizeof (buf));
+ nm_assert (strlen (base64) == 28);
+ nm_assert (base64[27] == '=');
+
+ base64[27] = '\0';
+ return base64;
+}
+
+static void
+_stable_id_append (GString *str,
+ const char *substitution)
+{
+ if (!substitution)
+ substitution = "";
+ g_string_append_printf (str, "=%zu{%s}", strlen (substitution), substitution);
+}
+
+NMUtilsStableType
+nm_utils_stable_id_parse (const char *stable_id,
+ const char *uuid,
+ const char *bootid,
+ char **out_generated)
+{
+ gsize i, idx_start;
+ GString *str = NULL;
+
+ g_return_val_if_fail (out_generated, NM_UTILS_STABLE_TYPE_RANDOM);
+
+ if (!stable_id) {
+ out_generated = NULL;
+ return NM_UTILS_STABLE_TYPE_UUID;
+ }
+
+ /* the stable-id allows for some dynamic by performing text-substitutions
+ * of ${...} patterns.
+ *
+ * At first, it looks a bit like bash parameter substitution.
+ * In contrast however, the process is unambigious so that the resulting
+ * effective id differs if:
+ * - the original, untranslated stable-id differs
+ * - or any of the subsitutions differs.
+ *
+ * The reason for that is, for example if you specify "${CONNECTION}" in the
+ * stable-id, then the resulting ID should be always(!) unique for this connection.
+ * There should be no way another connection could specify any stable-id that results
+ * in the same addresses to be generated (aside hash collisions).
+ *
+ *
+ * For example: say you have a connection with UUID
+ * "123e4567-e89b-12d3-a456-426655440000" which happens also to be
+ * the current boot-id.
+ * Then:
+ * (1) connection.stable-id =
+ * (2) connection.stable-id = "123e4567-e89b-12d3-a456-426655440000"
+ * (3) connection.stable-id = "${CONNECTION}"
+ * (3) connection.stable-id = "${BOOT}"
+ * will all generate different addresses, although in one way or the
+ * other, they all mangle the uuid "123e4567-e89b-12d3-a456-426655440000".
+ *
+ * For example, with stable-id="${FOO}${BAR}" the substitutions
+ * - FOO="ab", BAR="c"
+ * - FOO="a", BAR="bc"
+ * should give a different effective id.
+ *
+ * For example, with FOO="x" and BAR="x", the stable-ids
+ * - "${FOO}${BAR}"
+ * - "${BAR}${FOO}"
+ * should give a different effective id.
+ */
+
+ idx_start = 0;
+ for (i = 0; stable_id[i]; ) {
+ if (stable_id[i] != '$') {
+ i++;
+ continue;
+ }
+
+#define CHECK_PREFIX(prefix) \
+ ({ \
+ gboolean _match = FALSE; \
+ \
+ if (g_str_has_prefix (&stable_id[i], ""prefix"")) { \
+ _match = TRUE; \
+ if (!str) \
+ str = g_string_sized_new (256); \
+ i += NM_STRLEN (prefix); \
+ g_string_append_len (str, &(stable_id)[idx_start], i - idx_start); \
+ idx_start = i; \
+ } \
+ _match; \
+ })
+ if (CHECK_PREFIX ("${CONNECTION}"))
+ _stable_id_append (str, uuid);
+ else if (CHECK_PREFIX ("${BOOT}"))
+ _stable_id_append (str, bootid ?: nm_utils_get_boot_id ());
+ else if (g_str_has_prefix (&stable_id[i], "${RANDOM}")) {
+ /* RANDOM makes not so much sense for cloned-mac-address
+ * as the result is simmilar to specifing "cloned-mac-address=random".
+ * It makes however sense for RFC 7217 Stable Privacy IPv6 addresses
+ * where this is effectively the only way to generate a different
+ * (random) host identifier for each connect.
+ *
+ * With RANDOM, the user can switch the lifetime of the
+ * generated cloned-mac-address and IPv6 host identifier
+ * by toggeling only the stable-id property of the connection.
+ * With RANDOM being the most short-lived, ~non-stable~ variant.
+ */
+ if (str)
+ g_string_free (str, TRUE);
+ *out_generated = NULL;
+ return NM_UTILS_STABLE_TYPE_RANDOM;
+ } else {
+ /* The text following the '$' is not recognized as valid
+ * substitution pattern. Treat it verbatim. */
+ i++;
+
+ /* Note that using unrecognized substitution patterns might
+ * yield different results with future versions. Avoid that,
+ * by not using '$' (except for actual substitutions) or escape
+ * it as "$$" (which is guaranteed to be treated verbatim
+ * in future). */
+ if (stable_id[i] == '$')
+ i++;
+ }
+ }
+#undef CHECK_PREFIX
+
+ if (!str) {
+ *out_generated = NULL;
+ return NM_UTILS_STABLE_TYPE_STABLE_ID;
+ }
+
+ if (idx_start < i)
+ g_string_append_len (str, &stable_id[idx_start], i - idx_start);
+ *out_generated = g_string_free (str, FALSE);
+ return NM_UTILS_STABLE_TYPE_GENERATED;
+}
+
+/*****************************************************************************/
+
static gboolean
_set_stable_privacy (NMUtilsStableType stable_type,
struct in6_addr *addr,
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 25a59361fc..e3aa257f25 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -369,10 +369,19 @@ typedef enum {
* Also note, if we ever allocate ID 255, we must take care
* that nm_utils_ipv6_addr_set_stable_privacy() extends the
* uint8 encoding of this value. */
- NM_UTILS_STABLE_TYPE_UUID = 0,
+ NM_UTILS_STABLE_TYPE_UUID = 0,
NM_UTILS_STABLE_TYPE_STABLE_ID = 1,
+ NM_UTILS_STABLE_TYPE_GENERATED = 2,
+ NM_UTILS_STABLE_TYPE_RANDOM = 3,
} NMUtilsStableType;
+NMUtilsStableType nm_utils_stable_id_parse (const char *stable_id,
+ const char *uuid,
+ const char *bootid,
+ char **out_generated);
+
+char *nm_utils_stable_id_random (void);
+char *nm_utils_stable_id_generated_complete (const char *msg);
gboolean nm_utils_ipv6_addr_set_stable_privacy_impl (NMUtilsStableType stable_type,
struct in6_addr *addr,
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index a7337ba7b0..ed2f4e5119 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -1481,6 +1481,89 @@ test_reverse_dns_ip6 (void)
/*****************************************************************************/
+static void
+do_test_stable_id_parse (const char *stable_id,
+ NMUtilsStableType expected_stable_type,
+ const char *expected_generated)
+{
+ gs_free char *generated = NULL;
+ NMUtilsStableType stable_type;
+
+ if (expected_stable_type == NM_UTILS_STABLE_TYPE_GENERATED)
+ g_assert (expected_generated);
+ else
+ g_assert (!expected_generated);
+
+ if (expected_stable_type == NM_UTILS_STABLE_TYPE_UUID)
+ g_assert (!stable_id);
+ else
+ g_assert (stable_id);
+
+ stable_type = nm_utils_stable_id_parse (stable_id, "_CONNECTION", "_BOOT", &generated);
+
+ g_assert_cmpint (expected_stable_type, ==, stable_type);
+
+ if (stable_type == NM_UTILS_STABLE_TYPE_GENERATED) {
+ g_assert_cmpstr (expected_generated, ==, generated);
+ g_assert (generated);
+ } else
+ g_assert (!generated);
+}
+
+static void
+test_stable_id_parse (void)
+{
+#define _parse_stable_id(stable_id) do_test_stable_id_parse (""stable_id"", NM_UTILS_STABLE_TYPE_STABLE_ID, NULL)
+#define _parse_generated(stable_id, expected_generated) do_test_stable_id_parse (""stable_id"", NM_UTILS_STABLE_TYPE_GENERATED, ""expected_generated"")
+#define _parse_random(stable_id) do_test_stable_id_parse (""stable_id"", NM_UTILS_STABLE_TYPE_RANDOM, NULL)
+ do_test_stable_id_parse (NULL, NM_UTILS_STABLE_TYPE_UUID, NULL);
+ _parse_stable_id ("");
+ _parse_stable_id ("a");
+ _parse_stable_id ("a$");
+ _parse_stable_id ("a$x");
+ _parse_stable_id (" ${a$x");
+ _parse_stable_id ("${");
+ _parse_stable_id ("${=");
+ _parse_stable_id ("${a");
+ _parse_stable_id ("${a$x");
+ _parse_stable_id ("a$$");
+ _parse_stable_id ("a$$x");
+ _parse_stable_id ("a$${CONNECTION}");
+ _parse_stable_id ("a$${CONNECTION}x");
+ _parse_generated ("${CONNECTION}", "${CONNECTION}=11{_CONNECTION}");
+ _parse_generated ("${${CONNECTION}", "${${CONNECTION}=11{_CONNECTION}");
+ _parse_generated ("${CONNECTION}x", "${CONNECTION}=11{_CONNECTION}x");
+ _parse_generated ("x${CONNECTION}", "x${CONNECTION}=11{_CONNECTION}");
+ _parse_generated ("${BOOT}x", "${BOOT}=5{_BOOT}x");
+ _parse_generated ("x${BOOT}", "x${BOOT}=5{_BOOT}");
+ _parse_generated ("x${BOOT}${CONNECTION}", "x${BOOT}=5{_BOOT}${CONNECTION}=11{_CONNECTION}");
+ _parse_generated ("xX${BOOT}yY${CONNECTION}zZ", "xX${BOOT}=5{_BOOT}yY${CONNECTION}=11{_CONNECTION}zZ");
+ _parse_random ("${RANDOM}");
+ _parse_random (" ${RANDOM}");
+ _parse_random ("${BOOT}${RANDOM}");
+}
+
+/*****************************************************************************/
+
+static void
+test_stable_id_generated_complete (void)
+{
+#define ASSERT(str, expected) \
+ G_STMT_START { \
+ gs_free char *_s = NULL; \
+ \
+ _s = nm_utils_stable_id_generated_complete ((str)); \
+ g_assert_cmpstr ((expected), ==, _s); \
+ } G_STMT_END
+
+ ASSERT ("", "2jmj7l5rSw0yVb/vlWAYkK/YBwk");
+ ASSERT ("a", "hvfkN/qlp/zhXR3cuerq6jd2Z7g");
+ ASSERT ("password", "W6ph5Mm5Pz8GgiULbPgzG37mj9g");
+#undef ASSERT
+}
+
+/*****************************************************************************/
+
NMTST_DEFINE ();
int
@@ -1518,6 +1601,9 @@ main (int argc, char **argv)
g_test_add_func ("/general/reverse_dns/ip4", test_reverse_dns_ip4);
g_test_add_func ("/general/reverse_dns/ip6", test_reverse_dns_ip6);
+ g_test_add_func ("/general/stable-id/parse", test_stable_id_parse);
+ g_test_add_func ("/general/stable-id/generated-complete", test_stable_id_generated_complete);
+
return g_test_run ();
}