// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2004 - 2017 Red Hat, Inc.
 * Copyright (C) 2006 - 2008 Novell, Inc.
 */

#include "nm-default.h"

#include "nm-wifi-ap.h"

#include <stdlib.h>

#include "NetworkManagerUtils.h"
#include "devices/nm-device.h"
#include "nm-core-internal.h"
#include "nm-dbus-manager.h"
#include "nm-glib-aux/nm-ref-string.h"
#include "nm-setting-wireless.h"
#include "nm-utils.h"
#include "nm-wifi-utils.h"
#include "platform/nm-platform.h"
#include "supplicant/nm-supplicant-interface.h"

#define PROTO_WPA "wpa"
#define PROTO_RSN "rsn"

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

NM_GOBJECT_PROPERTIES_DEFINE (NMWifiAP,
	PROP_FLAGS,
	PROP_WPA_FLAGS,
	PROP_RSN_FLAGS,
	PROP_SSID,
	PROP_FREQUENCY,
	PROP_HW_ADDRESS,
	PROP_MODE,
	PROP_MAX_BITRATE,
	PROP_STRENGTH,
	PROP_LAST_SEEN,
);

struct _NMWifiAPPrivate {
	/* Scanned or cached values */
	GBytes *           ssid;
	char *             address;
	NM80211Mode        mode;
	guint8             strength;
	guint32            freq;        /* Frequency in MHz; ie 2412 (== 2.412 GHz) */
	guint32            max_bitrate; /* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */

	gint64             last_seen_msec; /* Timestamp when the AP was seen lastly (in nm_utils_get_monotonic_timestamp_*() scale).
	                                    * Note that this value might be negative! */

	NM80211ApFlags         flags;      /* General flags */
	NM80211ApSecurityFlags wpa_flags;  /* WPA-related flags */
	NM80211ApSecurityFlags rsn_flags;  /* RSN (WPA2) -related flags */

	bool               metered:1;

	/* Non-scanned attributes */
	bool               fake:1;       /* Whether or not the AP is from a scan */
	bool               hotspot:1;    /* Whether the AP is a local device's hotspot network */
};

typedef struct _NMWifiAPPrivate NMWifiAPPrivate;

struct _NMWifiAPClass {
	NMDBusObjectClass parent;
};

G_DEFINE_TYPE (NMWifiAP, nm_wifi_ap, NM_TYPE_DBUS_OBJECT)

#define NM_WIFI_AP_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMWifiAP, NM_IS_WIFI_AP)

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

GBytes *
nm_wifi_ap_get_ssid (const NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);

	return NM_WIFI_AP_GET_PRIVATE (ap)->ssid;
}

gboolean
nm_wifi_ap_set_ssid_arr (NMWifiAP *ap,
                         const guint8 *ssid,
                         gsize ssid_len)
{
	NMWifiAPPrivate *priv;

	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);

	if (ssid_len > 32)
		g_return_val_if_reached (FALSE);

	priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (nm_utils_gbytes_equal_mem (priv->ssid, ssid, ssid_len))
		return FALSE;

	nm_clear_pointer (&priv->ssid, g_bytes_unref);
	if (ssid_len > 0)
		priv->ssid = g_bytes_new (ssid, ssid_len);

	_notify (ap, PROP_SSID);
	return TRUE;
}

gboolean
nm_wifi_ap_set_ssid (NMWifiAP *ap, GBytes *ssid)
{
	NMWifiAPPrivate *priv;
	gsize l;

	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);

	if (ssid) {
		l = g_bytes_get_size (ssid);
		if (l == 0 || l > 32)
			g_return_val_if_reached (FALSE);
	}

	priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (ssid == priv->ssid)
		return FALSE;
	if (   ssid
	    && priv->ssid
	    && g_bytes_equal (ssid, priv->ssid))
		return FALSE;

	nm_clear_pointer (&priv->ssid, g_bytes_unref);
	if (ssid)
		priv->ssid = g_bytes_ref (ssid);

	_notify (ap, PROP_SSID);
	return TRUE;
}

