/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
 *
 * 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 of the License, 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) 2006 - 2012 Red Hat, Inc.
 * Copyright (C) 2006 - 2008 Novell, Inc.
 */

#include "nm-default.h"

#include <stdio.h>
#include <string.h>

#include "NetworkManagerUtils.h"
#include "nm-supplicant-interface.h"
#include "nm-supplicant-config.h"
#include "nm-core-internal.h"
#include "nm-dbus-compat.h"

#define WPAS_DBUS_IFACE_INTERFACE   WPAS_DBUS_INTERFACE ".Interface"
#define WPAS_DBUS_IFACE_BSS         WPAS_DBUS_INTERFACE ".BSS"
#define WPAS_DBUS_IFACE_NETWORK	    WPAS_DBUS_INTERFACE ".Network"
#define WPAS_ERROR_INVALID_IFACE    WPAS_DBUS_INTERFACE ".InvalidInterface"
#define WPAS_ERROR_EXISTS_ERROR     WPAS_DBUS_INTERFACE ".InterfaceExists"

G_DEFINE_TYPE (NMSupplicantInterface, nm_supplicant_interface, G_TYPE_OBJECT)

#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
                                                 NM_TYPE_SUPPLICANT_INTERFACE, \
                                                 NMSupplicantInterfacePrivate))

/* Signals */
enum {
	STATE,               /* change in the interface's state */
	REMOVED,             /* interface was removed by the supplicant */
	NEW_BSS,             /* interface saw a new access point from a scan */
	BSS_UPDATED,         /* a BSS property changed */
	BSS_REMOVED,         /* supplicant removed BSS from its scan list */
	SCAN_DONE,           /* wifi scan is complete */
	CONNECTION_ERROR,    /* an error occurred during a connection request */
	CREDENTIALS_REQUEST, /* 802.1x identity or password requested */
	LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };


/* Properties */
NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
	PROP_IFACE,
	PROP_SCANNING,
	PROP_CURRENT_BSS,
	PROP_IS_WIRELESS,
	PROP_FAST_SUPPORTED,
	PROP_AP_SUPPORT,
);

typedef struct {
	char *         dev;
	bool           is_wireless;
	bool           fast_supported;
	gboolean       has_credreq;  /* Whether querying 802.1x credentials is supported */
	NMSupplicantFeature ap_support;   /* Lightweight AP mode support */
	NMSupplicantFeature mac_randomization_support;
	guint32        max_scan_ssids;
	guint32        ready_count;

	char *         object_path;
	guint32        state;
	int            disconnect_reason;

	gboolean       scanning;

	GDBusProxy *   wpas_proxy;
	GCancellable * init_cancellable;
	GDBusProxy *   iface_proxy;
	GCancellable * other_cancellable;
	GCancellable * assoc_cancellable;
	char *         net_path;
	guint32        blobs_left;
	GHashTable *   bss_proxies;
	char *         current_bss;

	gint32         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_s() */

	NMSupplicantConfig *cfg;
} NMSupplicantInterfacePrivate;

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

#define _NMLOG_DOMAIN           LOGD_SUPPLICANT
#define _NMLOG_PREFIX_NAME      "sup-iface"
#define _NMLOG(level, ...) \
    G_STMT_START { \
         char _sbuf[64]; \
         \
         nm_log ((level), _NMLOG_DOMAIN, \
                 "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
                 _NMLOG_PREFIX_NAME, \
                 ((self) \
                      ? nm_sprintf_buf (_sbuf, \
                                        "[%p,%s]", \
                                        (self), \
                                        NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev) \
                      : "") \
                 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
    } G_STMT_END

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

static void
emit_error_helper (NMSupplicantInterface *self, GError *error)
{
	char *name = NULL;

	if (g_dbus_error_is_remote_error (error))
		name = g_dbus_error_get_remote_error (error);

	g_signal_emit (self, signals[CONNECTION_ERROR], 0, name, error->message);
	g_free (name);
}

static void
bss_props_changed_cb (GDBusProxy *proxy,
                      GVariant *changed_properties,
                      char **invalidated_properties,
                      gpointer user_data)
{
	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (priv->scanning)
		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();

	g_signal_emit (self, signals[BSS_UPDATED], 0,
	               g_dbus_proxy_get_object_path (proxy),
	               changed_properties);
}

static GVariant *
_get_bss_proxy_properties (NMSupplicantInterface *self, GDBusProxy *proxy)
{
	gs_strfreev char **properties = NULL;
	GVariantBuilder builder;
	char **iter;

	iter = properties = g_dbus_proxy_get_cached_property_names (proxy);
	if (!iter)
		return NULL;

	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
	while (*iter) {
		GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter);

		g_variant_builder_add (&builder, "{sv}", *iter++, copy);
		g_variant_unref (copy);
	}

	return g_variant_builder_end (&builder);
}

#define BSS_PROXY_INITED "bss-proxy-inited"

static void
on_bss_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	gs_free_error GError *error = NULL;
	gs_unref_variant GVariant *props = NULL;

	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
			self = NM_SUPPLICANT_INTERFACE (user_data);
			_LOGD ("failed to acquire BSS proxy: (%s)", error->message);
			g_hash_table_remove (NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->bss_proxies,
			                     g_dbus_proxy_get_object_path (proxy));
		}
		return;
	}

	self = NM_SUPPLICANT_INTERFACE (user_data);
	props = _get_bss_proxy_properties (self, proxy);
	if (!props)
		return;

	g_object_set_data (G_OBJECT (proxy), BSS_PROXY_INITED, GUINT_TO_POINTER (TRUE));

	g_signal_emit (self, signals[NEW_BSS], 0,
	               g_dbus_proxy_get_object_path (proxy),
	               g_variant_ref_sink (props));
}

