/* -*- 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 "nm-default.h"

#include "nm-fake-platform.h"

#include <errno.h>
#include <unistd.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <linux/rtnetlink.h>

#include "nm-utils.h"

#include "nm-core-utils.h"
#include "nm-platform-utils.h"
#include "nmp-object.h"

#include "nm-test-utils-core.h"

/*****************************************************************************/

typedef struct {
	NMPlatformLink link;

	char *udi;
	NMPObject *lnk;
	struct in6_addr ip6_lladdr;
} NMFakePlatformLink;

typedef struct {
	GHashTable *options;
	GArray *links;
	GArray *ip4_addresses;
	GArray *ip6_addresses;
	GArray *ip4_routes;
	GArray *ip6_routes;
} NMFakePlatformPrivate;

struct _NMFakePlatform {
	NMPlatform parent;
	NMFakePlatformPrivate _priv;
};

struct _NMFakePlatformClass {
	NMPlatformClass parent;
};

G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM)

#define NM_FAKE_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMFakePlatform, NM_IS_FAKE_PLATFORM)

/*****************************************************************************/

#define _NMLOG_PREFIX_NAME                "platform-fake"
#define _NMLOG_DOMAIN                     LOGD_PLATFORM
#define _NMLOG(level, ...)                _LOG(level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)

#define _LOG(level, domain, self, ...) \
    G_STMT_START { \
        const NMLogLevel __level = (level); \
        const NMLogDomain __domain = (domain); \
        \
        if (nm_logging_enabled (__level, __domain)) { \
            char __prefix[32]; \
            const char *__p_prefix = _NMLOG_PREFIX_NAME; \
            const void *const __self = (self); \
            \
            if (__self && __self != nm_platform_try_get ()) { \
                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
                __p_prefix = __prefix; \
            } \
            _nm_log (__level, __domain, 0, NULL, NULL, \
                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
        } \
    } G_STMT_END

/*****************************************************************************/

static void link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal);

static gboolean ip6_address_add (NMPlatform *platform,
                                 int ifindex,
                                 struct in6_addr addr,
                                 guint8 plen,
                                 struct in6_addr peer_addr,
                                 guint32 lifetime,
                                 guint32 preferred,
                                 guint flags);
static gboolean ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen);

/*****************************************************************************/

static gboolean
_ip4_address_equal_peer_net (in_addr_t peer1, in_addr_t peer2, guint8 plen)
{
	return ((peer1 ^ peer2) & nm_utils_ip4_prefix_to_netmask (plen)) == 0;
}

/*****************************************************************************/

#define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \
	G_STMT_START { \
		const char *const _pathid = (pathid); \
		const int _dirfd = (dirfd); \
		const char *const _path = (path); \
		\
		g_assert (_path && _path[0]); \
		g_assert (!strstr (_path, "/../")); \
		if (_dirfd < 0) { \
			g_assert (!_pathid); \
			g_assert (_path[0] == '/'); \
			g_assert (   g_str_has_prefix (_path, "/proc/sys/") \
			          || g_str_has_prefix (_path, "/sys/")); \
		} else { \
			g_assert_not_reached (); \
		} \
	} G_STMT_END

static gboolean
sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);

	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);

	g_hash_table_insert (priv->options, g_strdup (path), g_strdup (value));

	return TRUE;
}

static char *
sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *path)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);

	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);

	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)
{
	gs_free char *ip6_lladdr = NULL;

	g_assert (!name || strlen (name) < sizeof(device->link.name));

	memset (device, 0, sizeof (*device));

	ip6_lladdr = ifindex > 0 ? g_strdup_printf ("fe80::fa1e:%0x:%0x", ifindex / 256, ifindex % 256) : NULL;

	device->link.ifindex = name ? ifindex : 0;
	device->link.type = type;
	device->link.kind = type_to_type_name (type);
	device->link.driver = type_to_type_name (type);
	device->udi = g_strdup_printf ("fake:%d", ifindex);
	device->link.initialized = TRUE;
	device->ip6_lladdr = *nmtst_inet6_from_string (ip6_lladdr);
	if (name)
		strcpy (device->link.name, name);
	switch (device->link.type) {
	case NM_LINK_TYPE_DUMMY:
		device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_NOARP);
		break;
	default:
		device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_NOARP);
		break;
	}
}

static NMFakePlatformLink *
link_get (NMPlatform *platform, int ifindex)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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:
	_LOGD ("link not found: %d", ifindex);
	return NULL;
}