static gboolean
nm_wifi_ap_set_flags (NMWifiAP *ap, NM80211ApFlags flags)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (priv->flags != flags) {
		priv->flags = flags;
		_notify (ap, PROP_FLAGS);
		return TRUE;
	}
	return FALSE;
}

static gboolean
nm_wifi_ap_set_wpa_flags (NMWifiAP *ap, NM80211ApSecurityFlags flags)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (priv->wpa_flags != flags) {
		priv->wpa_flags = flags;
		_notify (ap, PROP_WPA_FLAGS);
		return TRUE;
	}
	return FALSE;
}

static gboolean
nm_wifi_ap_set_rsn_flags (NMWifiAP *ap, NM80211ApSecurityFlags flags)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (priv->rsn_flags != flags) {
		priv->rsn_flags = flags;
		_notify (ap, PROP_RSN_FLAGS);
		return TRUE;
	}
	return FALSE;
}

const char *
nm_wifi_ap_get_address (const NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);

	return NM_WIFI_AP_GET_PRIVATE (ap)->address;
}

static gboolean
nm_wifi_ap_set_address_bin (NMWifiAP *ap, const guint8 addr[static 6 /* ETH_ALEN */])
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (   !priv->address
	    || !nm_utils_hwaddr_matches (addr, ETH_ALEN, priv->address, -1)) {
		g_free (priv->address);
		priv->address = nm_utils_hwaddr_ntoa (addr, ETH_ALEN);
		_notify (ap, PROP_HW_ADDRESS);
		return TRUE;
	}
	return FALSE;
}

gboolean
nm_wifi_ap_set_address (NMWifiAP *ap, const char *addr)
{
	guint8 addr_buf[ETH_ALEN];

	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
	if (   !addr
	    || !nm_utils_hwaddr_aton (addr, addr_buf, sizeof (addr_buf)))
		g_return_val_if_reached (FALSE);

	return nm_wifi_ap_set_address_bin (ap, addr_buf);
}

NM80211Mode
nm_wifi_ap_get_mode (NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NM_802_11_MODE_UNKNOWN);

	return NM_WIFI_AP_GET_PRIVATE (ap)->mode;
}

static gboolean
nm_wifi_ap_set_mode (NMWifiAP *ap, NM80211Mode mode)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);

	nm_assert (NM_IN_SET (mode, NM_802_11_MODE_UNKNOWN,
	                            NM_802_11_MODE_ADHOC,
	                            NM_802_11_MODE_INFRA,
	                            NM_802_11_MODE_MESH));

	if (priv->mode != mode) {
		priv->mode = mode;
		_notify (ap, PROP_MODE);
		return TRUE;
	}
	return FALSE;
}

gboolean
nm_wifi_ap_is_hotspot (NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);

	return NM_WIFI_AP_GET_PRIVATE (ap)->hotspot;
}

gint8
nm_wifi_ap_get_strength (NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), 0);

	return NM_WIFI_AP_GET_PRIVATE (ap)->strength;
}

gboolean
nm_wifi_ap_set_strength (NMWifiAP *ap, gint8 strength)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (priv->strength != strength) {
		priv->strength = strength;
		_notify (ap, PROP_STRENGTH);
		return TRUE;
	}
	return FALSE;
}

guint32
nm_wifi_ap_get_freq (NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), 0);

	return NM_WIFI_AP_GET_PRIVATE (ap)->freq;
}

gboolean
nm_wifi_ap_set_freq (NMWifiAP *ap,
                     guint32 freq)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (priv->freq != freq) {
		priv->freq = freq;
		_notify (ap, PROP_FREQUENCY);
		return TRUE;
	}
	return FALSE;
}

guint32
nm_wifi_ap_get_max_bitrate (NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), 0);
	g_return_val_if_fail (nm_dbus_object_is_exported (NM_DBUS_OBJECT (ap)), 0);

	return NM_WIFI_AP_GET_PRIVATE (ap)->max_bitrate;
}

gboolean
nm_wifi_ap_set_max_bitrate (NMWifiAP *ap, guint32 bitrate)
{
	NMWifiAPPrivate *priv;

	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);

	priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (priv->max_bitrate != bitrate) {
		priv->max_bitrate = bitrate;
		_notify (ap, PROP_MAX_BITRATE);
		return TRUE;
	}
	return FALSE;
}

