/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2014  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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 St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <linux/wireless.h>

#ifndef IFF_LOWER_UP
#define IFF_LOWER_UP	0x10000
#endif

#include <dbus/dbus.h>
#include <glib.h>

#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/plugin.h>
#include <connman/inet.h>
#include <connman/device.h>
#include <connman/rtnl.h>
#include <connman/technology.h>
#include <connman/service.h>
#include <connman/peer.h>
#include <connman/log.h>
#include <connman/storage.h>
#include <include/setting.h>
#include <connman/provision.h>
#include <connman/utsname.h>
#include <connman/machine.h>
#include <connman/tethering.h>

#include <gsupplicant/gsupplicant.h>

#include "src/shared/util.h"

#define CLEANUP_TIMEOUT   8	/* in seconds */
#define INACTIVE_TIMEOUT  12	/* in seconds */
#define FAVORITE_MAXIMUM_RETRIES 2

#define BGSCAN_DEFAULT "simple:30:-65:300"
#define AUTOSCAN_EXPONENTIAL "exponential:3:300"
#define AUTOSCAN_SINGLE "single:3"
#define SCAN_MAX_DURATION 10

#define P2P_FIND_TIMEOUT 30
#define P2P_CONNECTION_TIMEOUT 100
#define P2P_LISTEN_PERIOD 500
#define P2P_LISTEN_INTERVAL 2000

#define ASSOC_STATUS_AUTH_TIMEOUT 16
#define ASSOC_STATUS_NO_CLIENT 17
#define LOAD_SHAPING_MAX_RETRIES 3

static struct connman_technology *wifi_technology = NULL;
static struct connman_technology *p2p_technology = NULL;

enum wifi_ap_capability{
	WIFI_AP_UNKNOWN 	= 0,
	WIFI_AP_SUPPORTED 	= 1,
	WIFI_AP_NOT_SUPPORTED 	= 2,
};

enum wifi_scanning_type {
	WIFI_SCANNING_UNKNOWN	= 0,
	WIFI_SCANNING_PASSIVE	= 1,
	WIFI_SCANNING_ACTIVE	= 2,
};

struct hidden_params {
	char ssid[32];
	unsigned int ssid_len;
	char *identity;
	char *anonymous_identity;
	char *subject_match;
	char *altsubject_match;
	char *domain_suffix_match;
	char *domain_match;
	char *passphrase;
	char *security;
	GSupplicantScanParams *scan_params;
	gpointer user_data;
};

/**
 * Used for autoscan "emulation".
 * Should be removed when wpa_s autoscan support will be by default.
 */
struct autoscan_params {
	int base;
	int limit;
	int interval;
	unsigned int timeout;
};

struct wifi_tethering_info {
	struct wifi_data *wifi;
	struct connman_technology *technology;
	char *ifname;
	GSupplicantSSID *ssid;
};

struct wifi_data {
	char *identifier;
	struct connman_device *device;
	struct connman_network *network;
	struct connman_network *pending_network;
	GSList *networks;
	GSupplicantInterface *interface;
	GSupplicantState state;
	bool connected;
	bool disconnecting;
	bool tethering;
	enum wifi_ap_capability ap_supported;
	bool bridged;
	bool interface_ready;
	const char *bridge;
	int index;
	unsigned flags;
	unsigned int watch;
	int retries;
	int load_shaping_retries;
	struct hidden_params *hidden;
	bool postpone_hidden;
	struct wifi_tethering_info *tethering_param;
	/**
	 * autoscan "emulation".
	 */
	struct autoscan_params *autoscan;
	enum wifi_scanning_type scanning_type;
	GSupplicantScanParams *scan_params;
	unsigned int p2p_find_timeout;
	unsigned int p2p_connection_timeout;
	struct connman_peer *pending_peer;
	GSList *peers;
	bool p2p_connecting;
	bool p2p_device;
	int servicing;
	int disconnect_code;
	int assoc_code;
};

struct wifi_network {
	unsigned int keymgmt;
};

struct disconnect_data {
	struct wifi_data *wifi;
	struct connman_network *network;
};

static GList *iface_list = NULL;

static GList *pending_wifi_device = NULL;
static GList *p2p_iface_list = NULL;
static bool wfd_service_registered = false;

static void start_autoscan(struct connman_device *device);
static int tech_set_tethering(struct connman_technology *technology,
				const char *bridge, bool enabled);

static int p2p_tech_probe(struct connman_technology *technology)
{
	p2p_technology = technology;

	return 0;
}

static void p2p_tech_remove(struct connman_technology *technology)
{
	p2p_technology = NULL;
}

static struct connman_technology_driver p2p_tech_driver = {
	.name		= "p2p",
	.type		= CONNMAN_SERVICE_TYPE_P2P,
	.probe		= p2p_tech_probe,
	.remove		= p2p_tech_remove,
};

static bool is_p2p_connecting(void)
{
	GList *list;

	for (list = iface_list; list; list = list->next) {
		struct wifi_data *wifi = list->data;

		if (wifi->p2p_connecting)
			return true;
	}

	return false;
}

static void add_pending_wifi_device(struct wifi_data *wifi)
{
	if (g_list_find(pending_wifi_device, wifi))
		return;

	pending_wifi_device = g_list_append(pending_wifi_device, wifi);
}

static struct wifi_data *get_pending_wifi_data(const char *ifname)
{
	GList *list;

	for (list = pending_wifi_device; list; list = list->next) {
		struct wifi_data *wifi;
		const char *dev_name;

		wifi = list->data;
		if (!wifi || !wifi->device)
			continue;

		dev_name = connman_device_get_string(wifi->device, "Interface");
		if (!g_strcmp0(ifname, dev_name)) {
			pending_wifi_device = g_list_delete_link(
						pending_wifi_device, list);
			return wifi;
		}
	}

	return NULL;
}

static void remove_pending_wifi_device(struct wifi_data *wifi)
{
	GList *link;

	link = g_list_find(pending_wifi_device, wifi);

	if (!link)
		return;

	pending_wifi_device = g_list_delete_link(pending_wifi_device, link);
}

static void peer_cancel_timeout(struct wifi_data *wifi)
{
	if (wifi->p2p_connection_timeout > 0)
		g_source_remove(wifi->p2p_connection_timeout);

	wifi->p2p_connection_timeout = 0;
	wifi->p2p_connecting = false;

	if (wifi->pending_peer) {
		connman_peer_unref(wifi->pending_peer);
		wifi->pending_peer = NULL;
	}
}

static gboolean peer_connect_timeout(gpointer data)
{
	struct wifi_data *wifi = data;

	DBG("");

	if (wifi->p2p_connecting) {
		enum connman_peer_state state = CONNMAN_PEER_STATE_FAILURE;
		GSupplicantPeer *gs_peer =
			g_supplicant_interface_peer_lookup(wifi->interface,
				connman_peer_get_identifier(wifi->pending_peer));

		if (g_supplicant_peer_has_requested_connection(gs_peer))
			state = CONNMAN_PEER_STATE_IDLE;

		connman_peer_set_state(wifi->pending_peer, state);
	}

	peer_cancel_timeout(wifi);

	return FALSE;
}

static void peer_connect_callback(int result, GSupplicantInterface *interface,
							void *user_data)
{
	struct wifi_data *wifi = user_data;
	struct connman_peer *peer = wifi->pending_peer;

	DBG("peer %p - %d", peer, result);

	if (!peer)
		return;

	if (result < 0) {
		peer_connect_timeout(wifi);
		return;
	}

	connman_peer_set_state(peer, CONNMAN_PEER_STATE_ASSOCIATION);

	wifi->p2p_connection_timeout = g_timeout_add_seconds(
						P2P_CONNECTION_TIMEOUT,
						peer_connect_timeout, wifi);
}

static int peer_connect(struct connman_peer *peer,
			enum connman_peer_wps_method wps_method,
			const char *wps_pin)
{
	struct connman_device *device = connman_peer_get_device(peer);
	GSupplicantPeerParams *peer_params;
	GSupplicantPeer *gs_peer;
	struct wifi_data *wifi;
	bool pbc, pin;
	int ret;

	DBG("peer %p", peer);

	if (!device)
		return -ENODEV;

	wifi = connman_device_get_data(device);
	if (!wifi || !wifi->interface)
		return -ENODEV;

	if (wifi->p2p_connecting)
		return -EBUSY;

	gs_peer = g_supplicant_interface_peer_lookup(wifi->interface,
					connman_peer_get_identifier(peer));
	if (!gs_peer)
		return -EINVAL;

	pbc = g_supplicant_peer_is_wps_pbc(gs_peer);
	pin = g_supplicant_peer_is_wps_pin(gs_peer);

	switch (wps_method) {
	case CONNMAN_PEER_WPS_UNKNOWN:
		if ((pbc && pin) || pin)
			return -ENOKEY;
		break;
	case CONNMAN_PEER_WPS_PBC:
		if (!pbc)
			return -EINVAL;
		wps_pin = NULL;
		break;
	case CONNMAN_PEER_WPS_PIN:
		if (!pin || !wps_pin)
			return -EINVAL;
		break;
	}

	peer_params = g_try_malloc0(sizeof(GSupplicantPeerParams));
	if (!peer_params)
		return -ENOMEM;

	peer_params->path = g_strdup(g_supplicant_peer_get_path(gs_peer));
	if (wps_pin)
		peer_params->wps_pin = g_strdup(wps_pin);

	peer_params->master = connman_peer_service_is_master();

	ret = g_supplicant_interface_p2p_connect(wifi->interface, peer_params,
						peer_connect_callback, wifi);
	if (ret == -EINPROGRESS) {
		wifi->pending_peer = connman_peer_ref(peer);
		wifi->p2p_connecting = true;
	} else if (ret < 0) {
		g_free(peer_params->path);
		g_free(peer_params->wps_pin);
		g_free(peer_params);
	}

	return ret;
}

static int peer_disconnect(struct connman_peer *peer)
{
	struct connman_device *device = connman_peer_get_device(peer);
	GSupplicantPeerParams peer_params = {};
	GSupplicantPeer *gs_peer;
	struct wifi_data *wifi;
	int ret;

	DBG("peer %p", peer);

	if (!device)
		return -ENODEV;

	wifi = connman_device_get_data(device);
	if (!wifi)
		return -ENODEV;

	gs_peer = g_supplicant_interface_peer_lookup(wifi->interface,
					connman_peer_get_identifier(peer));
	if (!gs_peer)
		return -EINVAL;

	peer_params.path = g_strdup(g_supplicant_peer_get_path(gs_peer));

	ret = g_supplicant_interface_p2p_disconnect(wifi->interface,
							&peer_params);
	g_free(peer_params.path);

	if (ret == -EINPROGRESS) {
		peer_cancel_timeout(wifi);
		wifi->p2p_device = false;
	}

	return ret;
}

struct peer_service_registration {
	peer_service_registration_cb_t callback;
	void *user_data;
};

static bool is_service_wfd(const unsigned char *specs, int length)
{
	if (length < 9 || specs[0] != 0 || specs[1] != 0 || specs[2] != 6)
		return false;

	return true;
}

static void apply_p2p_listen_on_iface(gpointer data, gpointer user_data)
{
	struct wifi_data *wifi = data;

	if (!wifi->interface ||
			!g_supplicant_interface_has_p2p(wifi->interface))
		return;

	if (!wifi->servicing) {
		g_supplicant_interface_p2p_listen(wifi->interface,
				P2P_LISTEN_PERIOD, P2P_LISTEN_INTERVAL);
	}

	wifi->servicing++;
}

static void register_wfd_service_cb(int result,
				GSupplicantInterface *iface, void *user_data)
{
	struct peer_service_registration *reg_data = user_data;

	DBG("");

	if (result == 0)
		g_list_foreach(iface_list, apply_p2p_listen_on_iface, NULL);

	if (reg_data && reg_data->callback) {
		reg_data->callback(result, reg_data->user_data);
		g_free(reg_data);
	}
}

static GSupplicantP2PServiceParams *fill_in_peer_service_params(
				const unsigned char *spec,
				int spec_length, const unsigned char *query,
				int query_length, int version)
{
	GSupplicantP2PServiceParams *params;

	params = g_try_malloc0(sizeof(GSupplicantP2PServiceParams));
	if (!params)
		return NULL;

	if (version > 0) {
		params->version = version;
		if (spec_length > 0) {
			params->service = g_malloc(spec_length);
			memcpy(params->service, spec, spec_length);
		}
	} else if (query_length > 0 && spec_length > 0) {
		params->query = g_malloc(query_length);
		memcpy(params->query, query, query_length);
		params->query_length = query_length;

		params->response = g_malloc(spec_length);
		memcpy(params->response, spec, spec_length);
		params->response_length = spec_length;
	} else {
		if (spec_length > 0) {
			params->wfd_ies = g_malloc(spec_length);
			memcpy(params->wfd_ies, spec, spec_length);
		}
		params->wfd_ies_length = spec_length;
	}

	return params;
}