static GArray *
link_get_all (NMPlatform *platform)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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 const NMPlatformLink *
_nm_platform_link_get (NMPlatform *platform, int ifindex)
{
	NMFakePlatformLink *device = link_get (platform, ifindex);

	return device ? &device->link : NULL;
}

static const NMPlatformLink *
_nm_platform_link_get_by_ifname (NMPlatform *platform, const char *ifname)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	guint i;

	for (i = 0; i < priv->links->len; i++) {
		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);

		if (!strcmp (device->link.name, ifname))
			return &device->link;
	}
	return NULL;
}

static const NMPlatformLink *
_nm_platform_link_get_by_address (NMPlatform *platform,
                                  gconstpointer address,
                                  size_t length)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	guint i;

	if (   length == 0
	    || length > NM_UTILS_HWADDR_LEN_MAX
	    || !address)
		g_return_val_if_reached (NULL);

	for (i = 0; i < priv->links->len; i++) {
		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);

		if (   device->link.addr.len == length
		    && memcmp (device->link.addr.data, address, length) == 0) {
			return &device->link;
		}
	}
	return NULL;
}

static const NMPObject *
link_get_lnk (NMPlatform *platform,
              int ifindex,
              NMLinkType link_type,
              const NMPlatformLink **out_link)
{
	NMFakePlatformLink *device = link_get (platform, ifindex);

	if (!device)
		return NULL;

	NM_SET_OUT (out_link, &device->link);

	if (!device->lnk)
		return NULL;

	if (link_type == NM_LINK_TYPE_NONE)
		return device->lnk;

	if (   link_type != device->link.type
	    || link_type != NMP_OBJECT_GET_CLASS (device->lnk)->lnk_link_type)
		return NULL;

	return device->lnk;
}

static gboolean
link_add (NMPlatform *platform,
          const char *name,
          NMLinkType type,
          const void *address,
          size_t address_len,
          const NMPlatformLink **out_link)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	NMFakePlatformLink device;
	NMFakePlatformLink *new_device;

	link_init (&device, priv->links->len, type, name);

	if (address) {
		g_return_val_if_fail (address_len > 0 && address_len <= sizeof (device.link.addr.data), FALSE);
		memcpy (device.link.addr.data, address, address_len);
		device.link.addr.len = address_len;
	}

	g_array_append_val (priv->links, device);
	new_device = &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1);

	if (device.link.ifindex) {
		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, (int) NMP_OBJECT_TYPE_LINK, device.link.ifindex, &device, (int) NM_PLATFORM_SIGNAL_ADDED);

		link_changed (platform, &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1), FALSE);
	}

	if (out_link)
		*out_link = &new_device->link;
	return TRUE;
}

