Add Nss and BW parsing after association.

Parse HT/VHT/HE capabilities and operation IE of association request
and response frames to get max_nss_rx, max_nss_tx and channel_bandwidth
capabilities of the current network.

This CL only includes changes to be upstreamed to wpa_supplicant and
doesn't include hidl change.

Bug: 147390882
Test: VTS test
Test: manual test with various APs.
Change-Id: Ie43aca62f7f02cb3c0862f7a36bc66fa5899191b
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index 0d37674..b330454 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -271,10 +271,14 @@
 		elems->password_id_len = elen;
 		break;
 	case WLAN_EID_EXT_HE_CAPABILITIES:
+		if (elen < HE_CAPABILITIES_IE_MIN_LEN)
+			break;
 		elems->he_capabilities = pos;
 		elems->he_capabilities_len = elen;
 		break;
 	case WLAN_EID_EXT_HE_OPERATION:
+		if (elen < HE_OPERATION_IE_MIN_LEN)
+			break;
 		elems->he_operation = pos;
 		elems->he_operation_len = elen;
 		break;
@@ -2389,3 +2393,273 @@
 	}
 	return CHANWIDTH_USE_HT;
 }
+
+
+/* Parse HT capabilities to get maximum number of supported spatial streams */
+static int parse_ht_mcs_set_for_max_nss(
+				struct ieee80211_ht_capabilities *htcaps,
+				u8 parse_for_rx)
+{
+	int max_nss_rx = 1;
+	if (htcaps == NULL)
+		return max_nss_rx;
+	int i;
+	for (i = 4; i >= 1; i--) {
+		if (htcaps->supported_mcs_set[i - 1] > 0) {
+			max_nss_rx = i;
+			break;
+		}
+	}
+	if (parse_for_rx)
+		return max_nss_rx;
+	u8 supported_tx_mcs_set = htcaps->supported_mcs_set[12];
+	u8 tx_mcs_set_defined = supported_tx_mcs_set & 0x1;
+	u8 tx_rx_mcs_set_not_equal = (supported_tx_mcs_set >> 1) & 0x1;
+	if (tx_mcs_set_defined && tx_rx_mcs_set_not_equal) {
+		int max_nss_tx_field_value = (supported_tx_mcs_set >> 2) & 0x3;
+		// The maximum number of Tx streams is 1 more than the field value.
+		return max_nss_tx_field_value + 1;
+	}
+	return max_nss_rx;
+}
+
+
+/* Parse MCS map to get maximum number of supported spatial streams */
+static int parse_mcs_map_for_max_nss (u16 mcs_map, int max_streams_allowed)
+{
+	int max_nss = 1;
+	int i;
+	for (i = max_streams_allowed; i >= 1; i--) {
+		int stream_map = (mcs_map >> ((i - 1) * 2)) & 0x3;
+		// 3 means unsupported
+		if (stream_map != 3) {
+			max_nss = i;
+			break;
+		}
+	}
+	return max_nss;
+}
+
+
+/* Parse capabilities IEs to get maximum number of supported spatial streams */
+int get_max_nss_capability(struct ieee802_11_elems *elems, int parse_for_rx)
+{
+	int max_nss = 1;
+	struct ieee80211_ht_capabilities *htcaps =
+		(struct ieee80211_ht_capabilities *) elems->ht_capabilities;
+	struct ieee80211_vht_capabilities *vhtcaps =
+		(struct ieee80211_vht_capabilities *) elems->vht_capabilities;
+	struct ieee80211_he_capabilities *hecaps =
+		(struct ieee80211_he_capabilities *) elems->he_capabilities;
+	if (htcaps) {
+		int max_nss_ht = parse_ht_mcs_set_for_max_nss(htcaps, parse_for_rx);
+		if (max_nss_ht > max_nss)
+			max_nss = max_nss_ht;
+	}
+	le16 mcs_map;
+	if (vhtcaps) {
+		mcs_map = (parse_for_rx) ? vhtcaps->vht_supported_mcs_set.rx_map :
+			vhtcaps->vht_supported_mcs_set.tx_map;
+		int max_nss_vht = parse_mcs_map_for_max_nss(
+			le_to_host16(mcs_map), VHT_RX_NSS_MAX_STREAMS);
+		if (max_nss_vht > max_nss)
+			max_nss = max_nss_vht;
+	}
+	if (hecaps) {
+		mcs_map = (parse_for_rx) ? hecaps->he_basic_supported_mcs_set.rx_map :
+			hecaps->he_basic_supported_mcs_set.tx_map;
+		int max_nss_he = parse_mcs_map_for_max_nss(
+			le_to_host16(mcs_map), HE_NSS_MAX_STREAMS);
+		if (max_nss_he > max_nss)
+			max_nss = max_nss_he;
+	}
+	return max_nss;
+}
+
+
+/* Parse VHT/HE capabilities IEs to get supported channel width */
+struct supported_chan_width get_supported_channel_width(
+				struct ieee802_11_elems *elems)
+{
+	struct supported_chan_width supported_width;
+	supported_width.is_160_supported = 0;
+	supported_width.is_80p80_supported = 0;
+	if (elems == NULL)
+		return supported_width;
+
+	struct ieee80211_vht_capabilities *vhtcaps =
+		(struct ieee80211_vht_capabilities *) elems->vht_capabilities;
+	struct ieee80211_he_capabilities *hecaps =
+		(struct ieee80211_he_capabilities *) elems->he_capabilities;
+
+	if (vhtcaps) {
+		le32 vht_capabilities_info =
+			le_to_host32(vhtcaps->vht_capabilities_info);
+		if (vht_capabilities_info & VHT_CAP_SUPP_CHAN_WIDTH_160MHZ)
+			supported_width.is_160_supported = 1;
+		if (vht_capabilities_info & VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
+			supported_width.is_80p80_supported = 1;
+	}
+	if (hecaps) {
+		u8 channel_width_set =
+        	hecaps->he_phy_capab_info[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX];
+		if (channel_width_set & HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G)
+			supported_width.is_160_supported = 1;
+		if (channel_width_set & HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)
+			supported_width.is_80p80_supported = 1;
+	}
+	wpa_printf(MSG_DEBUG, " IE indicate 160 supported: %u, 80+80 supported: %u",
+        supported_width.is_160_supported, supported_width.is_80p80_supported);
+	return supported_width;
+}
+
+/*
+ * Parse VHT operation info fields to get operation channel width
+ * note that VHT operation info fields could come from VHT operation IE
+ * or from HE operation IE
+ */
+static enum chan_width get_vht_operation_channel_width(
+				struct ieee80211_vht_operation_info *vht_oper_info)
+{
+	enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+	u8 seg0, seg1;
+	switch (vht_oper_info->vht_op_info_chwidth) {
+	case 1:
+		seg0 = vht_oper_info->vht_op_info_chan_center_freq_seg0_idx;
+		seg1 = vht_oper_info->vht_op_info_chan_center_freq_seg1_idx;
+		if (seg1 && abs(seg1 - seg0) == 8)
+			channel_width = CHAN_WIDTH_160;
+		else if (seg1)
+			channel_width = CHAN_WIDTH_80P80;
+		else
+			channel_width = CHAN_WIDTH_80;
+		break;
+	case 2:
+		channel_width = CHAN_WIDTH_160;
+		break;
+	case 3:
+		channel_width = CHAN_WIDTH_80P80;
+		break;
+	default:
+		break;
+	}
+	wpa_printf(MSG_DEBUG, " VHT operation CBW: %u", channel_width);
+	return channel_width;
+}
+
+/* Parse 6GHz operation info fields to get operation channel width */
+static enum chan_width get_6ghz_operation_channel_width(
+				struct ieee80211_6ghz_operation_info * six_ghz_oper_info)
+{
+	enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+	u8 seg0, seg1;
+	switch (six_ghz_oper_info->control & SIX_GHZ_CONTROL_CHANNEL_WIDTH_MASK) {
+	case 0:
+		channel_width = CHAN_WIDTH_20;
+		break;
+	case 1:
+		channel_width = CHAN_WIDTH_40;
+		break;
+	case 2:
+		channel_width = CHAN_WIDTH_80;
+		break;
+	case 3:
+		seg0 = six_ghz_oper_info->chan_center_freq_seg0_idx;
+		seg1 = six_ghz_oper_info->chan_center_freq_seg1_idx;
+		if (abs(seg1 - seg0) == 8)
+			channel_width = CHAN_WIDTH_160;
+		else
+			channel_width = CHAN_WIDTH_80P80;
+		break;
+	default:
+		break;
+	}
+	wpa_printf(MSG_DEBUG, " 6GHz operation CBW: %u", channel_width);
+	return channel_width;
+}
+
+
+/* Parse HE operation IE to get HE operation channel width */
+static enum chan_width get_he_operation_channel_width(
+				struct ieee80211_he_operation *he_oper,
+				int he_oper_len)
+{
+	enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+	u8 is_6ghz_info_present =
+		(he_oper->he_oper_params & HE_OPERATION_6GHZ_OPER_INFO) ? 1 : 0;
+	u8 is_vht_info_present =
+		(he_oper->he_oper_params & HE_OPERATION_VHT_OPER_INFO) ? 1 : 0;
+	u8 is_cohosted_bss_present =
+		(he_oper->he_oper_params & HE_OPERATION_COHOSTED_BSS) ? 1 : 0;
+	int expected_len = HE_OPERATION_IE_MIN_LEN
+		+ (is_6ghz_info_present ? HE_OPERATION_6GHZ_OPER_INFO_LEN : 0)
+		+ (is_vht_info_present ? HE_OPERATION_VHT_OPER_INFO_LEN : 0)
+		+ (is_cohosted_bss_present
+		? HE_OPERATION_COHOSTED_BSSID_INDICATOR_LEN : 0);
+	if (he_oper_len < expected_len)
+		return channel_width;
+
+	if (is_6ghz_info_present) {
+		struct ieee80211_6ghz_operation_info *six_ghz_oper_info =
+			(struct ieee80211_6ghz_operation_info *)
+			(he_oper + HE_OPERATION_IE_MIN_LEN
+			+ (is_vht_info_present ? HE_OPERATION_VHT_OPER_INFO_LEN : 0)
+			+ (is_cohosted_bss_present
+			? HE_OPERATION_COHOSTED_BSSID_INDICATOR_LEN : 0));
+		channel_width = get_6ghz_operation_channel_width(six_ghz_oper_info);
+	}
+	if (channel_width == CHAN_WIDTH_UNKNOWN && is_vht_info_present) {
+		struct ieee80211_vht_operation_info *vht_oper_info  =
+			(struct ieee80211_vht_operation_info *)
+			(he_oper + HE_OPERATION_IE_MIN_LEN);
+		channel_width = get_vht_operation_channel_width(vht_oper_info);
+	}
+	wpa_printf(MSG_DEBUG, " HE operation CBW: %u", channel_width);
+	return channel_width;
+}
+
+/* Parse HT/VHT/HE operation IEs to get operation channel width */
+enum chan_width get_operation_channel_width(struct ieee802_11_elems *elems)
+{
+	enum chan_width channel_width = CHAN_WIDTH_UNKNOWN;
+	if (elems == NULL)
+		return channel_width;
+
+	struct ieee80211_ht_operation *ht_oper =
+	    (struct ieee80211_ht_operation *) elems->ht_operation;
+	struct ieee80211_vht_operation_info *vht_oper_info =
+	    (struct ieee80211_vht_operation_info *) elems->vht_operation;
+	struct ieee80211_he_operation *he_oper =
+	    (struct ieee80211_he_operation *) elems->he_operation;
+	if (he_oper)
+		channel_width = get_he_operation_channel_width(
+			he_oper, elems->he_operation_len);
+
+	if (channel_width == CHAN_WIDTH_UNKNOWN && vht_oper_info)
+		channel_width = get_vht_operation_channel_width(vht_oper_info);
+
+	if (channel_width == CHAN_WIDTH_UNKNOWN && ht_oper) {
+		u8 sec_chan_offset =
+			ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK;
+		channel_width = (sec_chan_offset == 0) ? CHAN_WIDTH_20 : CHAN_WIDTH_40;
+	}
+	wpa_printf(MSG_DEBUG, " overall operation CBW: %u", channel_width);
+	return channel_width;
+}
+
+/*
+ * Get STA operation channel width from AP's operation channel width and
+ *  STA's supported channel width
+ */
+enum chan_width get_sta_operation_chan_width(
+				enum chan_width ap_operation_chan_width,
+				struct supported_chan_width sta_supported_chan_width)
+{
+	if (ap_operation_chan_width == CHAN_WIDTH_160)
+		return (sta_supported_chan_width.is_160_supported)
+			? CHAN_WIDTH_160 : CHAN_WIDTH_80;
+	if (ap_operation_chan_width == CHAN_WIDTH_80P80)
+		return (sta_supported_chan_width.is_80p80_supported)
+			? CHAN_WIDTH_80P80 : CHAN_WIDTH_80;
+	return ap_operation_chan_width;
+}
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index fbda10c..2c66507 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -291,4 +291,18 @@
 int ieee802_edmg_is_allowed(struct ieee80211_edmg_config allowed,
 			    struct ieee80211_edmg_config requested);
 
+int get_max_nss_capability(struct ieee802_11_elems *elems, int parse_for_rx);
+
+struct supported_chan_width {
+	u8 is_160_supported;
+	u8 is_80p80_supported;
+};
+
+struct supported_chan_width get_supported_channel_width(struct ieee802_11_elems *elems);
+
+enum chan_width get_operation_channel_width(struct ieee802_11_elems *elems);
+
+enum chan_width get_sta_operation_chan_width(enum chan_width ap_operation_chan_width,
+					     struct supported_chan_width sta_supported_width);
+
 #endif /* IEEE802_11_COMMON_H */
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index e3c2ed3..fdc51dc 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -1102,6 +1102,12 @@
 	le16 vht_basic_mcs_set;
 } STRUCT_PACKED;
 