static void
handle_new_bss (NMSupplicantInterface *self, const char *object_path)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
	GDBusProxy *bss_proxy;

	g_return_if_fail (object_path != NULL);

	if (g_hash_table_lookup (priv->bss_proxies, object_path))
		return;

	bss_proxy = g_object_new (G_TYPE_DBUS_PROXY,
	                          "g-bus-type", G_BUS_TYPE_SYSTEM,
	                          "g-flags", G_DBUS_PROXY_FLAGS_NONE,
	                          "g-name", WPAS_DBUS_SERVICE,
	                          "g-object-path", object_path,
	                          "g-interface-name", WPAS_DBUS_IFACE_BSS,
	                          NULL);
	g_hash_table_insert (priv->bss_proxies,
	                     (char *) g_dbus_proxy_get_object_path (bss_proxy),
	                     bss_proxy);
	g_signal_connect (bss_proxy, "g-properties-changed", G_CALLBACK (bss_props_changed_cb), self);
	g_async_initable_init_async (G_ASYNC_INITABLE (bss_proxy),
	                             G_PRIORITY_DEFAULT,
	                             priv->other_cancellable,
	                             (GAsyncReadyCallback) on_bss_proxy_acquired,
	                             self);
}

static void
set_state (NMSupplicantInterface *self, guint32 new_state)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
	guint32 old_state = priv->state;

	g_return_if_fail (new_state < NM_SUPPLICANT_INTERFACE_STATE_LAST);

	if (new_state == priv->state)
		return;

	/* DOWN is a terminal state */
	g_return_if_fail (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN);

	/* Cannot regress to READY, STARTING, or INIT from higher states */
	if (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
		g_return_if_fail (new_state > NM_SUPPLICANT_INTERFACE_STATE_READY);

	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_READY) {
		if (priv->other_cancellable) {
			g_warn_if_fail (priv->other_cancellable == NULL);
			g_cancellable_cancel (priv->other_cancellable);
			g_clear_object (&priv->other_cancellable);
		}
		priv->other_cancellable = g_cancellable_new ();
	} else if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
		if (priv->init_cancellable)
			g_cancellable_cancel (priv->init_cancellable);
		g_clear_object (&priv->init_cancellable);

		if (priv->other_cancellable)
			g_cancellable_cancel (priv->other_cancellable);
		g_clear_object (&priv->other_cancellable);

		if (priv->iface_proxy)
			g_signal_handlers_disconnect_by_data (priv->iface_proxy, self);
	}

	priv->state = new_state;

	if (   priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
	    || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();

	/* Disconnect reason is no longer relevant when not in the DISCONNECTED state */
	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED)
		priv->disconnect_reason = 0;

	g_signal_emit (self, signals[STATE], 0,
	               priv->state,
	               old_state,
	               priv->disconnect_reason);
}

static int
wpas_state_string_to_enum (const char *str_state)
{
	if (!strcmp (str_state, "interface_disabled"))
		return NM_SUPPLICANT_INTERFACE_STATE_DISABLED;
	else if (!strcmp (str_state, "disconnected"))
		return NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED;
	else if (!strcmp (str_state, "inactive"))
		return NM_SUPPLICANT_INTERFACE_STATE_INACTIVE;
	else if (!strcmp (str_state, "scanning"))
		return NM_SUPPLICANT_INTERFACE_STATE_SCANNING;
	else if (!strcmp (str_state, "authenticating"))
		return NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING;
	else if (!strcmp (str_state, "associating"))
		return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING;
	else if (!strcmp (str_state, "associated"))
		return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED;
	else if (!strcmp (str_state, "4way_handshake"))
		return NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE;
	else if (!strcmp (str_state, "group_handshake"))
		return NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE;
	else if (!strcmp (str_state, "completed"))
		return NM_SUPPLICANT_INTERFACE_STATE_COMPLETED;

	return -1;
}

static void
set_state_from_string (NMSupplicantInterface *self, const char *new_state)
{
	int state;

	state = wpas_state_string_to_enum (new_state);
	if (state == -1) {
		_LOGW ("unknown supplicant state '%s'", new_state);
		return;
	}
	set_state (self, (guint32) state);
}

static void
set_scanning (NMSupplicantInterface *self, gboolean new_scanning)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (priv->scanning != new_scanning) {
		priv->scanning = new_scanning;

		/* Cache time of last scan completion */
		if (priv->scanning == FALSE)
			priv->last_scan = nm_utils_get_monotonic_timestamp_s ();

		_notify (self, PROP_SCANNING);
	}
}

gboolean
nm_supplicant_interface_get_scanning (NMSupplicantInterface *self)
{
	NMSupplicantInterfacePrivate *priv;

	g_return_val_if_fail (self != NULL, FALSE);

	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
	if (priv->scanning)
		return TRUE;
	if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
		return TRUE;
	return FALSE;
}

const char *
nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self)
{
	NMSupplicantInterfacePrivate *priv;

	g_return_val_if_fail (self != NULL, FALSE);

	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
	return priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY ? priv->current_bss : NULL;
}

gint32
nm_supplicant_interface_get_last_scan_time (NMSupplicantInterface *self)
{
	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->last_scan;
}

#define MATCH_PROPERTY(p, n, v, t) (!strcmp (p, n) && g_variant_is_of_type (v, t))

static void
parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
	gboolean have_active = FALSE, have_ssid = FALSE;
	gint32 max_scan_ssids = -1;
	const char **array;

	g_return_if_fail (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));

	if (g_variant_lookup (capabilities, "Scan", "^a&s", &array)) {
		if (_nm_utils_string_in_list ("active", array))
			have_active = TRUE;
		if (_nm_utils_string_in_list ("ssid", array))
			have_ssid = TRUE;
		g_free (array);
	}

	if (g_variant_lookup (capabilities, "MaxScanSSID", "i", &max_scan_ssids)) {
		/* We need active scan and SSID probe capabilities to care about MaxScanSSIDs */
		if (max_scan_ssids > 0 && have_active && have_ssid) {
			/* wpa_supplicant's WPAS_MAX_SCAN_SSIDS value is 16, but for speed
			 * and to ensure we don't disclose too many SSIDs from the hidden
			 * list, we'll limit to 5.
			 */
			priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, 5);
			_LOGI ("supports %d scan SSIDs", priv->max_scan_ssids);
		}
	}
}