static gboolean
link_delete (NMPlatform *platform, int ifindex)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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));
	g_clear_pointer (&device->lnk, nmp_object_unref);
	g_clear_pointer (&device->udi, g_free);

	/* 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, (int) NMP_OBJECT_TYPE_LINK, ifindex, &deleted_device, (int) NM_PLATFORM_SIGNAL_REMOVED);

	return TRUE;
}

static const char *
link_get_type_name (NMPlatform *platform, int ifindex)
{
	return type_to_type_name (nm_platform_link_get_type (platform, ifindex));
}

static void
link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	int i;

	if (raise_signal)
		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, (int) NMP_OBJECT_TYPE_LINK, device->link.ifindex, &device->link, (int) NM_PLATFORM_SIGNAL_CHANGED);

	if (device->link.ifindex && !IN6_IS_ADDR_UNSPECIFIED (&device->ip6_lladdr)) {
		if (device->link.connected)
			ip6_address_add (platform, device->link.ifindex, in6addr_any, 64, device->ip6_lladdr, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0);
		else
			ip6_address_delete (platform, device->link.ifindex, device->ip6_lladdr, 64);
	}

	if (device->link.master) {
		gboolean connected = FALSE;

		NMFakePlatformLink *master = link_get (platform, device->link.master);

		g_return_if_fail (master && master != device);

		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)
				connected = TRUE;
		}

		if (master->link.connected != connected) {
			master->link.connected = connected;
			link_changed (platform, master, TRUE);
		}
	}
}

static gboolean
link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
{
	NMFakePlatformLink *device = link_get (platform, ifindex);
	gboolean up, connected;

	if (out_no_firmware)
		*out_no_firmware = FALSE;

	if (!device) {
		_LOGE ("failure changing link: netlink error (No such device)");
		return FALSE;
	}

	up = TRUE;
	connected = TRUE;
	switch (device->link.type) {
	case NM_LINK_TYPE_DUMMY:
	case NM_LINK_TYPE_VLAN:
		break;
	case NM_LINK_TYPE_BRIDGE:
	case NM_LINK_TYPE_BOND:
	case NM_LINK_TYPE_TEAM:
		connected = FALSE;
		break;
	default:
		connected = FALSE;
		g_error ("Unexpected device type: %d", device->link.type);
	}

	if (   NM_FLAGS_HAS (device->link.n_ifi_flags, IFF_UP) != !!up
	    || device->link.connected != connected) {
		device->link.n_ifi_flags = NM_FLAGS_ASSIGN (device->link.n_ifi_flags, IFF_UP, up);
		device->link.connected = connected;
		link_changed (platform, device, TRUE);
	}

	return TRUE;
}

static gboolean
link_set_down (NMPlatform *platform, int ifindex)
{
	NMFakePlatformLink *device = link_get (platform, ifindex);

	if (!device) {
		_LOGE ("failure changing link: netlink error (No such device)");
		return FALSE;
	}

	if (NM_FLAGS_HAS (device->link.n_ifi_flags, IFF_UP) || device->link.connected) {
		device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_UP);
		device->link.connected = FALSE;

		link_changed (platform, device, TRUE);
	}

	return TRUE;
}

static gboolean
link_set_arp (NMPlatform *platform, int ifindex)
{
	NMFakePlatformLink *device = link_get (platform, ifindex);

	if (!device) {
		_LOGE ("failure changing link: netlink error (No such device)");
		return FALSE;
	}

	device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_NOARP);

	link_changed (platform, device, TRUE);

	return TRUE;
}

static gboolean
link_set_noarp (NMPlatform *platform, int ifindex)
{
	NMFakePlatformLink *device = link_get (platform, ifindex);

	if (!device) {
		_LOGE ("failure changing link: netlink error (No such device)");
		return FALSE;
	}

	device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_NOARP);

	link_changed (platform, device, TRUE);

	return TRUE;
}

static NMPlatformError
link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
{
	NMFakePlatformLink *device = link_get (platform, ifindex);

	if (   !device
	    || len == 0
	    || len > NM_UTILS_HWADDR_LEN_MAX
	    || !addr)
		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);

	if (   device->link.addr.len != len
	    || (   len > 0
	        && memcmp (device->link.addr.data, addr, len) != 0)) {
		memcpy (device->link.addr.data, addr, len);
		device->link.addr.len = len;
		link_changed (platform, link_get (platform, ifindex), TRUE);
	}

	return NM_PLATFORM_ERROR_SUCCESS;
}

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, TRUE);
	} else
		_LOGE ("failure changing link: netlink error (No such device)");

	return !!device;
}

static const char *
link_get_udi (NMPlatform *platform, int ifindex)
{
	NMFakePlatformLink *device = link_get (platform, ifindex);

	if (!device)
		return NULL;
	return device->udi;
}

static gboolean
link_get_driver_info (NMPlatform *platform,
                      int ifindex,
                      char **out_driver_name,
                      char **out_driver_version,
                      char **out_fw_version)
{
	if (out_driver_name)
		*out_driver_name = NULL;
	if (out_driver_version)
		*out_driver_version = NULL;
	if (out_fw_version)
		*out_fw_version = NULL;

	return TRUE;
}

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);
	NMFakePlatformLink *master_device = link_get (platform, master);

	g_return_val_if_fail (device, FALSE);
	g_return_val_if_fail (master_device, FALSE);

	if (device->link.master != master) {
		device->link.master = master;

		if (NM_IN_SET (master_device->link.type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) {
			device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_UP);
			device->link.connected = TRUE;
		}

		link_changed (platform, device, TRUE);
	}

	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)
		return FALSE;

	slave->link.master = 0;

	link_changed (platform, slave, TRUE);
	link_changed (platform, master, TRUE);

	return TRUE;
}

static gboolean
vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link)
{
	NMFakePlatformLink *device;

	if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0, out_link))
		return FALSE;

	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));

	g_return_val_if_fail (device, FALSE);
	g_return_val_if_fail (!device->lnk, FALSE);

	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
	device->lnk->lnk_vlan.id = vlan_id;
	device->link.parent = parent;

	if (out_link)
		*out_link = &device->link;
	return TRUE;
}

static gboolean
link_vlan_change (NMPlatform *platform,
                  int ifindex,
                  NMVlanFlags flags_mask,
                  NMVlanFlags flags_set,
                  gboolean ingress_reset_all,
                  const NMVlanQosMapping *ingress_map,
                  gsize n_ingress_map,
                  gboolean egress_reset_all,
                  const NMVlanQosMapping *egress_map,
                  gsize n_egress_map)
{
	return FALSE;
}

static gboolean
link_vxlan_add (NMPlatform *platform,
                const char *name,
                const NMPlatformLnkVxlan *props,
                const NMPlatformLink **out_link)
{
	NMFakePlatformLink *device;

	if (!link_add (platform, name, NM_LINK_TYPE_VXLAN, NULL, 0, out_link))
		return FALSE;

	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));

	g_return_val_if_fail (device, FALSE);
	g_return_val_if_fail (!device->lnk, FALSE);

	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
	device->lnk->lnk_vxlan = *props;
	device->link.parent = props->parent_ifindex;

	if (out_link)
		*out_link = &device->link;
	return TRUE;
}

static gboolean
infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link)
{
	NMFakePlatformLink *device, *parent_device;
	char name[IFNAMSIZ];

	parent_device = link_get (platform, parent);
	g_return_val_if_fail (parent_device != NULL, FALSE);

	nm_utils_new_infiniband_name (name, parent_device->link.name, p_key);

	if (!link_add (platform, name, NM_LINK_TYPE_INFINIBAND, NULL, 0, out_link))
		return FALSE;

	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
	g_return_val_if_fail (device, FALSE);
	g_return_val_if_fail (!device->lnk, FALSE);

	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
	device->lnk->lnk_infiniband.p_key = p_key;
	device->lnk->lnk_infiniband.mode = "datagram";
	device->link.parent = parent;
	return TRUE;
}

static gboolean
infiniband_partition_delete (NMPlatform *platform, int parent, int p_key)
{
	NMFakePlatformLink *parent_device;
	gs_free char *name = NULL;

	parent_device = link_get (platform, parent);
	g_return_val_if_fail (parent_device != NULL, FALSE);

	nm_utils_new_infiniband_name (name, parent_device->link.name, p_key);
	return link_delete (platform, nm_platform_link_get_ifindex (platform, name));
}

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, guint8 *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 guint8 *ssid, gsize len)
{
	return FALSE;
}

/*****************************************************************************/