static void free_peer_service_params(GSupplicantP2PServiceParams *params)
{
	if (!params)
		return;

	g_free(params->service);
	g_free(params->query);
	g_free(params->response);
	g_free(params->wfd_ies);

	g_free(params);
}

static int peer_register_wfd_service(const unsigned char *specification,
				int specification_length,
				peer_service_registration_cb_t callback,
				void *user_data)
{
	struct peer_service_registration *reg_data = NULL;
	static GSupplicantP2PServiceParams *params;
	int ret;

	DBG("");

	if (wfd_service_registered)
		return -EBUSY;

	params = fill_in_peer_service_params(specification,
					specification_length, NULL, 0, 0);
	if (!params)
		return -ENOMEM;

	reg_data = g_try_malloc0(sizeof(*reg_data));
	if (!reg_data) {
		ret = -ENOMEM;
		goto error;
	}

	reg_data->callback = callback;
	reg_data->user_data = user_data;

	ret = g_supplicant_set_widi_ies(params,
					register_wfd_service_cb, reg_data);
	if (ret < 0 && ret != -EINPROGRESS)
		goto error;

	wfd_service_registered = true;

	return ret;
error:
	free_peer_service_params(params);
	g_free(reg_data);

	return ret;
}

static void register_peer_service_cb(int result,
				GSupplicantInterface *iface, void *user_data)
{
	struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
	struct peer_service_registration *reg_data = user_data;

	DBG("");

	if (result == 0)
		apply_p2p_listen_on_iface(wifi, NULL);

	if (reg_data->callback)
		reg_data->callback(result, reg_data->user_data);

	g_free(reg_data);
}

static int peer_register_service(const unsigned char *specification,
				int specification_length,
				const unsigned char *query,
				int query_length, int version,
				peer_service_registration_cb_t callback,
				void *user_data)
{
	struct peer_service_registration *reg_data;
	GSupplicantP2PServiceParams *params;
	bool found = false;
	int ret, ret_f;
	GList *list;

	DBG("");

	if (specification && !version && !query &&
			is_service_wfd(specification, specification_length)) {
		return peer_register_wfd_service(specification,
				specification_length, callback, user_data);
	}

	reg_data = g_try_malloc0(sizeof(*reg_data));
	if (!reg_data)
		return -ENOMEM;

	reg_data->callback = callback;
	reg_data->user_data = user_data;

	ret_f = -EOPNOTSUPP;

	for (list = iface_list; list; list = list->next) {
		struct wifi_data *wifi = list->data;
		GSupplicantInterface *iface = wifi->interface;

		if (!g_supplicant_interface_has_p2p(iface))
			continue;

		params = fill_in_peer_service_params(specification,
						specification_length, query,
						query_length, version);
		if (!params)
			continue;

		if (!found) {
			ret_f = g_supplicant_interface_p2p_add_service(iface,
				register_peer_service_cb, params, reg_data);
			if (ret_f == 0 || ret_f == -EINPROGRESS)
				found = true;
			ret = ret_f;
		} else
			ret = g_supplicant_interface_p2p_add_service(iface,
				register_peer_service_cb, params, NULL);
		if (ret != 0 && ret != -EINPROGRESS)
			free_peer_service_params(params);
	}

	if (ret_f != 0 && ret_f != -EINPROGRESS)
		g_free(reg_data);

	return ret_f;
}

static int peer_unregister_wfd_service(void)
{
	GSupplicantP2PServiceParams *params;
	GList *list;

	if (!wfd_service_registered)
		return -EALREADY;

	params = fill_in_peer_service_params(NULL, 0, NULL, 0, 0);
	if (!params)
		return -ENOMEM;

	wfd_service_registered = false;

	g_supplicant_set_widi_ies(params, NULL, NULL);

	for (list = iface_list; list; list = list->next) {
		struct wifi_data *wifi = list->data;

		if (!g_supplicant_interface_has_p2p(wifi->interface))
			continue;

		wifi->servicing--;
		if (!wifi->servicing || wifi->servicing < 0) {
			g_supplicant_interface_p2p_listen(wifi->interface,
									0, 0);
			wifi->servicing = 0;
		}
	}

	return 0;
}

static int peer_unregister_service(const unsigned char *specification,
						int specification_length,
						const unsigned char *query,
						int query_length, int version)
{
	GSupplicantP2PServiceParams *params;
	bool wfd = false;
	GList *list;
	int ret;

	if (specification && !version && !query &&
			is_service_wfd(specification, specification_length)) {
		ret = peer_unregister_wfd_service();
		if (ret != 0 && ret != -EINPROGRESS)
			return ret;
		wfd = true;
	}

	for (list = iface_list; list; list = list->next) {
		struct wifi_data *wifi = list->data;
		GSupplicantInterface *iface = wifi->interface;

		if (wfd)
			goto stop_listening;

		if (!g_supplicant_interface_has_p2p(iface))
			continue;

		params = fill_in_peer_service_params(specification,
						specification_length, query,
						query_length, version);
		if (!params)
			continue;

		ret = g_supplicant_interface_p2p_del_service(iface, params);
		if (ret != 0 && ret != -EINPROGRESS)
			free_peer_service_params(params);
stop_listening:
		wifi->servicing--;
		if (!wifi->servicing || wifi->servicing < 0) {
			g_supplicant_interface_p2p_listen(iface, 0, 0);
			wifi->servicing = 0;
		}
	}

	return 0;
}

static struct connman_peer_driver peer_driver = {
	.connect    = peer_connect,
	.disconnect = peer_disconnect,
	.register_service = peer_register_service,
	.unregister_service = peer_unregister_service,
};

static void handle_tethering(struct wifi_data *wifi)
{
	if (!wifi->tethering)
		return;

	if (!wifi->bridge)
		return;

	if (wifi->bridged)
		return;

	DBG("index %d bridge %s", wifi->index, wifi->bridge);

	if (connman_inet_add_to_bridge(wifi->index, wifi->bridge) < 0)
		return;

	wifi->bridged = true;
}

static void wifi_newlink(unsigned flags, unsigned change, void *user_data)
{
	struct connman_device *device = user_data;
	struct wifi_data *wifi = connman_device_get_data(device);

	if (!wifi)
		return;

	DBG("index %d flags %d change %d", wifi->index, flags, change);

	if ((wifi->flags & IFF_UP) != (flags & IFF_UP)) {
		if (flags & IFF_UP)
			DBG("interface up");
		else
			DBG("interface down");
	}

	if ((wifi->flags & IFF_LOWER_UP) != (flags & IFF_LOWER_UP)) {
		if (flags & IFF_LOWER_UP)
			DBG("carrier on");
		else
			DBG("carrier off");
	}

	if (flags & IFF_LOWER_UP)
		handle_tethering(wifi);

	wifi->flags = flags;
}

static int wifi_probe(struct connman_device *device)
{
	struct wifi_data *wifi;

	DBG("device %p", device);

	wifi = g_try_new0(struct wifi_data, 1);
	if (!wifi)
		return -ENOMEM;

	wifi->state = G_SUPPLICANT_STATE_INACTIVE;
	wifi->ap_supported = WIFI_AP_UNKNOWN;
	wifi->tethering_param = NULL;

	connman_device_set_data(device, wifi);
	wifi->device = connman_device_ref(device);

	wifi->index = connman_device_get_index(device);
	wifi->flags = 0;

	wifi->watch = connman_rtnl_add_newlink_watch(wifi->index,
							wifi_newlink, device);
	if (is_p2p_connecting())
		add_pending_wifi_device(wifi);
	else
		iface_list = g_list_append(iface_list, wifi);

	return 0;
}

static void remove_networks(struct connman_device *device,
				struct wifi_data *wifi)
{
	GSList *list;

	for (list = wifi->networks; list; list = list->next) {
		struct connman_network *network = list->data;

		g_free(connman_network_get_data(network));
		connman_device_remove_network(device, network);
		connman_network_unref(network);
	}

	g_slist_free(wifi->networks);
	wifi->networks = NULL;
}

static void remove_peers(struct wifi_data *wifi)
{
	GSList *list;

	for (list = wifi->peers; list; list = list->next) {
		struct connman_peer *peer = list->data;

		connman_peer_unregister(peer);
		connman_peer_unref(peer);
	}

	g_slist_free(wifi->peers);
	wifi->peers = NULL;
}

static void reset_autoscan(struct connman_device *device)
{
	struct wifi_data *wifi = connman_device_get_data(device);
	struct autoscan_params *autoscan;

	DBG("");

	if (!wifi || !wifi->autoscan)
		return;

	autoscan = wifi->autoscan;

	autoscan->interval = 0;

	if (autoscan->timeout == 0)
		return;

	g_source_remove(autoscan->timeout);
	autoscan->timeout = 0;

	connman_device_unref(device);
}

static void stop_autoscan(struct connman_device *device)
{
	const struct wifi_data *wifi = connman_device_get_data(device);

	if (!wifi || !wifi->autoscan)
		return;

	reset_autoscan(device);

	connman_device_set_scanning(device, CONNMAN_SERVICE_TYPE_WIFI, false);
}

static void check_p2p_technology(void)
{
	bool p2p_exists = false;
	GList *list;

	for (list = iface_list; list; list = list->next) {
		struct wifi_data *w = list->data;

		if (w->interface &&
				g_supplicant_interface_has_p2p(w->interface))
			p2p_exists = true;
	}

	if (!p2p_exists) {
		connman_technology_driver_unregister(&p2p_tech_driver);
		connman_peer_driver_unregister(&peer_driver);
	}
}

static void wifi_remove(struct connman_device *device)
{
	struct wifi_data *wifi = connman_device_get_data(device);

	DBG("device %p wifi %p", device, wifi);

	if (!wifi)
		return;

	stop_autoscan(device);

	if (wifi->p2p_device)
		p2p_iface_list = g_list_remove(p2p_iface_list, wifi);
	else
		iface_list = g_list_remove(iface_list, wifi);

	check_p2p_technology();

	remove_pending_wifi_device(wifi);

	if (connman_device_get_scanning(device, CONNMAN_SERVICE_TYPE_P2P)) {
		g_source_remove(wifi->p2p_find_timeout);
		connman_device_unref(wifi->device);
	}

	if (wifi->p2p_connection_timeout)
		g_source_remove(wifi->p2p_connection_timeout);

	remove_networks(device, wifi);
	remove_peers(wifi);

	connman_device_set_powered(device, false);
	connman_device_set_data(device, NULL);
	connman_device_unref(wifi->device);
	connman_rtnl_remove_watch(wifi->watch);

	g_supplicant_interface_set_data(wifi->interface, NULL);

	g_supplicant_interface_cancel(wifi->interface);

	if (wifi->scan_params)
		g_supplicant_free_scan_params(wifi->scan_params);

	g_free(wifi->autoscan);
	g_free(wifi->identifier);
	g_free(wifi);
}

static bool is_duplicate(GSList *list, gchar *ssid, int ssid_len)
{
	GSList *iter;

	for (iter = list; iter; iter = g_slist_next(iter)) {
		struct scan_ssid *scan_ssid = iter->data;

		if (ssid_len == scan_ssid->ssid_len &&
				memcmp(ssid, scan_ssid->ssid, ssid_len) == 0)
			return true;
	}

	return false;
}