gboolean
nm_wifi_ap_get_fake (const NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);

	return NM_WIFI_AP_GET_PRIVATE (ap)->fake;
}

gboolean
nm_wifi_ap_set_fake (NMWifiAP *ap, gboolean fake)
{
	NMWifiAPPrivate *priv;

	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);

	priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (priv->fake != !!fake) {
		priv->fake = fake;
		return TRUE;
	}
	return FALSE;
}

NM80211ApFlags
nm_wifi_ap_get_flags (const NMWifiAP *ap)
{
	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NM_802_11_AP_FLAGS_NONE);

	return NM_WIFI_AP_GET_PRIVATE (ap)->flags;
}

static gboolean
nm_wifi_ap_set_last_seen (NMWifiAP *ap, gint32 last_seen_msec)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);

	if (priv->last_seen_msec != last_seen_msec) {
		priv->last_seen_msec = last_seen_msec;
		_notify (ap, PROP_LAST_SEEN);
		return TRUE;
	}
	return FALSE;
}

gboolean
nm_wifi_ap_get_metered (const NMWifiAP *self)
{
	return NM_WIFI_AP_GET_PRIVATE (self)->metered;
}

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

gboolean
nm_wifi_ap_update_from_properties (NMWifiAP *ap,
                                   const NMSupplicantBssInfo *bss_info)
{
	NMWifiAPPrivate *priv;
	gboolean changed = FALSE;

	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
	g_return_val_if_fail (bss_info, FALSE);
	nm_assert (NM_IS_REF_STRING (bss_info->bss_path));

	priv = NM_WIFI_AP_GET_PRIVATE (ap);

	nm_assert (   !ap->_supplicant_path
	           || ap->_supplicant_path == bss_info->bss_path);

	g_object_freeze_notify (G_OBJECT (ap));

	if (!ap->_supplicant_path) {
		ap->_supplicant_path = nm_ref_string_ref (bss_info->bss_path);
		changed = TRUE;
	}

	changed |= nm_wifi_ap_set_flags (ap, bss_info->ap_flags);
	changed |= nm_wifi_ap_set_mode (ap, bss_info->mode);
	changed |= nm_wifi_ap_set_strength (ap, bss_info->signal_percent);
	changed |= nm_wifi_ap_set_freq (ap, bss_info->frequency);
	changed |= nm_wifi_ap_set_ssid (ap, bss_info->ssid);

	if (bss_info->bssid_valid)
		changed |= nm_wifi_ap_set_address_bin (ap, bss_info->bssid);
	else {
		/* we don't actually clear the value. */
	}

	changed |= nm_wifi_ap_set_max_bitrate (ap, bss_info->max_rate);

	if (priv->metered != bss_info->metered) {
		priv->metered = bss_info->metered;
		changed = TRUE;
	}

	changed |= nm_wifi_ap_set_wpa_flags (ap, bss_info->wpa_flags);
	changed |= nm_wifi_ap_set_rsn_flags (ap, bss_info->rsn_flags);

	changed |= nm_wifi_ap_set_last_seen (ap, bss_info->last_seen_msec);

	changed |= nm_wifi_ap_set_fake (ap, FALSE);

	g_object_thaw_notify (G_OBJECT (ap));

	return changed;
}

static gboolean
has_proto (NMSettingWirelessSecurity *sec, const char *proto)
{
	guint32 num_protos = nm_setting_wireless_security_get_num_protos (sec);
	guint32 i;

	if (num_protos == 0)
		return TRUE; /* interpret no protos as "all" */

	for (i = 0; i < num_protos; i++) {
		if (!strcmp (nm_setting_wireless_security_get_proto (sec, i), proto))
			return TRUE;
	}
	return FALSE;
}