static GArray *
ip4_address_get_all (NMPlatform *platform, int ifindex)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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 ((NMFakePlatform *) 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,
                 guint8 plen,
                 in_addr_t peer_addr,
                 guint32 lifetime,
                 guint32 preferred,
                 guint32 flags,
                 const char *label)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	NMPlatformIP4Address address;
	int i;

	memset (&address, 0, sizeof (address));
	address.addr_source = NM_IP_CONFIG_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.n_ifa_flags = flags;
	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);
		gboolean changed;

		if (   item->ifindex != address.ifindex
		    || item->address != address.address
		    || item->plen != address.plen
		    || !_ip4_address_equal_peer_net (item->peer_address, address.peer_address, address.plen))
			continue;

		changed = !nm_platform_ip4_address_cmp (item, &address);

		memcpy (item, &address, sizeof (address));
		if (changed)
			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_CHANGED);
		return TRUE;
	}

	g_array_append_val (priv->ip4_addresses, address);
	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_ADDED);

	return TRUE;
}

static gboolean
ip6_address_add (NMPlatform *platform,
                 int ifindex,
                 struct in6_addr addr,
                 guint8 plen,
                 struct in6_addr peer_addr,
                 guint32 lifetime,
                 guint32 preferred,
                 guint32 flags)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	NMPlatformIP6Address address;
	int i;

	memset (&address, 0, sizeof (address));
	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
	address.ifindex = ifindex;
	address.address = addr;
	address.peer_address = (IN6_IS_ADDR_UNSPECIFIED (&peer_addr) || IN6_ARE_ADDR_EQUAL (&addr, &peer_addr)) ? in6addr_any : peer_addr;
	address.plen = plen;
	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
	address.lifetime = lifetime;
	address.preferred = preferred;
	address.n_ifa_flags = flags;

	for (i = 0; i < priv->ip6_addresses->len; i++) {
		NMPlatformIP6Address *item = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
		gboolean changed;

		if (   item->ifindex != address.ifindex
		    || !IN6_ARE_ADDR_EQUAL (&item->address, &address.address))
			continue;

		changed = !nm_platform_ip6_address_cmp (item, &address);

		memcpy (item, &address, sizeof (address));
		if (changed)
			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_CHANGED);
		return TRUE;
	}

	g_array_append_val (priv->ip6_addresses, address);
	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_ADDED);

	return TRUE;
}