static void
iface_check_ready (NMSupplicantInterface *self)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (priv->ready_count && priv->state < NM_SUPPLICANT_INTERFACE_STATE_READY) {
		priv->ready_count--;
		if (priv->ready_count == 0)
			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_READY);
	}
}

gboolean
nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
                                           const char *field,
                                           const char *value,
                                           GError **error)
{
	NMSupplicantInterfacePrivate *priv;
	gs_unref_variant GVariant *reply = NULL;

	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
	g_return_val_if_fail (field != NULL, FALSE);
	g_return_val_if_fail (value != NULL, FALSE);

	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
	g_return_val_if_fail (priv->has_credreq == TRUE, FALSE);

	/* Need a network block object path */
	g_return_val_if_fail (priv->net_path, FALSE);
	reply = g_dbus_proxy_call_sync (priv->iface_proxy,
	                                "NetworkReply",
	                                g_variant_new ("(oss)",
	                                               priv->net_path,
	                                               field,
	                                               value),
	                                G_DBUS_CALL_FLAGS_NONE,
	                                5000,
	                                NULL,
	                                error);
	if (error && *error)
		g_dbus_error_strip_remote_error (*error);

	return !!reply;
}

static void
iface_check_netreply_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_unref_variant GVariant *variant = NULL;
	gs_free_error GError *error = NULL;

	/* We know NetworkReply is supported if the NetworkReply method returned
	 * successfully (which is unexpected since we sent a bogus network
	 * object path) or if we got an "InvalidArgs" (which indicates NetworkReply
	 * is supported).  We know it's not supported if we get an
	 * "UnknownMethod" error.
	 */

	variant = g_dbus_proxy_call_finish (proxy, result, &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (variant || _nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.InvalidArgs"))
		priv->has_credreq = TRUE;

	_LOGD ("supplicant %s network credentials requests",
	       priv->has_credreq ? "supports" : "does not support");

	iface_check_ready (self);
}

NMSupplicantFeature
nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self)
{
	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ap_support;
}

void
nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                        NMSupplicantFeature ap_support)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	/* Use the best indicator of support between the supplicant global
	 * Capabilities property and the interface's introspection data.
	 */
	if (ap_support > priv->ap_support)
		priv->ap_support = ap_support;
}

NMSupplicantFeature
nm_supplicant_interface_get_mac_randomization_support (NMSupplicantInterface *self)
{
	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->mac_randomization_support;
}

static void
set_preassoc_scan_mac_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	gs_unref_variant GVariant *variant = NULL;
	gs_free_error GError *error = NULL;

	variant = _nm_dbus_proxy_call_finish (proxy, result,
	                                      G_VARIANT_TYPE ("()"),
	                                      &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	if (error)
		_LOGW ("failed to enable scan MAC address randomization (%s)", error->message);
	iface_check_ready (self);
}

static void
iface_introspect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_unref_variant GVariant *variant = NULL;
	gs_free_error GError *error = NULL;
	const char *data;

	variant = _nm_dbus_proxy_call_finish (proxy, result,
	                                      G_VARIANT_TYPE ("(s)"),
	                                      &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (variant) {
		g_variant_get (variant, "(&s)", &data);

		/* The ProbeRequest method only exists if AP mode has been enabled */
		if (strstr (data, "ProbeRequest"))
			priv->ap_support = NM_SUPPLICANT_FEATURE_YES;

		if (strstr (data, "PreassocMacAddr")) {
			priv->mac_randomization_support = NM_SUPPLICANT_FEATURE_YES;

			/* Turn on MAC randomization during scans by default */
			priv->ready_count++;
			g_dbus_proxy_call (priv->iface_proxy,
			                   DBUS_INTERFACE_PROPERTIES ".Set",
			                   g_variant_new ("(ssv)",
			                                  WPAS_DBUS_IFACE_INTERFACE,
			                                  "PreassocMacAddr",
			                                  g_variant_new_string ("1")),
			                   G_DBUS_CALL_FLAGS_NONE,
			                   -1,
			                   priv->init_cancellable,
			                   (GAsyncReadyCallback) set_preassoc_scan_mac_cb,
			                   self);
		}
	}

	iface_check_ready (self);
}

static void
wpas_iface_scan_done (GDBusProxy *proxy,
                      gboolean success,
                      gpointer user_data)
{
	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
	GVariant *props;
	GHashTableIter iter;
	char *bss_path;
	GDBusProxy *bss_proxy;

	/* Cache last scan completed time */
	priv->last_scan = nm_utils_get_monotonic_timestamp_s ();

	g_signal_emit (self, signals[SCAN_DONE], 0, success);

	/* Emit NEW_BSS so that wifi device has the APs (in case it removed them) */
	g_hash_table_iter_init (&iter, priv->bss_proxies);
	while (g_hash_table_iter_next (&iter, (gpointer) &bss_path, (gpointer) &bss_proxy)) {
		if (g_object_get_data (G_OBJECT (bss_proxy), BSS_PROXY_INITED)) {
			props = _get_bss_proxy_properties (self, bss_proxy);
			if (props) {
				g_signal_emit (self, signals[NEW_BSS], 0,
				               bss_path,
				               g_variant_ref_sink (props));
				g_variant_unref (props);
			}
		}
	}
}

static void
wpas_iface_bss_added (GDBusProxy *proxy,
                      const char *path,
                      GVariant *props,
                      gpointer user_data)
{
	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (priv->scanning)
		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();

	handle_new_bss (self, path);
}

static void
wpas_iface_bss_removed (GDBusProxy *proxy,
                        const char *path,
                        gpointer user_data)
{
	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	g_signal_emit (self, signals[BSS_REMOVED], 0, path);
	g_hash_table_remove (priv->bss_proxies, path);
}