static void
add_pair_ciphers (NMWifiAP *ap, NMSettingWirelessSecurity *sec)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
	guint32 num = nm_setting_wireless_security_get_num_pairwise (sec);
	NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE;
	guint32 i;

	/* If no ciphers are specified, that means "all" WPA ciphers */
	if (num == 0) {
		flags |= NM_802_11_AP_SEC_PAIR_TKIP | NM_802_11_AP_SEC_PAIR_CCMP;
	} else {
		for (i = 0; i < num; i++) {
			const char *cipher = nm_setting_wireless_security_get_pairwise (sec, i);

			if (!strcmp (cipher, "tkip"))
				flags |= NM_802_11_AP_SEC_PAIR_TKIP;
			else if (!strcmp (cipher, "ccmp"))
				flags |= NM_802_11_AP_SEC_PAIR_CCMP;
		}
	}

	if (has_proto (sec, PROTO_WPA))
		nm_wifi_ap_set_wpa_flags (ap, priv->wpa_flags | flags);
	if (has_proto (sec, PROTO_RSN))
		nm_wifi_ap_set_rsn_flags (ap, priv->rsn_flags | flags);
}

static void
add_group_ciphers (NMWifiAP *ap, NMSettingWirelessSecurity *sec)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
	guint32 num = nm_setting_wireless_security_get_num_groups (sec);
	NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE;
	guint32 i;

	/* If no ciphers are specified, that means "all" WPA ciphers */
	if (num == 0) {
		flags |= NM_802_11_AP_SEC_GROUP_TKIP | NM_802_11_AP_SEC_GROUP_CCMP;
	} else {
		for (i = 0; i < num; i++) {
			const char *cipher = nm_setting_wireless_security_get_group (sec, i);

			if (!strcmp (cipher, "wep40"))
				flags |= NM_802_11_AP_SEC_GROUP_WEP40;
			else if (!strcmp (cipher, "wep104"))
				flags |= NM_802_11_AP_SEC_GROUP_WEP104;
			else if (!strcmp (cipher, "tkip"))
				flags |= NM_802_11_AP_SEC_GROUP_TKIP;
			else if (!strcmp (cipher, "ccmp"))
				flags |= NM_802_11_AP_SEC_GROUP_CCMP;
		}
	}

	if (has_proto (sec, PROTO_WPA))
		nm_wifi_ap_set_wpa_flags (ap, priv->wpa_flags | flags);
	if (has_proto (sec, PROTO_RSN))
		nm_wifi_ap_set_rsn_flags (ap, priv->rsn_flags | flags);
}

const char *
nm_wifi_ap_to_string (const NMWifiAP *self,
                      char *str_buf,
                      gulong buf_len,
                      gint64 now_msec)
{
	const NMWifiAPPrivate *priv;
	const char *supplicant_id = "-";
	const char *export_path;
	guint32 chan;
	gs_free char *ssid_to_free = NULL;
	char str_buf_ts[100];

	g_return_val_if_fail (NM_IS_WIFI_AP (self), NULL);

	priv = NM_WIFI_AP_GET_PRIVATE (self);

	chan = nm_utils_wifi_freq_to_channel (priv->freq);
	if (self->_supplicant_path)
		supplicant_id = strrchr (self->_supplicant_path->str, '/') ?: supplicant_id;

	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
	if (export_path)
		export_path = strrchr (export_path, '/') ?: export_path;
	else
		export_path = "/";

	nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);

	g_snprintf (str_buf, buf_len,
	            "%17s %-35s [ %c %3u %3u%% %c%c W:%04X R:%04X ] %s sup:%s [nm:%s]",
	            priv->address ?: "(none)",
	            (ssid_to_free = _nm_utils_ssid_to_string (priv->ssid)),
	            (priv->mode == NM_802_11_MODE_ADHOC
	                 ? '*'
	                 : (priv->hotspot
	                        ? '#'
	                        : (priv->fake
	                               ? 'f'
	                               : (priv->mode == NM_802_11_MODE_MESH
	                                      ? 'm'
	                                      : 'a')))),
	            chan,
	            priv->strength,
	            priv->flags & NM_802_11_AP_FLAGS_PRIVACY ? 'P' : '_',
	            priv->metered ? 'M' : '_',
	            priv->wpa_flags & 0xFFFF,
	            priv->rsn_flags & 0xFFFF,
	              priv->last_seen_msec != G_MININT64
	            ? nm_sprintf_buf (str_buf_ts,
	                              "%3u.%03us",
	                              (guint) ((now_msec - priv->last_seen_msec) / 1000),
	                              (guint) ((now_msec - priv->last_seen_msec) % 1000))
	            : "        ",
	            supplicant_id,
	            export_path);
	return str_buf;
}