static int add_scan_param(gchar *hex_ssid, char *raw_ssid, int ssid_len,
			int freq, GSupplicantScanParams *scan_data,
			int driver_max_scan_ssids, char *ssid_name)
{
	unsigned int i;
	struct scan_ssid *scan_ssid;

	if ((driver_max_scan_ssids == 0 ||
			driver_max_scan_ssids > scan_data->num_ssids) &&
			(hex_ssid || raw_ssid)) {
		gchar *ssid;
		unsigned int j = 0, hex;

		if (hex_ssid) {
			size_t hex_ssid_len = strlen(hex_ssid);

			ssid = g_try_malloc0(hex_ssid_len / 2);
			if (!ssid)
				return -ENOMEM;

			for (i = 0; i < hex_ssid_len; i += 2) {
				sscanf(hex_ssid + i, "%02x", &hex);
				ssid[j++] = hex;
			}
		} else {
			ssid = raw_ssid;
			j = ssid_len;
		}

		/*
		 * If we have already added hidden AP to the list,
		 * then do not do it again. This might happen if you have
		 * used or are using multiple wifi cards, so in that case
		 * you might have multiple service files for same AP.
		 */
		if (is_duplicate(scan_data->ssids, ssid, j)) {
			if (hex_ssid)
				g_free(ssid);
			return 0;
		}

		scan_ssid = g_try_new(struct scan_ssid, 1);
		if (!scan_ssid) {
			if (hex_ssid)
				g_free(ssid);
			return -ENOMEM;
		}

		memcpy(scan_ssid->ssid, ssid, j);
		scan_ssid->ssid_len = j;
		scan_data->ssids = g_slist_prepend(scan_data->ssids,
								scan_ssid);

		scan_data->num_ssids++;

		DBG("SSID %s added to scanned list of %d entries", ssid_name,
							scan_data->num_ssids);

		if (hex_ssid)
			g_free(ssid);
	} else
		return -EINVAL;

	scan_data->ssids = g_slist_reverse(scan_data->ssids);

	if (!scan_data->freqs) {
		scan_data->freqs = g_try_malloc0(sizeof(uint16_t));
		if (!scan_data->freqs) {
			g_slist_free_full(scan_data->ssids, g_free);
			return -ENOMEM;
		}

		scan_data->num_freqs = 1;
		scan_data->freqs[0] = freq;
	} else {
		bool duplicate = false;

		/* Don't add duplicate entries */
		for (i = 0; i < scan_data->num_freqs; i++) {
			if (scan_data->freqs[i] == freq) {
				duplicate = true;
				break;
			}
		}

		if (!duplicate) {
			scan_data->num_freqs++;
			scan_data->freqs = g_try_realloc(scan_data->freqs,
				sizeof(uint16_t) * scan_data->num_freqs);
			if (!scan_data->freqs) {
				g_slist_free_full(scan_data->ssids, g_free);
				return -ENOMEM;
			}
			scan_data->freqs[scan_data->num_freqs - 1] = freq;
		}
	}

	return 1;
}

static int get_hidden_connections(GSupplicantScanParams *scan_data)
{
	struct connman_config_entry **entries;
	GKeyFile *keyfile;
	gchar **services;
	char *ssid, *name;
	int i, ret;
	bool value;
	int num_ssids = 0, add_param_failed = 0;

	services = connman_storage_get_services();
	for (i = 0; services && services[i]; i++) {
		if (strncmp(services[i], "wifi_", 5) != 0)
			continue;

		keyfile = connman_storage_load_service(services[i]);
		if (!keyfile)
			continue;

		value = g_key_file_get_boolean(keyfile,
					services[i], "Hidden", NULL);
		if (!value) {
			g_key_file_free(keyfile);
			continue;
		}

		value = g_key_file_get_boolean(keyfile,
					services[i], "Favorite", NULL);
		if (!value) {
			g_key_file_free(keyfile);
			continue;
		}

		ssid = g_key_file_get_string(keyfile,
					services[i], "SSID", NULL);

		name = g_key_file_get_string(keyfile, services[i], "Name",
								NULL);

		ret = add_scan_param(ssid, NULL, 0, 0, scan_data, 0, name);
		if (ret < 0)
			add_param_failed++;
		else if (ret > 0)
			num_ssids++;

		g_free(ssid);
		g_free(name);
		g_key_file_free(keyfile);
	}

	/*
	 * Check if there are any hidden AP that needs to be provisioned.
	 */
	entries = connman_config_get_entries("wifi");
	for (i = 0; entries && entries[i]; i++) {
		int len;

		if (!entries[i]->hidden)
			continue;

		if (!entries[i]->ssid) {
			ssid = entries[i]->name;
			len = strlen(ssid);
		} else {
			ssid = entries[i]->ssid;
			len = entries[i]->ssid_len;
		}

		if (!ssid)
			continue;

		ret = add_scan_param(NULL, ssid, len, 0, scan_data, 0, ssid);
		if (ret < 0)
			add_param_failed++;
		else if (ret > 0)
			num_ssids++;
	}

	connman_config_free_entries(entries);

	if (add_param_failed > 0)
		DBG("Unable to scan %d out of %d SSIDs",
					add_param_failed, num_ssids);

	g_strfreev(services);

	return num_ssids;
}

static int get_hidden_connections_params(struct wifi_data *wifi,
					GSupplicantScanParams *scan_params)
{
	int driver_max_ssids, i;
	GSupplicantScanParams *orig_params;

	/*
	 * Scan hidden networks so that we can autoconnect to them.
	 * We will assume 1 as a default number of ssid to scan.
	 */
	driver_max_ssids = g_supplicant_interface_get_max_scan_ssids(
							wifi->interface);
	if (driver_max_ssids == 0)
		driver_max_ssids = 1;

	DBG("max ssids %d", driver_max_ssids);

	if (!wifi->scan_params) {
		wifi->scan_params = g_try_malloc0(sizeof(GSupplicantScanParams));
		if (!wifi->scan_params)
			return 0;

		if (get_hidden_connections(wifi->scan_params) == 0) {
			g_supplicant_free_scan_params(wifi->scan_params);
			wifi->scan_params = NULL;

			return 0;
		}
	}

	orig_params = wifi->scan_params;

	/* Let's transfer driver_max_ssids params */
	for (i = 0; i < driver_max_ssids; i++) {
		struct scan_ssid *ssid;

		if (!wifi->scan_params->ssids)
			break;

		ssid = orig_params->ssids->data;
		orig_params->ssids = g_slist_remove(orig_params->ssids, ssid);
		scan_params->ssids = g_slist_prepend(scan_params->ssids, ssid);
	}

	if (i > 0) {
		scan_params->num_ssids = i;
		scan_params->ssids = g_slist_reverse(scan_params->ssids);

		if (orig_params->num_freqs <= 0)
			goto err;

		scan_params->freqs =
			g_malloc(sizeof(uint16_t) * orig_params->num_freqs);
		memcpy(scan_params->freqs, orig_params->freqs,
			sizeof(uint16_t) *orig_params->num_freqs);

		scan_params->num_freqs = orig_params->num_freqs;

	} else
		goto err;

	orig_params->num_ssids -= scan_params->num_ssids;

	return scan_params->num_ssids;

err:
	g_slist_free_full(scan_params->ssids, g_free);
	g_supplicant_free_scan_params(wifi->scan_params);
	wifi->scan_params = NULL;

	return 0;
}

static int throw_wifi_scan(struct connman_device *device,
			GSupplicantInterfaceCallback callback)
{
	struct wifi_data *wifi = connman_device_get_data(device);
	int ret;

	if (!wifi)
		return -ENODEV;

	DBG("device %p %p", device, wifi->interface);

	if (wifi->tethering)
		return -EBUSY;

	if (connman_device_get_scanning(device, CONNMAN_SERVICE_TYPE_WIFI))
		return -EALREADY;

	connman_device_ref(device);

	ret = g_supplicant_interface_scan(wifi->interface, NULL,
						callback, device);
	if (ret == 0) {
		connman_device_set_scanning(device,
				CONNMAN_SERVICE_TYPE_WIFI, true);
	} else
		connman_device_unref(device);

	return ret;
}

static void hidden_free(struct hidden_params *hidden)
{
	if (!hidden)
		return;

	if (hidden->scan_params)
		g_supplicant_free_scan_params(hidden->scan_params);
	g_free(hidden->identity);
	g_free(hidden->passphrase);
	g_free(hidden->security);
	g_free(hidden);
}

static void scan_callback(int result, GSupplicantInterface *interface,
						void *user_data)
{
	struct connman_device *device = user_data;
	struct wifi_data *wifi = connman_device_get_data(device);
	bool scanning;

	DBG("result %d wifi %p", result, wifi);

	if (wifi) {
		if (wifi->hidden && !wifi->postpone_hidden) {
			connman_network_clear_hidden(wifi->hidden->user_data);
			hidden_free(wifi->hidden);
			wifi->hidden = NULL;
		}

		if (wifi->scan_params) {
			g_supplicant_free_scan_params(wifi->scan_params);
			wifi->scan_params = NULL;
		}
	}

	if (result < 0)
		connman_device_reset_scanning(device);

	/* User is connecting to a hidden AP, let's wait for finished event */
	if (wifi && wifi->hidden && wifi->postpone_hidden) {
		GSupplicantScanParams *scan_params;
		int ret;

		wifi->postpone_hidden = false;
		scan_params = wifi->hidden->scan_params;
		wifi->hidden->scan_params = NULL;

		reset_autoscan(device);

		ret = g_supplicant_interface_scan(wifi->interface, scan_params,
							scan_callback, device);
		if (ret == 0)
			return;

		/* On error, let's recall scan_callback, which will cleanup */
		return scan_callback(ret, interface, user_data);
	}

	scanning = connman_device_get_scanning(device, CONNMAN_SERVICE_TYPE_WIFI);

	if (scanning) {
		connman_device_set_scanning(device,
				CONNMAN_SERVICE_TYPE_WIFI, false);
	}

	if (result != -ENOLINK)
		start_autoscan(device);

	/*
	 * If we are here then we were scanning; however, if we are
	 * also mid-flight disabling the interface, then wifi_disable
	 * has already cleared the device scanning state and
	 * unreferenced the device, obviating the need to do it here.
	 */

	if (scanning)
		connman_device_unref(device);
}

static void scan_callback_hidden(int result,
			GSupplicantInterface *interface, void *user_data)
{
	struct connman_device *device = user_data;
	struct wifi_data *wifi = connman_device_get_data(device);
	GSupplicantScanParams *scan_params;
	int ret;

	DBG("result %d wifi %p", result, wifi);

	if (!wifi)
		goto out;

	/* User is trying to connect to a hidden AP */
	if (wifi->hidden && wifi->postpone_hidden)
		goto out;

	scan_params = g_try_malloc0(sizeof(GSupplicantScanParams));
	if (!scan_params)
		goto out;

	if (get_hidden_connections_params(wifi, scan_params) > 0) {
		ret = g_supplicant_interface_scan(wifi->interface,
							scan_params,
							scan_callback_hidden,
							device);
		if (ret == 0)
			return;
	}

	g_supplicant_free_scan_params(scan_params);

out:
	scan_callback(result, interface, user_data);
}

static gboolean autoscan_timeout(gpointer data)
{
	struct connman_device *device = data;
	struct wifi_data *wifi = connman_device_get_data(device);
	struct autoscan_params *autoscan;
	int interval;

	if (!wifi)
		return FALSE;

	autoscan = wifi->autoscan;

	if (autoscan->interval <= 0) {
		interval = autoscan->base;
		goto set_interval;
	} else
		interval = autoscan->interval * autoscan->base;

	if (interval > autoscan->limit)
		interval = autoscan->limit;

	throw_wifi_scan(wifi->device, scan_callback_hidden);

	/*
	 * In case BackgroundScanning is disabled, interval will reach the
	 * limit exactly after the very first passive scanning. It allows
	 * to ensure at most one passive scan is performed in such cases.
	 */
	if (!connman_setting_get_bool("BackgroundScanning") &&
					interval == autoscan->limit) {
		g_source_remove(autoscan->timeout);
		autoscan->timeout = 0;

		connman_device_unref(device);

		return FALSE;
	}

set_interval:
	DBG("interval %d", interval);

	autoscan->interval = interval;

	autoscan->timeout = g_timeout_add_seconds(interval,
						autoscan_timeout, device);

	return FALSE;
}

static void start_autoscan(struct connman_device *device)
{
	struct wifi_data *wifi = connman_device_get_data(device);
	struct autoscan_params *autoscan;

	DBG("");

	if (!wifi)
		return;

	if (wifi->p2p_device)
		return;

	if (wifi->connected)
		return;

	autoscan = wifi->autoscan;
	if (!autoscan)
		return;

	if (autoscan->timeout > 0 || autoscan->interval > 0)
		return;

	connman_device_ref(device);

	autoscan_timeout(device);
}

static struct autoscan_params *parse_autoscan_params(const char *params)
{
	struct autoscan_params *autoscan;
	char **list_params;
	int limit;
	int base;

	DBG("");

	list_params = g_strsplit(params, ":", 0);
	if (list_params == 0)
		return NULL;

	if (!g_strcmp0(list_params[0], "exponential") &&
				g_strv_length(list_params) == 3) {
		base = atoi(list_params[1]);
		limit = atoi(list_params[2]);
	} else if (!g_strcmp0(list_params[0], "single") &&
				g_strv_length(list_params) == 2)
		base = limit = atoi(list_params[1]);
	else {
		g_strfreev(list_params);
		return NULL;
	}

	DBG("Setup %s autoscanning", list_params[0]);

	g_strfreev(list_params);

	autoscan = g_try_malloc0(sizeof(struct autoscan_params));
	if (!autoscan) {
		DBG("Could not allocate memory for autoscan");
		return NULL;
	}

	DBG("base %d - limit %d", base, limit);
	autoscan->base = base;
	autoscan->limit = limit;

	return autoscan;
}