static void
wpas_iface_network_request (GDBusProxy *proxy,
                            const char *path,
                            const char *field,
                            const char *message,
                            gpointer user_data)
{
	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (priv->has_credreq && priv->net_path && !g_strcmp0 (path, priv->net_path))
		g_signal_emit (self, signals[CREDENTIALS_REQUEST], 0, field, message);
}

static void
props_changed_cb (GDBusProxy *proxy,
                  GVariant *changed_properties,
                  GStrv invalidated_properties,
                  gpointer user_data)
{
	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
	const char *s, **array, **iter;
	gboolean b = FALSE;
	gint32 i32;
	GVariant *v;

	g_object_freeze_notify (G_OBJECT (self));

	if (g_variant_lookup (changed_properties, "Scanning", "b", &b))
		set_scanning (self, b);

	if (   g_variant_lookup (changed_properties, "State", "&s", &s)
	    && priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY) {
		/* Only transition to actual wpa_supplicant interface states (ie,
		 * anything > READY) after the NMSupplicantInterface has had a
		 * chance to initialize, which is signalled by entering the READY
		 * state.
		 */
		set_state_from_string (self, s);
	}

	if (g_variant_lookup (changed_properties, "BSSs", "^a&o", &array)) {
		iter = array;
		while (*iter)
			handle_new_bss (self, *iter++);
		g_free (array);
	}

	if (g_variant_lookup (changed_properties, "CurrentBSS", "&o", &s)) {
		if (strcmp (s, "/") == 0)
			s = NULL;
		if (g_strcmp0 (s, priv->current_bss) != 0) {
			g_free (priv->current_bss);
			priv->current_bss = g_strdup (s);
			_notify (self, PROP_CURRENT_BSS);
		}
	}

	v = g_variant_lookup_value (changed_properties, "Capabilities", G_VARIANT_TYPE_VARDICT);
	if (v) {
		parse_capabilities (self, v);
		g_variant_unref (v);
	}

	if (g_variant_lookup (changed_properties, "DisconnectReason", "i", &i32)) {
		/* Disconnect reason is currently only given for deauthentication events,
		 * not disassociation; currently they are IEEE 802.11 "reason codes",
		 * defined by (IEEE 802.11-2007, 7.3.1.7, Table 7-22).  Any locally caused
		 * deauthentication will be negative, while authentications caused by the
		 * AP will be positive.
		 */
		priv->disconnect_reason = i32;
		if (priv->disconnect_reason != 0)
			_LOGW ("connection disconnected (reason %d)", priv->disconnect_reason);
	}

	g_object_thaw_notify (G_OBJECT (self));
}

static void
on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_free_error GError *error = NULL;

	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
			self = NM_SUPPLICANT_INTERFACE (user_data);
			_LOGW ("failed to acquire wpa_supplicant interface proxy: (%s)", error->message);
			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
		}
		return;
	}

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	_nm_dbus_signal_connect (priv->iface_proxy, "ScanDone", G_VARIANT_TYPE ("(b)"),
	                         G_CALLBACK (wpas_iface_scan_done), self);
	_nm_dbus_signal_connect (priv->iface_proxy, "BSSAdded", G_VARIANT_TYPE ("(oa{sv})"),
	                         G_CALLBACK (wpas_iface_bss_added), self);
	_nm_dbus_signal_connect (priv->iface_proxy, "BSSRemoved", G_VARIANT_TYPE ("(o)"),
	                         G_CALLBACK (wpas_iface_bss_removed), self);
	_nm_dbus_signal_connect (priv->iface_proxy, "NetworkRequest", G_VARIANT_TYPE ("(oss)"),
	                         G_CALLBACK (wpas_iface_network_request), self);

	/* Scan result aging parameters */
	g_dbus_proxy_call (priv->iface_proxy,
	                   "org.freedesktop.DBus.Properties.Set",
	                   g_variant_new ("(ssv)",
	                                  WPAS_DBUS_IFACE_INTERFACE,
	                                  "BSSExpireAge",
	                                  g_variant_new_uint32 (250)),
	                   G_DBUS_CALL_FLAGS_NONE,
	                   -1,
	                   priv->init_cancellable,
	                   NULL,
	                   NULL);
	g_dbus_proxy_call (priv->iface_proxy,
	                   "org.freedesktop.DBus.Properties.Set",
	                   g_variant_new ("(ssv)",
	                                  WPAS_DBUS_IFACE_INTERFACE,
	                                  "BSSExpireCount",
	                                  g_variant_new_uint32 (2)),
	                   G_DBUS_CALL_FLAGS_NONE,
	                   -1,
	                   priv->init_cancellable,
	                   NULL,
	                   NULL);

	/* Check whether NetworkReply and AP mode are supported */
	priv->ready_count = 1;
	g_dbus_proxy_call (priv->iface_proxy,
	                   "NetworkReply",
	                   g_variant_new ("(oss)",
	                                  "/fff",
	                                  "foobar",
	                                  "foobar"),
	                   G_DBUS_CALL_FLAGS_NONE,
	                   -1,
	                   priv->init_cancellable,
	                   (GAsyncReadyCallback) iface_check_netreply_cb,
	                   self);

	if (priv->ap_support == NM_SUPPLICANT_FEATURE_UNKNOWN ||
	    priv->mac_randomization_support == NM_SUPPLICANT_FEATURE_UNKNOWN) {
		/* If the global supplicant capabilities property is not present, we can
		 * fall back to checking whether the ProbeRequest method is supported.  If
		 * neither of these works we have no way of determining if AP mode is
		 * supported or not.  hostap 1.0 and earlier don't support either of these.
		 */
		priv->ready_count++;
		g_dbus_proxy_call (priv->iface_proxy,
		                   DBUS_INTERFACE_INTROSPECTABLE ".Introspect",
		                   NULL,
		                   G_DBUS_CALL_FLAGS_NONE,
		                   -1,
		                   priv->init_cancellable,
		                   (GAsyncReadyCallback) iface_introspect_cb,
		                   self);
	}
}