static guint
freq_to_band (guint32 freq)
{
	if (freq >= 4915 && freq <= 5825)
		return 5;
	else if (freq >= 2412 && freq <= 2484)
		return 2;
	return 0;
}

gboolean
nm_wifi_ap_check_compatible (NMWifiAP *self,
                             NMConnection *connection)
{
	NMWifiAPPrivate *priv;
	NMSettingWireless *s_wireless;
	NMSettingWirelessSecurity *s_wireless_sec;
	GBytes *ssid;
	const char *mode;
	const char *band;
	const char *bssid;
	guint32 channel;

	g_return_val_if_fail (NM_IS_WIFI_AP (self), FALSE);
	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);

	priv = NM_WIFI_AP_GET_PRIVATE (self);

	s_wireless = nm_connection_get_setting_wireless (connection);
	if (s_wireless == NULL)
		return FALSE;

	ssid = nm_setting_wireless_get_ssid (s_wireless);
	if (ssid != priv->ssid) {
		if (!ssid || !priv->ssid)
			return FALSE;
		if (!g_bytes_equal (ssid, priv->ssid))
			return FALSE;
	}

	bssid = nm_setting_wireless_get_bssid (s_wireless);
	if (bssid && (!priv->address || !nm_utils_hwaddr_matches (bssid, -1, priv->address, -1)))
		return FALSE;

	mode = nm_setting_wireless_get_mode (s_wireless);
	if (mode) {
		if (!strcmp (mode, "infrastructure") && (priv->mode != NM_802_11_MODE_INFRA))
			return FALSE;
		if (!strcmp (mode, "adhoc") && (priv->mode != NM_802_11_MODE_ADHOC))
			return FALSE;
		if (   !strcmp (mode, "ap")
		    && (priv->mode != NM_802_11_MODE_INFRA || priv->hotspot != TRUE))
			return FALSE;
		if (!strcmp (mode, "mesh") && (priv->mode != NM_802_11_MODE_MESH))
			return FALSE;
	}

	band = nm_setting_wireless_get_band (s_wireless);
	if (band) {
		guint ap_band = freq_to_band (priv->freq);

		if (!strcmp (band, "a") && ap_band != 5)
			return FALSE;
		else if (!strcmp (band, "bg") && ap_band != 2)
			return FALSE;
	}

	channel = nm_setting_wireless_get_channel (s_wireless);
	if (channel) {
		guint32 ap_chan = nm_utils_wifi_freq_to_channel (priv->freq);

		if (channel != ap_chan)
			return FALSE;
	}

	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);

	return nm_setting_wireless_ap_security_compatible (s_wireless,
	                                                   s_wireless_sec,
	                                                   priv->flags,
	                                                   priv->wpa_flags,
	                                                   priv->rsn_flags,
	                                                   priv->mode);
}

gboolean
nm_wifi_ap_complete_connection (NMWifiAP *self,
                                NMConnection *connection,
                                gboolean lock_bssid,
                                GError **error)
{
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);

	g_return_val_if_fail (connection != NULL, FALSE);

	return nm_wifi_utils_complete_connection (priv->ssid,
	                                          priv->address,
	                                          priv->mode,
	                                          priv->freq,
	                                          priv->flags,
	                                          priv->wpa_flags,
	                                          priv->rsn_flags,
	                                          connection,
	                                          lock_bssid,
	                                          error);
}

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