static void setup_autoscan(struct wifi_data *wifi)
{
	/*
	 * If BackgroundScanning is enabled, setup exponential
	 * autoscanning if it has not been previously done.
	 */
	if (connman_setting_get_bool("BackgroundScanning")) {
		wifi->autoscan = parse_autoscan_params(AUTOSCAN_EXPONENTIAL);
		return;
	}

	/*
	 * On the contrary, if BackgroundScanning is disabled, update autoscan
	 * parameters based on the type of scanning that is being performed.
	 */
	if (wifi->autoscan) {
		g_free(wifi->autoscan);
		wifi->autoscan = NULL;
	}

	switch (wifi->scanning_type) {
	case WIFI_SCANNING_PASSIVE:
		/* Do not setup autoscan. */
		break;
	case WIFI_SCANNING_ACTIVE:
		/* Setup one single passive scan after active. */
		wifi->autoscan = parse_autoscan_params(AUTOSCAN_SINGLE);
		break;
	case WIFI_SCANNING_UNKNOWN:
		/* Setup autoscan in this case but we should never fall here. */
		wifi->autoscan = parse_autoscan_params(AUTOSCAN_SINGLE);
		break;
	}
}

static void finalize_interface_creation(struct wifi_data *wifi)
{
	DBG("interface is ready wifi %p tethering %d", wifi, wifi->tethering);

	if (!wifi->device) {
		connman_error("WiFi device not set");
		return;
	}

	connman_device_set_powered(wifi->device, true);

	if (wifi->p2p_device)
		return;

	if (!wifi->autoscan)
		setup_autoscan(wifi);

	start_autoscan(wifi->device);
}

static void interface_create_callback(int result,
					GSupplicantInterface *interface,
							void *user_data)
{
	struct wifi_data *wifi = user_data;
	char *bgscan_range_max;
	long value;

	DBG("result %d ifname %s, wifi %p", result,
				g_supplicant_interface_get_ifname(interface),
				wifi);

	if (result < 0 || !wifi)
		return;

	wifi->interface = interface;
	g_supplicant_interface_set_data(interface, wifi);

	if (g_supplicant_interface_get_ready(interface)) {
		wifi->interface_ready = true;
		finalize_interface_creation(wifi);
	}

	/*
	 * Set the BSS expiration age to match the long scanning
	 * interval to avoid the loss of unconnected networks between
	 * two scans.
	 */
	bgscan_range_max = strrchr(BGSCAN_DEFAULT, ':');
	if (!bgscan_range_max || strlen(bgscan_range_max) < 1)
		return;

	value = strtol(bgscan_range_max + 1, NULL, 10);
	if (value <= 0 || errno == ERANGE)
		return;

	if (g_supplicant_interface_set_bss_expiration_age(interface,
					value + SCAN_MAX_DURATION) < 0) {
		connman_warn("Failed to set bss expiration age");
	}
}

static int wifi_enable(struct connman_device *device)
{
	struct wifi_data *wifi = connman_device_get_data(device);
	int index;
	char *interface;
	const char *driver = connman_setting_get_string("wifi");
	int ret;

	DBG("device %p %p", device, wifi);

	index = connman_device_get_index(device);
	if (!wifi || index < 0)
		return -ENODEV;

	if (is_p2p_connecting())
		return -EINPROGRESS;

	interface = connman_inet_ifname(index);
	ret = g_supplicant_interface_create(interface, driver, NULL,
						interface_create_callback,
							wifi);
	g_free(interface);

	if (ret < 0)
		return ret;

	return -EINPROGRESS;
}

static int wifi_disable(struct connman_device *device)
{
	struct wifi_data *wifi = connman_device_get_data(device);
	int ret;

	DBG("device %p wifi %p", device, wifi);

	if (!wifi)
		return -ENODEV;

	wifi->connected = false;
	wifi->disconnecting = false;

	if (wifi->pending_network)
		wifi->pending_network = NULL;

	stop_autoscan(device);

	if (connman_device_get_scanning(device, CONNMAN_SERVICE_TYPE_P2P)) {
		g_source_remove(wifi->p2p_find_timeout);
		wifi->p2p_find_timeout = 0;
		connman_device_set_scanning(device, CONNMAN_SERVICE_TYPE_P2P, false);
		connman_device_unref(wifi->device);
	}

	/* In case of a user scan, device is still referenced */
	if (connman_device_get_scanning(device, CONNMAN_SERVICE_TYPE_WIFI)) {
		connman_device_set_scanning(device,
				CONNMAN_SERVICE_TYPE_WIFI, false);
		connman_device_unref(wifi->device);
	}

	remove_networks(device, wifi);
	remove_peers(wifi);

	ret = g_supplicant_interface_remove(wifi->interface, NULL, NULL);
	if (ret < 0)
		return ret;

	return -EINPROGRESS;
}

struct last_connected {
	struct timeval modified;
	gchar *ssid;
	int freq;
};

static gint sort_entry(gconstpointer a, gconstpointer b, gpointer user_data)
{
	struct timeval *aval = (struct timeval *)a;
	struct timeval *bval = (struct timeval *)b;

	/* Note that the sort order is descending */
	if (aval->tv_sec < bval->tv_sec)
		return 1;

	if (aval->tv_sec > bval->tv_sec)
		return -1;

	return 0;
}

static void free_entry(gpointer data)
{
	struct last_connected *entry = data;

	g_free(entry->ssid);
	g_free(entry);
}

static int get_latest_connections(int max_ssids,
				GSupplicantScanParams *scan_data)
{
	GSequenceIter *iter;
	GSequence *latest_list;
	struct last_connected *entry;
	GKeyFile *keyfile;
	struct timeval modified;
	gchar **services;
	gchar *str;
	char *ssid;
	int i, freq;
	int num_ssids = 0;

	latest_list = g_sequence_new(free_entry);
	if (!latest_list)
		return -ENOMEM;

	services = connman_storage_get_services();
	for (i = 0; services && services[i]; i++) {
		if (strncmp(services[i], "wifi_", 5) != 0)
			continue;

		keyfile = connman_storage_load_service(services[i]);
		if (!keyfile)
			continue;

		str = g_key_file_get_string(keyfile,
					services[i], "Favorite", NULL);
		if (!str || g_strcmp0(str, "true")) {
			g_free(str);
			g_key_file_free(keyfile);
			continue;
		}
		g_free(str);

		str = g_key_file_get_string(keyfile,
					services[i], "AutoConnect", NULL);
		if (!str || g_strcmp0(str, "true")) {
			g_free(str);
			g_key_file_free(keyfile);
			continue;
		}
		g_free(str);

		str = g_key_file_get_string(keyfile,
					services[i], "Modified", NULL);
		if (!str) {
			g_key_file_free(keyfile);
			continue;
		}
		util_iso8601_to_timeval(str, &modified);
		g_free(str);

		ssid = g_key_file_get_string(keyfile,
					services[i], "SSID", NULL);

		freq = g_key_file_get_integer(keyfile, services[i],
					"Frequency", NULL);
		if (freq) {
			entry = g_try_new(struct last_connected, 1);
			if (!entry) {
				g_sequence_free(latest_list);
				g_key_file_free(keyfile);
				g_free(ssid);
				return -ENOMEM;
			}

			entry->ssid = ssid;
			entry->modified = modified;
			entry->freq = freq;

			g_sequence_insert_sorted(latest_list, entry,
						sort_entry, NULL);
			num_ssids++;
		} else
			g_free(ssid);

		g_key_file_free(keyfile);
	}

	g_strfreev(services);

	num_ssids = num_ssids > max_ssids ? max_ssids : num_ssids;

	iter = g_sequence_get_begin_iter(latest_list);

	for (i = 0; i < num_ssids; i++) {
		entry = g_sequence_get(iter);

		DBG("ssid %s freq %d modified %lu", entry->ssid, entry->freq,
						entry->modified.tv_sec);

		add_scan_param(entry->ssid, NULL, 0, entry->freq, scan_data,
						max_ssids, entry->ssid);

		iter = g_sequence_iter_next(iter);
	}

	g_sequence_free(latest_list);
	return num_ssids;
}

static void wifi_update_scanner_type(struct wifi_data *wifi,
					enum wifi_scanning_type new_type)
{
	DBG("");

	if (!wifi || wifi->scanning_type == new_type)
		return;

	wifi->scanning_type = new_type;

	setup_autoscan(wifi);
}

static int wifi_scan_simple(struct connman_device *device)
{
	struct wifi_data *wifi = connman_device_get_data(device);

	reset_autoscan(device);

	/* Distinguish between devices performing passive and active scanning */
	if (wifi)
		wifi_update_scanner_type(wifi, WIFI_SCANNING_PASSIVE);

	return throw_wifi_scan(device, scan_callback_hidden);
}

static gboolean p2p_find_stop(gpointer data)
{
	struct connman_device *device = data;
	struct wifi_data *wifi = connman_device_get_data(device);

	DBG("");

	if (wifi) {
		wifi->p2p_find_timeout = 0;

		g_supplicant_interface_p2p_stop_find(wifi->interface);
	}

	connman_device_set_scanning(device, CONNMAN_SERVICE_TYPE_P2P, false);

	connman_device_unref(device);
	start_autoscan(device);

	return FALSE;
}

static void p2p_find_callback(int result, GSupplicantInterface *interface,
							void *user_data)
{
	struct connman_device *device = user_data;
	struct wifi_data *wifi = connman_device_get_data(device);

	DBG("result %d wifi %p", result, wifi);

	if (!wifi)
		goto error;

	if (wifi->p2p_find_timeout) {
		g_source_remove(wifi->p2p_find_timeout);
		wifi->p2p_find_timeout = 0;
	}

	if (result)
		goto error;

	wifi->p2p_find_timeout = g_timeout_add_seconds(P2P_FIND_TIMEOUT,
							p2p_find_stop, device);
	if (!wifi->p2p_find_timeout)
		goto error;

	return;
error:
	p2p_find_stop(device);
}

static int p2p_find(struct connman_device *device)
{
	struct wifi_data *wifi;
	int ret;

	DBG("");

	if (!p2p_technology)
		return -ENOTSUP;

	wifi = connman_device_get_data(device);

	if (g_supplicant_interface_is_p2p_finding(wifi->interface))
		return -EALREADY;

	reset_autoscan(device);
	connman_device_ref(device);

	ret = g_supplicant_interface_p2p_find(wifi->interface,
						p2p_find_callback, device);
	if (ret) {
		connman_device_unref(device);
		start_autoscan(device);
	} else {
		connman_device_set_scanning(device,
				CONNMAN_SERVICE_TYPE_P2P, true);
	}

	return ret;
}

/*
 * Note that the hidden scan is only used when connecting to this specific
 * hidden AP first time. It is not used when system autoconnects to hidden AP.
 */
static int wifi_scan(struct connman_device *device,
			struct connman_device_scan_params *params)
{
	struct wifi_data *wifi = connman_device_get_data(device);
	GSupplicantScanParams *scan_params = NULL;
	struct scan_ssid *scan_ssid;
	struct hidden_params *hidden;
	int ret;
	int driver_max_ssids = 0;
	bool do_hidden;
	bool scanning;

	if (!wifi)
		return -ENODEV;

	if (wifi->p2p_device)
		return -EBUSY;

	if (wifi->tethering)
		return -EBUSY;

	if (params->type == CONNMAN_SERVICE_TYPE_P2P)
		return p2p_find(device);

	DBG("device %p wifi %p hidden ssid %s", device, wifi->interface,
		params->ssid);

	scanning = connman_device_get_scanning(device, CONNMAN_SERVICE_TYPE_WIFI);

	if (!params->ssid || params->ssid_len == 0 || params->ssid_len > 32) {
		if (scanning)
			return -EALREADY;

		driver_max_ssids = g_supplicant_interface_get_max_scan_ssids(
							wifi->interface);
		DBG("max ssids %d", driver_max_ssids);
		if (driver_max_ssids == 0)
			return wifi_scan_simple(device);

		do_hidden = false;
	} else {
		if (scanning && wifi->hidden && wifi->postpone_hidden)
			return -EALREADY;

		do_hidden = true;
	}

	scan_params = g_try_malloc0(sizeof(GSupplicantScanParams));
	if (!scan_params)
		return -ENOMEM;

