Эх сурвалжийг харах

qcacmn: Generate Link Specific Assoc Request for MLO

Generalize the existing link specific assoc response generation
functionality to make it common between link specific assoc request and
assoc response generation, and expose modified APIs for both. Add
handling of Non-Inheritance IEs as well as fixed field parsing to both.
Add guard checks and other fixes.

CRs-Fixed: 3083541
Change-Id: Ibf642ddc6b40ce258d50f3eeb4f820648222a758
Krishna Rao 3 жил өмнө
parent
commit
a50a68c40d

+ 3 - 0
umac/cmn_services/cmn_defs/inc/wlan_cmn_ieee80211.h

@@ -192,6 +192,9 @@
 #define OUI_TYPE_BITS           24
 #define MAX_ADAPTIVE_11R_IE_LEN 8
 
+/* Minimum length of Non-Inheritance element (inclusive of the IE header) */
+#define MIN_NONINHERITANCEELEM_LEN 5
+
 /*
  * sae single pmk vendor specific IE details
  * Category     Data

+ 75 - 12
umac/mlo_mgr/inc/utils_mlo.h

@@ -25,24 +25,74 @@
 #include "wlan_mlo_mgr_public_structs.h"
 #include <wlan_cm_ucfg_api.h>
 #include <wlan_objmgr_vdev_obj.h>
+
 #ifdef WLAN_FEATURE_11BE_MLO
 
-#define FC0_IEEE_MGMT_FRM 0x10
-#define FC1_IEEE_MGMT_FRM 0x00
+/**
+ * util_gen_link_assoc_req() - Generate link specific assoc request
+ * @frame: Pointer to original association request. This should not contain the
+ * 802.11 header, and must start from the fixed fields in the association
+ * request. This is required due to some caller semantics built into the end to
+ * end design.
+ * @frame_len: Length of original association request
+ * @isreassoc: Whether this is a re-association request
+ * @link_addr: Secondary link's MAC address
+ * @link_frame: Generated secondary link specific association request. Note that
+ * this will start from the 802.11 header (unlike the original association
+ * request). This should be ignored in the case of failure.
+ * @link_frame_maxsize: Maximum size of generated secondary link specific
+ * association request
+ * @link_frame_len: Pointer to location where populated length of generated
+ * secondary link specific association request should be written. This should be
+ * ignored in the case of failure.
+ *
+ * Generate a link specific logically equivalent association request for the
+ * secondary link from the original association request containing a Multi-Link
+ * element. This applies to both association and re-association requests.
+ * Currently, only two link MLO is supported.
+ *
+ * Return: QDF_STATUS_SUCCESS in the case of success, QDF_STATUS value giving
+ * the reason for error in the case of failure.
+ */
+QDF_STATUS
+util_gen_link_assoc_req(uint8_t *frame, qdf_size_t frame_len, bool isreassoc,
+			struct qdf_mac_addr link_addr,
+			uint8_t *link_frame,
+			qdf_size_t link_frame_maxsize,
+			qdf_size_t *link_frame_len);
 
 /**
- * util_gen_link_assoc_rsp - Generate link association response
+ * util_gen_link_assoc_rsp() - Generate link specific assoc response
+ * @frame: Pointer to original association response. This should not contain the
+ * 802.11 header, and must start from the fixed fields in the association
+ * response. This is required due to some caller semantics built into the end to
+ * end design.
+ * @frame_len: Length of original association response
+ * @isreassoc: Whether this is a re-association response
+ * @link_addr: Secondary link's MAC address
+ * @link_frame: Generated secondary link specific association response. Note
+ * that this will start from the 802.11 header (unlike the original association
+ * response). This should be ignored in the case of failure.
+ * @link_frame_maxsize: Maximum size of generated secondary link specific
+ * association response
+ * @link_frame_len: Pointer to location where populated length of generated
+ * secondary link specific association response should be written. This should
+ * be ignored in the case of failure.
  *
- * @frame: association response frame ptr
- * @len: length of assoc rsp frame
- * @link_addr: link mac addr
- * @new_ie: Generated Link assoc rsp
+ * Generate a link specific logically equivalent association response for the
+ * secondary link from the original association response containing a Multi-Link
+ * element. This applies to both association and re-association responses.
+ * Currently, only two link MLO is supported.
  *
- * Return: true if vdev is a link vdev, false otherwise
+ * Return: QDF_STATUS_SUCCESS in the case of success, QDF_STATUS value giving
+ * the reason for error in the case of failure.
  */
 QDF_STATUS
-util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
-			struct qdf_mac_addr link_addr, uint8_t *new_ie);
+util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t frame_len, bool isreassoc,
+			struct qdf_mac_addr link_addr,
+			uint8_t *link_frame,
+			qdf_size_t link_frame_maxsize,
+			qdf_size_t *link_frame_len);
 
 /**
  * util_find_mlie - Find the first Multi-Link element or the start of the first
@@ -172,8 +222,21 @@ util_get_bvmlie_persta_partner_info(uint8_t *mlie, qdf_size_t mlielen,
 				    struct mlo_partner_info *partner_info);
 #else
 static inline QDF_STATUS
-util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
-			struct qdf_mac_addr link_addr, uint8_t *new_ie)
+util_gen_link_assoc_req(uint8_t *frame, qdf_size_t frame_len, bool isreassoc,
+			struct qdf_mac_addr link_addr,
+			uint8_t *link_frame,
+			qdf_size_t link_frame_maxsize,
+			qdf_size_t *link_frame_len)
+{
+	return QDF_STATUS_E_NOSUPPORT;
+}
+
+static inline QDF_STATUS
+util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t frame_len, bool isreassoc,
+			struct qdf_mac_addr link_addr,
+			uint8_t *link_frame,
+			qdf_size_t link_frame_maxsize,
+			qdf_size_t *link_frame_len)
 {
 	return QDF_STATUS_E_NOSUPPORT;
 }

+ 1304 - 157
umac/mlo_mgr/src/utils_mlo.c

@@ -132,13 +132,13 @@ uint8_t *util_parse_multi_link_ctrl(uint8_t *element,
 }
 
 static
-uint8_t *util_parse_perstaprofile(uint8_t *subelement,
-				  qdf_size_t len,
-				  bool is_staprof_reqd,
-				  qdf_size_t *staprof_len,
-				  uint8_t *linkid,
-				  bool *is_macaddr_valid,
-				  struct qdf_mac_addr *macaddr)
+uint8_t *util_parse_bvmlie_perstaprofile(uint8_t *subelement,
+					 qdf_size_t len,
+					 bool is_staprof_reqd,
+					 qdf_size_t *staprof_len,
+					 uint8_t *linkid,
+					 bool *is_macaddr_valid,
+					 struct qdf_mac_addr *macaddr)
 {
 	qdf_size_t subelement_len = 0;
 	struct wlan_ml_bv_linfo_perstaprof *perstaprof_fixed;
@@ -371,7 +371,8 @@ QDF_STATUS util_parse_partner_info_from_linkinfo(uint8_t *linkinfo,
 			/* Per-STA profile fragmentation support may be added
 			 * once support for this is introduced in the standard.
 			 */