static void
get_property (GObject *object, guint prop_id,
              GValue *value, GParamSpec *pspec)
{
	NMWifiAP *self = NM_WIFI_AP (object);
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);

	switch (prop_id) {
	case PROP_FLAGS:
		g_value_set_uint (value, priv->flags);
		break;
	case PROP_WPA_FLAGS:
		g_value_set_uint (value, priv->wpa_flags);
		break;
	case PROP_RSN_FLAGS:
		g_value_set_uint (value, priv->rsn_flags);
		break;
	case PROP_SSID:
		g_value_take_variant (value,
		                      nm_utils_gbytes_to_variant_ay (priv->ssid));
		break;
	case PROP_FREQUENCY:
		g_value_set_uint (value, priv->freq);
		break;
	case PROP_HW_ADDRESS:
		g_value_set_string (value, priv->address);
		break;
	case PROP_MODE:
		g_value_set_uint (value, priv->mode);
		break;
	case PROP_MAX_BITRATE:
		g_value_set_uint (value, priv->max_bitrate);
		break;
	case PROP_STRENGTH:
		g_value_set_uchar (value, priv->strength);
		break;
	case PROP_LAST_SEEN:
		g_value_set_int (value,
		                   priv->last_seen_msec != G_MININT64
		                 ? (int) NM_MAX (nm_utils_monotonic_timestamp_as_boottime (priv->last_seen_msec, NM_UTILS_NSEC_PER_MSEC) / 1000, 1)
		                 : -1);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

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

static void
nm_wifi_ap_init (NMWifiAP *self)
{
	NMWifiAPPrivate *priv;

	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_WIFI_AP, NMWifiAPPrivate);

	self->_priv = priv;

	c_list_init (&self->aps_lst);

	priv->mode = NM_802_11_MODE_INFRA;
	priv->flags = NM_802_11_AP_FLAGS_NONE;
	priv->wpa_flags = NM_802_11_AP_SEC_NONE;
	priv->rsn_flags = NM_802_11_AP_SEC_NONE;
	priv->last_seen_msec = G_MININT64;
}

NMWifiAP *
nm_wifi_ap_new_from_properties (const NMSupplicantBssInfo *bss_info)
{
	NMWifiAP *ap;

	ap = g_object_new (NM_TYPE_WIFI_AP, NULL);
	nm_wifi_ap_update_from_properties (ap, bss_info);
	return ap;
}

NMWifiAP *
nm_wifi_ap_new_fake_from_connection (NMConnection *connection)
{
	NMWifiAP *ap;
	NMWifiAPPrivate *priv;
	NMSettingWireless *s_wireless;
	NMSettingWirelessSecurity *s_wireless_sec;
	const char *mode, *band, *key_mgmt;
	guint32 channel;
	NM80211ApSecurityFlags flags;
	gboolean psk = FALSE, eap = FALSE, adhoc = FALSE;

	g_return_val_if_fail (connection != NULL, NULL);

	s_wireless = nm_connection_get_setting_wireless (connection);
	g_return_val_if_fail (s_wireless != NULL, NULL);

	ap = (NMWifiAP *) g_object_new (NM_TYPE_WIFI_AP, NULL);
	priv = NM_WIFI_AP_GET_PRIVATE (ap);
	priv->fake = TRUE;

	nm_wifi_ap_set_ssid (ap,
	                     nm_setting_wireless_get_ssid (s_wireless));

	// FIXME: bssid too?

	mode = nm_setting_wireless_get_mode (s_wireless);
	if (mode) {
		if (!strcmp (mode, "infrastructure"))
			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
		else if (!strcmp (mode, "adhoc")) {
			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_ADHOC);
			adhoc = TRUE;
		} else if (!strcmp (mode, "mesh"))
			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_MESH);
		else if (!strcmp (mode, "ap")) {
			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
			NM_WIFI_AP_GET_PRIVATE (ap)->hotspot = TRUE;
		} else
			goto error;
	} else {
		nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
	}

	band = nm_setting_wireless_get_band (s_wireless);
	channel = nm_setting_wireless_get_channel (s_wireless);

	if (band && channel) {
		guint32 freq = nm_utils_wifi_channel_to_freq (channel, band);

		if (freq == 0)
			goto error;

		nm_wifi_ap_set_freq (ap, freq);
	}

	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
	/* Assume presence of a security setting means the AP is encrypted */
	if (!s_wireless_sec)
		goto done;

	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wireless_sec);

	/* Everything below here uses encryption */
	nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_PRIVACY);

	/* Static & Dynamic WEP */
	if (!strcmp (key_mgmt, "none") || !strcmp (key_mgmt, "ieee8021x"))
		goto done;

	psk = !strcmp (key_mgmt, "wpa-psk");
	eap = !strcmp (key_mgmt, "wpa-eap");
	if (!adhoc && (psk || eap)) {
		if (has_proto (s_wireless_sec, PROTO_WPA)) {
			flags = priv->wpa_flags | (eap ? NM_802_11_AP_SEC_KEY_MGMT_802_1X : NM_802_11_AP_SEC_KEY_MGMT_PSK);
			nm_wifi_ap_set_wpa_flags (ap, flags);
		}
		if (has_proto (s_wireless_sec, PROTO_RSN)) {
			flags = priv->rsn_flags | (eap ? NM_802_11_AP_SEC_KEY_MGMT_802_1X : NM_802_11_AP_SEC_KEY_MGMT_PSK);
			nm_wifi_ap_set_rsn_flags (ap, flags);
		}

		add_pair_ciphers (ap, s_wireless_sec);
		add_group_ciphers (ap, s_wireless_sec);
	} else if (adhoc && psk) {
		/* Ad-Hoc has special requirements: proto=RSN, pairwise=CCMP and
		 * group=CCMP.
		 */
		flags = priv->wpa_flags | NM_802_11_AP_SEC_KEY_MGMT_PSK;

		/* Clear ciphers; only CCMP is supported */
		flags &= ~(  NM_802_11_AP_SEC_PAIR_WEP40
		           | NM_802_11_AP_SEC_PAIR_WEP104
		           | NM_802_11_AP_SEC_PAIR_TKIP
		           | NM_802_11_AP_SEC_GROUP_WEP40
		           | NM_802_11_AP_SEC_GROUP_WEP104
		           | NM_802_11_AP_SEC_GROUP_TKIP);

		flags |= NM_802_11_AP_SEC_PAIR_CCMP;
		flags |= NM_802_11_AP_SEC_GROUP_CCMP;
		nm_wifi_ap_set_rsn_flags (ap, flags);

		/* Don't use Ad-Hoc WPA (WPA-none) anymore */
		nm_wifi_ap_set_wpa_flags (ap, NM_802_11_AP_SEC_NONE);
	}
