about summary refs log tree commit diff
path: root/src/platform/nm-fake-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2014-07-06 03:04:17 +0200
committerMichael Biebl <biebl@debian.org>2014-07-06 03:04:17 +0200
commit3ce667b6ee6b86291bbe60ef93cba7f11a5c5400 (patch)
treeaf3daa7221e898b72f3de26eb2cf0c1b1c8661b0 /src/platform/nm-fake-platform.c
parentf02d31d95af29678092d1ff01f714621bc9dcc0f (diff)
parent33491bc4279481db8ae47213e34a6d695a0e8830 (diff)
Merge tag 'upstream/0.9.10.0'
Upstream version 0.9.10.0
Diffstat (limited to 'src/platform/nm-fake-platform.c')
-rw-r--r--src/platform/nm-fake-platform.c1352
1 files changed, 1352 insertions, 0 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
new file mode 100644
index 00000000..46272217
--- /dev/null
+++ b/src/platform/nm-fake-platform.c
@@ -0,0 +1,1352 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform-fake.c - Fake platform interaction code for testing NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2012–2013 Red Hat, Inc.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+
+#include "NetworkManagerUtils.h"
+#include "nm-fake-platform.h"
+#include "nm-logging.h"
+
+#define debug(format, ...) nm_log_dbg (LOGD_PLATFORM, format, __VA_ARGS__)
+
+typedef struct {
+	GHashTable *options;
+	GArray *links;
+	GArray *ip4_addresses;
+	GArray *ip6_addresses;
+	GArray *ip4_routes;
+	GArray *ip6_routes;
+} NMFakePlatformPrivate;
+
+typedef struct {
+	NMPlatformLink link;
+
+	char *udi;
+	GBytes *address;
+	int vlan_id;
+} NMFakePlatformLink;
+
+#define NM_FAKE_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_FAKE_PLATFORM, NMFakePlatformPrivate))
+
+G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM)
+
+/******************************************************************/
+
+void
+nm_fake_platform_setup (void)
+{
+	nm_platform_setup (NM_TYPE_FAKE_PLATFORM);
+}
+
+/******************************************************************/
+
+static gboolean
+sysctl_set (NMPlatform *platform, const char *path, const char *value)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+
+	g_hash_table_insert (priv->options, g_strdup (path), g_strdup (value));
+
+	return TRUE;
+}
+
+static char *
+sysctl_get (NMPlatform *platform, const char *path)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+
+	return g_strdup (g_hash_table_lookup (priv->options, path));
+}
+
+static const char *
+type_to_type_name (NMLinkType type)
+{
+	switch (type) {
+	case NM_LINK_TYPE_UNKNOWN:
+		return "unknown";
+	case NM_LINK_TYPE_LOOPBACK:
+		return "loopback";
+	case NM_LINK_TYPE_ETHERNET:
+		return "ethernet";
+	case NM_LINK_TYPE_DUMMY:
+		return "dummy";
+	case NM_LINK_TYPE_BRIDGE:
+		return "bridge";
+	case NM_LINK_TYPE_BOND:
+		return "bond";
+	case NM_LINK_TYPE_TEAM:
+		return "team";
+	case NM_LINK_TYPE_VLAN:
+		return "vlan";
+	case NM_LINK_TYPE_NONE:
+	default:
+		return NULL;
+	}
+}
+
+static void
+link_init (NMFakePlatformLink *device, int ifindex, int type, const char *name)
+{
+	g_assert (!name || strlen (name) < sizeof(device->link.name));
+
+	memset (device, 0, sizeof (*device));
+
+	device->link.ifindex = name ? ifindex : 0;
+	device->link.type = type;
+	device->link.type_name = type_to_type_name (type);
+	device->link.driver = type_to_type_name (type);
+	device->link.udi = device->udi = g_strdup_printf ("fake:%d", ifindex);
+	if (name)
+		strcpy (device->link.name, name);
+	switch (device->link.type) {
+	case NM_LINK_TYPE_DUMMY:
+		device->link.arp = FALSE;
+		break;
+	default:
+		device->link.arp = TRUE;
+	}
+	device->address = NULL;
+}
+
+static NMFakePlatformLink *
+link_get (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	NMFakePlatformLink *device;
+
+	if (ifindex >= priv->links->len)
+		goto not_found;
+	device = &g_array_index (priv->links, NMFakePlatformLink, ifindex);
+	if (!device->link.ifindex)
+		goto not_found;
+
+	return device;
+not_found:
+	debug ("link not found: %d", ifindex);
+	platform->error = NM_PLATFORM_ERROR_NOT_FOUND;
+	return NULL;
+}
+
+static GArray *
+link_get_all (NMPlatform *platform)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	GArray *links = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), priv->links->len);
+	int i;
+
+	for (i = 0; i < priv->links->len; i++)
+		if (g_array_index (priv->links, NMFakePlatformLink, i).link.ifindex)
+			g_array_append_val (links, g_array_index (priv->links, NMFakePlatformLink, i).link);
+
+	return links;
+}
+
+static gboolean
+_nm_platform_link_get (NMPlatform *platform, int ifindex, NMPlatformLink *link)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (device)
+		*link = device->link;
+	return !!device;
+}
+
+static gboolean
+link_add (NMPlatform *platform, const char *name, NMLinkType type, const void *address, size_t address_len)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	NMFakePlatformLink device;
+
+	link_init (&device, priv->links->len, type, name);
+
+	g_array_append_val (priv->links, device);
+
+	if (device.link.ifindex)
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, device.link.ifindex, &device, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+
+	return TRUE;
+}
+
+static gboolean
+link_delete (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+	NMPlatformLink deleted_device;
+	int i;
+
+	if (!device || !device->link.ifindex)
+		return FALSE;
+
+	memcpy (&deleted_device, &device->link, sizeof (deleted_device));
+	memset (&device->link, 0, sizeof (device->link));
+
+	/* Remove addresses and routes which belong to the deleted interface */
+	for (i = 0; i < priv->ip4_addresses->len; i++) {
+		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+
+		if (address->ifindex == ifindex)
+			memset (address, 0, sizeof (*address));
+	}
+	for (i = 0; i < priv->ip6_addresses->len; i++) {
+		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+
+		if (address->ifindex == ifindex)
+			memset (address, 0, sizeof (*address));
+	}
+	for (i = 0; i < priv->ip4_routes->len; i++) {
+		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
+
+		if (route->ifindex == ifindex)
+			memset (route, 0, sizeof (*route));
+	}
+	for (i = 0; i < priv->ip6_routes->len; i++) {
+		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
+
+		if (route->ifindex == ifindex)
+			memset (route, 0, sizeof (*route));
+	}
+
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, ifindex, &deleted_device, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+
+	return TRUE;
+}
+
+static int
+link_get_ifindex (NMPlatform *platform, const char *name)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	for (i = 0; i < priv->links->len; i++) {
+		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
+
+		if (device && !g_strcmp0 (device->link.name, name))
+			return device->link.ifindex;
+	}
+
+	return 0;
+}
+
+static const char *
+link_get_name (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	return device ? device->link.name : NULL;
+}
+
+static NMLinkType
+link_get_type (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	return device ? device->link.type : NM_LINK_TYPE_NONE;
+}
+
+static const char *
+link_get_type_name (NMPlatform *platform, int ifindex)
+{
+	return type_to_type_name (link_get_type (platform, ifindex));
+}
+
+static void
+link_changed (NMPlatform *platform, NMFakePlatformLink *device)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, device->link.ifindex, &device->link, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+
+	if (device->link.master) {
+		NMFakePlatformLink *master = link_get (platform, device->link.master);
+
+		g_return_if_fail (master != device);
+
+		master->link.connected = FALSE;
+		for (i = 0; i < priv->links->len; i++) {
+			NMFakePlatformLink *slave = &g_array_index (priv->links, NMFakePlatformLink, i);
+
+			if (slave && slave->link.master == master->link.ifindex && slave->link.connected)
+				master->link.connected = TRUE;
+		}
+
+		link_changed (platform, master);
+	}
+}
+
+static gboolean
+link_set_up (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (!device)
+		return FALSE;
+
+	device->link.up = TRUE;
+	switch (device->link.type) {
+	case NM_LINK_TYPE_DUMMY:
+	case NM_LINK_TYPE_VLAN:
+		device->link.connected = TRUE;
+		break;
+	case NM_LINK_TYPE_BRIDGE:
+	case NM_LINK_TYPE_BOND:
+	case NM_LINK_TYPE_TEAM:
+		device->link.connected = FALSE;
+		break;
+	default:
+		device->link.connected = FALSE;
+		g_error ("Unexpected device type: %d", device->link.type);
+	}
+
+	link_changed (platform, device);
+
+	return TRUE;
+}
+
+static gboolean
+link_set_down (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (!device)
+		return FALSE;
+
+	device->link.up = FALSE;
+	device->link.connected = FALSE;
+
+	link_changed (platform, device);
+
+	return TRUE;
+}
+
+static gboolean
+link_set_arp (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (!device)
+		return FALSE;
+
+	device->link.arp = TRUE;
+
+	link_changed (platform, device);
+
+	return TRUE;
+}
+
+static gboolean
+link_set_noarp (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (!device)
+		return FALSE;
+
+	device->link.arp = FALSE;
+
+	link_changed (platform, device);
+
+	return TRUE;
+}
+
+static gboolean
+link_is_up (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	return device ? device->link.up : FALSE;
+}
+
+static gboolean
+link_is_connected (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	return device ? device->link.connected : FALSE;
+}
+
+static gboolean
+link_uses_arp (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	return device ? device->link.arp : FALSE;
+}
+
+static gboolean
+link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (device->address)
+		g_bytes_unref (device->address);
+
+	device->address = g_bytes_new (addr, len);
+
+	link_changed (platform, link_get (platform, ifindex));
+
+	return TRUE;
+}
+
+static gconstpointer
+link_get_address (NMPlatform *platform, int ifindex, size_t *length)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (!device || !device->address) {
+		if (length)
+			*length = 0;
+		return NULL;
+	}
+
+	return g_bytes_get_data (device->address, length);
+}
+
+static gboolean
+link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (device) {
+		device->link.mtu = mtu;
+		link_changed (platform, device);
+	}
+
+	return !!device;
+}
+
+static guint32
+link_get_mtu (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	return device ? device->link.mtu : 0;
+}
+
+static char *
+link_get_physical_port_id (NMPlatform *platform, int ifindex)
+{
+	/* We call link_get just to cause an error to be set if @ifindex is bad. */
+	link_get (platform, ifindex);
+
+	return NULL;
+}
+
+static gboolean
+link_get_wake_on_lan (NMPlatform *platform, int ifindex)
+{
+	/* We call link_get just to cause an error to be set if @ifindex is bad. */
+	link_get (platform, ifindex);
+
+	return FALSE;
+}
+
+static gboolean
+link_supports_carrier_detect (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (!device)
+		return FALSE;
+
+	switch (device->link.type) {
+	case NM_LINK_TYPE_DUMMY:
+		return FALSE;
+	default:
+		return TRUE;
+	}
+}
+
+static gboolean
+link_supports_vlans (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	if (!device)
+		return FALSE;
+
+	switch (device->link.type) {
+	case NM_LINK_TYPE_LOOPBACK:
+		return FALSE;
+	default:
+		return TRUE;
+	}
+}
+
+static gboolean
+link_enslave (NMPlatform *platform, int master, int slave)
+{
+	NMFakePlatformLink *device = link_get (platform, slave);
+
+	g_return_val_if_fail (device, FALSE);
+
+	device->link.master = master;
+
+	link_changed (platform, device);
+
+	return TRUE;
+}
+
+static gboolean
+link_release (NMPlatform *platform, int master_idx, int slave_idx)
+{
+	NMFakePlatformLink *master = link_get (platform, master_idx);
+	NMFakePlatformLink *slave = link_get (platform, slave_idx);
+
+	g_return_val_if_fail (master, FALSE);
+	g_return_val_if_fail (slave, FALSE);
+
+	if (slave->link.master != master->link.ifindex) {
+		platform->error = NM_PLATFORM_ERROR_NOT_SLAVE;
+		return FALSE;
+	}
+
+	slave->link.master = 0;
+
+	link_changed (platform, slave);
+	link_changed (platform, master);
+
+	return TRUE;
+}
+
+static int
+link_get_master (NMPlatform *platform, int slave)
+{
+	NMFakePlatformLink *device = link_get (platform, slave);
+
+	g_return_val_if_fail (device, FALSE);
+
+	return device->link.master;
+}
+
+static gboolean
+master_set_option (NMPlatform *platform, int master, const char *option, const char *value)
+{
+	auto_g_free char *path = g_strdup_printf ("master:%d:%s", master, option);
+
+	return sysctl_set (platform, path, value);
+}
+
+static char *
+master_get_option (NMPlatform *platform, int master, const char *option)
+{
+	auto_g_free char *path = g_strdup_printf ("master:%d:%s", master, option);
+
+	return sysctl_get (platform, path);
+}
+
+static gboolean
+slave_set_option (NMPlatform *platform, int slave, const char *option, const char *value)
+{
+	auto_g_free char *path = g_strdup_printf ("slave:%d:%s", slave, option);
+
+	return sysctl_set (platform, path, value);
+}
+
+static char *
+slave_get_option (NMPlatform *platform, int slave, const char *option)
+{
+	auto_g_free char *path = g_strdup_printf ("slave:%d:%s", slave, option);
+
+	return sysctl_get (platform, path);
+}
+
+static gboolean
+vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags)
+{
+	NMFakePlatformLink *device;
+
+	if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0))
+		return FALSE;
+
+	device = link_get (platform, link_get_ifindex (platform, name));
+
+	g_return_val_if_fail (device, FALSE);
+
+	device->vlan_id = vlan_id;
+	device->link.parent = parent;
+
+	return TRUE;
+}
+
+static gboolean
+vlan_get_info (NMPlatform *platform, int ifindex, int *parent, int *vlan_id)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	g_return_val_if_fail (device, FALSE);
+
+	if (parent)
+		*parent = device->link.parent;
+	if (vlan_id)
+		*vlan_id = device->vlan_id;
+
+	return TRUE;
+}
+
+static gboolean
+vlan_set_ingress_map (NMPlatform *platform, int ifindex, int from, int to)
+{
+	return !!link_get (platform, ifindex);
+}
+
+static gboolean
+vlan_set_egress_map (NMPlatform *platform, int ifindex, int from, int to)
+{
+	return !!link_get (platform, ifindex);
+}
+
+static gboolean
+infiniband_partition_add (NMPlatform *platform, int parent, int p_key)
+{
+	NMFakePlatformLink *parent_device;
+	char *name;
+	gboolean success;
+
+	parent_device = link_get (platform, parent);
+	g_return_val_if_fail (parent_device != NULL, FALSE);
+
+	name = g_strdup_printf ("%s.%04x", parent_device->link.name, p_key);
+	success = link_add (platform, name, NM_LINK_TYPE_INFINIBAND, NULL, 0);
+	g_free (name);
+
+	return success;
+}
+
+static gboolean
+veth_get_properties (NMPlatform *platform, int ifindex, NMPlatformVethProperties *props)
+{
+	return FALSE;
+}
+
+static gboolean
+tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *props)
+{
+	return FALSE;
+}
+
+static gboolean
+macvlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformMacvlanProperties *props)
+{
+	return FALSE;
+}
+
+static gboolean
+vxlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformVxlanProperties *props)
+{
+	return FALSE;
+}
+
+static gboolean
+gre_get_properties (NMPlatform *platform, int ifindex, NMPlatformGreProperties *props)
+{
+	return FALSE;
+}
+
+static gboolean
+wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps)
+{
+	NMFakePlatformLink *device = link_get (platform, ifindex);
+
+	g_return_val_if_fail (device, FALSE);
+
+	if (device->link.type != NM_LINK_TYPE_WIFI)
+		return FALSE;
+
+	if (caps) {
+		*caps = (  NM_WIFI_DEVICE_CAP_CIPHER_WEP40
+		         | NM_WIFI_DEVICE_CAP_CIPHER_WEP104
+		         | NM_WIFI_DEVICE_CAP_CIPHER_TKIP
+		         | NM_WIFI_DEVICE_CAP_CIPHER_CCMP
+		         | NM_WIFI_DEVICE_CAP_WPA
+		         | NM_WIFI_DEVICE_CAP_RSN
+		         | NM_WIFI_DEVICE_CAP_AP
+		         | NM_WIFI_DEVICE_CAP_ADHOC);
+	}
+	return TRUE;
+}
+
+static gboolean
+wifi_get_bssid (NMPlatform *platform, int ifindex, struct ether_addr *bssid)
+{
+	return FALSE;
+}
+
+static GByteArray *
+wifi_get_ssid (NMPlatform *platform, int ifindex)
+{
+	return NULL;
+}
+
+static guint32
+wifi_get_frequency (NMPlatform *platform, int ifindex)
+{
+	return 0;
+}
+
+static int
+wifi_get_quality (NMPlatform *platform, int ifindex)
+{
+	return 0;
+}
+
+static guint32
+wifi_get_rate (NMPlatform *platform, int ifindex)
+{
+	return 0;
+}
+
+static NM80211Mode
+wifi_get_mode (NMPlatform *platform, int ifindex)
+{
+	return NM_802_11_MODE_UNKNOWN;
+}
+
+static void
+wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode)
+{
+	;
+}
+
+static guint32
+wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs)
+{
+	return freqs[0];
+}
+
+static void
+wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running)
+{
+	;
+}
+
+static guint32
+mesh_get_channel (NMPlatform *platform, int ifindex)
+{
+	return 0;
+}
+
+static gboolean
+mesh_set_channel (NMPlatform *platform, int ifindex, guint32 channel)
+{
+	return FALSE;
+}
+
+static gboolean
+mesh_set_ssid (NMPlatform *platform, int ifindex, const GByteArray *ssid)
+{
+	return FALSE;
+}
+
+/******************************************************************/
+
+static GArray *
+ip4_address_get_all (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	GArray *addresses;
+	NMPlatformIP4Address *address;
+	int count = 0, i;
+
+	/* Count addresses */
+	for (i = 0; i < priv->ip4_addresses->len; i++) {
+		address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+		if (address && address->ifindex == ifindex)
+			count++;
+	}
+
+	addresses = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP4Address), count);
+
+	/* Fill addresses */
+	for (i = 0; i < priv->ip4_addresses->len; i++) {
+		address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+		if (address && address->ifindex == ifindex)
+			g_array_append_val (addresses, *address);
+	}
+
+	return addresses;
+}
+
+static GArray *
+ip6_address_get_all (NMPlatform *platform, int ifindex)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	GArray *addresses;
+	NMPlatformIP6Address *address;
+	int count = 0, i;
+
+	/* Count addresses */
+	for (i = 0; i < priv->ip6_addresses->len; i++) {
+		address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+		if (address && address->ifindex == ifindex)
+			count++;
+	}
+
+	addresses = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP6Address), count);
+
+	/* Fill addresses */
+	count = 0;
+	for (i = 0; i < priv->ip6_addresses->len; i++) {
+		address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+		if (address && address->ifindex == ifindex)
+			g_array_append_val (addresses, *address);
+	}
+
+	return addresses;
+}
+
+static gboolean
+ip4_address_add (NMPlatform *platform, int ifindex,
+                 in_addr_t addr, in_addr_t peer_addr,
+                 int plen, guint32 lifetime, guint32 preferred,
+                 const char *label)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	NMPlatformIP4Address address;
+	int i;
+
+	memset (&address, 0, sizeof (address));
+	address.source = NM_PLATFORM_SOURCE_KERNEL;
+	address.ifindex = ifindex;
+	address.address = addr;
+	address.peer_address = peer_addr;
+	address.plen = plen;
+	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+	address.lifetime = lifetime;
+	address.preferred = preferred;
+	if (label)
+		g_strlcpy (address.label, label, sizeof (address.label));
+
+	for (i = 0; i < priv->ip4_addresses->len; i++) {
+		NMPlatformIP4Address *item = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+
+		if (item->ifindex != address.ifindex)
+			continue;
+		if (item->address != address.address)
+			continue;
+		if (item->plen != address.plen)
+			continue;
+
+		memcpy (item, &address, sizeof (address));
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		return TRUE;
+	}
+
+	g_array_append_val (priv->ip4_addresses, address);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, ifindex, &address, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+
+	return TRUE;
+}
+
+static gboolean
+ip6_address_add (NMPlatform *platform, int ifindex,
+                 struct in6_addr addr, struct in6_addr peer_addr,
+                 int plen, guint32 lifetime, guint32 preferred, guint flags)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	NMPlatformIP6Address address;
+	int i;
+
+	memset (&address, 0, sizeof (address));
+	address.source = NM_PLATFORM_SOURCE_KERNEL;
+	address.ifindex = ifindex;
+	address.address = addr;
+	address.peer_address = peer_addr;
+	address.plen = plen;
+	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+	address.lifetime = lifetime;
+	address.preferred = preferred;
+	address.flags = flags;
+
+	for (i = 0; i < priv->ip6_addresses->len; i++) {
+		NMPlatformIP6Address *item = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+
+		if (item->ifindex != address.ifindex)
+			continue;
+		if (!IN6_ARE_ADDR_EQUAL (&item->address, &address.address))
+			continue;
+		if (item->plen != address.plen)
+			continue;
+
+		memcpy (item, &address, sizeof (address));
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		return TRUE;
+	}
+
+	g_array_append_val (priv->ip6_addresses, address);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, ifindex, &address, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+
+	return TRUE;
+}
+
+static gboolean
+ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	for (i = 0; i < priv->ip4_addresses->len; i++) {
+		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+
+		if (address->ifindex == ifindex && address->plen == plen && address->address == addr) {
+			NMPlatformIP4Address deleted_address;
+
+			memcpy (&deleted_address, address, sizeof (deleted_address));
+			memset (address, 0, sizeof (*address));
+			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, ifindex, &deleted_address, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+			return TRUE;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	for (i = 0; i < priv->ip6_addresses->len; i++) {
+		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+
+		if (address->ifindex == ifindex && address->plen == plen
+				&& IN6_ARE_ADDR_EQUAL (&address->address, &addr)) {
+			NMPlatformIP6Address deleted_address;
+
+			memcpy (&deleted_address, address, sizeof (deleted_address));
+			memset (address, 0, sizeof (*address));
+			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, ifindex, &deleted_address, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+			return TRUE;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+ip4_address_exists (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	for (i = 0; i < priv->ip4_addresses->len; i++) {
+		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+
+		if (address->ifindex == ifindex && address->plen == plen && address->address == addr)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+ip6_address_exists (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	for (i = 0; i < priv->ip6_addresses->len; i++) {
+		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+
+		if (address->ifindex == ifindex && address->plen == plen &&
+				IN6_ARE_ADDR_EQUAL (&address->address, &addr))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+/******************************************************************/
+
+static GArray *
+ip4_route_get_all (NMPlatform *platform, int ifindex, gboolean include_default)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	GArray *routes;
+	NMPlatformIP4Route *route;
+	int count = 0, i;
+
+	/* Count routes */
+	for (i = 0; i < priv->ip4_routes->len; i++) {
+		route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
+		if (route && route->ifindex == ifindex)
+			count++;
+	}
+
+	routes = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP4Route), count);
+
+	/* Fill routes */
+	for (i = 0; i < priv->ip4_routes->len; i++) {
+		route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
+		if (route && route->ifindex == ifindex) {
+			if (route->plen != 0 || include_default)
+				g_array_append_val (routes, *route);
+		}
+	}
+
+	return routes;
+}
+
+static GArray *
+ip6_route_get_all (NMPlatform *platform, int ifindex, gboolean include_default)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	GArray *routes;
+	NMPlatformIP6Route *route;
+	int count = 0, i;
+
+	/* Count routes */
+	for (i = 0; i < priv->ip6_routes->len; i++) {
+		route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
+		if (route && route->ifindex == ifindex)
+			count++;
+	}
+
+	routes = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP6Route), count);
+
+	/* Fill routes */
+	for (i = 0; i < priv->ip6_routes->len; i++) {
+		route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
+		if (route && route->ifindex == ifindex) {
+			if (route->plen != 0 || include_default)
+				g_array_append_val (routes, *route);
+		}
+	}
+
+	return routes;
+}
+
+static gboolean
+ip4_route_add (NMPlatform *platform, int ifindex, NMPlatformSource source,
+               in_addr_t network, int plen, in_addr_t gateway,
+               int metric, int mss)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	NMPlatformIP4Route route;
+	guint i;
+
+	memset (&route, 0, sizeof (route));
+	route.source = NM_PLATFORM_SOURCE_KERNEL;
+	route.ifindex = ifindex;
+	route.source = source;
+	route.network = network;
+	route.plen = plen;
+	route.gateway = gateway;
+	route.metric = metric;
+	route.mss = mss;
+
+	for (i = 0; i < priv->ip4_routes->len; i++) {
+		NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
+
+		if (item->ifindex != route.ifindex)
+			continue;
+		if (item->network != route.network)
+			continue;
+		if (item->plen != route.plen)
+			continue;
+
+		memcpy (item, &route, sizeof (route));
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		return TRUE;
+	}
+
+	g_array_append_val (priv->ip4_routes, route);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, ifindex, &route, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+
+	return TRUE;
+}
+
+static gboolean
+ip6_route_add (NMPlatform *platform, int ifindex, NMPlatformSource source,
+               struct in6_addr network, int plen, struct in6_addr gateway,
+               int metric, int mss)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	NMPlatformIP6Route route;
+	guint i;
+
+	memset (&route, 0, sizeof (route));
+	route.source = NM_PLATFORM_SOURCE_KERNEL;
+	route.ifindex = ifindex;
+	route.source = source;
+	route.network = network;
+	route.plen = plen;
+	route.gateway = gateway;
+	route.metric = metric;
+	route.mss = mss;
+
+	for (i = 0; i < priv->ip6_routes->len; i++) {
+		NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
+
+		if (item->ifindex != route.ifindex)
+			continue;
+		if (!IN6_ARE_ADDR_EQUAL (&item->network, &route.network))
+			continue;
+		if (item->plen != route.plen)
+			continue;
+
+		memcpy (item, &route, sizeof (route));
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_INTERNAL);
+		return TRUE;
+	}
+
+	g_array_append_val (priv->ip6_routes, route);
+	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, ifindex, &route, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_INTERNAL);
+
+	return TRUE;
+}
+
+static NMPlatformIP4Route *
+ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, int metric)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	for (i = 0; i < priv->ip4_routes->len; i++) {
+		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
+
+		if (route->ifindex == ifindex
+				&& route->network == network
+				&& route->plen == plen
+				&& route->metric == metric)
+			return route;
+	}
+
+	return NULL;
+}
+
+static NMPlatformIP6Route *
+ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, int metric)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
+	int i;
+
+	for (i = 0; i < priv->ip6_routes->len; i++) {
+		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
+
+		if (route->ifindex == ifindex
+				&& IN6_ARE_ADDR_EQUAL (&route->network, &network)
+				&& route->plen == plen
+				&& route->metric == metric)
+			return route;
+	}
+
+	return NULL;
+}
+
+static gboolean
+ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen, int metric)
+{
+	NMPlatformIP4Route *route = ip4_route_get (platform, ifindex, network, plen, metric);
+	NMPlatformIP4Route deleted_route;
+
+	if (route) {
+		memcpy (&deleted_route, route, sizeof (deleted_route));
+		memset (route, 0, sizeof (*route));
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, int metric)
+{
+	NMPlatformIP6Route *route = ip6_route_get (platform, ifindex, network, plen, metric);
+	NMPlatformIP6Route deleted_route;
+
+	if (route) {
+		memcpy (&deleted_route, route, sizeof (deleted_route));
+		memset (route, 0, sizeof (*route));
+		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_INTERNAL);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+ip4_route_exists (NMPlatform *platform, int ifindex, in_addr_t network, int plen, int metric)
+{
+	return !!ip4_route_get (platform, ifindex, network, plen, metric);
+}
+
+static gboolean
+ip6_route_exists (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, int metric)
+{
+	return !!ip6_route_get (platform, ifindex, network, plen, metric);
+}
+
+/******************************************************************/
+
+static void
+nm_fake_platform_init (NMFakePlatform *fake_platform)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (fake_platform);
+
+	priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	priv->links = g_array_new (TRUE, TRUE, sizeof (NMFakePlatformLink));
+	priv->ip4_addresses = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Address));
+	priv->ip6_addresses = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Address));
+	priv->ip4_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Route));
+	priv->ip6_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route));
+}
+
+static gboolean
+setup (NMPlatform *platform)
+{
+	/* skip zero element */
+	link_add (platform, NULL, NM_LINK_TYPE_NONE, NULL, 0);
+
+	/* add loopback interface */
+	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, 0);
+
+	/* add some ethernets */
+	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, 0);
+	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, 0);
+	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, 0);
+
+	return TRUE;
+}
+
+static void
+nm_fake_platform_finalize (GObject *object)
+{
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (object);
+	int i;
+
+	g_hash_table_unref (priv->options);
+	for (i = 0; i < priv->links->len; i++) {
+		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
+
+		g_bytes_unref (device->address);
+		g_free (device->udi);
+	}
+	g_array_unref (priv->links);
+	g_array_unref (priv->ip4_addresses);
+	g_array_unref (priv->ip6_addresses);
+	g_array_unref (priv->ip4_routes);
+	g_array_unref (priv->ip6_routes);
+
+	G_OBJECT_CLASS (nm_fake_platform_parent_class)->finalize (object);
+}
+
+static void
+nm_fake_platform_class_init (NMFakePlatformClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMPlatformClass *platform_class = NM_PLATFORM_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (NMFakePlatformPrivate));
+
+	/* virtual methods */
+	object_class->finalize = nm_fake_platform_finalize;
+
+	platform_class->setup = setup;
+
+	platform_class->sysctl_set = sysctl_set;
+	platform_class->sysctl_get = sysctl_get;
+
+	platform_class->link_get = _nm_platform_link_get;
+	platform_class->link_get_all = link_get_all;
+	platform_class->link_add = link_add;
+	platform_class->link_delete = link_delete;
+	platform_class->link_get_ifindex = link_get_ifindex;
+	platform_class->link_get_name = link_get_name;
+	platform_class->link_get_type = link_get_type;
+	platform_class->link_get_type_name = link_get_type_name;
+
+	platform_class->link_set_up = link_set_up;
+	platform_class->link_set_down = link_set_down;
+	platform_class->link_set_arp = link_set_arp;
+	platform_class->link_set_noarp = link_set_noarp;
+	platform_class->link_is_up = link_is_up;
+	platform_class->link_is_connected = link_is_connected;
+	platform_class->link_uses_arp = link_uses_arp;
+
+	platform_class->link_set_address = link_set_address;
+	platform_class->link_get_address = link_get_address;
+	platform_class->link_get_mtu = link_get_mtu;
+	platform_class->link_set_mtu = link_set_mtu;
+
+	platform_class->link_get_physical_port_id = link_get_physical_port_id;
+	platform_class->link_get_wake_on_lan = link_get_wake_on_lan;
+
+	platform_class->link_supports_carrier_detect = link_supports_carrier_detect;
+	platform_class->link_supports_vlans = link_supports_vlans;
+
+	platform_class->link_enslave = link_enslave;
+	platform_class->link_release = link_release;
+	platform_class->link_get_master = link_get_master;
+	platform_class->master_set_option = master_set_option;
+	platform_class->master_get_option = master_get_option;
+	platform_class->slave_set_option = slave_set_option;
+	platform_class->slave_get_option = slave_get_option;
+
+	platform_class->vlan_add = vlan_add;
+	platform_class->vlan_get_info = vlan_get_info;
+	platform_class->vlan_set_ingress_map = vlan_set_ingress_map;
+	platform_class->vlan_set_egress_map = vlan_set_egress_map;
+
+	platform_class->infiniband_partition_add = infiniband_partition_add;
+
+	platform_class->veth_get_properties = veth_get_properties;
+	platform_class->tun_get_properties = tun_get_properties;
+	platform_class->macvlan_get_properties = macvlan_get_properties;
+	platform_class->vxlan_get_properties = vxlan_get_properties;
+	platform_class->gre_get_properties = gre_get_properties;
+
+	platform_class->wifi_get_capabilities = wifi_get_capabilities;
+	platform_class->wifi_get_bssid = wifi_get_bssid;
+	platform_class->wifi_get_ssid = wifi_get_ssid;
+	platform_class->wifi_get_frequency = wifi_get_frequency;
+	platform_class->wifi_get_quality = wifi_get_quality;
+	platform_class->wifi_get_rate = wifi_get_rate;
+	platform_class->wifi_get_mode = wifi_get_mode;
+	platform_class->wifi_set_mode = wifi_set_mode;
+	platform_class->wifi_find_frequency = wifi_find_frequency;
+	platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running;
+
+	platform_class->mesh_get_channel = mesh_get_channel;
+	platform_class->mesh_set_channel = mesh_set_channel;
+	platform_class->mesh_set_ssid = mesh_set_ssid;
+
+	platform_class->ip4_address_get_all = ip4_address_get_all;
+	platform_class->ip6_address_get_all = ip6_address_get_all;
+	platform_class->ip4_address_add = ip4_address_add;
+	platform_class->ip6_address_add = ip6_address_add;
+	platform_class->ip4_address_delete = ip4_address_delete;
+	platform_class->ip6_address_delete = ip6_address_delete;
+	platform_class->ip4_address_exists = ip4_address_exists;
+	platform_class->ip6_address_exists = ip6_address_exists;
+
+	platform_class->ip4_route_get_all = ip4_route_get_all;
+	platform_class->ip6_route_get_all = ip6_route_get_all;
+	platform_class->ip4_route_add = ip4_route_add;
+	platform_class->ip6_route_add = ip6_route_add;
+	platform_class->ip4_route_delete = ip4_route_delete;
+	platform_class->ip6_route_delete = ip6_route_delete;
+	platform_class->ip4_route_exists = ip4_route_exists;
+	platform_class->ip6_route_exists = ip6_route_exists;
+}