static void
interface_add_done (NMSupplicantInterface *self, const char *path)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	_LOGD ("interface added to supplicant");

	priv->object_path = g_strdup (path);
	priv->iface_proxy = g_object_new (G_TYPE_DBUS_PROXY,
	                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
	                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
	                                  "g-name", WPAS_DBUS_SERVICE,
	                                  "g-object-path", priv->object_path,
	                                  "g-interface-name", WPAS_DBUS_IFACE_INTERFACE,
	                                  NULL);
	g_signal_connect (priv->iface_proxy, "g-properties-changed", G_CALLBACK (props_changed_cb), self);
	g_async_initable_init_async (G_ASYNC_INITABLE (priv->iface_proxy),
	                             G_PRIORITY_DEFAULT,
	                             priv->init_cancellable,
	                             (GAsyncReadyCallback) on_iface_proxy_acquired,
	                             self);
}

static void
interface_get_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_unref_variant GVariant *variant = NULL;
	gs_free_error GError *error = NULL;
	const char *path;

	variant = _nm_dbus_proxy_call_finish (proxy, result,
	                                      G_VARIANT_TYPE ("(o)"),
	                                      &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (variant) {
		g_variant_get (variant, "(&o)", &path);
		interface_add_done (self, path);
	} else {
		g_dbus_error_strip_remote_error (error);
		_LOGE ("error getting interface: %s", error->message);
		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
	}
}

static void
interface_add_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_free_error GError *error = NULL;
	gs_unref_variant GVariant *variant = NULL;
	const char *path;

	variant = _nm_dbus_proxy_call_finish (proxy, result,
	                                      G_VARIANT_TYPE ("(o)"),
	                                      &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (variant) {
		g_variant_get (variant, "(&o)", &path);
		interface_add_done (self, path);
	} else if (_nm_dbus_error_has_name (error, WPAS_ERROR_EXISTS_ERROR)) {
		/* Interface already added, just get its object path */
		g_dbus_proxy_call (priv->wpas_proxy,
		                   "GetInterface",
		                   g_variant_new ("(s)", priv->dev),
		                   G_DBUS_CALL_FLAGS_NONE,
		                   -1,
		                   priv->init_cancellable,
		                   (GAsyncReadyCallback) interface_get_cb,
		                   self);
	} else if (   g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED)
	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED)
	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED)
	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT)
	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)
	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMED_OUT)
	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
		/* Supplicant wasn't running and could not be launched via service
		 * activation.  Wait for it to start by moving back to the INIT
		 * state.
		 */
		g_dbus_error_strip_remote_error (error);
		_LOGD ("failed to activate supplicant: %s", error->message);
		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_INIT);
	} else {
		g_dbus_error_strip_remote_error (error);
		_LOGE ("error adding interface: %s", error->message);
		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
	}
}

#if HAVE_WEXT
#define DEFAULT_WIFI_DRIVER "nl80211,wext"
#else
#define DEFAULT_WIFI_DRIVER "nl80211"
#endif

static void
on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_free_error GError *error = NULL;
	GDBusProxy *wpas_proxy;
	GVariantBuilder props;

	wpas_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
	if (!wpas_proxy) {
		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
			self = NM_SUPPLICANT_INTERFACE (user_data);
			_LOGW ("failed to acquire wpa_supplicant proxy: (%s)", error->message);
			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
		}
		return;
	}

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	priv->wpas_proxy = wpas_proxy;

	/* Try to add the interface to the supplicant.  If the supplicant isn't
	 * running, this will start it via D-Bus activation and return the response
	 * when the supplicant has started.
	 */

	g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT);
	g_variant_builder_add (&props, "{sv}",
	                       "Driver",
	                       g_variant_new_string (priv->is_wireless ? DEFAULT_WIFI_DRIVER : "wired"));
	g_variant_builder_add (&props, "{sv}",
	                       "Ifname",
	                       g_variant_new_string (priv->dev));

	g_dbus_proxy_call (priv->wpas_proxy,
	                   "CreateInterface",
	                   g_variant_new ("(a{sv})", &props),
	                   G_DBUS_CALL_FLAGS_NONE,
	                   -1,
	                   priv->init_cancellable,
	                   (GAsyncReadyCallback) interface_add_cb,
	                   self);
}

static void
interface_add (NMSupplicantInterface *self)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	/* Can only start the interface from INIT state */
	g_return_if_fail (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT);

	_LOGD ("adding interface to supplicant");

	/* Move to starting to prevent double-calls of interface_add() */
	set_state (self, NM_SUPPLICANT_INTERFACE_STATE_STARTING);

	g_warn_if_fail (priv->init_cancellable == NULL);
	g_clear_object (&priv->init_cancellable);
	priv->init_cancellable = g_cancellable_new ();

	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
	                            G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
	                          NULL,
	                          WPAS_DBUS_SERVICE,
	                          WPAS_DBUS_PATH,
	                          WPAS_DBUS_INTERFACE,
	                          priv->init_cancellable,
	                          (GAsyncReadyCallback) on_wpas_proxy_acquired,
	                          self);
}

void
nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
                                                  gboolean available)
{
	NMSupplicantInterfacePrivate *priv;

	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));

	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (available) {
		/* This can happen if the supplicant couldn't be activated but
		 * for some reason was started after the activation failure.
		 */
		if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT)
			interface_add (self);
	} else {
		/* The supplicant stopped; so we must tear down the interface */
		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
	}
}

static void
log_result_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	gs_unref_variant GVariant *reply = NULL;
	gs_free_error GError *error = NULL;

	reply = g_dbus_proxy_call_finish (proxy, result, &error);
	if (   !reply
	    && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
	    && !strstr (error->message, "fi.w1.wpa_supplicant1.NotConnected")) {
		g_dbus_error_strip_remote_error (error);
		nm_log_warn (_NMLOG_DOMAIN, "%s: failed to %s: %s",
		             _NMLOG_PREFIX_NAME, (const char *) user_data, error->message);
	}
}