-			endofstainfo = util_parse_perstaprofile(currpos,
+			endofstainfo =
+				util_parse_bvmlie_perstaprofile(currpos,
 								currlen,
 								false,
 								NULL,
@@ -415,197 +416,1343 @@ QDF_STATUS util_parse_partner_info_from_linkinfo(uint8_t *linkinfo,
 	return QDF_STATUS_SUCCESS;
 }
 
-QDF_STATUS util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t len,
-				   struct qdf_mac_addr link_addr,
-				   uint8_t *assoc_link_frame)
+static
+QDF_STATUS util_get_noninheritlists(uint8_t *buff, qdf_size_t buff_len,
+				    uint8_t **ninherit_elemlist,
+				    qdf_size_t *ninherit_elemlist_len,
+				    uint8_t **ninherit_elemextlist,
+				    qdf_size_t *ninherit_elemextlist_len)
+{
+	uint8_t *ninherit_ie;
+	qdf_size_t unparsed_len;
+
+	/* Note: This funtionality provided by this helper may be combined with
+	 * other, older non-inheritance parsing helper functionality and exposed
+	 * as a common API as part of future efforts once the older
+	 * functionality can be made generic.
+	 */
+
+	if (!buff) {
+		mlo_err("Pointer to buffer for IEs is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!buff_len) {
+		mlo_err("IE buffer length is zero");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if (!ninherit_elemlist) {
+		mlo_err("Pointer to Non-Inheritance element ID list array is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!ninherit_elemlist_len) {
+		mlo_err("Pointer to Non-Inheritance element ID list array length is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!ninherit_elemextlist) {
+		mlo_err("Pointer to Non-Inheritance element ID extension list array is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!ninherit_elemextlist_len) {
+		mlo_err("Pointer to Non-Inheritance element ID extension list array length is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	ninherit_ie = NULL;
+	*ninherit_elemlist_len = 0;
+	*ninherit_elemlist = NULL;
+	*ninherit_elemextlist_len = 0;
+	*ninherit_elemextlist = NULL;
+
+	ninherit_ie =
+		(uint8_t *)util_find_extn_eid(WLAN_ELEMID_EXTN_ELEM,
+					      WLAN_EXTN_ELEMID_NONINHERITANCE,
+					      buff,
+					      buff_len);
+
+	if (ninherit_ie) {
+		if ((ninherit_ie + TAG_LEN_POS) > (buff + buff_len - 1)) {
+			mlo_err_rl("Position of length field of Non-Inheritance element would exceed IE buffer boundary");
+			return QDF_STATUS_E_PROTO;
+		}
+
+		if ((ninherit_ie + ninherit_ie[TAG_LEN_POS] + MIN_IE_LEN) >
+				(buff + buff_len)) {
+			mlo_err_rl("Non-Inheritance element with total length %u would exceed IE buffer boundary",
+				   ninherit_ie[TAG_LEN_POS] + MIN_IE_LEN);
+			return QDF_STATUS_E_PROTO;
+		}
+
+		if ((ninherit_ie[TAG_LEN_POS] + MIN_IE_LEN) <
+				MIN_NONINHERITANCEELEM_LEN) {
+			mlo_err_rl("Non-Inheritance element size %u is smaller than the minimum required %u",
+				   ninherit_ie[TAG_LEN_POS] + MIN_IE_LEN,
+				   MIN_NONINHERITANCEELEM_LEN);
+			return QDF_STATUS_E_PROTO;
+		}
+
+		/* Track the number of unparsed octets, excluding the IE header.
+		 */
+		unparsed_len = ninherit_ie[TAG_LEN_POS];
+
+		/* Mark the element ID extension as parsed */
+		unparsed_len--;
+
+		*ninherit_elemlist_len = ninherit_ie[ELEM_ID_LIST_LEN_POS];
+		unparsed_len--;
+
+		/* While checking if the Non-Inheritance element ID list length
+		 * exceeds the remaining unparsed IE space, we factor in one
+		 * octet for the element extension ID list length and subtract
+		 * this from the unparsed IE space.
+		 */
+		if (*ninherit_elemlist_len > (unparsed_len - 1)) {
+			mlo_err_rl("Non-Inheritance element ID list length %zu exceeds remaining unparsed IE space, minus an octet for element extension ID list length %zu",
+				   *ninherit_elemlist_len, unparsed_len - 1);
+
+			return QDF_STATUS_E_PROTO;
+		}
+
+		if (*ninherit_elemlist_len != 0) {
+			*ninherit_elemlist = ninherit_ie + ELEM_ID_LIST_POS;
+			unparsed_len -= *ninherit_elemlist_len;
+		}
+
+		*ninherit_elemextlist_len =
+			ninherit_ie[ELEM_ID_LIST_LEN_POS + *ninherit_elemlist_len + 1];
+		unparsed_len--;
+
+		if (*ninherit_elemextlist_len > unparsed_len) {
+			mlo_err_rl("Non-Inheritance element ID extension list length %zu exceeds remaining unparsed IE space %zu",
+				   *ninherit_elemextlist_len, unparsed_len);
+
+			return QDF_STATUS_E_PROTO;
+		}
+
+		if (*ninherit_elemextlist_len != 0) {
+			*ninherit_elemextlist = ninherit_ie +
+				ELEM_ID_LIST_LEN_POS + (*ninherit_elemlist_len)
+				+ 2;
+			unparsed_len -= *ninherit_elemlist_len;
+		}
+
+		if (unparsed_len > 0) {
+			mlo_err_rl("Unparsed length is %zu, expected 0",
+				   unparsed_len);
+			return QDF_STATUS_E_PROTO;
+		}
+	}
+
+	/* If Non-Inheritance element is not found, we still return success,
+	 * with the list lengths kept at zero.
+	 */
+	mlo_debug("Non-Inheritance element ID list array length=%zu",
+		  *ninherit_elemlist_len);
+	mlo_debug("Non-Inheritance element ID extension list array length=%zu",
+		  *ninherit_elemextlist_len);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+static
+QDF_STATUS util_eval_ie_in_noninheritlist(uint8_t *ie, qdf_size_t total_ie_len,
+					  uint8_t *ninherit_elemlist,
+					  qdf_size_t ninherit_elemlist_len,
+					  uint8_t *ninherit_elemextlist,
+					  qdf_size_t ninherit_elemextlist_len,
+					  bool *is_in_noninheritlist)
+{
+	int i;
+
+	/* Evaluate whether the given IE is in the given Non-Inheritance element
+	 * ID list or Non-Inheritance element ID extension list, and update the
+	 * result into is_in_noninheritlist. If any list is empty, then the IE
+	 * is considered to not be present in that list. Both lists can be
+	 * empty.
+	 *
+	 * If QDF_STATUS_SUCCESS is returned, it means that the evaluation is
+	 * successful, and that is_in_noninheritlist contains a valid value
+	 * (which could be true or false). If a QDF_STATUS error value is
+	 * returned, the value in is_in_noninheritlist is invalid and the caller
+	 * should ignore it.
+	 */
+
+	/* Note: The funtionality provided by this helper may be combined with
+	 * other, older non-inheritance parsing helper functionality and exposed
+	 * as a common API as part of future efforts once the older
+	 * functionality can be made generic.
+	 */
+
+	/* Except for is_in_noninheritlist and ie, other pointer arguments are
+	 * permitted to be NULL if they are inapplicable. If they are
+	 * applicable, they will be checked to ensure they are not NULL.
+	 */
+
+	if (!is_in_noninheritlist) {
+		mlo_err("NULL pointer to flag that indicates if element is in a Non-Inheritance list");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	/* If ninherit_elemlist_len and ninherit_elemextlist_len are both zero
+	 * as checked soon in this function, we won't be accessing the IE.
+	 * However, we still check right-away if the pointer to the IE is
+	 * non-NULL and whether the total IE length is sane enough to access the
+	 * element ID and if applicable, the element ID extension, since it
+	 * doesn't make sense to set the flag in is_in_noninheritlist for a NULL
+	 * IE pointer or an IE whose total length is not sane enough to
+	 * distinguish the identity of the IE.
+	 */
+	if (!ie) {
+		mlo_err("NULL pointer to IE");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (total_ie_len < (ID_POS + 1)) {
+		mlo_err("Total IE length %zu is smaller than minimum required to access element ID %u",
+			total_ie_len, ID_POS + 1);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if ((ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
+	    (total_ie_len < (IDEXT_POS + 1))) {
+		mlo_err("Total IE length %zu is smaller than minimum required to access element ID extension %u",
+			total_ie_len, IDEXT_POS + 1);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	*is_in_noninheritlist = false;
+
+	/* If both the Non-Inheritance element list and Non-Inheritance element
+	 * ID extension list are empty, then return success since we can
+	 * conclude immediately that the given element does not occur in any
+	 * Non-Inheritance list. The is_in_noninheritlist remains set to false
+	 * as required.
+	 */
+	if (!ninherit_elemlist_len && !ninherit_elemextlist_len)
+		return QDF_STATUS_SUCCESS;
+
+	if (ie[ID_POS] != WLAN_ELEMID_EXTN_ELEM) {
+		if (!ninherit_elemlist_len)
+			return QDF_STATUS_SUCCESS;
+
+		if (!ninherit_elemlist) {
+			mlo_err("NULL pointer to Non-Inheritance element ID list though length of element ID list is %zu",
+				ninherit_elemlist_len);
+			return QDF_STATUS_E_NULL_VALUE;
+		}
+
+		for (i = 0; i < ninherit_elemlist_len; i++) {
+			if (ie[ID_POS] == ninherit_elemlist[i]) {
+				*is_in_noninheritlist = true;
+				return QDF_STATUS_SUCCESS;
+			}
+		}
+	} else {
+		if (!ninherit_elemextlist_len)
+			return QDF_STATUS_SUCCESS;
+
+		if (!ninherit_elemextlist) {
+			mlo_err("NULL pointer to Non-Inheritance element ID extension list though length of element ID extension list is %zu",
+				ninherit_elemextlist_len);
+			return QDF_STATUS_E_NULL_VALUE;
+		}
+
+		for (i = 0; i < ninherit_elemextlist_len; i++) {
+			if (ie[IDEXT_POS] == ninherit_elemextlist[i]) {
+				*is_in_noninheritlist = true;
+				return QDF_STATUS_SUCCESS;
+			}
+		}
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+static inline
+QDF_STATUS util_validate_reportingsta_ie(const uint8_t *reportingsta_ie,
+					 const uint8_t *frame_iesection,
+					 const qdf_size_t frame_iesection_len)
 {
-	uint8_t *tmp = NULL;
-	const uint8_t *tmp_old, *rsn_ie;
-	qdf_size_t sub_len, tmp_rem_len;
-	qdf_size_t link_info_len, sta_prof_len = 0;
-	uint8_t *subelement;
-	uint8_t *pos;
-	uint8_t *sub_copy, *orig_copy;
-	bool is_bssid_valid;
-	struct qdf_mac_addr bssid;
-	struct wlan_frame_hdr *hdr;
-
-	if (!frame || !len)
+	qdf_size_t reportingsta_ie_size;
+
+	if (!reportingsta_ie) {
+		mlo_err("Pointer to reporting STA IE is NULL");
 		return QDF_STATUS_E_NULL_VALUE;
+	}
 
-	pos = assoc_link_frame;
-	hdr = (struct wlan_frame_hdr *)pos;
-	pos = pos + WLAN_MAC_HDR_LEN_3A;
-
-	/* Assoc resp Capability(2) + AID(2) + Status Code(2) */
-	qdf_mem_copy(pos, frame, WLAN_ASSOC_RSP_IES_OFFSET);
-	pos = pos + WLAN_ASSOC_RSP_IES_OFFSET;
-
-	rsn_ie = wlan_get_ie_ptr_from_eid(WLAN_ELEMID_RSN, frame, len);
-	if (rsn_ie) {
-		qdf_mem_copy(pos, rsn_ie, rsn_ie[1]);
-		pos = pos + rsn_ie[1];
-	}
-	/* find MLO IE */
-	subelement = util_find_extn_eid(WLAN_ELEMID_EXTN_ELEM,
-					WLAN_EXTN_ELEMID_MULTI_LINK,
-					frame,
-					len);
-	if (!subelement)
+	if (!frame_iesection) {
+		mlo_err("Pointer to start of IE section in reporting frame is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!frame_iesection_len) {
+		mlo_err("Length of IE section in reporting frame is zero");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if ((reportingsta_ie + ID_POS) > (frame_iesection +
+			frame_iesection_len - 1)) {
+		mlo_err_rl("Position of element ID field of element for reporting STA would exceed frame IE section boundary");
+		return QDF_STATUS_E_PROTO;
+	}
+
+	if ((reportingsta_ie + TAG_LEN_POS) > (frame_iesection +
+			frame_iesection_len - 1)) {
+		mlo_err_rl("Position of length field of element with element ID %u for reporting STA would exceed frame IE section boundary",
+			   reportingsta_ie[ID_POS]);
+		return QDF_STATUS_E_PROTO;
+	}
+
+	if ((reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
+	    ((reportingsta_ie + IDEXT_POS) > (frame_iesection +
+				frame_iesection_len - 1))) {
+		mlo_err_rl("Position of element ID extension field of element would exceed frame IE section boundary");
+		return QDF_STATUS_E_PROTO;
+	}
+
+	reportingsta_ie_size = reportingsta_ie[TAG_LEN_POS] + MIN_IE_LEN;
+
+	if ((reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
+	    (reportingsta_ie_size < (IDEXT_POS + 1))) {
+		mlo_err_rl("Total length %zu of element for reporting STA is smaller than minimum required to access element ID extension %u",
+			   reportingsta_ie_size, IDEXT_POS + 1);
+		return QDF_STATUS_E_PROTO;
+	}
+
+	if ((reportingsta_ie + reportingsta_ie_size) >
+			(frame_iesection + frame_iesection_len)) {
+		if (reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
+			mlo_err_rl("Total size %zu octets of element with element ID %u element ID extension %u for reporting STA would exceed frame IE section boundary",
+				   reportingsta_ie_size,
+				   reportingsta_ie[ID_POS],
+				   reportingsta_ie[IDEXT_POS]);
+		} else {
+			mlo_err_rl("Total size %zu octets of element with element ID %u for reporting STA would exceed frame IE section boundary",
+				   reportingsta_ie_size,
+				   reportingsta_ie[ID_POS]);
+		}
+
+		return QDF_STATUS_E_PROTO;
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+static inline
+QDF_STATUS util_validate_sta_prof_ie(const uint8_t *sta_prof_ie,
+				     const uint8_t *sta_prof_iesection,
+				     const qdf_size_t sta_prof_iesection_len)
+{
+	qdf_size_t sta_prof_ie_size;
+
+	if (!sta_prof_ie) {
+		mlo_err("Pointer to STA profile IE is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!sta_prof_iesection) {
+		mlo_err("Pointer to start of IE section in STA profile is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!sta_prof_iesection_len) {
+		mlo_err("Length of IE section in STA profile is zero");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if ((sta_prof_ie + ID_POS) > (sta_prof_iesection +
+			sta_prof_iesection_len - 1)) {
+		mlo_err_rl("Position of element ID field of STA profile element would exceed STA profile IE section boundary");
+		return QDF_STATUS_E_PROTO;
+	}
+
+	if ((sta_prof_ie + TAG_LEN_POS) > (sta_prof_iesection +
+			sta_prof_iesection_len - 1)) {
+		mlo_err_rl("Position of length field of element with element ID %u in STA profile would exceed STA profile IE section boundary",
+			   sta_prof_ie[ID_POS]);
+		return QDF_STATUS_E_PROTO;
+	}
+
+	if ((sta_prof_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
+	    ((sta_prof_ie + IDEXT_POS) > (sta_prof_iesection +
+				sta_prof_iesection_len - 1))) {
+		mlo_err_rl("Position of element ID extension field of element would exceed STA profile IE section boundary");
+		return QDF_STATUS_E_PROTO;
+	}
+
+	sta_prof_ie_size = sta_prof_ie[TAG_LEN_POS] + MIN_IE_LEN;
+
+	if ((sta_prof_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
+	    (sta_prof_ie_size < (IDEXT_POS + 1))) {
+		mlo_err_rl("Total length %zu of STA profile element is smaller than minimum required to access element ID extension %u",
+			   sta_prof_ie_size, IDEXT_POS + 1);
+		return QDF_STATUS_E_PROTO;
+	}
+
+	if ((sta_prof_ie + sta_prof_ie_size) >
+			(sta_prof_iesection + sta_prof_iesection_len)) {
+		if (sta_prof_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
+			mlo_err_rl("Total size %zu octets of element with element ID %u element ID extension %u in STA profile would exceed STA profile IE section boundary",
+				   sta_prof_ie_size,
+				   sta_prof_ie[ID_POS],
+				   sta_prof_ie[IDEXT_POS]);
+		} else {
+			mlo_err_rl("Total size %zu octets of element with element ID %u in STA profile would exceed STA profile IE section boundary",
+				   sta_prof_ie_size,
+				   sta_prof_ie[ID_POS]);
+		}
+
+		return QDF_STATUS_E_PROTO;
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+#define MLO_LINKSPECIFIC_ASSOC_REQ_FC0  0x00
+#define MLO_LINKSPECIFIC_ASSOC_REQ_FC1  0x00
+#define MLO_LINKSPECIFIC_ASSOC_RESP_FC0 0x10
+#define MLO_LINKSPECIFIC_ASSOC_RESP_FC1 0x00
+
+static
+QDF_STATUS util_gen_link_assoc_reqrsp_cmn(uint8_t *frame, qdf_size_t frame_len,
+					  uint8_t subtype,
+					  struct qdf_mac_addr link_addr,
+					  uint8_t *link_frame,
+					  qdf_size_t link_frame_maxsize,
+					  qdf_size_t *link_frame_len)
+{
+	/* Please see documentation for util_gen_link_assoc_req() and
+	 * util_gen_link_assoc_resp() for information on the inputs to and
+	 * output from this helper, since those APIs are essentially wrappers
+	 * over this helper.
+	 */
+
+	/* Pointer to Multi-Link element */
+	uint8_t *mlie;
+	/* Total length of Multi-Link element sequence (including fragements if
+	 * any)
+	 */
+	qdf_size_t mlieseqlen;
+	/* Variant (i.e. type) of the Multi-Link element */
+	enum wlan_ml_variant variant;
+	/* Pointer to original copy of Multi-Link element */
+	uint8_t *orig_mlie_copy;
+
+	/* Length of the Link Info */
+	qdf_size_t link_info_len;
+
+	/* Pointer to the IE section that occurs after the fixed fields in the
+	 * original frame for the reporting STA.
+	 */
+	uint8_t *frame_iesection;
+	/* Offset to the start of the IE section in the original frame for the
+	 * reporting STA.
+	 */
+	qdf_size_t frame_iesection_offset;
+	/* Total length of the IE section in the original frame for the
+	 * reporting STA.
+	 */
+	qdf_size_t frame_iesection_len;
+
+	/* Pointer to the IEEE802.11 frame header in the link specific frame
+	 * being generated for the reported STA.
+	 */
+	struct wlan_frame_hdr *link_frame_hdr;
+	/* Current position in the link specific frame being generated for the
+	 * reported STA.
+	 */
+	uint8_t *link_frame_currpos;
+	/* Current length of the link specific frame being generated for the
+	 * reported STA.
+	 */
+	qdf_size_t link_frame_currlen;
+
+	/* Pointer to IE for reporting STA */
+	const uint8_t *reportingsta_ie;
+	/* Total size of IE for reporting STA, inclusive of the element header
+	 */
+	qdf_size_t reportingsta_ie_size;
+
+	/* Pointer to current position in STA profile */
+	uint8_t *sta_prof_currpos;
+	/* Remaining length of STA profile */
+	qdf_size_t sta_prof_remlen;
+	/* Pointer to start of IE section in STA profile that occurs after fixed
+	 * fields.
+	 */
+	uint8_t *sta_prof_iesection;
+	/* Total length of IE section in STA profile */
+	qdf_size_t sta_prof_iesection_len;
+	/* Pointer to current position being processed in IE section in STA
+	 * profile.
+	 */
+	uint8_t *sta_prof_iesection_currpos;
+	/* Remaining length of IE section in STA profile */
+	qdf_size_t sta_prof_iesection_remlen;
+
+	/* Pointer to IE in STA profile, that occurs within IE section */
+	uint8_t *sta_prof_ie;
+	/* Total size of IE in STA profile, inclusive of the element header */
+	qdf_size_t sta_prof_ie_size;
+
+	/* Pointer to element ID list in Non-Inheritance IE */
+	uint8_t *ninherit_elemlist;
+	/* Length of element ID list in Non-Inheritance IE */
+	qdf_size_t ninherit_elemlist_len;
+	/* Pointer to element ID extension list in Non-Inheritance IE */
+	uint8_t *ninherit_elemextlist;
+	/* Length of element ID extension list in Non-Inheritance IE */
+	qdf_size_t ninherit_elemextlist_len;
+	/* Whether a given IE is in a non-inheritance list */
+	bool is_in_noninheritlist;
+
+	/* Whether MAC address of reported STA is valid */
+	bool is_reportedmacaddr_valid;
+	/* MAC address of reported STA */
+	struct qdf_mac_addr reportedmacaddr;
+
+	/* Other variables for temporary purposes */
+	uint8_t *sub_copy;
+	QDF_STATUS ret;
+
+	if (!frame) {
+		mlo_err("Pointer to original frame is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!frame_len) {
+		mlo_err("Length of original frame is zero");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if ((subtype != WLAN_FC0_STYPE_ASSOC_REQ) &&
+	    (subtype != WLAN_FC0_STYPE_REASSOC_REQ) &&
+	    (subtype != WLAN_FC0_STYPE_ASSOC_RESP) &&
+	    (subtype != WLAN_FC0_STYPE_REASSOC_RESP)) {
+		mlo_err("802.11 frame subtype %u is invalid", subtype);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if (!link_frame) {
+		mlo_err("Pointer to secondary link specific frame is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	if (!link_frame_maxsize) {
+		mlo_err("Maximum size of secondary link specific frame is zero");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	if (!link_frame_len) {
+		mlo_err("Pointer to populated length of secondary link specific frame is NULL");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+
+	frame_iesection_offset = 0;
+
+	if (subtype == WLAN_FC0_STYPE_ASSOC_REQ) {
+		frame_iesection_offset = WLAN_ASSOC_REQ_IES_OFFSET;
+	} else if (subtype == WLAN_FC0_STYPE_REASSOC_REQ) {
+		frame_iesection_offset = WLAN_REASSOC_REQ_IES_OFFSET;
+	} else {
+		/* This is a (re)association response */
+		frame_iesection_offset = WLAN_ASSOC_RSP_IES_OFFSET;
+	}
+
+	if (frame_len < frame_iesection_offset) {
+		/* The caller is supposed to have confirmed that this is a valid
+		 * frame containing a Multi-Link element. Hence we treat this as
+		 * a case of invalid argument being passed to us.
+		 */
+		mlo_err("Frame length %zu is smaller than the IE section offset %zu for subtype %u",
+			frame_len, frame_iesection_offset, subtype);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	frame_iesection_len = frame_len - frame_iesection_offset;
+
+	if (frame_iesection_len == 0) {
+		/* The caller is supposed to have confirmed that this is a valid
+		 * frame containing a Multi-Link element. Hence we treat this as
+		 * a case of invalid argument being passed to us.
+		 */
+		mlo_err("No space left in frame for IE section");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	frame_iesection = frame + frame_iesection_offset;
+
+	mlie = NULL;
+	mlieseqlen = 0;
+
+	ret = util_find_mlie(frame_iesection, frame_iesection_len, &mlie,
+			     &mlieseqlen);
+	if (QDF_IS_STATUS_ERROR(ret))
+		return ret;
+
+	if (!mlie) {
+		/* The caller is supposed to have confirmed that a Multi-Link
+		 * element is present in the frame. Hence we treat this as a
+		 * case of invalid argument being passed to us.
+		 */
+		mlo_err("Invalid original frame since no Multi-Link element found");
+		return QDF_STATUS_E_INVAL;
+	}
+
+	/* Sanity check the Multi-Link element sequence length */
+	if (!mlieseqlen) {
+		mlo_err("Length of Multi-Link element sequence is zero. Investigate.");
 		return QDF_STATUS_E_FAILURE;
+	}
 
-	/*  EID(1) + len (1) = 2 */
-	sub_len = subelement[TAG_LEN_POS] + 2;
-	sub_copy = qdf_mem_malloc(sub_len);
-	if (!sub_copy)
+	ret = util_get_mlie_variant(mlie, mlieseqlen, (int *)&variant);
+	if (QDF_IS_STATUS_ERROR(ret))
+		return ret;
+
+	if (variant != WLAN_ML_VARIANT_BASIC) {
+		mlo_err_rl("Unexpected variant %u of Multi-Link element.",
+			   variant);
+		return QDF_STATUS_E_PROTO;
+	}
+
+	/* Note: Multi-Link element fragmentation support will be added in a
+	 * later change. As of now, we temporarily return error if the
+	 * Multi-Link element sequence length is greater than the max length for
+	 * an IE.
+	 */
+	if (mlieseqlen > (sizeof(struct ie_header) + WLAN_MAX_IE_LEN)) {
+		mlo_err_rl("Element fragmentation is not yet supported for this API");
+		return QDF_STATUS_E_NOSUPPORT;
+	}
+
+	sub_copy = qdf_mem_malloc(mlieseqlen);
+	if (!sub_copy) {
+		mlo_err_rl("Could not allocate memory for Multi-Link element copy");
 		return QDF_STATUS_E_NOMEM;
-	orig_copy = sub_copy;
-	qdf_mem_copy(sub_copy, subelement, sub_len);
+	}
+
+	orig_mlie_copy = sub_copy;
+	qdf_mem_copy(sub_copy, mlie, mlieseqlen);
 
-	/* parse ml ie */
 	sub_copy = util_parse_multi_link_ctrl(sub_copy,
-					      sub_len,
+					      mlieseqlen,
 					      &link_info_len);
 
-	if (!sub_copy)
-		return QDF_STATUS_E_NULL_VALUE;
+	/* As per the standard, the sender must include Link Info for
+	 * association request/response. Throw an error if we are unable to
+	 * obtain this.
+	 */
+	if (!sub_copy) {
+		mlo_err_rl("Unable to successfully parse Multi-Link element control and obtain Link Info");
+		qdf_mem_free(orig_mlie_copy);
+		return QDF_STATUS_E_PROTO;
+	}
 
-	mlo_debug("dumping hex after parsing multi link ctrl");
+	mlo_debug("Dumping hex after parsing Multi-Link element control");
 	QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_MLO, QDF_TRACE_LEVEL_DEBUG,
 			   sub_copy, link_info_len);
 
-	is_bssid_valid = false;
+	sta_prof_remlen = 0;
+	is_reportedmacaddr_valid = false;
+
+	/* Note: We may have a future change to skip subelements which are not
+	 * Per-STA Profile, handle more than two links in MLO, handle cases
+	 * where we unexpectedly find more Per-STA Profiles than expected, etc.
+	 */
 
 	/* Parse per-STA profile */
-	sub_copy = util_parse_perstaprofile(sub_copy,
-					    link_info_len,
-					    true,
-					    &sta_prof_len,
-					    NULL,
-					    &is_bssid_valid,
-					    &bssid);
-
-	if (!is_bssid_valid)
-		return QDF_STATUS_E_NULL_VALUE;
+	sta_prof_currpos =
+		util_parse_bvmlie_perstaprofile(sub_copy,
+						link_info_len,
+						true,
+						&sta_prof_remlen,
+						NULL,
+						&is_reportedmacaddr_valid,
+						&reportedmacaddr);
+
+	/* If we do not successfully find a STA Profile, we return an error.
+	 * This is because we need to get at least the expected fixed fields,
+	 * even if there is an (improbable) total inheritance.
+	 */
+	if (!sta_prof_currpos) {
+		mlo_err_rl("Unable to find STA profile");
+		qdf_mem_free(orig_mlie_copy);
+		return QDF_STATUS_E_PROTO;
+	}
 
-	if (!sub_copy) {
-		qdf_mem_copy(pos, frame, len);
-		pos += len - WLAN_MAC_HDR_LEN_3A;
-		goto update_header;
-	}
-
-	/* go through IEs in frame and subelement,
-	 * merge them into new_ie
-	 */
-	tmp_old = util_find_eid(WLAN_ELEMID_SSID, frame, len);
-	tmp_old = (tmp_old) ? tmp_old + tmp_old[TAG_LEN_POS] + MIN_IE_LEN : frame;
-
-	while (((tmp_old + tmp_old[TAG_LEN_POS] + MIN_IE_LEN) - frame) <= len) {
-		tmp = (uint8_t *)util_find_eid(tmp_old[0],
-					       sub_copy,
-					       sta_prof_len);
-		if (!tmp) {
-			/* ie in old ie but not in subelement */
-			if (tmp_old[2] != WLAN_EXTN_ELEMID_MULTI_LINK) {
-				if ((pos + tmp_old[TAG_LEN_POS] + MIN_IE_LEN) <=
-					(assoc_link_frame + len)) {
-					qdf_mem_copy(pos, tmp_old,
-						     (tmp_old[TAG_LEN_POS] +
-						     MIN_IE_LEN));
-					pos += tmp_old[TAG_LEN_POS] + MIN_IE_LEN;
+	/* As per the standard, the sender sets the MAC address in the per-STA
+	 * profile in association request/response. Without this, we cannot
+	 * generate the link specific frame.
+	 */
+	if (!is_reportedmacaddr_valid) {
+		mlo_err_rl("Unable to get MAC address from per-STA profile");
+		qdf_mem_free(orig_mlie_copy);
+		return QDF_STATUS_E_PROTO;
+	}
+
+	link_frame_currpos = link_frame;
+	*link_frame_len = 0;
+	link_frame_currlen = 0;
+
+	if (link_frame_maxsize < WLAN_MAC_HDR_LEN_3A) {
+		mlo_err("Insufficent space in link specific frame for 802.11 header. Required: %u octets, available: %zu octets",
+			WLAN_MAC_HDR_LEN_3A, link_frame_maxsize);
+
+		qdf_mem_free(orig_mlie_copy);
+		return QDF_STATUS_E_NOMEM;
+	}
+
+	link_frame_currpos += WLAN_MAC_HDR_LEN_3A;
+	link_frame_currlen += WLAN_MAC_HDR_LEN_3A;
+
+	if ((subtype == WLAN_FC0_STYPE_ASSOC_REQ) ||
+	    (subtype == WLAN_FC0_STYPE_REASSOC_REQ)) {
+		mlo_debug("Populating fixed fields for (re)assoc req in link specific frame");
+
+		if (sta_prof_remlen < WLAN_CAPABILITYINFO_LEN) {
+			mlo_err_rl("Remaining length of STA profile %zu octets is less than length of Capability Info %u",
+				   sta_prof_remlen,
+				   WLAN_CAPABILITYINFO_LEN);
+
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_PROTO;
+		}
+
+		/* Capability information is specific to the link. Copy this
+		 * from the STA profile.
+		 */
+
+		if ((link_frame_maxsize - link_frame_currlen) <
+				WLAN_CAPABILITYINFO_LEN) {
+			mlo_err("Insufficent space in link specific frame for Capability Info field. Required: %u octets, available: %zu octets",
+				WLAN_CAPABILITYINFO_LEN,
+				(link_frame_maxsize - link_frame_currlen));
+
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_NOMEM;
+		}
+
+		qdf_mem_copy(link_frame_currpos, sta_prof_currpos,
+			     WLAN_CAPABILITYINFO_LEN);
+		link_frame_currpos += WLAN_CAPABILITYINFO_LEN;
+		link_frame_currlen += WLAN_CAPABILITYINFO_LEN;
+		mlo_debug("Added Capablity Info field (%u octets) to link specific frame",
+			  WLAN_CAPABILITYINFO_LEN);
+
+		sta_prof_currpos += WLAN_CAPABILITYINFO_LEN;
+		sta_prof_remlen -= WLAN_CAPABILITYINFO_LEN;
+
+		/* Listen Interval is common between all links. Copy this from
+		 * the reporting section of the frame.
+		 */
+
+		if ((link_frame_maxsize - link_frame_currlen) <
+				WLAN_LISTENINTERVAL_LEN) {
+			mlo_err("Insufficent space in link specific frame for Listen Interval field. Required: %u octets, available: %zu octets",
+				WLAN_LISTENINTERVAL_LEN,
+				(link_frame_maxsize - link_frame_currlen));
+
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_NOMEM;
+		}
+
+		qdf_mem_copy(link_frame_currpos,
+			     frame + WLAN_CAPABILITYINFO_LEN,
+			     WLAN_LISTENINTERVAL_LEN);
+		link_frame_currpos += WLAN_LISTENINTERVAL_LEN;
+		link_frame_currlen += WLAN_LISTENINTERVAL_LEN;
+		mlo_debug("Added Listen Interval field (%u octets) to link specific frame",
+			  WLAN_LISTENINTERVAL_LEN);
+
+		if (subtype == WLAN_FC0_STYPE_REASSOC_REQ) {
+			/* Current AP address is common between all links. Copy
+			 * this from the reporting section of the frame.
+			 */
+			if ((link_frame_maxsize - link_frame_currlen) <
+				QDF_MAC_ADDR_SIZE) {
+				mlo_err("Insufficent space in link specific frame for current AP address. Required: %u octets, available: %zu octets",
+					QDF_MAC_ADDR_SIZE,
+					(link_frame_maxsize -
+						link_frame_currlen));
+
+				qdf_mem_free(orig_mlie_copy);
+				return QDF_STATUS_E_NOMEM;
+			}
+
+			qdf_mem_copy(link_frame_currpos,
+				     frame + WLAN_CAPABILITYINFO_LEN +
+						WLAN_LISTENINTERVAL_LEN,
+				     QDF_MAC_ADDR_SIZE);
+			link_frame_currpos += QDF_MAC_ADDR_SIZE;
+			link_frame_currlen += QDF_MAC_ADDR_SIZE;
+			mlo_debug("Reassoc req: Added Current AP address field (%u octets) to link specific frame",
+				  QDF_MAC_ADDR_SIZE);
+		}
+	} else {
+		/* This is a (re)association response */
+		mlo_debug("Populating fixed fields for (re)assoc resp in link specific frame");
+
+		if (sta_prof_remlen <
+			(WLAN_CAPABILITYINFO_LEN + WLAN_STATUSCODE_LEN)) {
+			mlo_err_rl("Remaining length of STA profile %zu octets is less than length of Capability Info + length of Status Code %u",
+				   sta_prof_remlen,
+				   WLAN_CAPABILITYINFO_LEN +
+					WLAN_STATUSCODE_LEN);
+
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_PROTO;
+		}
+
+		/* Capability information and Status Code are specific to the
+		 * link. Copy these from the STA profile.
+		 */
+
+		if ((link_frame_maxsize - link_frame_currlen) <
+			(WLAN_CAPABILITYINFO_LEN + WLAN_STATUSCODE_LEN)) {
+			mlo_err("Insufficent space in link specific frame for Capability Info and Status Code fields. Required: %u octets, available: %zu octets",
+				WLAN_CAPABILITYINFO_LEN + WLAN_STATUSCODE_LEN,
+				(link_frame_maxsize - link_frame_currlen));
+
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_NOMEM;
+		}
+
+		qdf_mem_copy(link_frame_currpos, sta_prof_currpos,
+			     (WLAN_CAPABILITYINFO_LEN + WLAN_STATUSCODE_LEN));
+		link_frame_currpos += (WLAN_CAPABILITYINFO_LEN +
+						WLAN_STATUSCODE_LEN);
+		link_frame_currlen += (WLAN_CAPABILITYINFO_LEN +
+				WLAN_STATUSCODE_LEN);
+		mlo_debug("Added Capability Info and Status Code fields (%u octets) to link specific frame",
+			  WLAN_CAPABILITYINFO_LEN + WLAN_STATUSCODE_LEN);
+
+		sta_prof_currpos += (WLAN_CAPABILITYINFO_LEN +
+				WLAN_STATUSCODE_LEN);
+		sta_prof_remlen -= (WLAN_CAPABILITYINFO_LEN +
+				WLAN_STATUSCODE_LEN);
+
+		/* AID is common between all links. Copy this from the original
+		 * frame.
+		 */
+
+		if ((link_frame_maxsize - link_frame_currlen) < WLAN_AID_LEN) {
+			mlo_err("Insufficent space in link specific frame for AID field. Required: %u octets, available: %zu octets",
+				WLAN_AID_LEN,
+				(link_frame_maxsize - link_frame_currlen));
+
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_NOMEM;
+		}
+
+		qdf_mem_copy(link_frame_currpos,
+			     frame + WLAN_CAPABILITYINFO_LEN +
+					WLAN_STATUSCODE_LEN,
+			     WLAN_AID_LEN);
+		link_frame_currpos += WLAN_AID_LEN;
+		link_frame_currlen += WLAN_AID_LEN;
+		mlo_debug("Added AID field (%u octets) to link specific frame",
+			  WLAN_AID_LEN);
+	}
+
+	sta_prof_iesection = sta_prof_currpos;
+	sta_prof_iesection_len = sta_prof_remlen;
+
+	/* Populate non-inheritance lists if applicable */
+	ninherit_elemlist_len = 0;
+	ninherit_elemlist = NULL;
+	ninherit_elemextlist_len = 0;
+	ninherit_elemextlist = NULL;
+
+	ret = util_get_noninheritlists(sta_prof_iesection,
+				       sta_prof_iesection_len,
+				       &ninherit_elemlist,
+				       &ninherit_elemlist_len,
+				       &ninherit_elemextlist,
+				       &ninherit_elemextlist_len);
+	if (QDF_IS_STATUS_ERROR(ret)) {
+		qdf_mem_free(orig_mlie_copy);
+		return ret;
+	}
+
+	/* Go through IEs of the reporting STA, and those in STA profile, merge
+	 * them into link_frame (except for elements in the Non-Inheritance
+	 * list).
+	 *
+	 * Note: Currently, only 2-link MLO is supported here. We may have a
+	 * future change to expand to more links.
+	 */
+	reportingsta_ie = util_find_eid(WLAN_ELEMID_SSID, frame_iesection,
+					frame_iesection_len);
+
+	if ((subtype == WLAN_FC0_STYPE_ASSOC_REQ) ||
+	    (subtype == WLAN_FC0_STYPE_REASSOC_REQ)) {
+		/* Sanity check that the SSID element is present for the
+		 * reporting STA. There is no stipulation in the standard for
+		 * the STA profile in this regard, so we do not check the STA
+		 * profile for the SSID element.
+		 */
+		if (!reportingsta_ie) {
+			mlo_err_rl("SSID element not found for reporting STA for (re)association request.");
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_PROTO;
+		}
+	} else {
+		/* This is a (re)association response. Sanity check that the
+		 * SSID element is present neither for the reporting STA nor in
+		 * the STA profile.
+		 */
+		if (reportingsta_ie) {
+			mlo_err_rl("SSID element found for reporting STA for (re)association response. It should not be present.");
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_PROTO;
+		}
+
+		sta_prof_ie = util_find_eid(WLAN_ELEMID_SSID,
+					    sta_prof_iesection,
+					    sta_prof_iesection_len);
+
+		if (sta_prof_ie) {
+			mlo_err_rl("SSID element found in STA profile for (re)association response. It should not be present.");
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_PROTO;
+		}
+	}
+
+	reportingsta_ie = reportingsta_ie ? reportingsta_ie : frame_iesection;
+
+	ret = util_validate_reportingsta_ie(reportingsta_ie, frame_iesection,
+					    frame_iesection_len);
+	if (QDF_IS_STATUS_ERROR(ret)) {
+		qdf_mem_free(orig_mlie_copy);
+		return ret;
+	}
+
+	reportingsta_ie_size = reportingsta_ie[TAG_LEN_POS] + MIN_IE_LEN;
+
+	while (((reportingsta_ie + reportingsta_ie_size) - frame_iesection)
+			<= frame_iesection_len) {
+		/* Skip Multi-Link element */
+		if ((reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
+		    (reportingsta_ie[IDEXT_POS] ==
+				WLAN_EXTN_ELEMID_MULTI_LINK)) {
+			if (((reportingsta_ie + reportingsta_ie_size) -
+					frame_iesection) == frame_iesection_len)
+				break;
+
+			reportingsta_ie += reportingsta_ie_size;
+
+			ret = util_validate_reportingsta_ie(reportingsta_ie,
+							    frame_iesection,
+							    frame_iesection_len);
+			if (QDF_IS_STATUS_ERROR(ret)) {
+				qdf_mem_free(orig_mlie_copy);
+				return ret;
+			}
+
+			reportingsta_ie_size = reportingsta_ie[TAG_LEN_POS] +
+				MIN_IE_LEN;
+
+			continue;
+		}
+
+		sta_prof_ie = NULL;
+		sta_prof_ie_size = 0;
+
+		if (sta_prof_iesection_len) {
+			if (reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
+				sta_prof_ie = (uint8_t *)util_find_extn_eid(reportingsta_ie[ID_POS],
+									    reportingsta_ie[IDEXT_POS],
+									    sta_prof_iesection,
+									    sta_prof_iesection_len);
+			} else {
+				sta_prof_ie = (uint8_t *)util_find_eid(reportingsta_ie[ID_POS],
+								       sta_prof_iesection,
+								       sta_prof_iesection_len);
+			}
+		}
+
+		if (!sta_prof_ie) {
+			/* IE is present for reporting STA, but not in STA
+			 * profile.
+			 */
+
+			is_in_noninheritlist = false;
+
+			ret = util_eval_ie_in_noninheritlist((uint8_t *)reportingsta_ie,
+							     reportingsta_ie_size,
+							     ninherit_elemlist,
+							     ninherit_elemlist_len,
+							     ninherit_elemextlist,
+							     ninherit_elemextlist_len,
+							     &is_in_noninheritlist);
+
+			if (QDF_IS_STATUS_ERROR(ret)) {
+				qdf_mem_free(orig_mlie_copy);
+				return ret;
+			}
+
+			if (!is_in_noninheritlist) {
+				if ((link_frame_currpos +
+						reportingsta_ie_size) <=
+					(link_frame + link_frame_maxsize)) {
+					qdf_mem_copy(link_frame_currpos,
+						     reportingsta_ie,
+						     reportingsta_ie_size);
+
+					link_frame_currpos +=
+						reportingsta_ie_size;
+					link_frame_currlen +=
+						reportingsta_ie_size;
+
+					if (reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
+						mlo_debug("IE with element ID : %u extension element ID : %u (%zu octets) present for reporting STA but not in STA profile. Copied IE from reporting frame to link specific frame",
+							  reportingsta_ie[ID_POS],
+							  reportingsta_ie[IDEXT_POS],
+							  reportingsta_ie_size);
+					} else {
+						mlo_debug("IE with element ID : %u (%zu octets) present for reporting STA but not in STA profile. Copied IE from reporting frame to link specific frame",
+							  reportingsta_ie[ID_POS],
+							  reportingsta_ie_size);
+					}
+				} else {
+					if (reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
+						mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u extension element ID : %u. Required: %zu octets, available: %zu octets",
+							   reportingsta_ie[ID_POS],
+							   reportingsta_ie[IDEXT_POS],
+							   reportingsta_ie_size,
+							   link_frame_maxsize -
+							   link_frame_currlen);
+					} else {
+						mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
+							   reportingsta_ie[ID_POS],
+							   reportingsta_ie_size,
+							   link_frame_maxsize -
+							   link_frame_currlen);
+					}
+
+					qdf_mem_free(orig_mlie_copy);
+					return QDF_STATUS_E_NOMEM;
+				}
+			} else {
+				if (reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
+					mlo_debug("IE with element ID : %u extension element ID : %u (%zu octets) present for reporting STA but not in STA profile. However it is in Non-Inheritance list, hence ignoring.",
+						  reportingsta_ie[ID_POS],
+						  reportingsta_ie[IDEXT_POS],
+						  reportingsta_ie_size);
+				} else {
+					mlo_debug("IE with element ID : %u (%zu octets) present for reporting STA but not in STA profile. However it is in Non-Inheritance list, hence ignoring.",
+						  reportingsta_ie[ID_POS],
+						  reportingsta_ie_size);
 				}
 			}
 		} else {
-			/* ie in transmitting ie also in subelement,
-			 * copy from subelement and flag the ie in subelement
-			 * as copied (by setting eid field to 0xff). For
-			 * vendor ie, compare OUI + type + subType to
-			 * determine if they are the same ie.
+			/* IE is present for reporting STA and also in STA
+			 * profile, copy from STA profile and flag the IE in STA
+			 * profile as copied (by setting EID field to 0). The
+			 * SSID element (with EID 0) is processed first to
+			 * enable this. For vendor IE, compare OUI + type +
+			 * subType to determine if they are the same IE.
+			 */
+			/* Note: This may be revisited in a future change, to
+			 * adhere to provisions in the standard for multiple
+			 * occurrences of a given element ID/extension element
+			 * ID.
 			 */
-			tmp_rem_len = sta_prof_len - (tmp - sub_copy);
-			if (tmp_old[0] == WLAN_ELEMID_VENDOR &&
-			    tmp_rem_len >= MIN_VENDOR_TAG_LEN) {
-				if (!qdf_mem_cmp(tmp_old + PAYLOAD_START_POS,
-						 tmp + PAYLOAD_START_POS,
+
+			ret = util_validate_sta_prof_ie(sta_prof_ie,
+							sta_prof_iesection,
+							sta_prof_iesection_len);
+			if (QDF_IS_STATUS_ERROR(ret)) {
+				qdf_mem_free(orig_mlie_copy);
+				return ret;
+			}
+
+			sta_prof_ie_size = sta_prof_ie[TAG_LEN_POS] +
+				MIN_IE_LEN;
+
+			sta_prof_iesection_remlen =
+				sta_prof_iesection_len -
+					(sta_prof_ie - sta_prof_iesection);
+
+			if ((reportingsta_ie[ID_POS] == WLAN_ELEMID_VENDOR) &&
+			    (sta_prof_iesection_remlen >= MIN_VENDOR_TAG_LEN)) {
+				if (!qdf_mem_cmp(reportingsta_ie +
+							PAYLOAD_START_POS,
+						 sta_prof_ie +
+							PAYLOAD_START_POS,
 						 OUI_LEN)) {
-					/* same vendor ie, copy from
-					 * subelement
+					/* Same vendor IE, copy from STA profile
 					 */
-					if ((pos + tmp[TAG_LEN_POS] + MIN_IE_LEN) <=
-						(assoc_link_frame + len)) {
-						qdf_mem_copy(pos, tmp,
-							     tmp[TAG_LEN_POS] +
-							     MIN_IE_LEN);
-						pos += tmp[TAG_LEN_POS] + MIN_IE_LEN;
-						tmp[0] = 0;
+					if ((link_frame_currpos +
+							sta_prof_ie_size) <=
+						(link_frame +
+							link_frame_maxsize)) {
+						qdf_mem_copy(link_frame_currpos,
+							     sta_prof_ie,
+							     sta_prof_ie_size);
+
+						link_frame_currpos +=
+							sta_prof_ie_size;
+						link_frame_currlen +=
+							sta_prof_ie_size;
+
+						mlo_debug("Vendor IE (%zu octets) for reporting STA also present in STA profile. Copied IE from STA profile to link specific frame",
+							  sta_prof_ie_size);
+
+						sta_prof_ie[0] = 0;
+					} else {
+						mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
+							   sta_prof_ie[ID_POS],
+							   sta_prof_ie_size,
+							   link_frame_maxsize -
+							   link_frame_currlen);
+
+						qdf_mem_free(orig_mlie_copy);
+						return QDF_STATUS_E_NOMEM;
 					}
 				} else {
-					if ((pos + tmp_old[TAG_LEN_POS] +
-						 MIN_IE_LEN) <=
-						(assoc_link_frame + len)) {
-						qdf_mem_copy(pos, tmp_old,
-							     tmp_old[TAG_LEN_POS] +
-							     MIN_IE_LEN);
-						pos += tmp_old[TAG_LEN_POS] +
-							MIN_IE_LEN;
+					if ((link_frame_currpos +
+							reportingsta_ie_size) <=
+						(link_frame +
+							link_frame_maxsize)) {
+						qdf_mem_copy(link_frame_currpos,
+							     reportingsta_ie,
+							     reportingsta_ie_size);
+
+						link_frame_currpos +=
+							reportingsta_ie_size;
+						link_frame_currlen +=
+							reportingsta_ie_size;
+
+						mlo_debug("Vendor IE (%zu octets) present for reporting STA but not present in STA profile. Copied IE from reporting frame to link specific frame",
+							  reportingsta_ie_size);
+					} else {
+						mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
+							   reportingsta_ie[ID_POS],
+							   reportingsta_ie_size,
+							   link_frame_maxsize -
+							   link_frame_currlen);
+
+						qdf_mem_free(orig_mlie_copy);
+						return QDF_STATUS_E_NOMEM;
 					}
 				}
-			} else if (tmp_old[0] == WLAN_ELEMID_EXTN_ELEM) {
-				if (tmp_old[PAYLOAD_START_POS] ==
-					tmp[PAYLOAD_START_POS]) {
-					/* same ie, copy from subelement */
-					if ((pos + tmp[TAG_LEN_POS] + MIN_IE_LEN) <=
-						(assoc_link_frame + len)) {
-						qdf_mem_copy(pos, tmp,
-							     tmp[TAG_LEN_POS] +
-							     MIN_IE_LEN);
-						pos += tmp[TAG_LEN_POS] + MIN_IE_LEN;
-						tmp[0] = 0;
+			} else {
+				/* Copy IE from STA profile into link specific
+				 * frame.
+				 */
+				if ((link_frame_currpos + sta_prof_ie_size) <=
+					(link_frame + link_frame_maxsize)) {
+					qdf_mem_copy(link_frame_currpos,
+						     sta_prof_ie,
+						     sta_prof_ie_size);
+
+					link_frame_currpos += sta_prof_ie_size;
+					link_frame_currlen +=
+						sta_prof_ie_size;
+
+					if (reportingsta_ie[ID_POS] ==
+							WLAN_ELEMID_EXTN_ELEM) {
+						mlo_debug("IE with element ID : %u extension element ID : %u (%zu octets) for reporting STA also present in STA profile. Copied IE from STA profile to link specific frame",
+							  sta_prof_ie[ID_POS],
+							  sta_prof_ie[IDEXT_POS],
+							  sta_prof_ie_size);
+					} else {
+						mlo_debug("IE with element ID : %u (%zu octets) for reporting STA also present in STA profile. Copied IE from STA profile to link specific frame",
+							  sta_prof_ie[ID_POS],
+							  sta_prof_ie_size);
 					}
+
+					sta_prof_ie[0] = 0;
 				} else {
-					if ((pos + tmp_old[TAG_LEN_POS] + MIN_IE_LEN) <=
-						(assoc_link_frame + len)) {
-						qdf_mem_copy(pos, tmp_old,
-							     tmp_old[TAG_LEN_POS] +
-							     MIN_IE_LEN);
-						pos += tmp_old[TAG_LEN_POS] +
-							MIN_IE_LEN;
+					if (sta_prof_ie[ID_POS] ==
+							WLAN_ELEMID_EXTN_ELEM) {
+						mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u extension element ID : %u. Required: %zu octets, available: %zu octets",
+							   sta_prof_ie[ID_POS],
+							   sta_prof_ie[IDEXT_POS],
+							   sta_prof_ie_size,
+							   link_frame_maxsize -
+							   link_frame_currlen);
+					} else {
+						mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
+							   sta_prof_ie[ID_POS],
+							   sta_prof_ie_size,
+							   link_frame_maxsize -
+							   link_frame_currlen);
 					}
-				}
-			} else {
-				/* copy ie from subelement into new ie */
-				if ((pos + tmp[TAG_LEN_POS] + MIN_IE_LEN) <=
-					(assoc_link_frame + len)) {
-					qdf_mem_copy(pos, tmp,
-						     tmp[TAG_LEN_POS] + MIN_IE_LEN);
-					pos += tmp[TAG_LEN_POS] + MIN_IE_LEN;
-					tmp[0] = 0;
+
+					qdf_mem_free(orig_mlie_copy);
+					return QDF_STATUS_E_NOMEM;
 				}
 			}
 		}
 
-		if (((tmp_old + tmp_old[TAG_LEN_POS] + MIN_IE_LEN) - frame) >= len)
+		if (((reportingsta_ie + reportingsta_ie_size) -
+					frame_iesection) == frame_iesection_len)
 			break;
 
-		tmp_old += tmp_old[TAG_LEN_POS] + MIN_IE_LEN;
+		reportingsta_ie += reportingsta_ie_size;
+
+		ret = util_validate_reportingsta_ie(reportingsta_ie,
+						    frame_iesection,
+						    frame_iesection_len);
+		if (QDF_IS_STATUS_ERROR(ret)) {
+			qdf_mem_free(orig_mlie_copy);
+			return ret;
+		}
+
+		reportingsta_ie_size = reportingsta_ie[TAG_LEN_POS] +
+			MIN_IE_LEN;
 	}
 
-update_header:
-	/* Copy the link mac addr */
-	qdf_mem_copy(hdr->i_addr3, bssid.bytes,
-		     QDF_MAC_ADDR_SIZE);
-	qdf_mem_copy(hdr->i_addr2, bssid.bytes,
-		     QDF_MAC_ADDR_SIZE);
-	qdf_mem_copy(hdr->i_addr1, &link_addr,
-		     QDF_MAC_ADDR_SIZE);
-	hdr->i_fc[0] = FC0_IEEE_MGMT_FRM;
-	hdr->i_fc[1] = FC1_IEEE_MGMT_FRM;
-	/* seq num not used so not populated */
-	qdf_mem_free(orig_copy);
+	/* Go through the remaining unprocessed IEs in STA profile and copy them
+	 * to the link specific frame. The processed ones are marked with 0 in
+	 * the first octet. The first octet corresponds to the element ID. In
+	 * the case of (re)association request, the element with actual ID
+	 * WLAN_ELEMID_SSID(0) has already been copied to the link specific
+	 * frame. In the case of (re)association response, it has been verified
+	 * that the element with actual ID WLAN_ELEMID_SSID(0) is present
+	 * neither for the reporting STA nor in the STA profile.
+	 */
+	sta_prof_iesection_currpos = sta_prof_iesection;
+	sta_prof_iesection_remlen = sta_prof_iesection_len;
+
+	while (sta_prof_iesection_remlen > 0) {
+		sta_prof_ie = sta_prof_iesection_currpos;
+		ret = util_validate_sta_prof_ie(sta_prof_ie,
+						sta_prof_iesection_currpos,
+						sta_prof_iesection_remlen);
+		if (QDF_IS_STATUS_ERROR(ret)) {
+			qdf_mem_free(orig_mlie_copy);
+			return ret;
+		}
+
+		sta_prof_ie_size = sta_prof_ie[TAG_LEN_POS] + MIN_IE_LEN;
+
+		if (!sta_prof_ie[0]) {
+			/* Skip this, since it has already been processed */
+			sta_prof_iesection_currpos += sta_prof_ie_size;
+			sta_prof_iesection_remlen -= sta_prof_ie_size;
+			continue;
+		}
+
+		/* Copy IE from STA profile into link specific frame. */
+		if ((link_frame_currpos + sta_prof_ie_size) <=
+			(link_frame + link_frame_maxsize)) {
+			qdf_mem_copy(link_frame_currpos,
+				     sta_prof_ie,
+				     sta_prof_ie_size);
+
+			link_frame_currpos += sta_prof_ie_size;
+			link_frame_currlen +=
+				sta_prof_ie_size;
+
+			if (reportingsta_ie[ID_POS] ==
+					WLAN_ELEMID_EXTN_ELEM) {
+				mlo_debug("IE with element ID : %u extension element ID : %u (%zu octets) is present only in STA profile. Copied IE from STA profile to link specific frame",
+					  sta_prof_ie[ID_POS],
+					  sta_prof_ie[IDEXT_POS],
+					  sta_prof_ie_size);
+			} else {
+				mlo_debug("IE with element ID : %u (%zu octets) is present only in STA profile. Copied IE from STA profile to link specific frame",
+					  sta_prof_ie[ID_POS],
+					  sta_prof_ie_size);
+			}
+
+			sta_prof_ie[0] = 0;
+		} else {
+			if (sta_prof_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
+				mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u extension element ID : %u. Required: %zu octets, available: %zu octets",
+					   sta_prof_ie[ID_POS],
+					   sta_prof_ie[IDEXT_POS],
+					   sta_prof_ie_size,
+					   link_frame_maxsize -
+					   link_frame_currlen);
+			} else {
+				mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
+					   sta_prof_ie[ID_POS],
+					   sta_prof_ie_size,
+					   link_frame_maxsize -
+					   link_frame_currlen);
+			}
+
+			qdf_mem_free(orig_mlie_copy);
+			return QDF_STATUS_E_NOMEM;
+		}
+
+		sta_prof_iesection_currpos += sta_prof_ie_size;
+		sta_prof_iesection_remlen -= sta_prof_ie_size;
+	}
+
+	/* Copy the link MAC addr */
+	link_frame_hdr = (struct wlan_frame_hdr *)link_frame;
+
+	if ((subtype == WLAN_FC0_STYPE_ASSOC_REQ) ||
+	    (subtype == WLAN_FC0_STYPE_REASSOC_REQ)) {
+		qdf_mem_copy(link_frame_hdr->i_addr3, &link_addr,
+			     QDF_MAC_ADDR_SIZE);
+		qdf_mem_copy(link_frame_hdr->i_addr2, reportedmacaddr.bytes,
+			     QDF_MAC_ADDR_SIZE);
+		qdf_mem_copy(link_frame_hdr->i_addr1, &link_addr,
+			     QDF_MAC_ADDR_SIZE);
+
+		link_frame_hdr->i_fc[0] = MLO_LINKSPECIFIC_ASSOC_REQ_FC0;
+		link_frame_hdr->i_fc[1] = MLO_LINKSPECIFIC_ASSOC_REQ_FC1;
+	} else {
+		/* This is a (re)association response */
+
+		qdf_mem_copy(link_frame_hdr->i_addr3, reportedmacaddr.bytes,
+			     QDF_MAC_ADDR_SIZE);
+		qdf_mem_copy(link_frame_hdr->i_addr2, reportedmacaddr.bytes,
+			     QDF_MAC_ADDR_SIZE);
+		qdf_mem_copy(link_frame_hdr->i_addr1, &link_addr,
+			     QDF_MAC_ADDR_SIZE);
+
+		link_frame_hdr->i_fc[0] = MLO_LINKSPECIFIC_ASSOC_RESP_FC0;
+		link_frame_hdr->i_fc[1] = MLO_LINKSPECIFIC_ASSOC_RESP_FC1;
+	}
+
+	/* Seq num not used so not populated */
+
+	qdf_mem_free(orig_mlie_copy);
+
+	*link_frame_len = link_frame_currlen;
 
 	return QDF_STATUS_SUCCESS;
 }
 
+QDF_STATUS
+util_gen_link_assoc_req(uint8_t *frame, qdf_size_t frame_len, bool isreassoc,
+			struct qdf_mac_addr link_addr,
+			uint8_t *link_frame,
+			qdf_size_t link_frame_maxsize,
+			qdf_size_t *link_frame_len)
+{
+	return util_gen_link_assoc_reqrsp_cmn(frame, frame_len,
+			(isreassoc ? WLAN_FC0_STYPE_REASSOC_REQ :
+				WLAN_FC0_STYPE_ASSOC_REQ),
+			link_addr, link_frame, link_frame_maxsize,
+			link_frame_len);
+}
+
+QDF_STATUS
+util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t frame_len, bool isreassoc,
+			struct qdf_mac_addr link_addr,
+			uint8_t *link_frame,
+			qdf_size_t link_frame_maxsize,
+			qdf_size_t *link_frame_len)
+{
+	return util_gen_link_assoc_reqrsp_cmn(frame, frame_len,
+			(isreassoc ?  WLAN_FC0_STYPE_REASSOC_RESP :
+				WLAN_FC0_STYPE_ASSOC_RESP),
+			link_addr, link_frame, link_frame_maxsize,
+			link_frame_len);
+}
+
 QDF_STATUS
 util_find_mlie(uint8_t *buf, qdf_size_t buflen, uint8_t **mlieseq,
 	       qdf_size_t *mlieseqlen)