done:
	return ap;

error:
	g_object_unref (ap);
	return NULL;
}

static void
finalize (GObject *object)
{
	NMWifiAP *self = NM_WIFI_AP (object);
	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);

	nm_assert (!self->wifi_device);
	nm_assert (c_list_is_empty (&self->aps_lst));

	nm_ref_string_unref (self->_supplicant_path);
	if (priv->ssid)
		g_bytes_unref (priv->ssid);
	g_free (priv->address);

	G_OBJECT_CLASS (nm_wifi_ap_parent_class)->finalize (object);
}

static const NMDBusInterfaceInfoExtended interface_info_access_point = {
	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
		NM_DBUS_INTERFACE_ACCESS_POINT,
		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
			&nm_signal_info_property_changed_legacy,
		),
		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Flags",      "u",  NM_WIFI_AP_FLAGS),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WpaFlags",   "u",  NM_WIFI_AP_WPA_FLAGS),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RsnFlags",   "u",  NM_WIFI_AP_RSN_FLAGS),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ssid",       "ay", NM_WIFI_AP_SSID),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Frequency",  "u",  NM_WIFI_AP_FREQUENCY),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",  "s",  NM_WIFI_AP_HW_ADDRESS),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",       "u",  NM_WIFI_AP_MODE),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("MaxBitrate", "u",  NM_WIFI_AP_MAX_BITRATE),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Strength",   "y",  NM_WIFI_AP_STRENGTH),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("LastSeen",   "i",  NM_WIFI_AP_LAST_SEEN),
		),
	),
	.legacy_property_changed = TRUE,
};