void
nm_supplicant_interface_disconnect (NMSupplicantInterface * self)
{
	NMSupplicantInterfacePrivate *priv;

	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));

	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	/* Cancel all pending calls related to a prior connection attempt */
	if (priv->assoc_cancellable) {
		g_cancellable_cancel (priv->assoc_cancellable);
		g_clear_object (&priv->assoc_cancellable);
	}

	/* Don't do anything if there is no connection to the supplicant yet. */
	if (!priv->iface_proxy)
		return;

	/* Disconnect from the current AP */
	if (   (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
	    && (priv->state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)) {
		g_dbus_proxy_call (priv->iface_proxy,
		                   "Disconnect",
		                   NULL,
		                   G_DBUS_CALL_FLAGS_NONE,
		                   -1,
		                   NULL,
		                   (GAsyncReadyCallback) log_result_cb,
		                   "disconnect");
	}

	/* Remove any network that was added by NetworkManager */
	if (priv->net_path) {
		g_dbus_proxy_call (priv->iface_proxy,
		                   "RemoveNetwork",
		                   g_variant_new ("(o)", priv->net_path),
		                   G_DBUS_CALL_FLAGS_NONE,
		                   -1,
		                   priv->other_cancellable,
		                   (GAsyncReadyCallback) log_result_cb,
		                   "remove network");
		g_free (priv->net_path);
		priv->net_path = NULL;
	}
}

static void
select_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	gs_unref_variant GVariant *reply = NULL;
	gs_free_error GError *error = NULL;

	reply = g_dbus_proxy_call_finish (proxy, result, &error);
	if (   !reply
	    && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
		self = NM_SUPPLICANT_INTERFACE (user_data);
		g_dbus_error_strip_remote_error (error);
		_LOGW ("couldn't select network config: %s", error->message);
		emit_error_helper (self, error);
	}
}

static void
call_select_network (NMSupplicantInterface *self)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	/* We only select the network after all blobs (if any) have been set */
	if (priv->blobs_left == 0) {
		g_dbus_proxy_call (priv->iface_proxy,
		                   "SelectNetwork",
		                   g_variant_new ("(o)", priv->net_path),
		                   G_DBUS_CALL_FLAGS_NONE,
		                   -1,
		                   priv->assoc_cancellable,
		                   (GAsyncReadyCallback) select_network_cb,
		                   self);
	}
}

static void
add_blob_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_unref_variant GVariant *reply = NULL;
	gs_free_error GError *error = NULL;

	reply = g_dbus_proxy_call_finish (proxy, result, &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	priv->blobs_left--;
	if (reply)
		call_select_network (self);
	else {
		g_dbus_error_strip_remote_error (error);
		_LOGW ("couldn't set network certificates: %s", error->message);
		emit_error_helper (self, error);
	}
}

static void
add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_unref_variant GVariant *reply = NULL;
	gs_free_error GError *error = NULL;
	GHashTable *blobs;
	GHashTableIter iter;
	const char *blob_name;
	GByteArray *blob_data;

	reply = _nm_dbus_proxy_call_finish (proxy, result,
	                                    G_VARIANT_TYPE ("(o)"),
	                                    &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	g_free (priv->net_path);
	priv->net_path = NULL;

	if (error) {
		g_dbus_error_strip_remote_error (error);
		_LOGW ("adding network to supplicant failed: %s", error->message);
		emit_error_helper (self, error);
		return;
	}

	g_variant_get (reply, "(o)", &priv->net_path);

	/* Send blobs first; otherwise jump to selecting the network */
	blobs = nm_supplicant_config_get_blobs (priv->cfg);
	priv->blobs_left = g_hash_table_size (blobs);

	g_hash_table_iter_init (&iter, blobs);
	while (g_hash_table_iter_next (&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
		g_dbus_proxy_call (priv->iface_proxy,
		                   "AddBlob",
		                   g_variant_new ("(s@ay)",
		                                  blob_name,
		                                  g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
		                                                             blob_data->data, blob_data->len, 1)),
		                   G_DBUS_CALL_FLAGS_NONE,
		                   -1,
		                   priv->assoc_cancellable,
		                   (GAsyncReadyCallback) add_blob_cb,
		                   self);
	}

	call_select_network (self);
}

static void
add_network (NMSupplicantInterface *self)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	g_dbus_proxy_call (priv->iface_proxy,
	                   "AddNetwork",
	                   g_variant_new ("(@a{sv})", nm_supplicant_config_to_variant (priv->cfg)),
	                   G_DBUS_CALL_FLAGS_NONE,
	                   -1,
	                   priv->assoc_cancellable,
	                   (GAsyncReadyCallback) add_network_cb,
	                   self);
}

static void
set_mac_randomization_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_unref_variant GVariant *reply = NULL;
	gs_free_error GError *error = NULL;

	reply = g_dbus_proxy_call_finish (proxy, result, &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (!reply) {
		g_dbus_error_strip_remote_error (error);
		_LOGW ("couldn't send MAC randomization mode to the supplicant interface: %s",
		       error->message);
		emit_error_helper (self, error);
		return;
	}

	_LOGI ("config: set MAC randomization to %s",
	       nm_supplicant_config_get_mac_randomization (priv->cfg));

	add_network (self);
}

static void
set_ap_scan_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	NMSupplicantInterfacePrivate *priv;
	gs_unref_variant GVariant *reply = NULL;
	gs_free_error GError *error = NULL;

	reply = g_dbus_proxy_call_finish (proxy, result, &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);
	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	if (!reply) {
		g_dbus_error_strip_remote_error (error);
		_LOGW ("couldn't send AP scan mode to the supplicant interface: %s",
		       error->message);
		emit_error_helper (self, error);
		return;
	}

	_LOGI ("config: set interface ap_scan to %d",
	       nm_supplicant_config_get_ap_scan (priv->cfg));

	if (priv->mac_randomization_support == NM_SUPPLICANT_FEATURE_YES) {
		const char *mac_randomization = nm_supplicant_config_get_mac_randomization (priv->cfg);

		/* Enable/disable association MAC address randomization */
		g_dbus_proxy_call (priv->iface_proxy,
		                   DBUS_INTERFACE_PROPERTIES ".Set",
		                   g_variant_new ("(ssv)",
		                                  WPAS_DBUS_IFACE_INTERFACE,
		                                  "MacAddr",
		                                  g_variant_new_string (mac_randomization)),
		                   G_DBUS_CALL_FLAGS_NONE,
		                   -1,
		                   priv->assoc_cancellable,
		                   (GAsyncReadyCallback) set_mac_randomization_cb,
		                   self);
	} else {
		add_network (self);
	}
}