	if (do_hidden) {
		scan_ssid = g_try_new(struct scan_ssid, 1);
		if (!scan_ssid) {
			g_free(scan_params);
			return -ENOMEM;
		}

		memcpy(scan_ssid->ssid, params->ssid, params->ssid_len);
		scan_ssid->ssid_len = params->ssid_len;
		scan_params->ssids = g_slist_prepend(scan_params->ssids,
								scan_ssid);
		scan_params->num_ssids = 1;

		hidden = g_try_new0(struct hidden_params, 1);
		if (!hidden) {
			g_supplicant_free_scan_params(scan_params);
			return -ENOMEM;
		}

		if (wifi->hidden) {
			hidden_free(wifi->hidden);
			wifi->hidden = NULL;
		}

		memcpy(hidden->ssid, params->ssid, params->ssid_len);
		hidden->ssid_len = params->ssid_len;
		hidden->identity = g_strdup(params->identity);
		hidden->passphrase = g_strdup(params->passphrase);
		hidden->security = g_strdup(params->security);
		hidden->user_data = params->user_data;
		wifi->hidden = hidden;

		if (scanning) {
			/* Let's keep this active scan for later,
			 * when current scan will be over. */
			wifi->postpone_hidden = TRUE;
			hidden->scan_params = scan_params;

			return 0;
		}
	} else if (wifi->connected) {
		g_supplicant_free_scan_params(scan_params);
		return wifi_scan_simple(device);
	} else if (!params->force_full_scan) {
		ret = get_latest_connections(driver_max_ssids, scan_params);
		if (ret <= 0) {
			g_supplicant_free_scan_params(scan_params);
			return wifi_scan_simple(device);
		}
	}

	/* Distinguish between devices performing passive and active scanning */
	wifi_update_scanner_type(wifi, WIFI_SCANNING_ACTIVE);

	connman_device_ref(device);

	reset_autoscan(device);

	ret = g_supplicant_interface_scan(wifi->interface, scan_params,
						scan_callback, device);
	if (ret == 0) {
		connman_device_set_scanning(device,
				CONNMAN_SERVICE_TYPE_WIFI, true);
	} else {
		g_supplicant_free_scan_params(scan_params);
		connman_device_unref(device);

		if (do_hidden) {
			hidden_free(wifi->hidden);
			wifi->hidden = NULL;
		}
	}

	return ret;
}

static void wifi_stop_scan(enum connman_service_type type,
			struct connman_device *device)
{
	struct wifi_data *wifi = connman_device_get_data(device);

	DBG("device %p wifi %p", device, wifi);

	if (!wifi)
		return;

	if (type == CONNMAN_SERVICE_TYPE_P2P) {
		if (connman_device_get_scanning(device, CONNMAN_SERVICE_TYPE_P2P)) {
			g_source_remove(wifi->p2p_find_timeout);
			p2p_find_stop(device);
		}
	}
}

static void wifi_regdom_callback(int result,
					const char *alpha2,
						void *user_data)
{
	struct connman_device *device = user_data;

	connman_device_regdom_notify(device, result, alpha2);

	connman_device_unref(device);
}

static int wifi_set_regdom(struct connman_device *device, const char *alpha2)
{
	struct wifi_data *wifi = connman_device_get_data(device);
	int ret;

	if (!wifi)
		return -EINVAL;

	connman_device_ref(device);

	ret = g_supplicant_interface_set_country(wifi->interface,
						wifi_regdom_callback,
							alpha2, device);
	if (ret != 0)
		connman_device_unref(device);

	return ret;
}

static struct connman_device_driver wifi_ng_driver = {
	.name		= "wifi",
	.type		= CONNMAN_DEVICE_TYPE_WIFI,
	.priority	= CONNMAN_DEVICE_PRIORITY_LOW,
	.probe		= wifi_probe,
	.remove		= wifi_remove,
	.enable		= wifi_enable,
	.disable	= wifi_disable,
	.scan		= wifi_scan,
	.stop_scan	= wifi_stop_scan,
	.set_regdom	= wifi_set_regdom,
};

static void system_ready(void)
{
	DBG("");

	if (connman_device_driver_register(&wifi_ng_driver) < 0)
		connman_error("Failed to register WiFi driver");
}

static void system_killed(void)
{
	DBG("");

	connman_device_driver_unregister(&wifi_ng_driver);
}

static int network_probe(struct connman_network *network)
{
	DBG("network %p", network);

	return 0;
}

static void network_remove(struct connman_network *network)
{
	struct connman_device *device = connman_network_get_device(network);
	struct wifi_data *wifi;

	DBG("network %p", network);

	wifi = connman_device_get_data(device);
	if (!wifi)
		return;

	if (wifi->network != network)
		return;

	wifi->network = NULL;
}

static void connect_callback(int result, GSupplicantInterface *interface,
							void *user_data)
{
	struct connman_network *network = user_data;

	DBG("network %p result %d", network, result);

	if (result == -ENOKEY) {
		connman_network_set_error(network,
					CONNMAN_NETWORK_ERROR_INVALID_KEY);
	} else if (result < 0) {
		connman_network_set_error(network,
					CONNMAN_NETWORK_ERROR_CONFIGURE_FAIL);
	}

	connman_network_unref(network);
}

static GSupplicantSecurity network_security(const char *security)
{
	if (g_str_equal(security, "none"))
		return G_SUPPLICANT_SECURITY_NONE;
	else if (g_str_equal(security, "wep"))
		return G_SUPPLICANT_SECURITY_WEP;
	else if (g_str_equal(security, "psk"))
		return G_SUPPLICANT_SECURITY_PSK;
	else if (g_str_equal(security, "wpa"))
		return G_SUPPLICANT_SECURITY_PSK;
	else if (g_str_equal(security, "rsn"))
		return G_SUPPLICANT_SECURITY_PSK;
	else if (g_str_equal(security, "ieee8021x"))
		return G_SUPPLICANT_SECURITY_IEEE8021X;

	return G_SUPPLICANT_SECURITY_UNKNOWN;
}

static void ssid_init(GSupplicantSSID *ssid, struct connman_network *network)
{
	struct wifi_network *network_data = connman_network_get_data(network);
	const char *ssid_blob;
	const char *security;

	memset(ssid, 0, sizeof(*ssid));
	ssid->mode = G_SUPPLICANT_MODE_INFRA;
	ssid->ssid_len = 0;
	ssid_blob =
		connman_network_get_blob(network, "WiFi.SSID", &ssid->ssid_len);
	ssid->ssid = g_try_malloc(ssid->ssid_len);
	if (ssid->ssid)
		memcpy((void *)ssid->ssid, ssid_blob, ssid->ssid_len);
	else
		ssid->ssid_len = 0;

	ssid->scan_ssid = 1;
	security = connman_network_get_string(network, "WiFi.Security");
	ssid->security = network_security(security);
	ssid->keymgmt = network_data->keymgmt;
	ssid->ieee80211w = G_SUPPLICANT_MFP_OPTIONAL;
	ssid->passphrase = g_strdup(
		connman_network_get_string(network, "WiFi.Passphrase"));

	ssid->eap = g_strdup(connman_network_get_string(network, "WiFi.EAP"));

	/*
	 * If our private key password is unset,
	 * we use the supplied passphrase. That is needed
	 * for PEAP where 2 passphrases (identity and client
	 * cert may have to be provided.
	 */
	if (!connman_network_get_string(network, "WiFi.PrivateKeyPassphrase"))
		connman_network_set_string(network, "WiFi.PrivateKeyPassphrase",
					   ssid->passphrase);
	/* We must have an identity for both PEAP and TLS */
	ssid->identity =
		g_strdup(connman_network_get_string(network, "WiFi.Identity"));

	/* Use agent provided identity as a fallback */
	if (!ssid->identity || strlen(ssid->identity) == 0)
		ssid->identity = g_strdup(connman_network_get_string(
			network, "WiFi.AgentIdentity"));

	ssid->anonymous_identity = g_strdup(
		connman_network_get_string(network, "WiFi.AnonymousIdentity"));
	ssid->ca_cert_path = g_strdup(
		connman_network_get_string(network, "WiFi.CACertFile"));
	ssid->subject_match = g_strdup(
		connman_network_get_string(network, "WiFi.SubjectMatch"));
	ssid->altsubject_match = g_strdup(
		connman_network_get_string(network, "WiFi.AltSubjectMatch"));
	ssid->domain_suffix_match = g_strdup(
		connman_network_get_string(network, "WiFi.DomainSuffixMatch"));
	ssid->domain_match = g_strdup(
		connman_network_get_string(network, "WiFi.DomainMatch"));
	ssid->client_cert_path = g_strdup(
		connman_network_get_string(network, "WiFi.ClientCertFile"));
	ssid->private_key_path = g_strdup(
		connman_network_get_string(network, "WiFi.PrivateKeyFile"));
	ssid->private_key_passphrase = g_strdup(connman_network_get_string(
		network, "WiFi.PrivateKeyPassphrase"));
	ssid->phase2_auth =
		g_strdup(connman_network_get_string(network, "WiFi.Phase2"));

	ssid->use_wps = connman_network_get_bool(network, "WiFi.UseWPS");
	ssid->pin_wps =
		g_strdup(connman_network_get_string(network, "WiFi.PinWPS"));

	if (connman_setting_get_bool("BackgroundScanning"))
		ssid->bgscan = g_strdup(BGSCAN_DEFAULT);
}

static int network_connect(struct connman_network *network)
{
	struct connman_device *device = connman_network_get_device(network);
	struct wifi_data *wifi;
	GSupplicantInterface *interface;
	GSupplicantSSID *ssid;

	DBG("network %p", network);

	if (!device)
		return -ENODEV;

	wifi = connman_device_get_data(device);
	if (!wifi)
		return -ENODEV;

	ssid = g_try_malloc0(sizeof(GSupplicantSSID));
	if (!ssid)
		return -ENOMEM;

	interface = wifi->interface;

	if (wifi->disconnecting) {
		wifi->pending_network = network;
		g_free(ssid);
	} else {
		ssid_init(ssid, network);

		wifi->network = connman_network_ref(network);
		wifi->retries = 0;

		return g_supplicant_interface_connect(interface, ssid,
						connect_callback, network);
	}

	return -EINPROGRESS;
}

static void disconnect_callback(int result, GSupplicantInterface *interface,
								void *user_data)
{
	struct disconnect_data *dd = user_data;
	struct connman_network *network = dd->network;
	struct wifi_data *wifi = dd->wifi;

	g_free(dd);

	DBG("result %d supplicant interface %p wifi %p networks: current %p "
		"pending %p disconnected %p", result, interface, wifi,
		wifi->network, wifi->pending_network, network);

	if (result == -ECONNABORTED) {
		DBG("wifi interface no longer available");
		return;
	}

	if (g_slist_find(wifi->networks, network))
		connman_network_set_connected(network, false);

	wifi->disconnecting = false;

	if (network != wifi->network) {
		if (network == wifi->pending_network)
			wifi->pending_network = NULL;
		DBG("current wifi network has changed since disconnection");
		return;
	}

	wifi->network = NULL;

	wifi->connected = false;

	if (wifi->pending_network) {
		network_connect(wifi->pending_network);
		wifi->pending_network = NULL;
	}

	start_autoscan(wifi->device);
}

static int network_disconnect(struct connman_network *network)
{
	struct connman_device *device = connman_network_get_device(network);
	struct disconnect_data *dd;
	struct wifi_data *wifi;
	int err;

	DBG("network %p", network);

	wifi = connman_device_get_data(device);
	if (!wifi || !wifi->interface)
		return -ENODEV;

	connman_network_set_associating(network, false);

	if (wifi->disconnecting)
		return -EALREADY;

	wifi->disconnecting = true;

	dd = g_malloc0(sizeof(*dd));
	dd->wifi = wifi;
	dd->network = network;

	err = g_supplicant_interface_disconnect(wifi->interface,
						disconnect_callback, dd);
	if (err < 0) {
		wifi->disconnecting = false;
		g_free(dd);
	}

	return err;
}

static struct connman_network_driver network_driver = {
	.name		= "wifi",
	.type		= CONNMAN_NETWORK_TYPE_WIFI,
	.priority	= CONNMAN_NETWORK_PRIORITY_LOW,
	.probe		= network_probe,
	.remove		= network_remove,
	.connect	= network_connect,
	.disconnect	= network_disconnect,
};

static void interface_added(GSupplicantInterface *interface)
{
	const char *ifname = g_supplicant_interface_get_ifname(interface);
	const char *driver = g_supplicant_interface_get_driver(interface);
	struct wifi_data *wifi;

	wifi = g_supplicant_interface_get_data(interface);
	if (!wifi) {
		wifi = get_pending_wifi_data(ifname);
		if (!wifi)
			return;

		wifi->interface = interface;
		g_supplicant_interface_set_data(interface, wifi);
		p2p_iface_list = g_list_append(p2p_iface_list, wifi);
		wifi->p2p_device = true;
	}

	DBG("ifname %s driver %s wifi %p tethering %d",
			ifname, driver, wifi, wifi->tethering);

	if (!wifi->device) {
		connman_error("WiFi device not set");
		return;
	}

	connman_device_set_powered(wifi->device, true);
}