static void
nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
{
#define ALL_SEC_FLAGS \
	( NM_802_11_AP_SEC_NONE \
	| NM_802_11_AP_SEC_PAIR_WEP40 \
	| NM_802_11_AP_SEC_PAIR_WEP104 \
	| NM_802_11_AP_SEC_PAIR_TKIP \
	| NM_802_11_AP_SEC_PAIR_CCMP \
	| NM_802_11_AP_SEC_GROUP_WEP40 \
	| NM_802_11_AP_SEC_GROUP_WEP104 \
	| NM_802_11_AP_SEC_GROUP_TKIP \
	| NM_802_11_AP_SEC_GROUP_CCMP \
	| NM_802_11_AP_SEC_KEY_MGMT_PSK \
	| NM_802_11_AP_SEC_KEY_MGMT_802_1X \
	| NM_802_11_AP_SEC_KEY_MGMT_SAE \
	| NM_802_11_AP_SEC_KEY_MGMT_OWE \
	| NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)

	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);

	g_type_class_add_private (object_class, sizeof (NMWifiAPPrivate));

	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_ACCESS_POINT);
	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_access_point);

	object_class->get_property = get_property;
	object_class->finalize = finalize;

	obj_properties[PROP_FLAGS] =
	    g_param_spec_uint (NM_WIFI_AP_FLAGS, "", "",
	                       NM_802_11_AP_FLAGS_NONE,
	                       NM_802_11_AP_FLAGS_PRIVACY,
	                       NM_802_11_AP_FLAGS_NONE,
	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_WPA_FLAGS] =
	    g_param_spec_uint (NM_WIFI_AP_WPA_FLAGS, "", "",
	                       NM_802_11_AP_SEC_NONE,
	                       ALL_SEC_FLAGS,
	                       NM_802_11_AP_SEC_NONE,
	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_RSN_FLAGS] =
	    g_param_spec_uint (NM_WIFI_AP_RSN_FLAGS, "", "",
	                       NM_802_11_AP_SEC_NONE,
	                       ALL_SEC_FLAGS,
	                       NM_802_11_AP_SEC_NONE,
	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_SSID] =
	    g_param_spec_variant (NM_WIFI_AP_SSID, "", "",
	                          G_VARIANT_TYPE ("ay"),
	                          NULL,
	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_FREQUENCY] =
	    g_param_spec_uint (NM_WIFI_AP_FREQUENCY, "", "",
	                       0, 10000, 0,
	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_HW_ADDRESS] =
	    g_param_spec_string (NM_WIFI_AP_HW_ADDRESS, "", "",
	                         NULL,
	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_MODE] =
	    g_param_spec_uint (NM_WIFI_AP_MODE, "", "",
	                       NM_802_11_MODE_ADHOC, NM_802_11_MODE_INFRA, NM_802_11_MODE_INFRA,
	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_MAX_BITRATE] =
	    g_param_spec_uint (NM_WIFI_AP_MAX_BITRATE, "", "",
	                       0, G_MAXUINT16, 0,
	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_STRENGTH] =
	    g_param_spec_uchar (NM_WIFI_AP_STRENGTH, "", "",
	                        0, G_MAXINT8, 0,
	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_LAST_SEEN] =
	    g_param_spec_int (NM_WIFI_AP_LAST_SEEN, "", "",
	                      -1, G_MAXINT, -1,
	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}

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

const char **
nm_wifi_aps_get_paths (const CList *aps_lst_head, gboolean include_without_ssid)
{
	NMWifiAP *ap;
	gsize i, n;
	const char **list;
	const char *path;

	n = c_list_length (aps_lst_head);
	list = g_new (const char *, n + 1);

	i = 0;
	if (n > 0) {
		c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
			nm_assert (i < n);
			if (   !include_without_ssid
			    && !nm_wifi_ap_get_ssid (ap))
				continue;

			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (ap));
			nm_assert (path);

			list[i++] = path;
		}
		nm_assert (i <= n);
		nm_assert (!include_without_ssid || i == n);
	}
	list[i] = NULL;
	return list;
}

NMWifiAP *
nm_wifi_aps_find_first_compatible (const CList *aps_lst_head,
                                   NMConnection *connection)
{
	NMWifiAP *ap;

	g_return_val_if_fail (connection, NULL);

	c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
		if (nm_wifi_ap_check_compatible (ap, connection))
			return ap;
	}
	return NULL;
}

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

NMWifiAP *
nm_wifi_ap_lookup_for_device (NMDevice *device, const char *exported_path)
{
	NMWifiAP *ap;

	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);

	ap = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
	                                    exported_path);
	if (   !ap
	    || !NM_IS_WIFI_AP (ap)
	    || ap->wifi_device != device)
		return NULL;

	return ap;
}