gboolean
nm_supplicant_interface_set_config (NMSupplicantInterface *self,
                                    NMSupplicantConfig *cfg,
                                    GError **error)
{
	NMSupplicantInterfacePrivate *priv;

	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);

	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	nm_supplicant_interface_disconnect (self);

	/* Make sure the supplicant supports EAP-FAST before trying to send
	 * it an EAP-FAST configuration.
	 */
	if (nm_supplicant_config_fast_required (cfg) && !priv->fast_supported) {
		g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
		             "EAP-FAST is not supported by the supplicant");
		return FALSE;
	}

	g_clear_object (&priv->cfg);
	if (cfg) {
		priv->cfg = g_object_ref (cfg);
		g_dbus_proxy_call (priv->iface_proxy,
		                   DBUS_INTERFACE_PROPERTIES ".Set",
		                   g_variant_new ("(ssv)",
		                                  WPAS_DBUS_IFACE_INTERFACE,
		                                  "ApScan",
		                                  g_variant_new_uint32 (nm_supplicant_config_get_ap_scan (priv->cfg))),
		                   G_DBUS_CALL_FLAGS_NONE,
		                   -1,
		                   priv->assoc_cancellable,
		                   (GAsyncReadyCallback) set_ap_scan_cb,
		                   self);
	}
	return TRUE;
}

static void
scan_request_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
	NMSupplicantInterface *self;
	gs_unref_variant GVariant *reply = NULL;
	gs_free_error GError *error = NULL;

	reply = g_dbus_proxy_call_finish (proxy, result, &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = NM_SUPPLICANT_INTERFACE (user_data);

	if (error) {
		if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError"))
			_LOGD ("could not get scan request result: %s", error->message);
		else {
			g_dbus_error_strip_remote_error (error);
			_LOGW ("could not get scan request result: %s", error->message);
		}
	}
	g_signal_emit (self, signals[SCAN_DONE], 0, error ? FALSE : TRUE);
}

gboolean
nm_supplicant_interface_request_scan (NMSupplicantInterface *self, const GPtrArray *ssids)
{
	NMSupplicantInterfacePrivate *priv;
	GVariantBuilder builder;
	guint i;

	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);

	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	/* Scan parameters */
	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
	g_variant_builder_add (&builder, "{sv}", "Type", g_variant_new_string ("active"));
	if (ssids) {
		GVariantBuilder ssids_builder;

		g_variant_builder_init (&ssids_builder, G_VARIANT_TYPE_BYTESTRING_ARRAY);
		for (i = 0; i < ssids->len; i++) {
			GByteArray *ssid = g_ptr_array_index (ssids, i);
			g_variant_builder_add (&ssids_builder, "@ay",
			                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
			                                                  ssid->data, ssid->len, 1));
		}
		g_variant_builder_add (&builder, "{sv}", "SSIDs", g_variant_builder_end (&ssids_builder));
	}

	g_dbus_proxy_call (priv->iface_proxy,
	                   "Scan",
	                   g_variant_new ("(a{sv})", &builder),
	                   G_DBUS_CALL_FLAGS_NONE,
	                   -1,
	                   priv->other_cancellable,
	                   (GAsyncReadyCallback) scan_request_cb,
	                   self);
	return TRUE;
}

guint32
nm_supplicant_interface_get_state (NMSupplicantInterface * self)
{
	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NM_SUPPLICANT_INTERFACE_STATE_DOWN);

	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->state;
}

const char *
nm_supplicant_interface_state_to_string (guint32 state)
{
	switch (state) {
	case NM_SUPPLICANT_INTERFACE_STATE_INIT:
		return "init";
	case NM_SUPPLICANT_INTERFACE_STATE_STARTING:
		return "starting";
	case NM_SUPPLICANT_INTERFACE_STATE_READY:
		return "ready";
	case NM_SUPPLICANT_INTERFACE_STATE_DISABLED:
		return "disabled";
	case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED:
		return "disconnected";
	case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE:
		return "inactive";
	case NM_SUPPLICANT_INTERFACE_STATE_SCANNING:
		return "scanning";
	case NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING:
		return "authenticating";
	case NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING:
		return "associating";
	case NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED:
		return "associated";
	case NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE:
		return "4-way handshake";
	case NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE:
		return "group handshake";
	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
		return "completed";
	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
		return "down";
	default:
		break;
	}
	return "unknown";
}

const char *
nm_supplicant_interface_get_object_path (NMSupplicantInterface *self)
{
	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);

	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->object_path;
}

const char *
nm_supplicant_interface_get_ifname (NMSupplicantInterface *self)
{
	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);

	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev;
}

guint
nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self)
{
	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), 0);

	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->max_scan_ssids;
}

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

NMSupplicantInterface *
nm_supplicant_interface_new (const char *ifname,
                             gboolean is_wireless,
                             gboolean fast_supported,
                             NMSupplicantFeature ap_support)
{
	g_return_val_if_fail (ifname != NULL, NULL);

	return g_object_new (NM_TYPE_SUPPLICANT_INTERFACE,
	                     NM_SUPPLICANT_INTERFACE_IFACE, ifname,
	                     NM_SUPPLICANT_INTERFACE_IS_WIRELESS, is_wireless,
	                     NM_SUPPLICANT_INTERFACE_FAST_SUPPORTED, fast_supported,
	                     NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support,
	                     NULL);
}