static bool is_idle(struct wifi_data *wifi)
{
	DBG("state %d", wifi->state);

	switch (wifi->state) {
	case G_SUPPLICANT_STATE_UNKNOWN:
	case G_SUPPLICANT_STATE_DISABLED:
	case G_SUPPLICANT_STATE_DISCONNECTED:
	case G_SUPPLICANT_STATE_INACTIVE:
	case G_SUPPLICANT_STATE_SCANNING:
		return true;

	case G_SUPPLICANT_STATE_AUTHENTICATING:
	case G_SUPPLICANT_STATE_ASSOCIATING:
	case G_SUPPLICANT_STATE_ASSOCIATED:
	case G_SUPPLICANT_STATE_4WAY_HANDSHAKE:
	case G_SUPPLICANT_STATE_GROUP_HANDSHAKE:
	case G_SUPPLICANT_STATE_COMPLETED:
		return false;
	}

	return false;
}

static bool is_idle_wps(GSupplicantInterface *interface,
						struct wifi_data *wifi)
{
	/* First, let's check if WPS processing did not went wrong */
	if (g_supplicant_interface_get_wps_state(interface) ==
		G_SUPPLICANT_WPS_STATE_FAIL)
		return false;

	/* Unlike normal connection, being associated while processing wps
	 * actually means that we are idling. */
	switch (wifi->state) {
	case G_SUPPLICANT_STATE_UNKNOWN:
	case G_SUPPLICANT_STATE_DISABLED:
	case G_SUPPLICANT_STATE_DISCONNECTED:
	case G_SUPPLICANT_STATE_INACTIVE:
	case G_SUPPLICANT_STATE_SCANNING:
	case G_SUPPLICANT_STATE_ASSOCIATED:
		return true;
	case G_SUPPLICANT_STATE_AUTHENTICATING:
	case G_SUPPLICANT_STATE_ASSOCIATING:
	case G_SUPPLICANT_STATE_4WAY_HANDSHAKE:
	case G_SUPPLICANT_STATE_GROUP_HANDSHAKE:
	case G_SUPPLICANT_STATE_COMPLETED:
		return false;
	}

	return false;
}

static bool handle_wps_completion(GSupplicantInterface *interface,
					struct connman_network *network,
					struct connman_device *device,
					struct wifi_data *wifi)
{
	bool wps;

	wps = connman_network_get_bool(network, "WiFi.UseWPS");
	if (wps) {
		const unsigned char *ssid, *wps_ssid;
		unsigned int ssid_len, wps_ssid_len;
		struct disconnect_data *dd;
		const char *wps_key;

		/* Checking if we got associated with requested
		 * network */
		ssid = connman_network_get_blob(network, "WiFi.SSID",
						&ssid_len);

		wps_ssid = g_supplicant_interface_get_wps_ssid(
			interface, &wps_ssid_len);

		if (!wps_ssid || wps_ssid_len != ssid_len ||
				memcmp(ssid, wps_ssid, ssid_len) != 0) {
			dd = g_malloc0(sizeof(*dd));
			dd->wifi = wifi;
			dd->network = network;

			connman_network_set_associating(network, false);
			g_supplicant_interface_disconnect(wifi->interface,
						disconnect_callback, dd);
			return false;
		}

		wps_key = g_supplicant_interface_get_wps_key(interface);
		connman_network_set_string(network, "WiFi.Passphrase",
					wps_key);

		connman_network_set_string(network, "WiFi.PinWPS", NULL);
	}

	return true;
}

static bool handle_assoc_status_code(GSupplicantInterface *interface,
                                     struct wifi_data *wifi)
{
	if (wifi->state == G_SUPPLICANT_STATE_ASSOCIATING &&
			wifi->assoc_code == ASSOC_STATUS_NO_CLIENT &&
			wifi->load_shaping_retries < LOAD_SHAPING_MAX_RETRIES) {
		wifi->load_shaping_retries ++;
		return TRUE;
	}
	wifi->load_shaping_retries = 0;
	return FALSE;
}

static bool handle_4way_handshake_failure(GSupplicantInterface *interface,
					struct connman_network *network,
					struct wifi_data *wifi)
{
	struct connman_service *service;

	if ((wifi->state != G_SUPPLICANT_STATE_4WAY_HANDSHAKE) &&
			!((wifi->state == G_SUPPLICANT_STATE_ASSOCIATING) &&
				(wifi->assoc_code == ASSOC_STATUS_AUTH_TIMEOUT)))
		return false;

	if (wifi->connected)
		return false;

	service = connman_service_lookup_from_network(network);
	if (!service)
		return false;

	wifi->retries++;

	if (connman_service_get_favorite(service)) {
		if (wifi->retries < FAVORITE_MAXIMUM_RETRIES)
			return true;
	}

	wifi->retries = 0;
	connman_network_set_error(network, CONNMAN_NETWORK_ERROR_INVALID_KEY);

	return false;
}

static bool handle_sae_authentication_failure(struct connman_network *network,
					      struct wifi_data *wifi)
{
	struct wifi_network *network_data = connman_network_get_data(network);

	if (!(network_data->keymgmt & G_SUPPLICANT_KEYMGMT_SAE))
		return false;

	if (wifi->state != G_SUPPLICANT_STATE_AUTHENTICATING)
		return false;

	if (wifi->connected)
		return false;

	connman_network_set_error(network, CONNMAN_NETWORK_ERROR_INVALID_KEY);

	return true;
}

static void wifi_data_free_tethering_info(struct wifi_data *wifi)
{
	if (!wifi->tethering_param)
		return;

	g_assert(wifi->tethering_param->ssid == NULL);
	g_free(wifi->tethering_param);
	wifi->tethering_param = NULL;
}

static void interface_state(GSupplicantInterface *interface)
{
	struct connman_network *network;
	struct connman_device *device;
	struct wifi_data *wifi;
	GSupplicantState state = g_supplicant_interface_get_state(interface);
	bool wps;
	bool old_connected;

	wifi = g_supplicant_interface_get_data(interface);

	DBG("wifi %p interface state %d", wifi, state);

	if (!wifi)
		return;

	device = wifi->device;
	if (!device)
		return;

	if (state == G_SUPPLICANT_STATE_COMPLETED) {
		wifi_data_free_tethering_info(wifi);

		if (wifi->tethering)
			stop_autoscan(device);
	}

	if (g_supplicant_interface_get_ready(interface) &&
					!wifi->interface_ready) {
		wifi->interface_ready = true;
		finalize_interface_creation(wifi);
	}

	network = wifi->network;
	if (!network)
		return;

	switch (state) {
	case G_SUPPLICANT_STATE_SCANNING:
		if (wifi->connected)
			connman_network_set_connected(network, false);

		break;

	case G_SUPPLICANT_STATE_AUTHENTICATING:
	case G_SUPPLICANT_STATE_ASSOCIATING:
		stop_autoscan(device);

		if (!wifi->connected)
			connman_network_set_associating(network, true);

		break;

	case G_SUPPLICANT_STATE_COMPLETED:
		/* though it should be already stopped: */
		stop_autoscan(device);

		if (!handle_wps_completion(interface, network, device, wifi))
			break;

		connman_network_set_connected(network, true);

		wifi->disconnect_code = 0;
		wifi->assoc_code = 0;
		wifi->load_shaping_retries = 0;
		break;

	case G_SUPPLICANT_STATE_DISCONNECTED:
		/*
		 * If we're in one of the idle modes, we have
		 * not started association yet and thus setting
		 * those ones to FALSE could cancel an association
		 * in progress.
		 */
		wps = connman_network_get_bool(network, "WiFi.UseWPS");
		if (wps)
			if (is_idle_wps(interface, wifi))
				break;

		if (is_idle(wifi))
			break;

		if (handle_assoc_status_code(interface, wifi))
			break;

		/* If previous state was 4way-handshake, then
		 * it's either: psk was incorrect and thus we retry
		 * or if we reach the maximum retries we declare the
		 * psk as wrong */
		if (handle_4way_handshake_failure(interface,
						network, wifi))
			break;

		/*
		 * On WPA3-SAE authentication, wpa_supplicant goes directly from
		 * authenticating to disconnected state if the key was invalid.
		 */
		if (handle_sae_authentication_failure(network, wifi))
			break;

		/* See table 8-36 Reason codes in IEEE Std 802.11 */
		switch (wifi->disconnect_code) {
		case 6: /* Class 2 frame received from nonauthenticated STA */
			connman_network_set_error(network,
						CONNMAN_NETWORK_ERROR_BLOCKED);
			break;

		default:
			break;
		}

		if (network != wifi->pending_network) {
			connman_network_set_connected(network, false);
			connman_network_set_associating(network, false);
		}
		wifi->disconnecting = false;

		start_autoscan(device);

		break;

	case G_SUPPLICANT_STATE_INACTIVE:
		connman_network_set_associating(network, false);
		start_autoscan(device);

		break;

	case G_SUPPLICANT_STATE_UNKNOWN:
	case G_SUPPLICANT_STATE_DISABLED:
	case G_SUPPLICANT_STATE_ASSOCIATED:
	case G_SUPPLICANT_STATE_4WAY_HANDSHAKE:
	case G_SUPPLICANT_STATE_GROUP_HANDSHAKE:
		break;
	}

	old_connected = wifi->connected;
	wifi->state = state;

	/* Saving wpa_s state policy:
	 * If connected and if the state changes are roaming related:
	 * --> We stay connected
	 * If completed
	 * --> We are connected
	 * All other case:
	 * --> We are not connected
	 * */
	switch (state) {
	case G_SUPPLICANT_STATE_AUTHENTICATING:
	case G_SUPPLICANT_STATE_ASSOCIATING:
	case G_SUPPLICANT_STATE_ASSOCIATED:
	case G_SUPPLICANT_STATE_4WAY_HANDSHAKE:
	case G_SUPPLICANT_STATE_GROUP_HANDSHAKE:
		if (wifi->connected)
			connman_warn("Probably roaming right now!"
						" Staying connected...");
		break;
	case G_SUPPLICANT_STATE_SCANNING:
		wifi->connected = false;

		if (old_connected)
			start_autoscan(device);
		break;
	case G_SUPPLICANT_STATE_COMPLETED:
		wifi->connected = true;
		break;
	default:
		wifi->connected = false;
		break;
	}

	DBG("DONE");
}

static void interface_removed(GSupplicantInterface *interface)
{
	const char *ifname = g_supplicant_interface_get_ifname(interface);
	struct wifi_data *wifi;

	DBG("ifname %s", ifname);

	wifi = g_supplicant_interface_get_data(interface);

	if (wifi)
		wifi->interface = NULL;

	if (wifi && wifi->tethering)
		return;

	if (!wifi || !wifi->device) {
		DBG("wifi interface already removed");
		return;
	}

	connman_device_set_powered(wifi->device, false);

	check_p2p_technology();
}

static void set_device_type(const char *type, char dev_type[17])
{
	const char *oui = "0050F204";
	const char *category = "0001";
	const char *sub_category = "0000";

	if (!g_strcmp0(type, "handset")) {
		category = "000A";
		sub_category = "0005";
	} else if (!g_strcmp0(type, "vm") || !g_strcmp0(type, "container"))
		sub_category = "0001";
	else if (!g_strcmp0(type, "server"))
		sub_category = "0002";
	else if (!g_strcmp0(type, "laptop"))
		sub_category = "0005";
	else if (!g_strcmp0(type, "desktop"))
		sub_category = "0006";
	else if (!g_strcmp0(type, "tablet"))
		sub_category = "0009";
	else if (!g_strcmp0(type, "watch"))
		category = "00FF";

	snprintf(dev_type, 17, "%s%s%s", category, oui, sub_category);
}

static void p2p_support(GSupplicantInterface *interface)
{
	char dev_type[17] = {};
	const char *hostname;

	DBG("");

	if (!interface)
		return;

	if (!g_supplicant_interface_has_p2p(interface))
		return;

	if (connman_technology_driver_register(&p2p_tech_driver) < 0) {
		DBG("Could not register P2P technology driver");
		return;
	}

	hostname = connman_utsname_get_hostname();
	if (!hostname)
		hostname = "ConnMan";

	set_device_type(connman_machine_get_type(), dev_type);
	g_supplicant_interface_set_p2p_device_config(interface,
							hostname, dev_type);
	connman_peer_driver_register(&peer_driver);
}

static void scan_started(GSupplicantInterface *interface)
{
	DBG("");
}

static void scan_finished(GSupplicantInterface *interface)
{
	DBG("");
}