static gboolean
ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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
		    && ((peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (plen)) == 0) {
			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, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &deleted_address, (int) NM_PLATFORM_SIGNAL_REMOVED);
			return TRUE;
		}
	}

	return TRUE;
}

static gboolean
ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &deleted_address, (int) NM_PLATFORM_SIGNAL_REMOVED);
			return TRUE;
		}
	}

	return TRUE;
}

static const NMPlatformIP4Address *
ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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
		    && _ip4_address_equal_peer_net (address->peer_address, peer_address, plen))
			return address;
	}

	return NULL;
}

static const NMPlatformIP6Address *
ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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 address;
	}

	return NULL;
}

/*****************************************************************************/

static GArray *
ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	GArray *routes;
	NMPlatformIP4Route *route;
	guint i;

	routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Route));

	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;

	/* Fill routes */
	for (i = 0; i < priv->ip4_routes->len; i++) {
		route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
		if (route && (!ifindex || route->ifindex == ifindex)) {
			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT))
					g_array_append_val (routes, *route);
			} else {
				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
					g_array_append_val (routes, *route);
			}
		}
	}

	return routes;
}

static GArray *
ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	GArray *routes;
	NMPlatformIP6Route *route;
	guint i;

	routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route));

	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;

	/* Fill routes */
	for (i = 0; i < priv->ip6_routes->len; i++) {
		route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
		if (route && (!ifindex || route->ifindex == ifindex)) {
			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT))
					g_array_append_val (routes, *route);
			} else {
				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
					g_array_append_val (routes, *route);
			}
		}
	}

	return routes;
}

static gboolean
ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	int i;

	for (i = 0; i < priv->ip4_routes->len; i++) {
		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
		NMPlatformIP4Route deleted_route;

		if (   route->ifindex != ifindex
		    || route->network != network
		    || route->plen != plen
		    || route->metric != metric)
			continue;

		memcpy (&deleted_route, route, sizeof (deleted_route));
		g_array_remove_index (priv->ip4_routes, i);
		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &deleted_route, (int) NM_PLATFORM_SIGNAL_REMOVED);
	}

	return TRUE;
}

static gboolean
ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	int i;

	metric = nm_utils_ip6_route_metric_normalize (metric);

	for (i = 0; i < priv->ip6_routes->len; i++) {
		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
		NMPlatformIP6Route deleted_route;

		if (   route->ifindex != ifindex
		    || !IN6_ARE_ADDR_EQUAL (&route->network, &network)
		    || route->plen != plen
		    || route->metric != metric)
			continue;

		memcpy (&deleted_route, route, sizeof (deleted_route));
		g_array_remove_index (priv->ip6_routes, i);
		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &deleted_route, (int) NM_PLATFORM_SIGNAL_REMOVED);
	}

	return TRUE;
}

static gboolean
ip4_route_add (NMPlatform *platform, const NMPlatformIP4Route *route)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	NMPlatformIP4Route rt = *route;
	guint i;

	rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
	rt.network = nm_utils_ip4_address_clear_host_address (rt.network, rt.plen);
	rt.scope_inv = nm_platform_route_scope_inv (rt.gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK);

	if (rt.gateway) {
		for (i = 0; i < priv->ip4_routes->len; i++) {
			NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes,
			                                           NMPlatformIP4Route, i);
			guint32 gate = ntohl (item->network) >> (32 - item->plen);
			guint32 host = ntohl (rt.gateway) >> (32 - item->plen);

			if (rt.ifindex == item->ifindex && gate == host)
				break;
		}
		if (i == priv->ip4_routes->len) {
			nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
			             rt.ifindex, nm_utils_inet4_ntop (rt.network, NULL), rt.plen, rt.metric);
			return FALSE;
		}
	}

	for (i = 0; i < priv->ip4_routes->len; i++) {
		NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);

		if (item->network != rt.network)
			continue;
		if (item->plen != rt.plen)
			continue;
		if (item->metric != rt.metric)
			continue;

		if (item->ifindex != rt.ifindex) {
			ip4_route_delete (platform, item->ifindex, item->network, item->plen, item->metric);
			i--;
			continue;
		}

		memcpy (item, &rt, sizeof (rt));
		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ROUTE,
		                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_CHANGED);
		return TRUE;
	}

	g_array_append_val (priv->ip4_routes, rt);
	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ROUTE,
	                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_ADDED);

	return TRUE;
}