static void
nm_supplicant_interface_init (NMSupplicantInterface * self)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);

	priv->state = NM_SUPPLICANT_INTERFACE_STATE_INIT;
	priv->bss_proxies = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
}

static void
set_property (GObject *object,
              guint prop_id,
              const GValue *value,
              GParamSpec *pspec)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_IFACE:
		/* construct-only */
		priv->dev = g_value_dup_string (value);
		g_return_if_fail (priv->dev);
		break;
	case PROP_IS_WIRELESS:
		/* construct-only */
		priv->is_wireless = g_value_get_boolean (value);
		break;
	case PROP_FAST_SUPPORTED:
		/* construct-only */
		priv->fast_supported = g_value_get_boolean (value);
		break;
	case PROP_AP_SUPPORT:
		/* construct-only */
		priv->ap_support = g_value_get_int (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
get_property (GObject *object,
              guint prop_id,
              GValue *value,
              GParamSpec *pspec)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_SCANNING:
		g_value_set_boolean (value, priv->scanning);
		break;
	case PROP_CURRENT_BSS:
		g_value_set_string (value, priv->current_bss);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
dispose (GObject *object)
{
	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (object);

	if (priv->iface_proxy)
		g_signal_handlers_disconnect_by_data (priv->iface_proxy, NM_SUPPLICANT_INTERFACE (object));
	g_clear_object (&priv->iface_proxy);

	if (priv->init_cancellable)
		g_cancellable_cancel (priv->init_cancellable);
	g_clear_object (&priv->init_cancellable);

	if (priv->other_cancellable)
		g_cancellable_cancel (priv->other_cancellable);
	g_clear_object (&priv->other_cancellable);

	g_clear_object (&priv->wpas_proxy);
	g_clear_pointer (&priv->bss_proxies, (GDestroyNotify) g_hash_table_destroy);

	g_clear_pointer (&priv->net_path, g_free);
	g_clear_pointer (&priv->dev, g_free);
	g_clear_pointer (&priv->object_path, g_free);
	g_clear_pointer (&priv->current_bss, g_free);

	g_clear_object (&priv->cfg);

	/* Chain up to the parent class */
	G_OBJECT_CLASS (nm_supplicant_interface_parent_class)->dispose (object);
}

static void
nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	g_type_class_add_private (object_class, sizeof (NMSupplicantInterfacePrivate));

	object_class->dispose = dispose;
	object_class->set_property = set_property;
	object_class->get_property = get_property;

	/* Properties */
	obj_properties[PROP_SCANNING] =
	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_SCANNING, "", "",
	                          FALSE,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_CURRENT_BSS] =
	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_CURRENT_BSS, "", "",
	                         NULL,
	                         G_PARAM_READABLE |
	                         G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_IFACE] =
	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_IFACE, "", "",
	                         NULL,
	                         G_PARAM_WRITABLE |
	                         G_PARAM_CONSTRUCT_ONLY |
	                         G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_IS_WIRELESS] =
	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_IS_WIRELESS, "", "",
	                          TRUE,
	                          G_PARAM_WRITABLE |
	                          G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_FAST_SUPPORTED] =
	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_FAST_SUPPORTED, "", "",
	                          TRUE,
	                          G_PARAM_WRITABLE |
	                          G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_AP_SUPPORT] =
	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_AP_SUPPORT, "", "",
	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
	                      NM_SUPPLICANT_FEATURE_YES,
	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
	                      G_PARAM_WRITABLE |
	                      G_PARAM_CONSTRUCT_ONLY |
	                      G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);

	/* Signals */
	signals[STATE] =
		g_signal_new (NM_SUPPLICANT_INTERFACE_STATE,
		              G_OBJECT_CLASS_TYPE (object_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (NMSupplicantInterfaceClass, state),
		              NULL, NULL, NULL,
		              G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT);

	signals[REMOVED] =
		g_signal_new (NM_SUPPLICANT_INTERFACE_REMOVED,
		              G_OBJECT_CLASS_TYPE (object_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (NMSupplicantInterfaceClass, removed),
		              NULL, NULL, NULL,
		              G_TYPE_NONE, 0);

	signals[NEW_BSS] =
		g_signal_new (NM_SUPPLICANT_INTERFACE_NEW_BSS,
		              G_OBJECT_CLASS_TYPE (object_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (NMSupplicantInterfaceClass, new_bss),
		              NULL, NULL, NULL,
		              G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);

	signals[BSS_UPDATED] =
		g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_UPDATED,
		              G_OBJECT_CLASS_TYPE (object_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (NMSupplicantInterfaceClass, bss_updated),
		              NULL, NULL, NULL,
		              G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);

	signals[BSS_REMOVED] =
		g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_REMOVED,
		              G_OBJECT_CLASS_TYPE (object_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (NMSupplicantInterfaceClass, bss_removed),
		              NULL, NULL, NULL,
		              G_TYPE_NONE, 1, G_TYPE_STRING);

	signals[SCAN_DONE] =
		g_signal_new (NM_SUPPLICANT_INTERFACE_SCAN_DONE,
		              G_OBJECT_CLASS_TYPE (object_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (NMSupplicantInterfaceClass, scan_done),
		              NULL, NULL, NULL,
		              G_TYPE_NONE, 1, G_TYPE_BOOLEAN);

	signals[CONNECTION_ERROR] =
		g_signal_new (NM_SUPPLICANT_INTERFACE_CONNECTION_ERROR,
		              G_OBJECT_CLASS_TYPE (object_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (NMSupplicantInterfaceClass, connection_error),
		              NULL, NULL, NULL,
		              G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);

	signals[CREDENTIALS_REQUEST] =
		g_signal_new (NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST,
		              G_OBJECT_CLASS_TYPE (object_class),
		              G_SIGNAL_RUN_LAST,
		              G_STRUCT_OFFSET (NMSupplicantInterfaceClass, credentials_request),
		              NULL, NULL, NULL,
		              G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
}