static void ap_create_fail(GSupplicantInterface *interface)
{
	struct wifi_data *wifi = g_supplicant_interface_get_data(interface);
	int ret;

	if ((wifi->tethering) && (wifi->tethering_param)) {
		DBG("%s create AP fail \n",
				g_supplicant_interface_get_ifname(wifi->interface));

		connman_inet_remove_from_bridge(wifi->index, wifi->bridge);
		wifi->ap_supported = WIFI_AP_NOT_SUPPORTED;
		wifi->tethering = false;

		ret = tech_set_tethering(wifi->tethering_param->technology,
				wifi->bridge, true);

		if ((ret == -EOPNOTSUPP) && (wifi_technology)) {
			connman_technology_tethering_notify(wifi_technology,false);
		}

		wifi_data_free_tethering_info(wifi);
	}
}

static unsigned char calculate_strength(GSupplicantNetwork *supplicant_network)
{
	unsigned char strength;

	strength = 120 + g_supplicant_network_get_signal(supplicant_network);
	if (strength > 100)
		strength = 100;

	return strength;
}

static void network_added(GSupplicantNetwork *supplicant_network)
{
	struct connman_network *network;
	GSupplicantInterface *interface;
	struct wifi_data *wifi;
	struct wifi_network *network_data;
	const char *name, *identifier, *security, *group, *mode;
	const unsigned char *ssid;
	unsigned int ssid_len;
	bool wps;
	bool wps_pbc;
	bool wps_ready;
	bool wps_advertizing;

	mode = g_supplicant_network_get_mode(supplicant_network);
	identifier = g_supplicant_network_get_identifier(supplicant_network);

	DBG("%s", identifier);

	if (!g_strcmp0(mode, "adhoc"))
		return;

	interface = g_supplicant_network_get_interface(supplicant_network);
	wifi = g_supplicant_interface_get_data(interface);
	name = g_supplicant_network_get_name(supplicant_network);
	security = g_supplicant_network_get_security(supplicant_network);
	group = g_supplicant_network_get_identifier(supplicant_network);
	wps = g_supplicant_network_get_wps(supplicant_network);
	wps_pbc = g_supplicant_network_is_wps_pbc(supplicant_network);
	wps_ready = g_supplicant_network_is_wps_active(supplicant_network);
	wps_advertizing = g_supplicant_network_is_wps_advertizing(
							supplicant_network);

	if (!wifi)
		return;

	ssid = g_supplicant_network_get_ssid(supplicant_network, &ssid_len);

	network = connman_device_get_network(wifi->device, identifier);

	if (!network) {
		network = connman_network_create(identifier,
						CONNMAN_NETWORK_TYPE_WIFI);
		if (!network)
			return;

		connman_network_set_index(network, wifi->index);

		if (connman_device_add_network(wifi->device, network) < 0) {
			connman_network_unref(network);
			return;
		}

		wifi->networks = g_slist_prepend(wifi->networks, network);

		network_data = g_new0(struct wifi_network, 1);
		connman_network_set_data(network, network_data);
	}

	network_data = connman_network_get_data(network);
	network_data->keymgmt =
		g_supplicant_network_get_keymgmt(supplicant_network);

	if (name && name[0] != '\0')
		connman_network_set_name(network, name);

	connman_network_set_blob(network, "WiFi.SSID",
						ssid, ssid_len);
	connman_network_set_string(network, "WiFi.Security", security);
	connman_network_set_strength(network,
				calculate_strength(supplicant_network));
	connman_network_set_bool(network, "WiFi.WPS", wps);
	connman_network_set_bool(network, "WiFi.WPSAdvertising",
				wps_advertizing);

	if (wps) {
		/* Is AP advertizing for WPS association?
		 * If so, we decide to use WPS by default */
		if (wps_ready && wps_pbc &&
						wps_advertizing)
			connman_network_set_bool(network, "WiFi.UseWPS", true);
	}

	connman_network_set_frequency(network,
			g_supplicant_network_get_frequency(supplicant_network));

	connman_network_set_available(network, true);
	connman_network_set_string(network, "WiFi.Mode", mode);

	if (ssid)
		connman_network_set_group(network, group);

	if (wifi->hidden && ssid) {
		if (!g_strcmp0(wifi->hidden->security, security) &&
				wifi->hidden->ssid_len == ssid_len &&
				!memcmp(wifi->hidden->ssid, ssid, ssid_len)) {
			connman_network_connect_hidden(network,
					wifi->hidden->identity,
					wifi->hidden->passphrase,
					wifi->hidden->user_data);
			wifi->hidden->user_data = NULL;
			hidden_free(wifi->hidden);
			wifi->hidden = NULL;
		}
	}
}

static void network_removed(GSupplicantNetwork *network)
{
	GSupplicantInterface *interface;
	struct wifi_data *wifi;
	const char *name, *identifier;
	struct connman_network *connman_network;

	interface = g_supplicant_network_get_interface(network);
	wifi = g_supplicant_interface_get_data(interface);
	identifier = g_supplicant_network_get_identifier(network);
	name = g_supplicant_network_get_name(network);

	DBG("name %s", name);

	if (!wifi)
		return;

	connman_network = connman_device_get_network(wifi->device, identifier);
	if (!connman_network)
		return;

	wifi->networks = g_slist_remove(wifi->networks, connman_network);

	g_free(connman_network_get_data(connman_network));
	connman_device_remove_network(wifi->device, connman_network);
	connman_network_unref(connman_network);
}

static void network_changed(GSupplicantNetwork *network, const char *property)
{
	GSupplicantInterface *interface;
	struct wifi_data *wifi;
	const char *name, *identifier;
	struct connman_network *connman_network;
	bool update_needed;

	interface = g_supplicant_network_get_interface(network);
	wifi = g_supplicant_interface_get_data(interface);
	identifier = g_supplicant_network_get_identifier(network);
	name = g_supplicant_network_get_name(network);

	DBG("name %s", name);

	if (!wifi)
		return;

	connman_network = connman_device_get_network(wifi->device, identifier);
	if (!connman_network)
		return;

	if (g_str_equal(property, "WPSCapabilities")) {
		bool wps;
		bool wps_pbc;
		bool wps_ready;
		bool wps_advertizing;

		wps = g_supplicant_network_get_wps(network);
		wps_pbc = g_supplicant_network_is_wps_pbc(network);
		wps_ready = g_supplicant_network_is_wps_active(network);
		wps_advertizing =
			g_supplicant_network_is_wps_advertizing(network);

		connman_network_set_bool(connman_network, "WiFi.WPS", wps);
		connman_network_set_bool(connman_network,
				"WiFi.WPSAdvertising", wps_advertizing);

		if (wps) {
			/*
			 * Is AP advertizing for WPS association?
			 * If so, we decide to use WPS by default
			 */
			if (wps_ready && wps_pbc && wps_advertizing)
				connman_network_set_bool(connman_network,
							"WiFi.UseWPS", true);
		}

		update_needed = true;
	} else if (g_str_equal(property, "Signal")) {
		connman_network_set_strength(connman_network,
					calculate_strength(network));
		update_needed = true;
	} else
		update_needed = false;

	if (update_needed)
		connman_network_update(connman_network);
}

static void network_associated(GSupplicantNetwork *network)
{
	GSupplicantInterface *interface;
	struct wifi_data *wifi;
	struct connman_network *connman_network;
	const char *identifier;

	DBG("");

	interface = g_supplicant_network_get_interface(network);
	if (!interface)
		return;

	wifi = g_supplicant_interface_get_data(interface);
	if (!wifi)
		return;

	/* P2P networks must not be treated as WiFi networks */
	if (wifi->p2p_connecting || wifi->p2p_device)
		return;

	identifier = g_supplicant_network_get_identifier(network);

	connman_network = connman_device_get_network(wifi->device, identifier);
	if (!connman_network)
		return;

	if (wifi->network) {
		if (wifi->network == connman_network)
			return;

		/*
		 * This should never happen, we got associated with
		 * a network different than the one we were expecting.
		 */
		DBG("Associated to %p while expecting %p",
					connman_network, wifi->network);

		connman_network_set_associating(wifi->network, false);
	}

	DBG("Reconnecting to previous network %p from wpa_s", connman_network);

	wifi->network = connman_network_ref(connman_network);
	wifi->retries = 0;

	/*
	 * Interface state changes callback (interface_state) is always
	 * called before network_associated callback thus we need to call
	 * interface_state again in order to process the new state now that
	 * we have the network properly set.
	 */
	interface_state(interface);
}

static void sta_authorized(GSupplicantInterface *interface,
					const char *addr)
{
	struct wifi_data *wifi = g_supplicant_interface_get_data(interface);

	DBG("wifi %p station %s authorized", wifi, addr);

	if (!wifi || !wifi->tethering)
		return;

	__connman_tethering_client_register(addr);
}

static void sta_deauthorized(GSupplicantInterface *interface,
					const char *addr)
{
	struct wifi_data *wifi = g_supplicant_interface_get_data(interface);

	DBG("wifi %p station %s deauthorized", wifi, addr);

	if (!wifi || !wifi->tethering)
		return;

	__connman_tethering_client_unregister(addr);
}

static void apply_peer_services(GSupplicantPeer *peer,
				struct connman_peer *connman_peer)
{
	const unsigned char *data;
	int length;

	DBG("");

	connman_peer_reset_services(connman_peer);

	data = g_supplicant_peer_get_widi_ies(peer, &length);
	if (data) {
		connman_peer_add_service(connman_peer,
			CONNMAN_PEER_SERVICE_WIFI_DISPLAY, data, length);
	}
}

static void peer_found(GSupplicantPeer *peer)
{
	GSupplicantInterface *iface = g_supplicant_peer_get_interface(peer);
	struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
	struct connman_peer *connman_peer;
	const char *identifier, *name;
	int ret;

	identifier = g_supplicant_peer_get_identifier(peer);
	name = g_supplicant_peer_get_name(peer);

	DBG("ident: %s", identifier);

	connman_peer = connman_peer_get(wifi->device, identifier);
	if (connman_peer)
		return;

	connman_peer = connman_peer_create(identifier);
	connman_peer_set_name(connman_peer, name);
	connman_peer_set_device(connman_peer, wifi->device);
	apply_peer_services(peer, connman_peer);

	ret = connman_peer_register(connman_peer);
	if (ret < 0 && ret != -EALREADY)
		connman_peer_unref(connman_peer);
	else
		wifi->peers = g_slist_prepend(wifi->peers, connman_peer);
}

static void peer_lost(GSupplicantPeer *peer)
{
	GSupplicantInterface *iface = g_supplicant_peer_get_interface(peer);
	struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
	struct connman_peer *connman_peer;
	const char *identifier;

	if (!wifi)
		return;

	identifier = g_supplicant_peer_get_identifier(peer);

	DBG("ident: %s", identifier);

	connman_peer = connman_peer_get(wifi->device, identifier);
	if (connman_peer) {
		if (wifi->p2p_connecting &&
				wifi->pending_peer == connman_peer) {
			peer_connect_timeout(wifi);
		}
		connman_peer_unregister(connman_peer);
		connman_peer_unref(connman_peer);
	}

	wifi->peers = g_slist_remove(wifi->peers, connman_peer);
}

static void peer_changed(GSupplicantPeer *peer, GSupplicantPeerState state)
{
	GSupplicantInterface *iface = g_supplicant_peer_get_interface(peer);
	struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
	enum connman_peer_state p_state = CONNMAN_PEER_STATE_UNKNOWN;
	struct connman_peer *connman_peer;
	const char *identifier;

	identifier = g_supplicant_peer_get_identifier(peer);

	DBG("ident: %s", identifier);

	if (!wifi)
		return;

	connman_peer = connman_peer_get(wifi->device, identifier);
	if (!connman_peer)
		return;

	switch (state) {
	case G_SUPPLICANT_PEER_SERVICES_CHANGED:
		apply_peer_services(peer, connman_peer);
		connman_peer_services_changed(connman_peer);
		return;
	case G_SUPPLICANT_PEER_GROUP_CHANGED:
		if (!g_supplicant_peer_is_in_a_group(peer))
			p_state = CONNMAN_PEER_STATE_IDLE;
		else
			p_state = CONNMAN_PEER_STATE_CONFIGURATION;
		break;
	case G_SUPPLICANT_PEER_GROUP_STARTED:
		break;
	case G_SUPPLICANT_PEER_GROUP_FINISHED:
		p_state = CONNMAN_PEER_STATE_IDLE;
		break;
	case G_SUPPLICANT_PEER_GROUP_JOINED:
		connman_peer_set_iface_address(connman_peer,
				g_supplicant_peer_get_iface_address(peer));
		break;
	case G_SUPPLICANT_PEER_GROUP_DISCONNECTED:
		p_state = CONNMAN_PEER_STATE_IDLE;
		break;
	case G_SUPPLICANT_PEER_GROUP_FAILED:
		if (g_supplicant_peer_has_requested_connection(peer))
			p_state = CONNMAN_PEER_STATE_IDLE;
		else
			p_state = CONNMAN_PEER_STATE_FAILURE;
		break;
	}

	if (p_state == CONNMAN_PEER_STATE_CONFIGURATION ||
					p_state == CONNMAN_PEER_STATE_FAILURE) {
		if (wifi->p2p_connecting
				&& connman_peer == wifi->pending_peer)
			peer_cancel_timeout(wifi);
		else
			p_state = CONNMAN_PEER_STATE_UNKNOWN;
	}

	if (p_state == CONNMAN_PEER_STATE_UNKNOWN)
		return;

	if (p_state == CONNMAN_PEER_STATE_CONFIGURATION) {
		GSupplicantInterface *g_iface;
		struct wifi_data *g_wifi;

		g_iface = g_supplicant_peer_get_group_interface(peer);
		if (!g_iface)
			return;

		g_wifi = g_supplicant_interface_get_data(g_iface);
		if (!g_wifi)
			return;

		connman_peer_set_as_master(connman_peer,
					!g_supplicant_peer_is_client(peer));
		connman_peer_set_sub_device(connman_peer, g_wifi->device);

		/*
		 * If wpa_supplicant didn't create a dedicated p2p-group
		 * interface then mark this interface as p2p_device to avoid
		 * scan and auto-scan are launched on it while P2P is connected.
		 */
		if (!g_list_find(p2p_iface_list, g_wifi))
			wifi->p2p_device = true;
	}

	connman_peer_set_state(connman_peer, p_state);
}