static gboolean
ip6_route_add (NMPlatform *platform, const NMPlatformIP6Route *route)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	NMPlatformIP6Route rt = *route;
	guint i;

	rt.metric = nm_utils_ip6_route_metric_normalize (rt.metric);
	rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
	nm_utils_ip6_address_clear_host_address (&rt.network, &rt.network, rt.plen);

	if (!IN6_IS_ADDR_UNSPECIFIED (&rt.gateway)) {
		for (i = 0; i < priv->ip6_routes->len; i++) {
			NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes,
			                                           NMPlatformIP6Route, i);
			guint8 gate_bits = rt.gateway.s6_addr[item->plen / 8] >> (8 - item->plen % 8);
			guint8 host_bits = item->network.s6_addr[item->plen / 8] >> (8 - item->plen % 8);

			if (   rt.ifindex == item->ifindex
			    && memcmp (&rt.gateway, &item->network, item->plen / 8) == 0
			    && gate_bits == host_bits)
				break;
		}
		if (i == priv->ip6_routes->len) {
			nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
			             rt.ifindex, nm_utils_inet6_ntop (&rt.network, NULL), rt.plen, rt.metric);
			return FALSE;
		}
	}

	for (i = 0; i < priv->ip6_routes->len; i++) {
		NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);

		if (!IN6_ARE_ADDR_EQUAL (&item->network, &rt.network))
			continue;
		if (item->plen != rt.plen)
			continue;
		if (item->metric != rt.metric)
			continue;

		if (item->ifindex != rt.ifindex) {
			ip6_route_delete (platform, item->ifindex, item->network, item->plen, item->metric);
			i--;
			continue;
		}

		memcpy (item, &rt, sizeof (rt));
		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ROUTE,
		                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_CHANGED);
		return TRUE;
	}

	g_array_append_val (priv->ip6_routes, rt);
	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ROUTE,
	                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_ADDED);

	return TRUE;
}

static const NMPlatformIP4Route *
ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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 const NMPlatformIP6Route *
ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
	int i;

	metric = nm_utils_ip6_route_metric_normalize (metric);

	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 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));
}

void
nm_fake_platform_setup (void)
{
	NMPlatform *platform;

	platform = g_object_new (NM_TYPE_FAKE_PLATFORM, NULL);

	nm_platform_setup (platform);

	/* skip zero element */
	link_add (platform, NULL, NM_LINK_TYPE_NONE, NULL, 0, NULL);

	/* add loopback interface */
	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, 0, NULL);

	/* add some ethernets */
	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
}

static void
finalize (GObject *object)
{
	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) 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_free (device->udi);
		g_clear_pointer (&device->lnk, nmp_object_unref);
	}
	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);

	object_class->finalize = finalize;

	platform_class->sysctl_set = sysctl_set;
	platform_class->sysctl_get = sysctl_get;

	platform_class->link_get = _nm_platform_link_get;
	platform_class->link_get_by_ifname = _nm_platform_link_get_by_ifname;
	platform_class->link_get_by_address = _nm_platform_link_get_by_address;
	platform_class->link_get_all = link_get_all;
	platform_class->link_add = link_add;
	platform_class->link_delete = link_delete;
	platform_class->link_get_type_name = link_get_type_name;

	platform_class->link_get_lnk = link_get_lnk;

	platform_class->link_get_udi = link_get_udi;

	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_set_address = link_set_address;
	platform_class->link_set_mtu = link_set_mtu;

	platform_class->link_get_driver_info = link_get_driver_info;

	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->vlan_add = vlan_add;
	platform_class->link_vlan_change = link_vlan_change;
	platform_class->link_vxlan_add = link_vxlan_add;

	platform_class->infiniband_partition_add = infiniband_partition_add;
	platform_class->infiniband_partition_delete = infiniband_partition_delete;

	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 = ip4_address_get;
	platform_class->ip6_address_get = ip6_address_get;
	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_route_get = ip4_route_get;
	platform_class->ip6_route_get = ip6_route_get;
	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;
}