+struct ieee80211_vht_operation_info {
+	u8 vht_op_info_chwidth;
+	u8 vht_op_info_chan_center_freq_seg0_idx;
+	u8 vht_op_info_chan_center_freq_seg1_idx;
+} STRUCT_PACKED;
+
 struct ieee80211_ampe_ie {
 	u8 selected_pairwise_suite[4];
 	u8 local_nonce[32];
@@ -2122,9 +2128,13 @@
 struct ieee80211_he_capabilities {
 	u8 he_mac_capab_info[6];
 	u8 he_phy_capab_info[11];
-	/* Followed by 4, 8, or 12 octets of Supported HE-MCS And NSS Set field
+	struct {
+		le16 rx_map;
+		le16 tx_map;
+	} he_basic_supported_mcs_set;
+	/* Followed by 0, 4, or 8 octets of optional supported HE-MCS And NSS Set field
 	* and optional variable length PPE Thresholds field. */
-	u8 optional[37];
+	u8 optional[33];
 } STRUCT_PACKED;
 
 struct ieee80211_he_operation {
@@ -2152,8 +2162,17 @@
 	u8 params[19];
 } STRUCT_PACKED;
 
-/* HE Capabilities Information defines */
+struct ieee80211_6ghz_operation_info {
+	u8 primary_chan;
+	u8 control;
+	u8 chan_center_freq_seg0_idx;
+	u8 chan_center_freq_seg1_idx;
+	u8 minimum_rate;
+} STRUCT_PACKED;
 
+#define HE_CAPABILITIES_IE_MIN_LEN 21
+
+/* HE Capabilities Information defines */
 #define HE_PHYCAP_CHANNEL_WIDTH_SET_IDX		0
 #define HE_PHYCAP_CHANNEL_WIDTH_MASK		((u8) (BIT(1) | BIT(2) | \
 						      BIT(3) | BIT(4)))