static void peer_request(GSupplicantPeer *peer)
{
	GSupplicantInterface *iface = g_supplicant_peer_get_interface(peer);
	struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
	struct connman_peer *connman_peer;
	const char *identifier;

	identifier = g_supplicant_peer_get_identifier(peer);

	DBG("ident: %s", identifier);

	connman_peer = connman_peer_get(wifi->device, identifier);
	if (!connman_peer)
		return;

	connman_peer_request_connection(connman_peer);
}

static void debug(const char *str)
{
	if (getenv("CONNMAN_SUPPLICANT_DEBUG"))
		connman_debug("%s", str);
}

static void disconnect_reasoncode(GSupplicantInterface *interface,
				int reasoncode)
{
	struct wifi_data *wifi = g_supplicant_interface_get_data(interface);

	if (wifi != NULL) {
		wifi->disconnect_code = reasoncode;
	}
}

static void assoc_status_code(GSupplicantInterface *interface, int status_code)
{
	struct wifi_data *wifi = g_supplicant_interface_get_data(interface);

	if (wifi != NULL) {
		wifi->assoc_code = status_code;
	}
}

static const GSupplicantCallbacks callbacks = {
	.system_ready		= system_ready,
	.system_killed		= system_killed,
	.interface_added	= interface_added,
	.interface_state	= interface_state,
	.interface_removed	= interface_removed,
	.p2p_support		= p2p_support,
	.scan_started		= scan_started,
	.scan_finished		= scan_finished,
	.ap_create_fail		= ap_create_fail,
	.network_added		= network_added,
	.network_removed	= network_removed,
	.network_changed	= network_changed,
	.network_associated	= network_associated,
	.sta_authorized		= sta_authorized,
	.sta_deauthorized	= sta_deauthorized,
	.peer_found		= peer_found,
	.peer_lost		= peer_lost,
	.peer_changed		= peer_changed,
	.peer_request		= peer_request,
	.debug			= debug,
	.disconnect_reasoncode  = disconnect_reasoncode,
	.assoc_status_code      = assoc_status_code,
};


static int tech_probe(struct connman_technology *technology)
{
	wifi_technology = technology;

	return 0;
}

static void tech_remove(struct connman_technology *technology)
{
	wifi_technology = NULL;
}

static GSupplicantSSID *ssid_ap_init(const struct connman_technology *technology)
{
	GSupplicantSSID *ap;
	const char *ssid, *passphrase;
	int freq;
	bool ret;

	ap = g_try_malloc0(sizeof(GSupplicantSSID));
	if (!ap)
		return NULL;

	ret = connman_technology_get_wifi_tethering(technology,
						&ssid, &passphrase,
						&freq);
	if (ret == false) {
		g_free(ap);
		return NULL;
	}

	ap->mode = G_SUPPLICANT_MODE_MASTER;
	ap->ssid = g_strdup(ssid);
	ap->ssid_len = strlen(ssid);
	ap->scan_ssid = 0;
	if (freq)
		ap->freq = freq;
	else
		ap->freq = 2412;

	if (!passphrase || strlen(passphrase) == 0) {
		ap->security = G_SUPPLICANT_SECURITY_NONE;
		ap->passphrase = NULL;
	} else {
		ap->security = G_SUPPLICANT_SECURITY_PSK;
		ap->protocol = G_SUPPLICANT_PROTO_RSN;
		ap->pairwise_cipher = G_SUPPLICANT_PAIRWISE_CCMP;
		ap->group_cipher = G_SUPPLICANT_GROUP_CCMP;
		ap->passphrase = g_strdup(passphrase);
	}

	return ap;
}

static void ap_start_callback(int result, GSupplicantInterface *interface,
							void *user_data)
{
	struct wifi_tethering_info *info = user_data;

	DBG("result %d index %d bridge %s",
		result, info->wifi->index, info->wifi->bridge);

	if ((result < 0) || (info->wifi->ap_supported != WIFI_AP_SUPPORTED)) {
		connman_inet_remove_from_bridge(info->wifi->index,
							info->wifi->bridge);

		if (info->wifi->ap_supported == WIFI_AP_SUPPORTED) {
			connman_technology_tethering_notify(info->technology, false);
			wifi_data_free_tethering_info(info->wifi);
		}
	}

	g_free(info->ifname);
	g_free(info);
}

static void ap_create_callback(int result,
				GSupplicantInterface *interface,
					void *user_data)
{
	struct wifi_tethering_info *info = user_data;

	DBG("result %d ifname %s", result,
				g_supplicant_interface_get_ifname(interface));

	if ((result < 0) || (info->wifi->ap_supported != WIFI_AP_SUPPORTED)) {
		connman_inet_remove_from_bridge(info->wifi->index,
							info->wifi->bridge);

		if (info->wifi->ap_supported == WIFI_AP_SUPPORTED) {
			connman_technology_tethering_notify(info->technology, false);
			wifi_data_free_tethering_info(info->wifi);
		}

		g_free(info->ifname);
		g_free(info);
		return;
	}

	info->wifi->interface = interface;
	g_supplicant_interface_set_data(interface, info->wifi);

	if (g_supplicant_interface_set_apscan(interface, 2) < 0)
		connman_error("Failed to set interface ap_scan property");

	g_supplicant_interface_connect(interface, info->ssid,
						ap_start_callback, info);
}

static void sta_remove_callback(int result,
				GSupplicantInterface *interface,
					void *user_data)
{
	struct wifi_tethering_info *info = user_data;
	const char *driver = connman_setting_get_string("wifi");

	DBG("ifname %s result %d ", info->ifname, result);

	if ((result < 0) || (info->wifi->ap_supported != WIFI_AP_SUPPORTED)) {
		info->wifi->tethering = false;
		connman_technology_tethering_notify(info->technology, false);

		if (info->wifi->ap_supported == WIFI_AP_SUPPORTED)
			wifi_data_free_tethering_info(info->wifi);

		g_free(info->ifname);
		g_free(info);
		return;
	}

	info->wifi->interface = NULL;

	g_supplicant_interface_create(info->ifname, driver, info->wifi->bridge,
						ap_create_callback,
							info);
}

static int enable_wifi_tethering(struct connman_technology *technology,
				const char *bridge, bool available)
{
	GList *list;
	GSupplicantInterface *interface;
	struct wifi_data *wifi;
	struct wifi_tethering_info *info;
	const char *ifname;
	unsigned int mode;
	int err, berr = 0;

	for (list = iface_list; list; list = list->next) {
		wifi = list->data;

		DBG("wifi %p network %p pending_network %p", wifi,
			wifi->network, wifi->pending_network);

		interface = wifi->interface;

		if (!interface)
			continue;

		ifname = g_supplicant_interface_get_ifname(wifi->interface);
		if (!ifname)
			continue;

		if (wifi->ap_supported == WIFI_AP_NOT_SUPPORTED) {
			DBG("%s does not support AP mode (detected)", ifname);
			continue;
		}

		mode = g_supplicant_interface_get_mode(interface);
		if ((mode & G_SUPPLICANT_CAPABILITY_MODE_AP) == 0) {
			wifi->ap_supported = WIFI_AP_NOT_SUPPORTED;
			DBG("%s does not support AP mode (capability)", ifname);
			continue;
		}

		if (wifi->network && available)
			continue;

		info = g_try_malloc0(sizeof(struct wifi_tethering_info));
		if (!info)
			return -ENOMEM;

		wifi->tethering_param = g_try_malloc0(sizeof(struct wifi_tethering_info));
		if (!wifi->tethering_param) {
			g_free(info);
			return -ENOMEM;
		}

		info->wifi = wifi;
		info->technology = technology;
		info->wifi->bridge = bridge;
		info->ssid = ssid_ap_init(technology);
		if (!info->ssid)
			goto failed;

		info->ifname = g_strdup(ifname);

		wifi->tethering_param->technology = technology;

		info->wifi->tethering = true;
		info->wifi->ap_supported = WIFI_AP_SUPPORTED;

		berr = connman_technology_tethering_notify(technology, true);
		if (berr < 0)
			goto failed;

		err = g_supplicant_interface_remove(interface,
						sta_remove_callback,
							info);
		if (err >= 0) {
			DBG("tethering wifi %p ifname %s", wifi, ifname);
			return 0;
		}

	failed:
		g_free(info->ifname);
		if (info->ssid) {
			g_free((char *)info->ssid->ssid);
			g_free((char *)info->ssid->passphrase);
			g_free(info->ssid);
		}
		g_free(info);
		wifi_data_free_tethering_info(wifi);

		/*
		 * Remove bridge if it was correctly created but remove
		 * operation failed. Instead, if bridge creation failed then
		 * break out and do not try again on another interface,
		 * bridge set-up does not depend on it.
		 */
		if (berr == 0)
			connman_technology_tethering_notify(technology, false);
		else
			break;
	}

	return -EOPNOTSUPP;
}

static int tech_set_tethering(struct connman_technology *technology,
				const char *bridge, bool enabled)
{
	GList *list;
	struct wifi_data *wifi;
	int err;

	DBG("");

	if (!enabled) {
		for (list = iface_list; list; list = list->next) {
			wifi = list->data;

			if (wifi->tethering) {
				wifi->tethering = false;

				connman_inet_remove_from_bridge(wifi->index,
									bridge);
				wifi->bridged = false;
			}
		}

		connman_technology_tethering_notify(technology, false);

		return 0;
	}

	DBG("trying tethering for available devices");
	err = enable_wifi_tethering(technology, bridge, true);

	if (err < 0) {
		DBG("trying tethering for any device");
		err = enable_wifi_tethering(technology, bridge, false);
	}

	return err;
}

static void regdom_callback(int result, const char *alpha2, void *user_data)
{
	DBG("");

	if (!wifi_technology)
		return;

	if (result != 0)
		alpha2 = NULL;

	connman_technology_regdom_notify(wifi_technology, alpha2);
}

static int tech_set_regdom(struct connman_technology *technology, const char *alpha2)
{
	return g_supplicant_set_country(alpha2, regdom_callback, NULL);
}

static struct connman_technology_driver tech_driver = {
	.name		= "wifi",
	.type		= CONNMAN_SERVICE_TYPE_WIFI,
	.probe		= tech_probe,
	.remove		= tech_remove,
	.set_tethering	= tech_set_tethering,
	.set_regdom	= tech_set_regdom,
};

static int wifi_init(void)
{
	int err;

	err = connman_network_driver_register(&network_driver);
	if (err < 0)
		return err;

	err = g_supplicant_register(&callbacks);
	if (err < 0) {
		connman_network_driver_unregister(&network_driver);
		return err;
	}

	err = connman_technology_driver_register(&tech_driver);
	if (err < 0) {
		g_supplicant_unregister(&callbacks);
		connman_network_driver_unregister(&network_driver);
		return err;
	}

	return 0;
}

static void wifi_exit(void)
{
	DBG();

	connman_technology_driver_unregister(&tech_driver);

	g_supplicant_unregister(&callbacks);

	connman_network_driver_unregister(&network_driver);
}

CONNMAN_PLUGIN_DEFINE(wifi, "WiFi interface plugin", VERSION,
		CONNMAN_PLUGIN_PRIORITY_DEFAULT, wifi_init, wifi_exit)