@@ -2200,6 +2219,12 @@
 #define HE_OPERATION_BSS_COLOR_DISABLED		((u32) BIT(31))
 #define HE_OPERATION_BSS_COLOR_OFFSET		24
 
+/* HE operation fields length*/
+#define HE_OPERATION_IE_MIN_LEN 6
+#define HE_OPERATION_VHT_OPER_INFO_LEN 3
+#define HE_OPERATION_COHOSTED_BSSID_INDICATOR_LEN 1
+#define HE_OPERATION_6GHZ_OPER_INFO_LEN 5
+
 /* Spatial Reuse defines */
 #define SPATIAL_REUSE_SRP_DISALLOWED		BIT(0)
 #define SPATIAL_REUSE_NON_SRG_OBSS_PD_SR_DISALLOWED	BIT(1)
@@ -2207,6 +2232,10 @@
 #define SPATIAL_REUSE_SRG_INFORMATION_PRESENT	BIT(3)
 #define SPATIAL_REUSE_HESIGA_SR_VAL15_ALLOWED	BIT(4)
 
+/* 6GHz operation control field defines*/
+#define SIX_GHZ_CONTROL_CHANNEL_WIDTH_MASK 	((u8) BIT(0) | BIT(1))
+#define SIX_GHZ_CONTROL_DUPLICATE_BEACON 	BIT(2)
+
 struct ieee80211_he_mu_edca_parameter_set {
 	u8 he_qos_info;
 	u8 he_mu_ac_be_param[3];
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 15ef0f7..4c459cf 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -2476,6 +2476,30 @@
 				resp_elems.vht_capabilities;
 			wpa_s->connection_he = req_elems.he_capabilities &&
 				resp_elems.he_capabilities;
+
+			int max_nss_rx_req = get_max_nss_capability(&req_elems, 1);
+			int max_nss_rx_resp = get_max_nss_capability(&resp_elems, 1);
+			wpa_s->connection_max_nss_rx = (max_nss_rx_resp > max_nss_rx_req) ?
+				max_nss_rx_req : max_nss_rx_resp;
+			int max_nss_tx_req = get_max_nss_capability(&req_elems, 0);
+			int max_nss_tx_resp = get_max_nss_capability(&resp_elems, 0);
+			wpa_s->connection_max_nss_tx = (max_nss_tx_resp > max_nss_tx_req) ?
+				max_nss_tx_req : max_nss_tx_resp;
+
+			struct supported_chan_width sta_supported_chan_width =
+				get_supported_channel_width(&req_elems);
+			enum chan_width ap_operation_chan_width =
+				get_operation_channel_width(&resp_elems);
+			if (wpa_s->connection_vht || wpa_s->connection_he) {
+				wpa_s->connection_channel_bandwidth =
+					get_sta_operation_chan_width(ap_operation_chan_width,
+					sta_supported_chan_width);
+			} else if (wpa_s->connection_ht) {
+				wpa_s->connection_channel_bandwidth = (ap_operation_chan_width
+					== CHAN_WIDTH_40) ? CHAN_WIDTH_40 : CHAN_WIDTH_20;
+			} else {
+				wpa_s->connection_channel_bandwidth = CHAN_WIDTH_20;
+			}
 		}
 	}
 
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index dcc2a6d..967298b 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -759,6 +759,9 @@
 	unsigned int connection_ht:1;
 	unsigned int connection_vht:1;
 	unsigned int connection_he:1;
+	unsigned int connection_max_nss_rx:4;
+	unsigned int connection_max_nss_tx:4;
+	unsigned int connection_channel_bandwidth:5;
 	unsigned int disable_mbo_oce:1;
 
 	struct os_reltime last_mac_addr_change;