--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -324,6 +324,9 @@ NEED_SHA384=y
 NEED_SHA512=y
 endif
 
+CFLAGS += -DCONFIG_MTK_IEEE80211BE
+OBJS += ../src/ml/ml.o
+
 ifdef CONFIG_AIRTIME_POLICY
 CFLAGS += -DCONFIG_AIRTIME_POLICY
 OBJS += ../src/ap/airtime_policy.o
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -43,6 +43,11 @@
 #include "fils_hlp.h"
 #include "neighbor_db.h"
 
+#ifdef CONFIG_MTK_IEEE80211BE
+#include "wpa_auth_i.h"
+#include "ml/ml.h"
+#endif
+
 
 #ifdef CONFIG_FILS
 void hostapd_notify_assoc_fils_finish(struct hostapd_data *hapd,
@@ -723,6 +728,10 @@ skip_wpa_check:
 	else
 		wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC);
 
+#ifdef CONFIG_MTK_IEEE80211BE
+	ml_new_assoc_sta(sta->wpa_sm, elems.ml, elems.ml_len);
+#endif
+
 	hostapd_new_assoc_sta(hapd, sta, !new_assoc);
 
 	ieee802_1x_notify_port_enabled(sta->eapol_sm, 1);
@@ -1849,6 +1858,12 @@ err:
 }
 #endif /* CONFIG_OWE */
 
+static int hostapd_event_bss_ml_info(struct hostapd_data *hapd, 
+		u8 mld_grp_idx, u8 link_id)
+{
+	wpa_printf(MSG_DEBUG, "hostapd_event_bss_mlo_info, mld_grp_idx=%d, link_id=%d\n", mld_grp_idx, link_id);
+	ml_group_init(hapd, mld_grp_idx, link_id);
+}
 
 void hostapd_wpa_event(void *ctx, enum wpa_event_type event,
 		       union wpa_event_data *data)
@@ -2089,6 +2104,11 @@ void hostapd_wpa_event(void *ctx, enum w
 			data->wds_sta_interface.ifname,
 			data->wds_sta_interface.sta_addr);
 		break;
+	case EVENT_UPDATE_BSS_ML_INFO:
+		hostapd_event_bss_ml_info(hapd, 
+			data->ml_info_event.mld_grp_idx,
+			data->ml_info_event.link_id);
+		break;
 	default:
 		wpa_printf(MSG_DEBUG, "Unknown event %d", event);
 		break;
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -56,6 +56,9 @@
 #include "airtime_policy.h"
 #include "wpa_auth_kay.h"
 
+#ifdef CONFIG_MTK_IEEE80211BE
+#include "ml/ml.h"
+#endif
 
 static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason);
 #ifdef CONFIG_WEP
@@ -454,6 +457,9 @@ void hostapd_free_hapd_data(struct hosta
 	hostapd_ubus_free_bss(hapd);
 	accounting_deinit(hapd);
 	hostapd_deinit_wpa(hapd);
+#ifdef CONFIG_MTK_IEEE80211BE
+	ml_group_deinit(hapd);
+#endif
 	vlan_deinit(hapd);
 	hostapd_acl_deinit(hapd);
 #ifndef CONFIG_NO_RADIUS
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -243,6 +243,10 @@ struct hostapd_data {
 
 	struct l2_packet_data *l2;
 
+#ifdef CONFIG_MTK_IEEE80211BE
+	struct wpa_ml_group *ml_group;
+#endif
+
 #ifdef CONFIG_IEEE80211R_AP
 	struct dl_list l2_queue;
 	struct dl_list l2_oui_queue;
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -56,6 +56,9 @@
 #include "dpp_hostapd.h"
 #include "gas_query_ap.h"
 
+#ifdef CONFIG_MTK_IEEE80211BE
+#include "ml/ml.h"
+#endif
 
 #ifdef CONFIG_FILS
 static struct wpabuf *
@@ -541,13 +544,24 @@ static struct wpabuf * auth_build_sae_co
 	int use_pt = 0;
 	struct sae_pt *pt = NULL;
 	const struct sae_pk *pk = NULL;
+	const u8 *own_addr = hapd->own_addr;
+	const u8 *peer_addr = sta->addr;
+	u8 ext_len = 0;
+
+#ifdef CONFIG_MTK_IEEE80211BE
+	if (sta->sae->dot11MultiLinkActivated) {
+		own_addr = hapd->ml_group->ml_addr;
+		peer_addr = sta->sae->peer_ml_ie->ml_addr;
+		ext_len = 12;
+	}
+#endif /* CONFIG_MTK_IEEE80211BE */
 
 	if (sta->sae->tmp) {
 		rx_id = sta->sae->tmp->pw_id;
 		use_pt = sta->sae->h2e;
 #ifdef CONFIG_SAE_PK
-		os_memcpy(sta->sae->tmp->own_addr, hapd->own_addr, ETH_ALEN);
-		os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN);
+		os_memcpy(sta->sae->tmp->own_addr, own_addr, ETH_ALEN);
+		os_memcpy(sta->sae->tmp->peer_addr, peer_addr, ETH_ALEN);
 #endif /* CONFIG_SAE_PK */
 	}
 
@@ -566,12 +580,12 @@ static struct wpabuf * auth_build_sae_co
 	}
 
 	if (update && use_pt &&
-	    sae_prepare_commit_pt(sta->sae, pt, hapd->own_addr, sta->addr,
+	    sae_prepare_commit_pt(sta->sae, pt, own_addr, peer_addr,
 				  NULL, pk) < 0)
 		return NULL;
 
 	if (update && !use_pt &&
-	    sae_prepare_commit(hapd->own_addr, sta->addr,
+	    sae_prepare_commit(own_addr, peer_addr,
 			       (u8 *) password, os_strlen(password),
 			       sta->sae) < 0) {
 		wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
@@ -587,7 +601,7 @@ static struct wpabuf * auth_build_sae_co
 		sta->sae->tmp->vlan_id = pw->vlan_id;
 	}
 
-	buf = wpabuf_alloc(SAE_COMMIT_MAX_LEN +
+	buf = wpabuf_alloc(SAE_COMMIT_MAX_LEN + ext_len +
 			   (rx_id ? 3 + os_strlen(rx_id) : 0));
 	if (buf &&
 	    sae_write_commit(sta->sae, buf, sta->sae->tmp ?
@@ -597,6 +611,14 @@ static struct wpabuf * auth_build_sae_co
 		buf = NULL;
 	}
 
+#ifdef CONFIG_MTK_IEEE80211BE
+	if (buf &&
+		ml_sae_write_auth(hapd, sta->sae, buf) < 0) {
+		wpabuf_free(buf);
+		buf = NULL;
+	}
+#endif
+
 	return buf;
 }
 
@@ -605,8 +627,14 @@ static struct wpabuf * auth_build_sae_co
 					      struct sta_info *sta)
 {
 	struct wpabuf *buf;
+	u8 ext_len = 0;
+
+#ifdef CONFIG_MTK_IEEE80211BE
+	if (sta->sae->dot11MultiLinkActivated)
+		ext_len = 16;
+#endif /* CONFIG_MTK_IEEE80211BE */
 
-	buf = wpabuf_alloc(SAE_CONFIRM_MAX_LEN);
+	buf = wpabuf_alloc(SAE_CONFIRM_MAX_LEN + ext_len);
 	if (buf == NULL)
 		return NULL;
 
@@ -622,6 +650,14 @@ static struct wpabuf * auth_build_sae_co
 		return NULL;
 	}
 
+#ifdef CONFIG_MTK_IEEE80211BE
+	if (buf &&
+		ml_sae_write_auth(hapd, sta->sae, buf) < 0) {
+		wpabuf_free(buf);
+		buf = NULL;
+	}
+#endif
+
 	return buf;
 }
 
@@ -1338,6 +1374,14 @@ static void handle_auth_sae(struct hosta
 		}
 		sae_set_state(sta, SAE_NOTHING, "Init");
 		sta->sae->sync = 0;
+#ifdef CONFIG_MTK_IEEE80211BE
+		if (hapd->ml_group) {
+			os_memcpy(sta->sae->own_ml_addr,
+				hapd->ml_group->ml_addr, ETH_ALEN);
+			wpa_printf(MSG_DEBUG, "ML: SAE init own ml addr "MACSTR"",
+				   MAC2STR(sta->sae->own_ml_addr));
+		}
+#endif
 	}
 
 	if (sta->mesh_sae_pmksa_caching) {
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -33,6 +33,11 @@
 #include "wpa_auth_i.h"
 #include "wpa_auth_ie.h"
 
+#ifdef CONFIG_MTK_IEEE80211BE
+#include "hostapd.h"
+#include "ml/ml.h"
+#endif
+
 #define STATE_MACHINE_DATA struct wpa_state_machine
 #define STATE_MACHINE_DEBUG_PREFIX "WPA"
 #define STATE_MACHINE_ADDR sm->addr
@@ -326,12 +331,76 @@ static void wpa_rekey_gmk(void *eloop_ct
 }
 
 
-static void wpa_rekey_gtk(void *eloop_ctx, void *timeout_ctx)
+void wpa_rekey_gtk(void *eloop_ctx, void *timeout_ctx)
 {
 	struct wpa_authenticator *wpa_auth = eloop_ctx;
 	struct wpa_group *group, *next;
+#ifdef CONFIG_MTK_IEEE80211BE
+	struct wpa_ml_group *ml_group = ((struct hostapd_data *)wpa_auth->cb_ctx)->ml_group;
+#endif
 
 	wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, "rekeying GTK");
+#ifdef CONFIG_MTK_IEEE80211BE
+		if (ml_group) {
+			u8 i = 0;
+			struct wpa_authenticator *wpa_auth_tmp;
+			struct wpa_ml_link *link;
+			struct sta_info *sta = NULL;
+	
+			if (1) {
+				group = wpa_auth->group;
+				while (group) {
+					wpa_group_get(wpa_auth, group);
+	
+					group->GTKReKey = true;
+					do {
+						group->changed = false;
+						wpa_group_sm_step(wpa_auth, group);
+					} while (group->changed);
+	
+					next = group->next;
+					wpa_group_put(wpa_auth, group);
+					group = next;
+				}
+			}
+	
+			if (1) {
+				for (i = 0; i < ml_group->ml_link_num; i++) {
+					link = &ml_group->links[i];
+					if (link) {
+						for (sta = ((struct hostapd_data *)link->ctx)->sta_list; sta; sta = sta->next) {
+							if (sta->wpa_sm)
+								if (sta->wpa_sm->sta_ml_ie) { /*find mld STA, so it is the assoc link */
+									wpa_printf(MSG_DEBUG, "find mld sta ");
+									if (link->ctx == wpa_auth->cb_ctx) {
+										wpa_printf(MSG_DEBUG, "the rekey happen in assoc link ");
+										break;
+									} else {
+										wpa_printf(MSG_DEBUG, "the rekey happen in not assoc link ");
+										wpa_auth_tmp = ((struct hostapd_data *)link->ctx)->wpa_auth;
+										group = wpa_auth_tmp->group;
+										while (group) {
+											wpa_group_get(wpa_auth_tmp, group);
+	
+											group->GTKReKey = true;
+											do {
+												group->changed = false;
+												wpa_group_sm_step(wpa_auth_tmp, group);
+											} while (group->changed);
+	
+											next = group->next;
+											wpa_group_put(wpa_auth_tmp, group);
+											group = next;
+										}
+									}
+								}
+						}
+					}
+				}
+			}
+		}	
+#else
+
 	group = wpa_auth->group;
 	while (group) {
 		wpa_group_get(wpa_auth, group);
@@ -346,6 +415,7 @@ static void wpa_rekey_gtk(void *eloop_ct
 		wpa_group_put(wpa_auth, group);
 		group = next;
 	}
+#endif
 
 	if (wpa_auth->conf.wpa_group_rekey) {
 		eloop_register_timeout(wpa_auth->conf.wpa_group_rekey,
@@ -758,6 +828,9 @@ static void wpa_free_sta_sm(struct wpa_s
 #ifdef CONFIG_DPP2
 	wpabuf_clear_free(sm->dpp_z);
 #endif /* CONFIG_DPP2 */
+#ifdef CONFIG_MTK_IEEE80211BE
+			os_free(sm->sta_ml_ie);
+#endif
 	bin_clear_free(sm, sizeof(*sm));
 }
 
@@ -1105,6 +1178,9 @@ void wpa_receive(struct wpa_authenticato
 		msg = GROUP_2;
 		msgtxt = "2/2 Group";
 	} else if (key_data_length == 0 ||
+#ifdef CONFIG_MTK_IEEE80211BE
+		key_data_length == 12 /* len of mac addr kde */ ||
+#endif
 		   (mic_len == 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) &&
 		    key_data_length == AES_BLOCK_SIZE)) {
 		msg = PAIRWISE_4;
@@ -1261,6 +1337,13 @@ continue_processing:
 					   WLAN_REASON_PREV_AUTH_NOT_VALID);
 			return;
 		}
+#ifdef CONFIG_MTK_IEEE80211BE
+			if (ml_process_m2_kde(sm, key_data, key_data_length)) {
+				wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO,
+						 "received invalid ML EAPOL-Key msg 2/4 - dropped");
+				return;
+			}
+#endif
 		break;
 	case PAIRWISE_4:
 		if (sm->wpa_ptk_state != WPA_PTK_PTKINITNEGOTIATING ||
@@ -1270,6 +1353,14 @@ continue_processing:
 					 sm->wpa_ptk_state);
 			return;
 		}
+#ifdef CONFIG_MTK_IEEE80211BE
+		if (ml_process_m4_kde(sm, key_data, key_data_length)) {
+			wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO,
+					 "received invalid ML EAPOL-Key msg 4/4 - dropped");
+			return;
+		}
+#endif
+
 		break;
 	case GROUP_2:
 		if (sm->wpa_ptk_group_state != WPA_PTK_GROUP_REKEYNEGOTIATING
@@ -1376,6 +1467,10 @@ continue_processing:
 			   wpa_parse_kde_ies(key_data, key_data_length,
 					     &kde) == 0 &&
 			   kde.mac_addr) {
+#ifdef CONFIG_MTK_IEEE80211BE
+				ml_rekey_gtk(sm, &kde);
+#endif /* CONFIG_MTK_IEEE80211BE */
+
 		} else {
 			wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
 					"received EAPOL-Key Request for GTK rekeying");
@@ -2179,7 +2274,13 @@ SM_STATE(WPA_PTK, INITPSK)
 
 SM_STATE(WPA_PTK, PTKSTART)
 {
-	u8 buf[2 + RSN_SELECTOR_LEN + PMKID_LEN], *pmkid = NULL;
+#ifdef CONFIG_MTK_IEEE80211BE
+	u8 buf[2 + RSN_SELECTOR_LEN + PMKID_LEN +
+			   2 + RSN_SELECTOR_LEN + ETH_ALEN];
+#else
+	u8 buf[2 + RSN_SELECTOR_LEN + PMKID_LEN];
+#endif
+	u8 *pmkid = NULL;
 	size_t pmkid_len = 0;
 
 	SM_ENTRY_MA(WPA_PTK, PTKSTART, wpa_ptk);
@@ -2283,6 +2384,13 @@ SM_STATE(WPA_PTK, PTKSTART)
 	}
 	if (!pmkid)
 		pmkid_len = 0;
+	
+#ifdef CONFIG_MTK_IEEE80211BE
+	if (!pmkid)
+		pmkid = buf;
+	pmkid_len = ml_add_m1_kde(sm, pmkid + pmkid_len) - pmkid;
+#endif
+
 	wpa_send_eapol(sm->wpa_auth, sm,
 		       WPA_KEY_INFO_ACK | WPA_KEY_INFO_KEY_TYPE, NULL,
 		       sm->ANonce, pmkid, pmkid_len, 0, 0);
@@ -3512,11 +3620,20 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 		kde_len += 2 + RSN_SELECTOR_LEN + 2;
 #endif /* CONFIG_DPP2 */
 
+#ifdef CONFIG_MTK_IEEE80211BE
+	kde_len += 512; /*For ML link KDE, GTK/IGTK/BIGTK KDE*/
+#endif
+
 	kde = os_malloc(kde_len);
 	if (!kde)
 		goto done;
 
 	pos = kde;
+#ifdef CONFIG_MTK_IEEE80211BE
+	if (sm->dot11MultiLinkActivated)
+		goto skip;
+#endif
+
 	os_memcpy(pos, wpa_ie, wpa_ie_len);
 	pos += wpa_ie_len;
 #ifdef CONFIG_IEEE80211R_AP
@@ -3548,6 +3665,12 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 				  gtk, gtk_len);
 	}
 	pos = ieee80211w_kde_add(sm, pos);
+
+#ifdef CONFIG_MTK_IEEE80211BE
+skip:
+		pos = ml_add_m3_kde(sm, pos);
+#endif
+
 	if (ocv_oci_add(sm, &pos, conf->oci_freq_override_eapol_m3) < 0)
 		goto done;
 
@@ -3892,16 +4015,29 @@ SM_STATE(WPA_PTK_GROUP, REKEYNEGOTIATING
 	if (sm->wpa == WPA_VERSION_WPA2) {
 		kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len +
 			ieee80211w_kde_len(sm) + ocv_oci_len(sm);
+#ifdef CONFIG_MTK_IEEE80211BE
+		kde_len += 512; /*For ML MAC KDE, GTK/IGTK/BIGTK KDE*/
+#endif
+
 		kde_buf = os_malloc(kde_len);
 		if (!kde_buf)
 			return;
-
 		kde = pos = kde_buf;
+#ifdef CONFIG_MTK_IEEE80211BE
+		if (sm->dot11MultiLinkActivated)
+			goto skip;
+#endif  /* CONFIG_MTK_IEEE80211BE */
+
 		hdr[0] = gsm->GN & 0x03;
 		hdr[1] = 0;
 		pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2,
 				  gtk, gsm->GTK_len);
 		pos = ieee80211w_kde_add(sm, pos);
+#ifdef CONFIG_MTK_IEEE80211BE
+skip:
+		pos = ml_add_rekey_kde(sm, pos);
+#endif /* CONFIG_MTK_IEEE80211BE */
+
 		if (ocv_oci_add(sm, &pos,
 				conf->oci_freq_override_eapol_g1) < 0) {
 			os_free(kde_buf);
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -561,6 +561,7 @@ int hostapd_wpa_auth_send_eapol(void *ct
 				int encrypt);
 void wpa_auth_set_ptk_rekey_timer(struct wpa_state_machine *sm);
 void wpa_auth_set_ft_rsnxe_used(struct wpa_authenticator *wpa_auth, int val);
+void wpa_rekey_gtk(void *eloop_ctx, void *timeout_ctx);
 
 enum wpa_auth_ocv_override_frame {
 	WPA_AUTH_OCV_OVERRIDE_EAPOL_M3,
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -15,6 +15,9 @@
 #define RSNA_MAX_EAPOL_RETRIES 4
 
 struct wpa_group;
+#ifdef CONFIG_MTK_IEEE80211BE
+struct wpa_ml_ie_parse;
+#endif
 
 struct wpa_state_machine {
 	struct wpa_authenticator *wpa_auth;
@@ -96,6 +99,12 @@ struct wpa_state_machine {
 	unsigned int is_wnmsleep:1;
 	unsigned int pmkid_set:1;
 
+#ifdef CONFIG_MTK_IEEE80211BE
+	unsigned int dot11MultiLinkActivated:1;
+	struct wpa_ml_ie_parse *sta_ml_ie;
+#endif
+
+
 #ifdef CONFIG_OCV
 	int ocv_enabled;
 #endif /* CONFIG_OCV */
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -307,6 +307,17 @@ static int ieee802_11_parse_extension(co
 		elems->pasn_params = pos;
 		elems->pasn_params_len = elen;
 		break;
+#ifdef CONFIG_MTK_IEEE80211BE
+	case WLAN_EID_EXT_MLD:
+		elems->ml = pos;
+		elems->ml_len = elen;
+	case WLAN_EID_EXT_EHT_OP:
+	case WLAN_EID_EXT_EHT_CAPS:
+	case WLAN_EID_EXT_TID2LNK_MAP:
+	case WLAN_EID_EXT_MLT:
+		break;
+#endif
+
 	default:
 		if (show_errors) {
 			wpa_printf(MSG_MSGDUMP,
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -118,6 +118,10 @@ struct ieee802_11_elems {
 	const u8 *s1g_capab;
 	const u8 *pasn_params;
 
+#ifdef CONFIG_MTK_IEEE80211BE
+	const u8 *ml;
+#endif
+
 	u8 ssid_len;
 	u8 supp_rates_len;
 	u8 challenge_len;
@@ -171,6 +175,9 @@ struct ieee802_11_elems {
 	u8 short_ssid_list_len;
 	u8 sae_pk_len;
 	u8 pasn_params_len;
+#ifdef CONFIG_MTK_IEEE80211BE
+	u8 ml_len;
+#endif
 
 	struct mb_ies_info mb_ies;
 	struct frag_ies_info frag_ies;
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -490,6 +490,12 @@
 #define WLAN_EID_EXT_ANTI_CLOGGING_TOKEN 93
 #define WLAN_EID_EXT_PASN_PARAMS 100
 
+#define WLAN_EID_EXT_EHT_OP 106
+#define WLAN_EID_EXT_MLD 107
+#define WLAN_EID_EXT_EHT_CAPS 108
+#define WLAN_EID_EXT_TID2LNK_MAP 109
+#define WLAN_EID_EXT_MLT 110
+
 /* Extended Capabilities field */
 #define WLAN_EXT_CAPAB_20_40_COEX 0
 #define WLAN_EXT_CAPAB_GLK 1
--- a/src/common/sae.c
+++ b/src/common/sae.c
@@ -19,6 +19,9 @@
 #include "ieee802_11_defs.h"
 #include "dragonfly.h"
 #include "sae.h"
+#ifdef CONFIG_MTK_IEEE80211BE
+#include "ml/ml.h"
+#endif
 
 
 int sae_set_group(struct sae_data *sae, int group)
@@ -119,12 +122,26 @@ void sae_clear_temp_data(struct sae_data
 
 void sae_clear_data(struct sae_data *sae)
 {
+#ifdef CONFIG_MTK_IEEE80211BE
+	u8 own_ml_addr[ETH_ALEN];
+#endif
+
 	if (sae == NULL)
 		return;
 	sae_clear_temp_data(sae);
 	crypto_bignum_deinit(sae->peer_commit_scalar, 0);
 	crypto_bignum_deinit(sae->peer_commit_scalar_accepted, 0);
+#ifdef CONFIG_MTK_IEEE80211BE
+	os_free(sae->peer_ml_ie);
+	os_memcpy(own_ml_addr, sae->own_ml_addr, ETH_ALEN);
+#endif
 	os_memset(sae, 0, sizeof(*sae));
+#ifdef CONFIG_MTK_IEEE80211BE
+	os_memcpy(sae->own_ml_addr, own_ml_addr, ETH_ALEN);
+	wpa_printf(MSG_DEBUG, "ML: SAE own ml addr "MACSTR"",
+		MAC2STR(sae->own_ml_addr));
+#endif
+
 }
 
 
@@ -1839,15 +1856,16 @@ static void sae_parse_commit_token(struc
 
 
 static void sae_parse_token_container(struct sae_data *sae,
-				      const u8 *pos, const u8 *end,
+				      const u8 **pos, const u8 *end,
 				      const u8 **token, size_t *token_len)
 {
 	wpa_hexdump(MSG_DEBUG, "SAE: Possible elements at the end of the frame",
-		    pos, end - pos);
-	if (!sae_is_token_container_elem(pos, end))
+		    *pos, end - *pos);
+	if (!sae_is_token_container_elem(*pos, end))
 		return;
-	*token = pos + 3;
-	*token_len = pos[1] - 1;
+	*token = *pos + 3;
+	*token_len = (*pos)[1] - 1;
+	*pos += *token_len + 3;
 	wpa_hexdump(MSG_DEBUG, "SAE: Anti-Clogging Token (in container)",
 		    *token, *token_len);
 }
@@ -2131,7 +2149,14 @@ u16 sae_parse_commit(struct sae_data *sa
 
 	/* Optional Anti-Clogging Token Container element */
 	if (h2e)
-		sae_parse_token_container(sae, pos, end, token, token_len);
+		sae_parse_token_container(sae, &pos, end, token, token_len);
+
+#ifdef CONFIG_MTK_IEEE80211BE
+	res = ml_sae_process_auth(sae, 1, pos, end - pos);
+	if (res < 0) {
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+#endif
 
 	/*
 	 * Check whether peer-commit-scalar and PEER-COMMIT-ELEMENT are same as
@@ -2341,6 +2366,13 @@ int sae_check_confirm(struct sae_data *s
 		return -1;
 #endif /* CONFIG_SAE_PK */
 
+#ifdef CONFIG_MTK_IEEE80211BE /*if sae pk on, there will be error?*/
+	if (ml_sae_process_auth(sae, 2, data + 2 + hash_len,
+				len - 2 - hash_len) < 0) {
+		return -1;
+	}
+#endif
+
 	return 0;
 }

--- a/src/common/sae.h
+++ b/src/common/sae.h
@@ -114,6 +114,13 @@ struct sae_data {
 	unsigned int h2e:1;
 	unsigned int pk:1;
 	struct sae_temporary_data *tmp;
+#ifdef CONFIG_MTK_IEEE80211BE
+	u8 own_ml_addr[ETH_ALEN];
+	/* ml ie from peer */
+	struct wpa_ml_ie_parse *peer_ml_ie;
+	unsigned int dot11MultiLinkActivated:1;
+#endif
+
 };
 
 int sae_set_group(struct sae_data *sae, int group);
--- a/src/common/wpa_common.c
+++ b/src/common/wpa_common.c
@@ -3170,6 +3170,63 @@ static int wpa_parse_generic(const u8 *p
 			    pos, pos[1] + 2);
 		return 0;
 	}
+#ifdef CONFIG_MTK_IEEE80211BE
+	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
+		RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_MLO_GTK) {
+		struct kde_data *kde;
+
+		if (ie->mlo_gtk.num >= ML_MAX_LINK_NUM)
+			return 1;
+		kde = &ie->mlo_gtk.kdes[ie->mlo_gtk.num++];
+		kde->data = pos + 2 + RSN_SELECTOR_LEN;
+		kde->len = pos[1] - RSN_SELECTOR_LEN;
+		wpa_hexdump_key(MSG_DEBUG, "WPA: MLO GTK in EAPOL-Key",
+				pos, pos[1] + 2);
+		return 0;
+	}
+
+	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
+		RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_MLO_IGTK) {
+		struct kde_data *kde;
+
+		if (ie->mlo_igtk.num >= ML_MAX_LINK_NUM)
+			return 1;
+		kde = &ie->mlo_igtk.kdes[ie->mlo_igtk.num++];
+		kde->data = pos + 2 + RSN_SELECTOR_LEN;
+		kde->len = pos[1] - RSN_SELECTOR_LEN;
+		wpa_hexdump_key(MSG_DEBUG, "WPA: MLO IGTK in EAPOL-Key",
+				pos, pos[1] + 2);
+		return 0;
+	}
+
+	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
+		RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_MLO_BIGTK) {
+		struct kde_data *kde;
+
+		if (ie->mlo_bigtk.num >= ML_MAX_LINK_NUM)
+			return 1;
+		kde = &ie->mlo_bigtk.kdes[ie->mlo_bigtk.num++];
+		kde->data = pos + 2 + RSN_SELECTOR_LEN;
+		kde->len = pos[1] - RSN_SELECTOR_LEN;
+		wpa_hexdump_key(MSG_DEBUG, "WPA: MLO BIGTK in EAPOL-Key",
+				pos, pos[1] + 2);
+		return 0;
+	}
+
+	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
+		RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_MLO_LINK) {
+		struct kde_data *kde;
+
+		if (ie->mlo_link.num >= ML_MAX_LINK_NUM)
+			return 1;
+		kde = &ie->mlo_link.kdes[ie->mlo_link.num++];
+		kde->data = pos + 2 + RSN_SELECTOR_LEN;
+		kde->len = pos[1] - RSN_SELECTOR_LEN;
+		wpa_hexdump_key(MSG_DEBUG, "WPA: MLO Link in EAPOL-Key",
+				pos, pos[1] + 2);
+		return 0;
+	}
+#endif
 
 	return 2;
 }
--- a/src/common/wpa_common.h
+++ b/src/common/wpa_common.h
@@ -127,6 +127,13 @@ WPA_CIPHER_BIP_CMAC_256)
 #define RSN_KEY_DATA_OCI RSN_SELECTOR(0x00, 0x0f, 0xac, 13)
 #define RSN_KEY_DATA_BIGTK RSN_SELECTOR(0x00, 0x0f, 0xac, 14)
 
+#ifdef CONFIG_MTK_IEEE80211BE
+#define RSN_KEY_DATA_MLO_GTK RSN_SELECTOR(0x00, 0x0f, 0xac, 16)
+#define RSN_KEY_DATA_MLO_IGTK RSN_SELECTOR(0x00, 0x0f, 0xac, 17)
+#define RSN_KEY_DATA_MLO_BIGTK RSN_SELECTOR(0x00, 0x0f, 0xac, 18)
+#define RSN_KEY_DATA_MLO_LINK RSN_SELECTOR(0x00, 0x0f, 0xac, 19)
+#endif
+
 #define WFA_KEY_DATA_IP_ADDR_REQ RSN_SELECTOR(0x50, 0x6f, 0x9a, 4)
 #define WFA_KEY_DATA_IP_ADDR_ALLOC RSN_SELECTOR(0x50, 0x6f, 0x9a, 5)
 #define WFA_KEY_DATA_TRANSITION_DISABLE RSN_SELECTOR(0x50, 0x6f, 0x9a, 0x20)
@@ -554,6 +561,37 @@ struct wpa_pasn_params_data {
 int wpa_ft_parse_ies(const u8 *ies, size_t ies_len, struct wpa_ft_ies *parse,
 		     int use_sha384);
 
+#ifdef CONFIG_MTK_IEEE80211BE
+
+#define ML_MAX_LINK_NUM 3
+
+struct kde_data {
+	const u8 *data;
+	size_t len;
+};
+
+struct ml_kde {
+	size_t num;
+	struct kde_data kdes[ML_MAX_LINK_NUM];
+};
+
+struct ml_gtk_holder {
+	size_t num;
+	struct wpa_gtk gtks[ML_MAX_LINK_NUM];
+};
+
+struct ml_igtk_holder {
+	size_t num;
+	struct wpa_igtk igtks[ML_MAX_LINK_NUM];
+};
+
+struct ml_bigtk_holder {
+	size_t num;
+	struct wpa_bigtk bigtks[ML_MAX_LINK_NUM];
+};
+
+#endif
+
 struct wpa_eapol_ie_parse {
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
@@ -608,6 +646,13 @@ struct wpa_eapol_ie_parse {
 	u16 aid;
 	const u8 *wmm;
 	size_t wmm_len;
+#ifdef CONFIG_MTK_IEEE80211BE
+	struct ml_kde mlo_gtk;
+	struct ml_kde mlo_igtk;
+	struct ml_kde mlo_bigtk;
+	struct ml_kde mlo_link;
+#endif
+
 };
 
 int wpa_parse_kde_ies(const u8 *buf, size_t len, struct wpa_eapol_ie_parse *ie);
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -5142,6 +5142,10 @@ enum wpa_event_type {
 	 * is required to provide more details of the frame.
 	 */
 	EVENT_UNPROT_BEACON,
+	/**
+	  * EVENT_UPDATE_BSS_MLO_INFO - Notification of BSS MLO INFO
+	  */
+	EVENT_UPDATE_BSS_ML_INFO,
 };
 
 
@@ -6036,6 +6040,11 @@ union wpa_event_data {
 	struct unprot_beacon {
 		const u8 *sa;
 	} unprot_beacon;
+
+	struct ml_info_event {
+		u8 mld_grp_idx;
+		u8 link_id;
+	} ml_info_event;
 };
 
 /**
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -20,6 +20,8 @@
 #include "common/ieee802_11_common.h"
 #include "driver_nl80211.h"
 
+#define OUI_MTK 0x000ce7
+#define MTK_NL80211_VENDOR_EVENT_SEND_MLO_INFO 4
 
 static void
 nl80211_control_port_frame_tx_status(struct wpa_driver_nl80211_data *drv,
@@ -2370,6 +2372,28 @@ static void qca_nl80211_p2p_lo_stop_even
 
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 
+static void mtk_ml80211_bss_ml_info_event(struct wpa_driver_nl80211_data *drv,
+					u8 *data, size_t len)
+{
+	char *event = nla_data(data);	
+
+	if (event)
+		wpa_supplicant_event(drv->ctx, EVENT_UPDATE_BSS_ML_INFO, event);
+}
+static void nl80211_vendor_event_mtk(struct wpa_driver_nl80211_data *drv,
+				     u32 subcmd, u8 *data, size_t len)
+{
+	switch (subcmd) {
+	case MTK_NL80211_VENDOR_EVENT_SEND_MLO_INFO:
+		mtk_ml80211_bss_ml_info_event(drv, data, len);
+		break;
+	default:
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Ignore unsupported mtk vendor event %u",
+			   subcmd);	
+		break;
+	}
+}
 
 static void nl80211_vendor_event_qca(struct wpa_driver_nl80211_data *drv,
 				     u32 subcmd, u8 *data, size_t len)
@@ -2539,6 +2563,9 @@ static void nl80211_vendor_event(struct
 	case OUI_QCA:
 		nl80211_vendor_event_qca(drv, subcmd, data, len);
 		break;
+	case OUI_MTK:
+		/* handle mediatek vendor event */
+		nl80211_vendor_event_mtk(drv, subcmd, data, len);
 #ifdef CONFIG_DRIVER_NL80211_BRCM
 	case OUI_BRCM:
 		nl80211_vendor_event_brcm(drv, subcmd, data, len);
--- /dev/null
+++ b/src/ml/ml.c
@@ -0,0 +1,1810 @@
+/*******************************************************************************
+ *
+ * This file is provided under a dual license.  When you use or
+ * distribute this software, you may choose to be licensed under
+ * version 2 of the GNU General Public License ("GPLv2 License")
+ * or BSD License.
+ *
+ * GPLv2 License
+ *
+ * Copyright(C) 2016 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(C) 2016 MediaTek Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ ******************************************************************************/
+
+#include "includes.h"
+
+#include "common.h"
+
+#include "common/ieee802_11_defs.h"
+#include "common/ieee802_11_common.h"
+#include "common/sae.h"
+#include "rsn_supp/wpa.h"
+#include "rsn_supp/wpa_i.h"
+#include "rsn_supp/wpa_ie.h"
+#include "ap/wpa_auth.h"
+#include "ap/wpa_auth_i.h"
+#include "ap/wpa_auth_ie.h"
+#include "ap/hostapd.h"
+#include "crypto/random.h"
+#include "utils/eloop.h"
+#include "ml/ml.h"
+
+#ifndef BITS
+#define BITS(m, n) (~(BIT(m)-1) & ((BIT(n) - 1) | BIT(n)))
+#endif
+
+#define CMD_PRESET_LINKID	"PRESET_LINKID"
+static const u8 null_rsc[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+#define STATE_MACHINE_ML_GROUP \
+	(((struct hostapd_data *)sm->wpa_auth->cb_ctx)->ml_group)
+#define STATE_MACHINE_ML_GROUP_ADDR \
+	(((struct hostapd_data *)sm->wpa_auth->cb_ctx)->ml_group)->ml_addr
+
+struct wpa_ie_parse {
+	const u8 *ap_rsn_ie;
+	const u8 *ap_rsnxe;
+	size_t ap_rsn_ie_len;
+	size_t ap_rsnxe_len;
+};
+
+struct ml_gtk_data {
+	u8 link_id;
+	enum wpa_alg alg;
+	int tx, key_rsc_len, keyidx;
+	u8 gtk[32];
+	int gtk_len;
+};
+
+/* common */
+
+u8* ml_set_mac_kde(u8 *pos, const unsigned char *addr)
+{
+	if (addr == NULL)
+		return pos;
+
+	return wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR, addr, ETH_ALEN, NULL, 0);
+}
+
+u8* ml_set_ml_link_kde(u8 *pos, u8 link_id, const unsigned char *addr,
+	const u8 *rsne, size_t rsne_len, const u8 *rsnxe, size_t rsnxe_len)
+{
+	u8 i;
+	u8 *buf, *cp, *ori;
+	size_t len = 1 /* Link Information */ + ETH_ALEN + rsne_len + rsnxe_len;
+
+	cp = buf = os_malloc(len);
+	os_memset(cp, 0, len);
+	*cp = link_id & BITS(0, 3);
+	if (rsne && rsne_len)
+		*cp |= BIT(4);
+	if (rsnxe && rsnxe_len)
+		*cp |= BIT(5);
+	cp++;
+	os_memcpy(cp, addr, ETH_ALEN);
+	cp += ETH_ALEN;
+
+	if (rsne && rsne_len) {
+		os_memcpy(cp, rsne, rsne_len);
+		cp += rsne_len;
+	}
+
+	if (rsnxe && rsnxe_len) {
+		os_memcpy(cp, rsnxe, rsnxe_len);
+		cp += rsnxe_len;
+	}
+
+	ori = pos;
+	pos = wpa_add_kde(pos, RSN_KEY_DATA_MLO_LINK, buf, cp - buf, NULL, 0);
+	wpa_hexdump_key(MSG_DEBUG, "ML: Link KDE", ori, pos - ori);
+
+	os_free(buf);
+
+	return pos;
+}
+
+int ml_parse_ie(const u8 *ie, size_t len, struct wpa_ml_ie_parse *ml)
+{
+	const u8 *pos, *end;
+	u16 ml_ctrl;
+
+	wpa_hexdump(MSG_DEBUG, "ML IE", ie, len);
+
+	os_memset(ml, 0, sizeof(*ml));
+	pos = ie + 2; /* skip common ctrl */
+	end = ie + len;
+	if (pos > end)
+		return -1;
+
+	ml_ctrl = WPA_GET_LE16(ie);
+	ml->type = ml_ctrl & ML_CTRL_TYPE_MASK;
+	if (ml->type != ML_ELEMENT_TYPE_BASIC) {
+		wpa_printf(MSG_INFO, "ML: invalid ML control type = %d",
+			ml->type);
+		return -1;
+	}
+
+	ml->common_info_len = *pos++;
+
+	wpa_printf(MSG_INFO, "ML: common Info Len = %d", ml->common_info_len);
+
+	/* Check ML control that which common info exist */
+	os_memcpy(ml->ml_addr, pos, ETH_ALEN);
+	pos += ETH_ALEN;
+	wpa_printf(MSG_INFO, "ML: common Info MAC addr = "MACSTR"",
+		MAC2STR(ml->ml_addr));
+
+	if (ml_ctrl & ML_CTRL_LINK_ID_INFO_PRESENT) {
+		ml->link_id = *pos;
+		ml->link_id_present = 1;
+		wpa_printf(MSG_INFO, "ML: common Info LinkID = %d", ml->link_id);
+		pos += 1;
+	}
+	if (ml_ctrl & ML_CTRL_BSS_PARA_CHANGE_COUNT_PRESENT) {
+		ml->bss_para_change_count = *pos;
+		ml->bss_para_change_cnt_present = 1;
+		wpa_printf(MSG_INFO, "ML: common Info BssParaChangeCount = %d", *pos);
+		pos += 1;
+	}
+	if (ml_ctrl & ML_CTRL_MEDIUM_SYN_DELAY_INFO_PRESENT) {
+		ml->medium_sync_delay = WPA_GET_LE16(pos);
+		ml->medium_sync_delay_present = 1;
+		wpa_printf(MSG_INFO, "ML: common Info MediumSynDelayInfo = %d", *pos);
+		pos += 2;
+	}
+	if (ml_ctrl & ML_CTRL_EML_CAPA_PRESENT) {
+		ml->eml_cap = WPA_GET_LE16(pos);
+		ml->eml_cap_present = 1;
+		wpa_printf(MSG_INFO, "ML: common Info EML capa = 0x%x", ml->eml_cap);
+		pos += 2;
+	}
+	if (ml_ctrl & ML_CTRL_MLD_CAPA_PRESENT) {
+		ml->mld_cap = WPA_GET_LE16(pos);
+		ml->mld_cap_present = 1;
+		wpa_printf(MSG_INFO, "ML: common Info MLD capa = 0x%x", ml->mld_cap);
+		pos += 2;
+	}
+
+	if (pos - (ie + 2) != ml->common_info_len) {
+		ml->valid = false;
+		wpa_printf(MSG_INFO, "ML: invalid ML control info len = %d",
+			ml->common_info_len);
+		return -1;
+	} else {
+		ml->valid = true;
+	}
+
+	/* pos point to link info, recusive parse it */
+	while (pos < end) {
+		u16 sta_ctrl;
+		struct per_sta_profile *profile;
+		u8 sta_info_len;
+		const u8 *head, *tail;
+
+		if (*pos != ML_SUB_ID_PER_STA_PROFILE ||
+		    ml->ml_link_num >= ML_MAX_LINK_NUM)
+			break;
+
+		head = pos + 2;
+		tail = head + pos[1];
+		pos += 2;
+		sta_ctrl = WPA_GET_LE16(pos);
+		pos += 2;
+
+		profile = &ml->profiles[ml->ml_link_num++];
+		profile->link_id = sta_ctrl & ML_STA_CTRL_LINK_ID_MASK;
+		profile->complete_profile =
+			(sta_ctrl & ML_STA_CTRL_COMPLETE_PROFILE) > 0;
+
+		wpa_printf(MSG_INFO, "ML: LinkID=%d Ctrl=0x%x(%s) Total=%d",
+			profile->link_id, sta_ctrl,
+			profile->complete_profile ? "COMPLETE" : "PARTIAL",
+			ml->ml_link_num);
+
+		sta_info_len = *pos++;
+
+		if (sta_ctrl & ML_STA_CTRL_MAC_ADDR_PRESENT) {
+			os_memcpy(profile->addr, pos, ETH_ALEN);
+			profile->mac_addr_present = 1;
+			wpa_printf(MSG_INFO, "ML: LinkID=%d, LinkAddr="MACSTR"",
+				profile->link_id, MAC2STR(profile->addr));
+			pos += ETH_ALEN;
+		}
+		if (sta_ctrl & ML_STA_CTRL_BCN_INTV_PRESENT) {
+			profile->beacon_interval = WPA_GET_LE16(pos);
+			profile->bcn_intvl_present = 1;
+			wpa_printf(MSG_INFO, "ML: LinkID=%d, BCN_INTV = %d",
+				profile->link_id, profile->beacon_interval);
+			pos += 2;
+		}
+		if (sta_ctrl & ML_STA_CTRL_DTIM_INFO_PRESENT) {
+			profile->dtim = WPA_GET_LE16(pos);
+			profile->dtim_present = 1;
+			wpa_printf(MSG_INFO, "ML: LinkID=%d, DTIM_INFO = 0x%x",
+				profile->link_id, profile->dtim);
+			pos += 2;
+		}
+		/* If the Complete Profile subfield = 1 and
+		 * NSTR Link Pair Present = 1, then NSTR Indication Bitmap exist
+		 * NSTR Bitmap Size = 1 if the length of the corresponding
+		 * NSTR Indication Bitmap is 2 bytes, and = 0 if the
+		 * length of the corresponding NSTR Indication Bitmap = 1 byte
+		 */
+		if ((sta_ctrl & ML_STA_CTRL_COMPLETE_PROFILE) &&
+			(sta_ctrl & ML_STA_CTRL_NSTR_LINK_PAIR_PRESENT)) {
+			if (((sta_ctrl & ML_STA_CTRL_NSTR_BMP_SIZE) >>
+				ML_STA_CTRL_NSTR_BMP_SIZE_SHIFT) == 0) {
+				profile->nstr_bmap = *pos;
+				wpa_printf(MSG_INFO, "ML: LinkID=%d, NSTR_BMP0=0x%x",
+					profile->link_id, profile->nstr_bmap);
+				pos += 1;
+			} else {
+				profile->nstr_bmap = WPA_GET_LE16(pos);
+				wpa_printf(MSG_INFO, "ML: LinkID=%d, NSTR_BMP1=0x%x",
+					profile->link_id, profile->nstr_bmap);
+				pos += 2;
+			}
+			profile->nstr_present = 1;
+		}
+		if (pos - (head + 2) != sta_info_len) {
+			wpa_printf(MSG_INFO, "ML: invalid ML STA info len = %d",
+				sta_info_len);
+			ml->ml_link_num--;
+		}
+
+		/* point to next Per-STA profile*/
+		pos = tail;
+	}
+
+	return 0;
+}
+
+/* AP */
+struct wpa_ml_link * ml_setup_link(struct hostapd_data *hapd,
+	struct wpa_ml_group *ml_group, u8 link_id)
+{
+	struct wpa_ml_link *links, *link;
+
+	links = os_realloc_array(ml_group->links, ml_group->ml_link_num + 1,
+				 sizeof(struct wpa_ml_link));
+	if (links == NULL || hapd == NULL) {
+		wpa_printf(MSG_ERROR, "ML: links alloc fail");
+		return NULL;
+	}
+	ml_group->links = links;
+	link = &links[ml_group->ml_link_num++];
+	link->ctx = hapd;
+	link->link_id = link_id;
+	os_memcpy(link->addr, hapd->own_addr, ETH_ALEN);
+	hapd->ml_group = ml_group;
+
+	wpa_printf(MSG_INFO, "ML: Join ML Group=%p, link_id=%d, ml_group->ml_link_num=%d", ml_group, link_id, ml_group->ml_link_num);
+
+	return link;
+}
+
+struct wpa_ml_group *ml_alloc_group(struct hostapd_data *hapd,
+				    u8 group_id)
+{
+	struct wpa_ml_group *ml_group = NULL;
+	struct wpa_ml_link *link;
+	u8 i;
+
+	ml_group = os_zalloc(sizeof(*ml_group));
+	if (ml_group == NULL) {
+		wpa_printf(MSG_ERROR, "ML: ml_group alloc fail");
+		return NULL;
+	}
+		
+	ml_group->ctx = hapd;
+	os_memcpy(ml_group->ml_addr, hapd->own_addr, ETH_ALEN);
+	ml_group->ml_group_id = group_id;
+
+	wpa_printf(MSG_INFO,
+		"ML: Alloc ML Group=%p (ml_group_id=%d, ml_addr=" MACSTR ")",
+		ml_group, group_id, MAC2STR(ml_group->ml_addr));
+
+	return ml_group;
+}
+
+struct wpa_ml_group * ml_get_group(struct hapd_interfaces *interfaces,
+				u8 group_id)
+{
+	size_t i, j;
+
+	/* search interfaces to find existed ml group */
+	for (i = 0; i < interfaces->count; i++) {
+		struct hostapd_iface *iface = interfaces->iface[i];
+
+		for (j = 0; j < iface->num_bss; j++) {
+			struct hostapd_data *hapd = iface->bss[j];
+
+			if (hapd->ml_group &&
+			    hapd->ml_group->ml_group_id == group_id) {
+				return hapd->ml_group;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+int ml_group_init(struct hostapd_data *hapd, 
+		u8 mld_grp_idx, u8 link_id)
+{
+	struct hapd_interfaces *interfaces = hapd->iface->interfaces;
+	struct wpa_ml_group *ml_group = NULL;
+	struct wpa_ml_link *link;
+	u8 i;
+
+	wpa_printf(MSG_INFO, "ML: " MACSTR " ml_group_init, ml_group_id=%d",
+			MAC2STR(hapd->own_addr), mld_grp_idx);
+
+	if (!interfaces)
+		goto done;
+
+	ml_group = ml_get_group(interfaces, mld_grp_idx);
+
+	/* found, join it */
+	if (ml_group) {
+		/* error check */
+		for (i = 0; i < ml_group->ml_link_num; i++) {
+			link = &ml_group->links[i];
+			if (link->ctx == hapd) {
+				wpa_printf(MSG_INFO, "ML: reinit link");
+				return -1;
+			}
+		}
+		wpa_printf(MSG_INFO, "ML: group has created,join it");
+		if (ml_setup_link(hapd, ml_group, link_id) == NULL)
+			return -1;
+	} else {
+		ml_group = ml_alloc_group(hapd, mld_grp_idx);
+		if (ml_group == NULL)
+			return -1;
+		wpa_printf(MSG_INFO, "ML: creat group");
+		if (ml_setup_link(hapd, ml_group, link_id) == NULL) {
+			os_free(ml_group);
+			return -1;
+		}
+	}
+
+done:
+	return 0;
+}
+
+int ml_group_deinit(struct hostapd_data *hapd)
+{
+	struct wpa_ml_group *ml_group = hapd->ml_group;
+	struct wpa_ml_link *link;
+	size_t i, k = 0;
+
+	if (!ml_group)
+		return -1;
+
+	for (i = 0; i < ml_group->ml_link_num; i++) {
+		link = &ml_group->links[i];
+
+		if (link->ctx == hapd) {
+			wpa_printf(MSG_INFO, "Remove link %d", link->link_id);
+			k = i;
+			while (k < (ml_group->ml_link_num - 1)) {
+				os_memcpy(&ml_group->links[k],
+					&ml_group->links[k + 1], sizeof(*link));
+				k++;
+			}
+			ml_group->ml_link_num--;
+		}
+	}
+	hapd->ml_group = NULL;
+
+	/* free ml group by ml group owner */
+	if (ml_group->ctx == hapd) {
+		os_free(ml_group->links);
+		os_free(ml_group);
+	}
+
+	return 0;
+}
+
+int ml_new_assoc_sta(struct wpa_state_machine *sm, const u8 *ie, size_t len)
+{
+	if (!sm)
+		return -1;
+
+	os_free(sm->sta_ml_ie);
+	if (ie == NULL || len == 0 || STATE_MACHINE_ML_GROUP == NULL) {
+		sm->sta_ml_ie = NULL;
+		sm->dot11MultiLinkActivated = 0;
+	} else {
+		struct wpa_ml_ie_parse ml;
+		struct wpa_ml_group *ml_group = STATE_MACHINE_ML_GROUP;
+
+		if (ml_parse_ie(ie, len, &ml) != 0 ||
+		    ml.ml_link_num <= 1 ||
+		    ml.ml_link_num > ml_group->ml_link_num) {
+			sm->sta_ml_ie = NULL;
+			sm->dot11MultiLinkActivated = 0;
+			return -1;
+		} else {
+			sm->sta_ml_ie = os_memdup(&ml, sizeof(ml));
+			if (sm->sta_ml_ie == NULL) {
+				sm->dot11MultiLinkActivated = 0;
+				return -1;
+			}
+
+			sm->dot11MultiLinkActivated = 1;
+		}
+	}
+
+	return 0;
+}
+
+u8* ml_add_m1_kde(struct wpa_state_machine *sm, u8 *pos)
+{
+	if (!sm->dot11MultiLinkActivated)
+		return pos;
+
+	wpa_printf(MSG_INFO, "ML: Add Mac:(" MACSTR ") into EAPOL-Key 1/4", MAC2STR(STATE_MACHINE_ML_GROUP_ADDR));
+	return ml_set_mac_kde(pos, STATE_MACHINE_ML_GROUP_ADDR);
+}
+
+int ml_process_m2_kde(struct wpa_state_machine *sm,
+			const u8 *key_data, size_t key_data_len)
+{
+	struct wpa_eapol_ie_parse kde;
+	size_t i, j;
+
+	if (wpa_parse_kde_ies(key_data, key_data_len, &kde) != 0 ||
+	    !sm->dot11MultiLinkActivated)
+		return 0;
+
+	if (!kde.mac_addr ||
+	    os_memcmp(sm->sta_ml_ie->ml_addr, kde.mac_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_INFO, "ML: EAPOL-Key 2/4 wrong ml addr");
+		return -1;
+	}
+
+	if (kde.mlo_link.num == 0) {
+		wpa_printf(MSG_INFO, "ML: EAPOL-Key 2/4 no mlo link");
+		return -2;
+	}
+
+	for (i = 0; i < kde.mlo_link.num; i++) {
+		struct wpa_mlo_link_kde *mlo_link =
+			(struct wpa_mlo_link_kde *) kde.mlo_link.kdes[i].data;
+
+		if (kde.mlo_link.kdes[i].len < 7) {
+			wpa_printf(MSG_INFO, "ML: EAPOL-Key 2/4 error mlo link");
+			return -3;
+		}
+
+		for (j = 0; j < sm->sta_ml_ie->ml_link_num; j++) {
+			if (os_memcmp(sm->sta_ml_ie->profiles[j].addr,
+					mlo_link->addr, ETH_ALEN) == 0 &&
+			    sm->sta_ml_ie->profiles[j].link_id ==
+					(mlo_link->info & 0xf))
+				return 0;
+		}
+	}
+
+	wpa_printf(MSG_INFO, "ML: EAPOL-Key 2/4 mlo link not matched");
+	return -4;
+}
+
+u8* ml_add_m3_kde(struct wpa_state_machine *sm, u8 *pos)
+{
+	struct wpa_ml_group *ml_group = NULL;
+	struct wpa_ml_link *link;
+	u8 i;
+
+	if (!sm->dot11MultiLinkActivated)
+		return pos;
+
+	wpa_printf(MSG_INFO, "ML: Add Mac/Link/GTK into EAPOL-Key 3/4");
+	ml_group = STATE_MACHINE_ML_GROUP;
+	pos = ml_set_mac_kde(pos, ml_group->ml_addr);
+
+	for (i = 0; i < ml_group->ml_link_num; i++) {
+		struct wpa_authenticator *auth;
+		const u8 *rsne, *rsnxe;
+		u8 rsne_len, rsnxe_len;
+
+		link = &ml_group->links[i];
+		auth = ((struct hostapd_data *)link->ctx)->wpa_auth;
+		rsne = get_ie(auth->wpa_ie, auth->wpa_ie_len, WLAN_EID_RSN);
+		rsne_len = rsne ? rsne[1] + 2 : 0;
+		rsnxe = get_ie(auth->wpa_ie, auth->wpa_ie_len, WLAN_EID_RSNX);
+		rsnxe_len = rsnxe ? rsnxe[1] + 2 : 0;
+
+		pos = ml_set_ml_link_kde(pos, i, link->addr,
+			rsne, rsne_len, rsnxe, rsnxe_len);
+		pos = ml_set_gtk_kde(sm, pos, link);
+		pos = ml_set_ieee80211w_kde(sm, pos, link);
+	}
+
+	return pos;
+}
+
+int ml_process_m4_kde(struct wpa_state_machine *sm,
+		const u8 *key_data, size_t key_data_len)
+{
+	struct wpa_eapol_ie_parse kde;
+
+	if (wpa_parse_kde_ies(key_data, key_data_len, &kde) != 0 ||
+	    !sm->dot11MultiLinkActivated)
+		return 0;
+
+	if (os_memcmp(sm->sta_ml_ie->ml_addr, kde.mac_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_INFO, "ML: EAPOL-Key 4/4 wrong ml addr");
+		return -1;
+	}
+
+	return 0;
+}
+
+u8* ml_set_gtk_kde(struct wpa_state_machine *sm, u8 *pos,
+		   struct wpa_ml_link *link)
+{
+	struct wpa_authenticator *auth =
+		((struct hostapd_data *)link->ctx)->wpa_auth;
+	struct wpa_group *gsm = auth->group;
+	int gtkidx;
+	u8 *gtk, dummy_gtk[32], *ori;
+	size_t gtk_len;
+	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+	u8 hdr[7];
+
+	if (sm->wpa != WPA_VERSION_WPA2)
+		return pos;
+
+	gtk = gsm->GTK[gsm->GN - 1];
+	gtk_len = gsm->GTK_len;
+	if (conf->disable_gtk ||
+	    sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
+		/*
+		 * Provide unique random GTK to each STA to prevent use
+		 * of GTK in the BSS.
+		 */
+		if (random_get_bytes(dummy_gtk, gtk_len) < 0)
+			goto done;
+		gtk = dummy_gtk;
+	}
+	gtkidx = gsm->GN;
+
+	os_memset(hdr, 0, 7);
+	hdr[0] = (gtkidx & 0x03) | (link->link_id & 0x0f) << 4;
+	ori = pos;
+	pos = wpa_add_kde(pos, RSN_KEY_DATA_MLO_GTK, hdr, 7,
+			  gtk, gtk_len);
+	wpa_hexdump_key(MSG_DEBUG, "ML: GTK KDE", ori, pos - ori);
+done:
+	return pos;
+}
+
+static inline int ml_get_seqnum(struct wpa_authenticator *wpa_auth,
+				      const u8 *addr, int idx, u8 *seq)
+{
+	int res;
+
+	if (!wpa_auth->cb->get_seqnum)
+		return -1;
+	res = wpa_auth->cb->get_seqnum(wpa_auth->cb_ctx, addr, idx, seq);
+	return res;
+}
+
+u8* ml_set_ieee80211w_kde(struct wpa_state_machine *sm, u8 *pos,
+			  struct wpa_ml_link *link)
+{
+	struct wpa_authenticator *auth =
+		((struct hostapd_data *)link->ctx)->wpa_auth;
+	struct wpa_mlo_igtk_kde igtk;
+	struct wpa_mlo_bigtk_kde bigtk;
+	struct wpa_group *gsm = auth->group;
+	u8 rsc[WPA_KEY_RSC_LEN], *ori;
+	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+	size_t len = wpa_cipher_key_len(conf->group_mgmt_cipher);
+
+	if (!sm->mgmt_frame_prot)
+		return pos;
+
+	igtk.keyid[0] = gsm->GN_igtk;
+	igtk.keyid[1] = 0;
+	if (gsm->wpa_group_state != WPA_GROUP_SETKEYSDONE ||
+	    ml_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, rsc) < 0)
+		os_memset(igtk.pn, 0, sizeof(igtk.pn));
+	else
+		os_memcpy(igtk.pn, rsc, sizeof(igtk.pn));
+	os_memcpy(igtk.igtk, gsm->IGTK[gsm->GN_igtk - 4], len);
+	if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
+		/*
+		 * Provide unique random IGTK to each STA to prevent use of
+		 * IGTK in the BSS.
+		 */
+		if (random_get_bytes(igtk.igtk, len) < 0)
+			return pos;
+	}
+	igtk.info = (link->link_id & 0x0f) << 4;
+	ori = pos;
+	pos = wpa_add_kde(pos, RSN_KEY_DATA_MLO_IGTK,
+			  (const u8 *) &igtk, WPA_MLO_IGTK_KDE_PREFIX_LEN + len,
+			  NULL, 0);
+	wpa_hexdump_key(MSG_DEBUG, "ML: IGTK KDE", ori, pos - ori);
+
+	if (!conf->beacon_prot)
+		return pos;
+
+	bigtk.keyid[0] = gsm->GN_bigtk;
+	bigtk.keyid[1] = 0;
+	if (gsm->wpa_group_state != WPA_GROUP_SETKEYSDONE ||
+	    ml_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, rsc) < 0)
+		os_memset(bigtk.pn, 0, sizeof(bigtk.pn));
+	else
+		os_memcpy(bigtk.pn, rsc, sizeof(bigtk.pn));
+	os_memcpy(bigtk.bigtk, gsm->BIGTK[gsm->GN_bigtk - 6], len);
+	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
+		/*
+		 * Provide unique random BIGTK to each OSEN STA to prevent use
+		 * of BIGTK in the BSS.
+		 */
+		if (random_get_bytes(bigtk.bigtk, len) < 0)
+			return pos;
+	}
+	bigtk.info = (link->link_id & 0x0f) << 4;
+	ori = pos;
+	pos = wpa_add_kde(pos, RSN_KEY_DATA_MLO_BIGTK,
+			  (const u8 *) &bigtk, WPA_MLO_BIGTK_KDE_PREFIX_LEN + len,
+			  NULL, 0);
+	wpa_hexdump_key(MSG_DEBUG, "ML: BIGTK KDE", ori, pos - ori);
+
+	return pos;
+}
+
+u8* ml_add_rekey_kde(struct wpa_state_machine *sm, u8 *pos)
+{
+	struct wpa_ml_group *ml_group = NULL;
+	struct wpa_ml_link *link;
+	u8 i;
+
+	if (!sm->dot11MultiLinkActivated)
+		return pos;
+
+	wpa_printf(MSG_INFO, "ML: Add Mac/GTK into EAPOL-Key rekey");
+	ml_group = STATE_MACHINE_ML_GROUP;
+	pos = ml_set_mac_kde(pos, ml_group->ml_addr);
+
+	for (i = 0; i < ml_group->ml_link_num; i++) {
+		link = &ml_group->links[i];
+		pos = ml_set_gtk_kde(sm, pos, link);
+		pos = ml_set_ieee80211w_kde(sm, pos, link);
+	}
+
+	return pos;
+}
+
+int ml_rekey_gtk(struct wpa_state_machine *sm, struct wpa_eapol_ie_parse *kde)
+{
+	if (sm->dot11MultiLinkActivated &&
+	    os_memcmp(kde->mac_addr, sm->sta_ml_ie->ml_addr, ETH_ALEN) == 0) {
+		struct wpa_ml_group *ml_group;
+		size_t i;
+
+		wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_INFO,
+			"received EAPOL-Key Request for ML GTK rekeying");
+
+		ml_group = STATE_MACHINE_ML_GROUP;
+		for (i = 0; i < ml_group->ml_link_num; i++) {
+			struct wpa_authenticator *wpa_auth =
+				((struct hostapd_data *)ml_group->links[i].ctx)->wpa_auth;
+
+			eloop_cancel_timeout(wpa_rekey_gtk, wpa_auth, NULL);
+			wpa_rekey_gtk(wpa_auth,	NULL);
+		}
+	}
+	return 0;
+}
+
+#ifdef CONFIG_SAE
+int ml_sae_process_auth(struct sae_data *sae, u16 auth_transaction,
+	const u8 *ies, size_t ies_len)
+{
+	struct ieee802_11_elems elems;
+	struct wpa_ml_ie_parse ml;
+
+	if (!sae)
+		return -1;
+
+	wpa_hexdump(MSG_DEBUG, "ML: SAE Possible elements at the end of the frame",
+			    ies, ies_len);
+
+	if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) {
+		wpa_printf(MSG_DEBUG, "ML: SAE failed to parse elements");
+		return -1;
+	}
+
+	if (auth_transaction == 1) {
+		os_free(sae->peer_ml_ie);
+		if (!elems.ml) {
+			wpa_printf(MSG_DEBUG, "ML: SAE clearing STA ML IE");
+			sae->peer_ml_ie = NULL;
+			sae->dot11MultiLinkActivated = 0;
+		} else {
+			if (ml_parse_ie(elems.ml, elems.ml_len, &ml) != 0) {
+				sae->peer_ml_ie = NULL;
+				sae->dot11MultiLinkActivated = 0;
+				return -1;
+			} else {
+				sae->peer_ml_ie = os_memdup(&ml, sizeof(ml));
+				if (sae->peer_ml_ie == NULL) {
+					sae->dot11MultiLinkActivated = 0;
+					return -1;
+				}
+
+				sae->dot11MultiLinkActivated = 1;
+			}
+		}
+	} else if (auth_transaction == 2) {
+		if (sae->dot11MultiLinkActivated && !elems.ml) {
+			wpa_printf(MSG_ERROR, "ML: SAE confirm should have ml ie");
+			return -1;
+		} else if (!sae->dot11MultiLinkActivated && elems.ml) {
+			wpa_printf(MSG_ERROR, "ML: SAE confirm should not have ml ie");
+			return -1;
+		}
+
+		if (elems.ml) {
+			if (ml_parse_ie(elems.ml, elems.ml_len, &ml) != 0) {
+				wpa_printf(MSG_ERROR, "ML: SAE confirm failed to parse ml ie");
+				return -1;
+			} else if (os_memcmp(sae->peer_ml_ie->ml_addr, ml.ml_addr, ETH_ALEN) != 0) {
+				wpa_printf(MSG_DEBUG,
+					"ML: SAE trans = %d, mismatch ML addr (peer="MACSTR", recv="MACSTR")",
+					auth_transaction, MAC2STR(sae->peer_ml_ie->ml_addr), MAC2STR(ml.ml_addr));
+				return -1;
+			}
+		}
+	} else {
+		wpa_printf(MSG_DEBUG,
+		       "ML: unexpected SAE authentication transaction %u",
+		       auth_transaction);
+		return -1;
+	}
+
+	return 0;
+}
+
+int ml_sae_write_auth(struct hostapd_data *hapd,
+		      struct sae_data *sae, struct wpabuf *buf)
+{
+	u16 ctrl = 0;
+	size_t i;
+
+	if (!sae || !sae->dot11MultiLinkActivated || !hapd || !hapd->ml_group)
+		return 0;
+
+	wpa_printf(MSG_DEBUG, "ML: write ml ie for sae auth");
+
+	wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
+	/* extid: 1, common ctrl: 2, common info: 7(len:1, mac:6) */
+	wpabuf_put_u8(buf, 10);
+	wpabuf_put_u8(buf, WLAN_EID_EXT_MLD);
+
+	/* ml common control */
+	ML_SET_CTRL_TYPE(ctrl, ML_ELEMENT_TYPE_BASIC);
+
+	/* A Basic Multi-Link element in an Authentication frame:
+	 * the STA shall include the MLD MAC address of the MLD
+	 * the STA shall set all subfields in the Presence Bitmap subfield of
+	 * the Multi-Link Control field of the element to 0
+	 * the STA shall not include the Link Info field of the element.
+	 */
+	ML_SET_CTRL_PRESENCE(ctrl, 0);
+	wpabuf_put_le16(buf, ctrl);
+
+	/* len:1, mac:6 */
+	wpabuf_put_u8(buf, 7);
+
+	/* ml mac addr */
+	wpabuf_put_data(buf, hapd->ml_group->ml_addr, ETH_ALEN);
+
+	return 0;
+}
+#endif /* CONFIG_SAE */
+
+
+/* STA */
+#ifndef HOSTAPD
+int ml_set_assoc_req_ml_ie(struct wpa_sm *sm, const u8 *ies, size_t ies_len)
+{
+	struct ieee802_11_elems elems;
+	struct wpa_ml_ie_parse ml;
+
+	if (sm == NULL)
+		return -1;
+
+	if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) {
+		wpa_printf(MSG_DEBUG, "ML: Failed to parse elements");
+		return -1;
+	}
+
+	os_free(sm->sta_ml_ie);
+	if (!elems.ml) {
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "ML: clearing STA ML IE");
+		sm->sta_ml_ie = NULL;
+		sm->ml_link_num = 0;
+		sm->dot11MultiLinkActivated = 0;
+	} else {
+		if (ml_parse_ie(elems.ml, elems.ml_len, &ml) != 0) {
+			sm->sta_ml_ie = NULL;
+			sm->dot11MultiLinkActivated = 0;
+			return -1;
+		} else {
+			sm->sta_ml_ie = os_memdup(&ml, sizeof(ml));
+			if (sm->sta_ml_ie == NULL) {
+				sm->ml_link_num = 0;
+				sm->dot11MultiLinkActivated = 0;
+				return -1;
+			}
+
+			os_memcpy(sm->own_ml_addr, ml.ml_addr, ETH_ALEN);
+			sm->ml_link_num = ml.ml_link_num;
+			sm->dot11MultiLinkActivated = 1;
+		}
+	}
+
+	return 0;
+}
+
+int ml_set_assoc_resp_ml_ie(struct wpa_sm *sm, const u8 *ies, size_t ies_len)
+{
+	struct ieee802_11_elems elems;
+	struct wpa_ml_ie_parse ml;
+
+	if (sm == NULL)
+		return -1;
+
+	if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) {
+		wpa_printf(MSG_DEBUG, "ML: Failed to parse elements");
+		return -1;
+	}
+
+	os_free(sm->ap_ml_ie);
+	if (!elems.ml) {
+		WPA_ASSERT(!sm->dot11MultiLinkActivated);
+
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "ML: clearing AP ML IE");
+		sm->ap_ml_ie = NULL;
+	} else {
+		WPA_ASSERT(sm->dot11MultiLinkActivated);
+
+		if (ml_parse_ie(elems.ml, elems.ml_len, &ml) != 0) {
+			sm->ap_ml_ie = NULL;
+			sm->dot11MultiLinkActivated = 0;
+			return -1;
+		} else {
+			WPA_ASSERT(ml.ml_link_num == sm->ml_link_num);
+
+			sm->ap_ml_ie = os_memdup(&ml, sizeof(ml));
+			if (sm->ap_ml_ie == NULL) {
+				sm->dot11MultiLinkActivated = 0;
+				return -1;
+			}
+			os_memcpy(sm->ml_bssid, ml.ml_addr, ETH_ALEN);
+		}
+	}
+
+	return 0;
+}
+
+size_t ml_add_m2_kde(struct wpa_sm *sm, u8 *pos)
+{
+	struct wpa_ml_ie_parse *ml = sm->sta_ml_ie;
+	size_t i;
+	u8 *buf = pos;
+
+	if (!sm->dot11MultiLinkActivated)
+		return 0;
+
+	wpa_printf(MSG_DEBUG, "ML: Add Mac/Link into EAPOL-Key 2/4");
+
+	pos = ml_set_mac_kde(pos, sm->own_ml_addr);
+
+	for (i = 0; i < sm->ml_link_num; i++) {
+		struct per_sta_profile *sta = &ml->profiles[i];
+
+		pos = ml_set_ml_link_kde(pos, sta->link_id, sta->addr,
+			NULL, 0, NULL, 0);
+	}
+
+	return pos - buf;
+}
+
+static int ml_get_wpa_ie(struct wpa_supplicant *wpa_s, u8 *bssid,
+			 struct wpa_ie_parse *wpa)
+{
+	int ret = 0;
+	struct wpa_bss *curr = NULL, *bss;
+	const u8 *ie;
+
+	dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
+		if (os_memcmp(bss->bssid, bssid, ETH_ALEN) != 0)
+			continue;
+		curr = bss;
+		break;
+	}
+
+	if (!curr)
+		return -1;
+
+	os_memset(wpa, 0, sizeof(*wpa));
+
+	ie = wpa_bss_get_ie(curr, WLAN_EID_RSN);
+	if (ie) {
+		wpa->ap_rsn_ie = ie;
+		wpa->ap_rsn_ie_len = 2 + ie[1];
+	}
+
+	ie = wpa_bss_get_ie(curr, WLAN_EID_RSNX);
+	if (ie) {
+		wpa->ap_rsnxe = ie;
+		wpa->ap_rsnxe_len = 2 + ie[1];
+	}
+
+	return 0;
+}
+
+int ml_validate_m3_kde(struct wpa_sm *sm, const struct wpa_eapol_key *key,
+	struct wpa_eapol_ie_parse *ie)
+{
+	u16 key_info;
+	size_t i, j;
+	u8 found = 0;
+
+	key_info = WPA_GET_BE16(key->key_info);
+
+	if(!sm->dot11MultiLinkActivated) {
+		if (ie->mlo_gtk.num == 0 && ie->mlo_igtk.num == 0 &&
+		    ie->mlo_bigtk.num == 0 && ie->mlo_link.num == 0) {
+			wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 inactive");
+			return 0;
+		} else {
+			wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 inactive but "
+				"with ml kde (gtk=%d, igtk=%d, bigtk=%d link=%d)",
+				(int)ie->mlo_gtk.num, (int)ie->mlo_igtk.num,
+				(int)ie->mlo_bigtk.num, (int)ie->mlo_link.num);
+			return -1;
+		}
+	}
+
+	/* mac addr */
+	if (sm->ap_ml_ie &&
+	    os_memcmp(sm->ap_ml_ie->ml_addr, ie->mac_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 wrong ml addr");
+		return -1;
+	}
+
+	/* mlo link */
+	if (ie->mlo_link.num == 0) {
+		wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 no mlo link");
+		return -1;
+	}
+
+	if (ie->rsn_ie) {
+		wpa_printf(MSG_INFO,
+			"ML: EAPOL-Key 3/4 should not have Basic RSN IE");
+		return -1;
+	}
+
+	if (ie->rsnxe) {
+		wpa_printf(MSG_INFO,
+			"ML: EAPOL-Key 3/4 should not have Basic RSNXE IE");
+		return -1;
+	}
+
+	/* mlo link id & rsne & rsnxe */
+	for (i = 0; i < ie->mlo_link.num; i++) {
+		struct wpa_mlo_link_kde *mlo_link =
+			(struct wpa_mlo_link_kde *) ie->mlo_link.kdes[i].data;
+		size_t len = ie->mlo_link.kdes[i].len;
+		u8 *rsne = NULL, *rsnxe = NULL;
+		u8 rsne_len = 0, rsnxe_len = 0; /* including hdr */
+		struct wpa_ie_parse wpa;
+
+		if (ml_get_wpa_ie(sm->ctx->ctx, mlo_link->addr, &wpa) < 0) {
+			wpa_printf(MSG_INFO,
+				"ML: Could not find AP from "
+				"the scan results");
+			return -1;
+		}
+
+		if (len < sizeof(struct wpa_mlo_link_kde)) {
+			wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 error mlo link");
+			return -1;
+		}
+
+		len -= sizeof(struct wpa_mlo_link_kde);
+		if (mlo_link->info & BIT(4)) {
+			if (len < 2 || len < mlo_link->var[1] + 2) {
+				wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 wrong mlo rsne len");
+				return -1;
+			} else {
+				rsne = &mlo_link->var[0];
+				rsne_len = mlo_link->var[1] + 2;
+				len -= rsne_len;
+			}
+		}
+
+		if (mlo_link->info & BIT(5)) {
+			if (len < 2 || len < mlo_link->var[rsne_len + 1] + 2) {
+				wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 wrong mlo rsnxe len");
+				return -1;
+			} else {
+				rsnxe = &mlo_link->var[rsne_len];
+				rsnxe_len = mlo_link->var[rsne_len + 1] + 2;
+				len -= rsnxe_len;
+			}
+		}
+
+		if (len != 0) {
+			wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 wrong data len");
+			return -1;
+		}
+
+		if (sm->ap_ml_ie) {
+			found = 0;
+			for (j = 0; j < sm->ap_ml_ie->ml_link_num; j++) {
+				if (os_memcmp(sm->ap_ml_ie->profiles[j].addr,
+						mlo_link->addr, ETH_ALEN) == 0 &&
+				    sm->ap_ml_ie->profiles[j].link_id ==
+						(mlo_link->info & 0xf)) {
+					found = 1;
+					break;
+				}
+			}
+
+			if (!found) {
+				wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 wrong link");
+				return -1;
+			}
+		}
+
+		/* mlo without rsn/rsx but beacon does or length not matched */
+		if ((!(mlo_link->info & 0xf0) && (wpa.ap_rsn_ie || wpa.ap_rsnxe))) {
+			wpa_printf(MSG_INFO, "ML: IE in 3/4 msg does not match "
+					     "with IE in Beacon/ProbeResp (no IE?)");
+			return -1;
+		}
+
+		/* rsne */
+		if (rsne && wpa.ap_rsn_ie &&
+		    wpa_compare_rsn_ie(wpa_key_mgmt_ft(sm->key_mgmt),
+					wpa.ap_rsn_ie, wpa.ap_rsn_ie_len,
+					rsne, rsne_len)) {
+			wpa_printf(MSG_INFO, "ML: IE in 3/4 msg does not match "
+					     "with IE in Beacon/ProbeResp (rsne)");
+			return -1;
+		}
+
+		if (sm->proto == WPA_PROTO_WPA &&
+		    rsne && wpa.ap_rsn_ie == NULL && sm->rsn_enabled) {
+			wpa_printf(MSG_INFO, "ML: Possible downgrade attack "
+					       "detected - RSN was enabled and RSN IE "
+					       "was in msg 3/4, but not in "
+					       "Beacon/ProbeResp");
+			return -1;
+		}
+
+		if (sm->proto == WPA_PROTO_RSN &&
+		    ((wpa.ap_rsnxe && !rsnxe) ||
+		     (!wpa.ap_rsnxe && rsnxe) ||
+		     (wpa.ap_rsnxe && rsnxe &&
+		      (wpa.ap_rsnxe_len != rsnxe_len ||
+		       os_memcmp(wpa.ap_rsnxe, rsnxe, wpa.ap_rsnxe_len) != 0)))) {
+			wpa_printf(MSG_INFO, "ML: RSNXE mismatch between Beacon/ProbeResp and EAPOL-Key msg 3/4");
+			wpa_hexdump(MSG_INFO, "RSNXE in Beacon/ProbeResp",
+				    wpa.ap_rsnxe, wpa.ap_rsnxe_len);
+			wpa_hexdump(MSG_INFO, "RSNXE in EAPOL-Key msg 3/4",
+				    rsnxe, rsnxe_len);
+			return -1;
+		}
+	}
+
+	/* mlo gtk */
+	if (ie->gtk) {
+		wpa_printf(MSG_INFO,
+			"ML: EAPOL-Key 3/4 should not have Basic GTK IE");
+		return -1;
+	}
+
+	if (ie->mlo_gtk.num > 0 && !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
+		wpa_printf(MSG_INFO,
+			"ML: EAPOL-Key 3/4 MLO GTK IE in unencrypted key data");
+		return -1;
+	}
+
+
+	for (i = 0; i < ie->mlo_gtk.num; i++) {
+		struct wpa_mlo_gtk_kde *mlo_gtk =
+			(struct wpa_mlo_gtk_kde *) ie->mlo_gtk.kdes[i].data;
+		u8 link_id = (mlo_gtk->info & 0xf0) >> 4;
+
+		if (sm->ap_ml_ie) {
+			found = 0;
+			for (j = 0; j < sm->ap_ml_ie->ml_link_num; j++) {
+				if (link_id == sm->ap_ml_ie->profiles[j].link_id) {
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 wrong gtk link id");
+				return -1;
+			}
+		}
+	}
+
+
+	/* mlo igtk */
+	if (ie->igtk) {
+		wpa_printf(MSG_INFO,
+			"ML: EAPOL-Key 3/4 should not have Basic IGTK IE");
+		return -1;
+	}
+
+	if (ie->mlo_igtk.num > 0 && !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
+		wpa_printf(MSG_INFO,
+			"ML: EAPOL-Key 3/4 MLO IGTK IE in unencrypted key data");
+		return -1;
+	}
+
+	for (i = 0; i < ie->mlo_igtk.num; i++) {
+		struct wpa_mlo_igtk_kde *mlo_igtk =
+			(struct wpa_mlo_igtk_kde *) ie->mlo_igtk.kdes[i].data;
+		u8 link_id = (mlo_igtk->info & 0xf0) >> 4;
+		size_t len = ie->mlo_igtk.kdes[i].len;
+
+		if (sm->ap_ml_ie) {
+			found = 0;
+			for (j = 0; j < sm->ap_ml_ie->ml_link_num; j++) {
+				if (link_id == sm->ap_ml_ie->profiles[j].link_id) {
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 wrong igtk link id");
+				return -1;
+			}
+		}
+
+		if (sm->mgmt_group_cipher != WPA_CIPHER_GTK_NOT_USED &&
+		    wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher) &&
+		    len != WPA_MLO_IGTK_KDE_PREFIX_LEN +
+		    (unsigned int) wpa_cipher_key_len(sm->mgmt_group_cipher)) {
+			wpa_printf(MSG_INFO, "ML: Invalid IGTK KDE length %lu",
+				(unsigned long) len);
+			return -1;
+		}
+	}
+
+	/* mlo bigtk */
+	if (ie->bigtk) {
+		wpa_printf(MSG_INFO,
+			"ML: EAPOL-Key 3/4 should not have Basic BIGTK IE");
+		return -1;
+	}
+
+	if (ie->mlo_bigtk.num > 0 && !sm->beacon_prot) {
+		wpa_printf(MSG_INFO,
+			"ML: EAPOL-Key 3/4 MLO BIGTK IE in unencrypted key data");
+		return -1;
+	}
+
+	for (i = 0; i < ie->mlo_bigtk.num; i++) {
+		struct wpa_mlo_bigtk_kde *mlo_bigtk =
+			(struct wpa_mlo_bigtk_kde *) ie->mlo_bigtk.kdes[i].data;
+		u8 link_id = (mlo_bigtk->info & 0xf0) >> 4;
+		size_t len = ie->mlo_bigtk.kdes[i].len;
+
+		if (sm->ap_ml_ie) {
+			found = 0;
+			for (j = 0; j < sm->ap_ml_ie->ml_link_num; j++) {
+				if (link_id == sm->ap_ml_ie->profiles[j].link_id) {
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				wpa_printf(MSG_INFO, "ML: EAPOL-Key 3/4 wrong bigtk link id");
+				return -1;
+			}
+		}
+		if (sm->mgmt_group_cipher != WPA_CIPHER_GTK_NOT_USED &&
+		    wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher) &&
+		    len != WPA_MLO_BIGTK_KDE_PREFIX_LEN +
+		    (unsigned int) wpa_cipher_key_len(sm->mgmt_group_cipher)) {
+			wpa_printf(MSG_INFO, "ML: Invalid BIGTK KDE length %lu",
+				(unsigned long) len);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int ml_rsc_relaxation(const struct wpa_sm *sm, const u8 *rsc)
+{
+	int rsclen;
+
+	if (!sm->wpa_rsc_relaxation)
+		return 0;
+
+	rsclen = wpa_cipher_rsc_len(sm->group_cipher);
+
+	/*
+	 * Try to detect RSC (endian) corruption issue where the AP sends
+	 * the RSC bytes in EAPOL-Key message in the wrong order, both if
+	 * it's actually a 6-byte field (as it should be) and if it treats
+	 * it as an 8-byte field.
+	 * An AP model known to have this bug is the Sapido RB-1632.
+	 */
+	if (rsclen == 6 && ((rsc[5] && !rsc[0]) || rsc[6] || rsc[7])) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"RSC %02x%02x%02x%02x%02x%02x%02x%02x is likely bogus, using 0",
+			rsc[0], rsc[1], rsc[2], rsc[3],
+			rsc[4], rsc[5], rsc[6], rsc[7]);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+
+static int ml_gtk_tx_bit_workaround(const struct wpa_sm *sm,
+						int tx)
+{
+	if (tx && sm->pairwise_cipher != WPA_CIPHER_NONE) {
+		/* Ignore Tx bit for GTK if a pairwise key is used. One AP
+		 * seemed to set this bit (incorrectly, since Tx is only when
+		 * doing Group Key only APs) and without this workaround, the
+		 * data connection does not work because wpa_supplicant
+		 * configured non-zero keyidx to be used for unicast. */
+		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			"ML: Tx bit set for GTK, but pairwise "
+			"keys are used - ignore Tx bit");
+		return 0;
+	}
+	return tx;
+}
+
+static int ml_check_group_cipher(struct wpa_sm *sm,
+					     int group_cipher,
+					     int keylen, int maxkeylen,
+					     int *key_rsc_len,
+					     enum wpa_alg *alg)
+{
+	int klen;
+
+	*alg = wpa_cipher_to_alg(group_cipher);
+	if (*alg == WPA_ALG_NONE) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"ML: Unsupported Group Cipher %d",
+			group_cipher);
+		return -1;
+	}
+	*key_rsc_len = wpa_cipher_rsc_len(group_cipher);
+
+	klen = wpa_cipher_key_len(group_cipher);
+	if (keylen != klen || maxkeylen < klen) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"ML: Unsupported %s Group Cipher key length %d (%d)",
+			wpa_cipher_txt(group_cipher), keylen, maxkeylen);
+		return -1;
+	}
+	return 0;
+}
+
+static int ml_install_gtk(struct wpa_sm *sm,
+			const struct wpa_eapol_key *key,
+			struct wpa_eapol_ie_parse *ie, u8 wnm_sleep)
+{
+	struct wpa_supplicant *wpa_s = sm->ctx->ctx;
+	struct ml_gtk_data data, *gd = &data;
+	const u8 *key_rsc, *gtk;
+	size_t gtk_len, i;
+	char cmd[32], buf[256];
+	u8 gtk_buf[32], *_gtk;
+
+	for (i = 0; i < ie->mlo_gtk.num; i++) {
+		gtk = ie->mlo_gtk.kdes[i].data;
+		gtk_len = ie->mlo_gtk.kdes[i].len;
+
+		os_memset(gd, 0, sizeof(*gd));
+		wpa_hexdump_key(MSG_DEBUG, "ML: received GTK in pairwise handshake",
+				gtk, gtk_len);
+
+		if (gtk_len < WPA_MLO_GTK_KDE_PREFIX_LEN ||
+		    gtk_len - WPA_MLO_GTK_KDE_PREFIX_LEN > sizeof(gd->gtk))
+			return -1;
+
+		gd->link_id = (gtk[0] & 0xf0) >> 4;
+		gd->keyidx = gtk[0] & 0x3;
+		gd->tx = ml_gtk_tx_bit_workaround(sm, !!(gtk[0] & BIT(2)));
+		gtk += WPA_MLO_GTK_KDE_PREFIX_LEN ;
+		gtk_len -= WPA_MLO_GTK_KDE_PREFIX_LEN;
+
+		os_memcpy(gd->gtk, gtk, gtk_len);
+		gd->gtk_len = gtk_len;
+
+		key_rsc = key->key_rsc;
+		if (ml_rsc_relaxation(sm, key->key_rsc))
+			key_rsc = null_rsc;
+
+
+		if (ml_check_group_cipher(sm, sm->group_cipher,
+				       gtk_len, gtk_len,
+				       &gd->key_rsc_len, &gd->alg)) {
+			wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+				"ML: Check group cipher failed");
+			forced_memzero(gd, sizeof(*gd));
+			return -1;
+		}
+
+		_gtk = gd->gtk;
+
+		/* Detect possible key reinstallation */
+		if ((sm->ml_gtk.gtks[i].gtk_len == (size_t) gd->gtk_len &&
+		     os_memcmp(sm->ml_gtk.gtks[i].gtk, gd->gtk, sm->ml_gtk.gtks[i].gtk_len) == 0) ||
+		    (sm->ml_gtk_wnm_sleep.gtks[i].gtk_len == (size_t) gd->gtk_len &&
+		     os_memcmp(sm->ml_gtk_wnm_sleep.gtks[i].gtk, gd->gtk,
+			       sm->ml_gtk_wnm_sleep.gtks[i].gtk_len) == 0)) {
+			wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+				"ML: Not reinstalling already in-use GTK to the driver (keyidx=%d tx=%d len=%d)",
+				gd->keyidx, gd->tx, gd->gtk_len);
+			continue;
+		}
+
+		wpa_hexdump_key(MSG_DEBUG, "ML: Group Key", gd->gtk, gd->gtk_len);
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"ML: Installing GTK to the driver (keyidx=%d tx=%d len=%d)",
+			gd->keyidx, gd->tx, gd->gtk_len);
+		wpa_hexdump(MSG_DEBUG, "WPA: RSC", key_rsc, gd->key_rsc_len);
+		if (sm->group_cipher == WPA_CIPHER_TKIP) {
+			/* Swap Tx/Rx keys for Michael MIC */
+			os_memcpy(gtk_buf, gd->gtk, 16);
+			os_memcpy(gtk_buf + 16, gd->gtk + 24, 8);
+			os_memcpy(gtk_buf + 24, gd->gtk + 16, 8);
+			_gtk = gtk_buf;
+		}
+
+		// TODO: remove this when kernel is ready
+		os_snprintf(cmd, sizeof(cmd), CMD_PRESET_LINKID " %d", gd->link_id);
+		wpa_drv_driver_cmd(wpa_s, cmd, buf, sizeof(buf));
+
+		if (sm->pairwise_cipher == WPA_CIPHER_NONE) {
+			if (wpa_sm_set_key(sm, gd->alg, NULL,
+					   gd->keyidx, 1, key_rsc, gd->key_rsc_len,
+					   _gtk, gd->gtk_len,
+					   KEY_FLAG_GROUP_RX_TX_DEFAULT) < 0) {
+				wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+					"ML: Failed to set GTK to the driver "
+					"(Group only)");
+				forced_memzero(gtk_buf, sizeof(gtk_buf));
+				return -1;
+			}
+		} else if (wpa_sm_set_key(sm, gd->alg, broadcast_ether_addr,
+					  gd->keyidx, gd->tx, key_rsc, gd->key_rsc_len,
+					  _gtk, gd->gtk_len, KEY_FLAG_GROUP_RX) < 0) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+				"ML: Failed to set GTK to "
+				"the driver (alg=%d keylen=%d keyidx=%d)",
+				gd->alg, gd->gtk_len, gd->keyidx);
+			forced_memzero(gtk_buf, sizeof(gtk_buf));
+			return -1;
+		}
+
+		if (wnm_sleep) {
+			sm->ml_gtk_wnm_sleep.gtks[i].gtk_len = gd->gtk_len;
+			os_memcpy(sm->ml_gtk_wnm_sleep.gtks[i].gtk, gd->gtk,
+				  sm->ml_gtk_wnm_sleep.gtks[i].gtk_len);
+		} else {
+			sm->ml_gtk.gtks[i].gtk_len = gd->gtk_len;
+			os_memcpy(sm->ml_gtk.gtks[i].gtk, gd->gtk,
+				  sm->ml_gtk.gtks[i].gtk_len);
+		}
+
+		forced_memzero(gd, sizeof(*gd));
+		forced_memzero(gtk_buf, sizeof(gtk_buf));
+	}
+
+	return 0;
+}
+
+static int ml_install_igtk(struct wpa_sm *sm, const struct wpa_eapol_key *key,
+		struct wpa_eapol_ie_parse *ie, u8 wnm_sleep)
+{
+	struct wpa_supplicant *wpa_s = sm->ctx->ctx;
+	char cmd[32], buf[256];
+	size_t i;
+	size_t len = wpa_cipher_key_len(sm->mgmt_group_cipher);
+	struct wpa_mlo_igtk_kde *igtk;
+	size_t igtk_len;
+	u16 keyidx;
+	u8 link_id;
+
+	for (i = 0; i < ie->mlo_igtk.num; i++) {
+		igtk = (struct wpa_mlo_igtk_kde *) ie->mlo_igtk.kdes[i].data;
+		igtk_len = ie->mlo_igtk.kdes[i].len;
+		keyidx = WPA_GET_LE16(igtk->keyid);
+		link_id = (igtk->info & 0xf0) >> 4;
+
+		if (igtk_len != WPA_MLO_IGTK_KDE_PREFIX_LEN + len)
+			return -1;
+
+		/* Detect possible key reinstallation */
+		if ((sm->ml_igtk.igtks[i].igtk_len == len &&
+		     os_memcmp(sm->ml_igtk.igtks[i].igtk, igtk->igtk,
+			       sm->ml_igtk.igtks[i].igtk_len) == 0) ||
+		    (sm->ml_igtk_wnm_sleep.igtks[i].igtk_len == len &&
+		     os_memcmp(sm->ml_igtk_wnm_sleep.igtks[i].igtk, igtk->igtk,
+			       sm->ml_igtk_wnm_sleep.igtks[i].igtk_len) == 0)){
+			wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+				"ML: Not reinstalling already in-use IGTK to the driver (keyidx=%d)",
+				keyidx);
+			continue;
+		}
+
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"ML: IGTK keyid %d pn " COMPACT_MACSTR,
+			keyidx, MAC2STR(igtk->pn));
+		wpa_hexdump_key(MSG_DEBUG, "ML: IGTK", igtk->igtk, len);
+		if (keyidx > 4095) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+				"ML: Invalid IGTK KeyID %d", keyidx);
+			return -1;
+		}
+
+
+		// TODO: remove this when kernel is ready
+		os_snprintf(cmd, sizeof(cmd), CMD_PRESET_LINKID " %d", link_id);
+		wpa_drv_driver_cmd(wpa_s, cmd, buf, sizeof(buf));
+
+		if (wpa_sm_set_key(sm, wpa_cipher_to_alg(sm->mgmt_group_cipher),
+				   broadcast_ether_addr,
+				   keyidx, 0, igtk->pn, sizeof(igtk->pn),
+				   igtk->igtk, len, KEY_FLAG_GROUP_RX) < 0) {
+			if (keyidx == 0x0400 || keyidx == 0x0500) {
+				/* Assume the AP has broken PMF implementation since it
+				 * seems to have swapped the KeyID bytes. The AP cannot
+				 * be trusted to implement BIP correctly or provide a
+				 * valid IGTK, so do not try to configure this key with
+				 * swapped KeyID bytes. Instead, continue without
+				 * configuring the IGTK so that the driver can drop any
+				 * received group-addressed robust management frames due
+				 * to missing keys.
+				 *
+				 * Normally, this error behavior would result in us
+				 * disconnecting, but there are number of deployed APs
+				 * with this broken behavior, so as an interoperability
+				 * workaround, allow the connection to proceed. */
+				wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+					"ML: Ignore IGTK configuration error due to invalid IGTK KeyID byte order");
+			} else {
+				wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+					"ML: Failed to configure IGTK to the driver");
+				return -1;
+			}
+		}
+
+		if (wnm_sleep) {
+			sm->ml_igtk_wnm_sleep.igtks[i].igtk_len = len;
+			os_memcpy(sm->ml_igtk_wnm_sleep.igtks[i].igtk, igtk->igtk, len);
+		} else {
+			sm->ml_igtk.igtks[i].igtk_len = len;
+			os_memcpy(sm->ml_igtk.igtks[i].igtk, igtk->igtk, len);
+		}
+	}
+
+	return 0;
+}
+
+static int ml_install_bigtk(struct wpa_sm *sm, const struct wpa_eapol_key *key,
+		struct wpa_eapol_ie_parse *ie, u8 wnm_sleep)
+{
+	struct wpa_supplicant *wpa_s = sm->ctx->ctx;
+	char cmd[32], buf[256];
+	size_t i;
+	size_t len = wpa_cipher_key_len(sm->mgmt_group_cipher);
+	struct wpa_mlo_bigtk_kde *bigtk;
+	size_t bigtk_len;
+	u16 keyidx;
+	u8 link_id;
+
+	for (i = 0; i < ie->mlo_bigtk.num; i++) {
+		bigtk = (struct wpa_mlo_bigtk_kde *) ie->mlo_bigtk.kdes[i].data;
+		bigtk_len = ie->mlo_igtk.kdes[i].len;
+		keyidx = WPA_GET_LE16(bigtk->keyid);
+		link_id = (bigtk->info & 0xf0) >> 4;
+
+		if (bigtk_len != WPA_MLO_BIGTK_KDE_PREFIX_LEN + len)
+			return -1;
+
+		/* Detect possible key reinstallation */
+		if ((sm->ml_bigtk.bigtks[i].bigtk_len == len &&
+		     os_memcmp(sm->ml_bigtk.bigtks[i].bigtk, bigtk->bigtk,
+			       sm->ml_bigtk.bigtks[i].bigtk_len) == 0) ||
+		    (sm->ml_bigtk_wnm_sleep.bigtks[i].bigtk_len == len &&
+		     os_memcmp(sm->ml_bigtk_wnm_sleep.bigtks[i].bigtk, bigtk->bigtk,
+			       sm->ml_bigtk_wnm_sleep.bigtks[i].bigtk_len) == 0)) {
+			wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+				"ML: Not reinstalling already in-use BIGTK to the driver (keyidx=%d)",
+				keyidx);
+			return  0;
+		}
+
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"ML: BIGTK keyid %d pn " COMPACT_MACSTR,
+			keyidx, MAC2STR(bigtk->pn));
+		wpa_hexdump_key(MSG_DEBUG, "ML: BIGTK", bigtk->bigtk, len);
+		if (keyidx < 6 || keyidx > 7) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+				"ML: Invalid BIGTK KeyID %d", keyidx);
+			return -1;
+		}
+
+		// TODO: remove this when kernel is ready
+		os_snprintf(cmd, sizeof(cmd), CMD_PRESET_LINKID " %d", link_id);
+		wpa_drv_driver_cmd(wpa_s, cmd, buf, sizeof(buf));
+
+		if (wpa_sm_set_key(sm, wpa_cipher_to_alg(sm->mgmt_group_cipher),
+				   broadcast_ether_addr,
+				   keyidx, 0, bigtk->pn, sizeof(bigtk->pn),
+				   bigtk->bigtk, len, KEY_FLAG_GROUP_RX) < 0) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+				"WPA: Failed to configure BIGTK to the driver");
+			return -1;
+		}
+
+		if (wnm_sleep) {
+			sm->ml_bigtk_wnm_sleep.bigtks[i].bigtk_len = len;
+			os_memcpy(sm->ml_bigtk_wnm_sleep.bigtks[i].bigtk, bigtk->bigtk,
+				  sm->ml_bigtk_wnm_sleep.bigtks[i].bigtk_len);
+		} else {
+			sm->ml_bigtk.bigtks[i].bigtk_len = len;
+			os_memcpy(sm->ml_bigtk.bigtks[i].bigtk, bigtk->bigtk,
+				  sm->ml_bigtk.bigtks[i].bigtk_len);
+		}
+	}
+
+	return 0;
+}
+
+int ml_process_m1_kde(struct wpa_sm *sm, struct wpa_eapol_ie_parse *ie)
+{
+	if (sm->dot11MultiLinkActivated) {
+		if (ie->mac_addr) {
+			wpa_hexdump(MSG_DEBUG, "ML: MAC from "
+			    "Authenticator", ie->mac_addr, ie->mac_addr_len);
+			if (os_memcmp(ie->mac_addr, sm->ml_bssid, ETH_ALEN) != 0) {
+				wpa_dbg(sm->ctx->msg_ctx, MSG_ERROR,
+				"ML: ML MAC Addr from M1 is different");
+				return -1;
+			}
+		} else {
+			wpa_dbg(sm->ctx->msg_ctx, MSG_ERROR,
+				"ML: ML MAC Addr should be in M1");
+			return -1;
+		}
+	}
+	return 0;
+}
+
+int ml_process_m3_kde(struct wpa_sm *sm, const struct wpa_eapol_key *key,
+	struct wpa_eapol_ie_parse *ie)
+{
+	u16 key_info;
+	size_t i, j;
+	u8 found = 0;
+
+	key_info = WPA_GET_BE16(key->key_info);
+
+	if(!sm->dot11MultiLinkActivated)
+		return 0;
+
+	/* mlo gtk */
+	if (sm->group_cipher == WPA_CIPHER_GTK_NOT_USED) {
+		/* No GTK to be set to the driver */
+	} else if (ie->mlo_gtk.num > 0 &&
+		ml_install_gtk(sm, key, ie, 0) < 0) {
+		    wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			    "ML: Failed to configure GTK");
+		return -1;
+	}
+
+	if (!wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher) ||
+	    sm->mgmt_group_cipher == WPA_CIPHER_GTK_NOT_USED) {
+		/* No IGTK to be set to the driver */
+	} else if (ie->mlo_igtk.num > 0 &&
+		ml_install_igtk(sm, key, ie, 0) < 0) {
+		    wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			    "ML: Failed to configure IGTK");
+		return -1;
+	}
+
+	if (!sm->beacon_prot) {
+		/* No BIGTK to be set to the driver */
+	} else if (ie->mlo_bigtk.num > 0 &&
+		ml_install_bigtk(sm, key, ie, 0) < 0) {
+		    wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			    "ML: Failed to configure BIGTK");
+		return -1;
+	}
+
+	return 0;
+}
+
+int ml_process_1_of_2(struct wpa_sm *sm, const struct wpa_eapol_key *key,
+	const u8 *key_data, size_t key_data_len, u16 key_info)
+{
+	struct wpa_eapol_ie_parse parse;
+	struct wpa_eapol_ie_parse *ie = &parse;
+
+	if(!sm->dot11MultiLinkActivated)
+		return 0;
+
+	wpa_hexdump(MSG_DEBUG, "ML: Group 1/2 IE KeyData", key_data, key_data_len);
+	if (wpa_supplicant_parse_ies(key_data, key_data_len, ie) < 0)
+		return -1;
+
+	/* mlo gtk */
+	if (sm->group_cipher == WPA_CIPHER_GTK_NOT_USED) {
+		/* No GTK to be set to the driver */
+	} else if (ie->mlo_gtk.num > 0 &&
+		ml_install_gtk(sm, key, ie, 0) < 0) {
+		    wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			    "ML: Failed to configure GTK");
+		return -1;
+	}
+
+	if (!wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher) ||
+	    sm->mgmt_group_cipher == WPA_CIPHER_GTK_NOT_USED) {
+		/* No IGTK to be set to the driver */
+	} else if (ie->mlo_igtk.num > 0 &&
+		ml_install_igtk(sm, key, ie, 0) < 0) {
+		    wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			    "ML: Failed to configure IGTK");
+		return -1;
+	}
+
+	if (!sm->beacon_prot) {
+		/* No BIGTK to be set to the driver */
+	} else if (ie->mlo_bigtk.num > 0 &&
+		ml_install_bigtk(sm, key, ie, 0) < 0) {
+		    wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			    "ML: Failed to configure BIGTK");
+		return -1;
+	}
+
+	return 0;
+}
+
+size_t ml_add_key_request_kde(struct wpa_sm *sm, u8 *pos)
+{
+	struct wpa_ml_ie_parse *ml = sm->sta_ml_ie;
+	u8 i;
+
+	if (!sm->dot11MultiLinkActivated)
+		return 0;
+
+	wpa_printf(MSG_DEBUG, "ML: Add Mac into Key Request");
+	return ml_set_mac_kde(pos, sm->own_ml_addr) - pos;
+}
+
+size_t ml_add_m4_kde(struct wpa_sm *sm, u8 *pos)
+{
+	struct wpa_ml_ie_parse *ml = sm->sta_ml_ie;
+	u8 i;
+
+	if (!sm->dot11MultiLinkActivated)
+		return 0;
+
+	wpa_printf(MSG_DEBUG, "ML: Add Mac into EAPOL-Key 4/4");
+	return ml_set_mac_kde(pos, sm->own_ml_addr) - pos;
+}
+
+size_t ml_add_2_of_2_kde(struct wpa_sm *sm, u8 *pos)
+{
+	struct wpa_ml_ie_parse *ml = sm->sta_ml_ie;
+	u8 i;
+
+	if (!sm->dot11MultiLinkActivated)
+		return 0;
+
+	wpa_printf(MSG_DEBUG, "ML: Add Mac into EAPOL-Key Group 2/2");
+	return ml_set_mac_kde(pos, sm->own_ml_addr) - pos;
+}
+
+
+#if 0
+void p2p_buf_add_ml_channel_list(struct wpabuf *buf,
+				   const u32 *preferred_freq_list,
+				   unsigned int size)
+{
+	unsigned int i, count = 0;
+	u8 op_class, op_channel;
+
+	if (!size)
+		return;
+
+	/*
+	 * First, determine the number of P2P supported channels in the
+	 * pref_freq_list returned from driver. This is needed for calculations
+	 * of the vendor IE size.
+	 */
+	for (i = 0; i < size; i++) {
+		if (p2p_freq_to_channel(preferred_freq_list[i], &op_class,
+					&op_channel) == 0)
+			count++;
+	}
+
+	wpabuf_put_u8(buf, P2P_ATTR_VENDOR_SPECIFIC);
+	wpabuf_put_u8(buf, 4 + count * sizeof(u16));
+	wpabuf_put_be24(buf, OUI_MTK);
+	wpabuf_put_u8(buf, MTK_VENDOR_ATTR_P2P_ML_CHAN_LIST);
+	for (i = 0; i < size; i++) {
+		if (p2p_freq_to_channel(preferred_freq_list[i], &op_class,
+					&op_channel) < 0) {
+			wpa_printf(MSG_DEBUG, "Unsupported frequency %u MHz",
+				   preferred_freq_list[i]);
+			continue;
+		}
+		wpabuf_put_u8(buf, op_class);
+		wpabuf_put_u8(buf, op_channel);
+	}
+}
+#endif
+
+
+#endif /* HOSTAPD */
+
+
--- /dev/null
+++ b/src/ml/ml.h
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ *
+ * This file is provided under a dual license.  When you use or
+ * distribute this software, you may choose to be licensed under
+ * version 2 of the GNU General Public License ("GPLv2 License")
+ * or BSD License.
+ *
+ * GPLv2 License
+ *
+ * Copyright(C) 2016 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(C) 2016 MediaTek Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ ******************************************************************************/
+
+#ifndef WPA_ML_H
+#define WPA_ML_H
+
+#include "common/wpa_common.h"
+
+struct hostapd_data;
+struct wpa_state_machine;
+struct wpa_authenticator;
+struct wpa_sm;
+struct sae_data;
+
+#define OUI_MTK 0x000ce7
+#define MTK_MLD_IE_VENDOR_TYPE 0x000ce701
+
+
+#define MTK_VENDOR_ATTR_P2P_ML_CHAN_LIST  0
+
+#define ML_CTRL_TYPE_MASK				BITS(0, 2)
+#define ML_ELEMENT_TYPE_BASIC				0
+#define ML_ELEMENT_TYPE_PROBE_REQ			1
+
+#define ML_CTRL_PRE_BMP_MASK				BITS(4, 15)
+#define ML_CTRL_LINK_ID_INFO_PRESENT			BIT(4)
+#define ML_CTRL_BSS_PARA_CHANGE_COUNT_PRESENT		BIT(5)
+#define ML_CTRL_MEDIUM_SYN_DELAY_INFO_PRESENT		BIT(6)
+#define ML_CTRL_EML_CAPA_PRESENT			BIT(7)
+#define ML_CTRL_MLD_CAPA_PRESENT			BIT(8)
+
+#define ML_SUB_ID_PER_STA_PROFILE			0
+#define ML_STA_CTRL_LINK_ID_MASK			BITS(0, 3)
+#define ML_STA_CTRL_LINK_ID_SHIFT			0
+#define ML_STA_CTRL_COMPLETE_PROFILE			BIT(4)
+#define ML_STA_CTRL_MAC_ADDR_PRESENT			BIT(5)
+#define ML_STA_CTRL_BCN_INTV_PRESENT			BIT(6)
+#define ML_STA_CTRL_DTIM_INFO_PRESENT			BIT(7)
+#define ML_STA_CTRL_NSTR_LINK_PAIR_PRESENT		BIT(8)
+#define ML_STA_CTRL_NSTR_BMP_SIZE			BIT(9)
+#define ML_STA_CTRL_NSTR_BMP_SIZE_SHIFT			9
+
+#define ML_SET_CTRL_TYPE(_u2ctrl, _ctrl_type) \
+{\
+	(_u2ctrl) &= ~(ML_CTRL_TYPE_MASK); \
+	(_u2ctrl) |= ((_ctrl_type) & (ML_CTRL_TYPE_MASK)); \
+}
+
+#define ML_SET_CTRL_PRESENCE(_u2ctrl, _ctrl_type) \
+{\
+	(_u2ctrl) &= ~(ML_CTRL_PRE_BMP_MASK); \
+	(_u2ctrl) |= ((_ctrl_type) & (ML_CTRL_PRE_BMP_MASK)); \
+}
+
+#define WPA_MLO_GTK_KDE_PREFIX_LEN (1 + 6)
+struct wpa_mlo_gtk_kde {
+	u8 info; /* KeyId 2 | Tx 1 | Reserved 1 | LinkId 4 */
+	u8 pn[6];
+	u8 gtk[WPA_GTK_MAX_LEN];
+} STRUCT_PACKED;
+
+#define WPA_MLO_IGTK_KDE_PREFIX_LEN (2 + 6 + 1)
+struct wpa_mlo_igtk_kde {
+	u8 keyid[2];
+	u8 pn[6];
+	u8 info; /* Reserved 4 | LinkId 4 */
+	u8 igtk[WPA_IGTK_MAX_LEN];
+} STRUCT_PACKED;
+
+#define WPA_MLO_BIGTK_KDE_PREFIX_LEN (2 + 6 + 1)
+struct wpa_mlo_bigtk_kde {
+	u8 keyid[2];
+	u8 pn[6];
+	u8 info; /* Reserved 4 | LinkId 4 */
+	u8 bigtk[WPA_BIGTK_MAX_LEN];
+} STRUCT_PACKED;
+
+struct wpa_mlo_link_kde {
+	u8 info; /* LinkId 4 | RSNEInfo 1 | RSNXEInfo 1 | Reserved 2 */
+	u8 addr[ETH_ALEN];
+	u8 var[]; /* RSNE | RSNXE */
+} STRUCT_PACKED;
+
+struct wpa_ml_link {
+	u8 link_id;
+	u8 addr[ETH_ALEN];
+
+	void *ctx;
+};
+
+struct wpa_ml_group {
+	void *ctx;
+	u8 ml_addr[ETH_ALEN];
+	u8 ml_group_id;
+	size_t ml_link_num;
+
+	struct wpa_ml_link *links;
+};
+
+struct per_sta_profile {
+	u8 link_id;
+	u8 addr[ETH_ALEN];
+	u16 dtim;
+	u16 beacon_interval;
+	u16 nstr_bmap;
+	unsigned int complete_profile:1;
+	unsigned int mac_addr_present:1;
+	unsigned int bcn_intvl_present:1;
+	unsigned int dtim_present:1;
+	unsigned int nstr_present:1;
+};
+
+struct wpa_ml_ie_parse {
+	u8 type;
+	u8 ml_addr[ETH_ALEN];
+	u8 common_info_len;
+	u8 link_id;
+	u8 bss_para_change_count;
+	u16 medium_sync_delay;
+	u16 eml_cap;
+	u16 mld_cap;
+	u8 ml_link_num;
+	unsigned int valid:1;
+	unsigned int link_id_present:1;
+	unsigned int bss_para_change_cnt_present:1;
+	unsigned int medium_sync_delay_present:1;
+	unsigned int eml_cap_present:1;
+	unsigned int mld_cap_present:1;
+
+	struct per_sta_profile profiles[ML_MAX_LINK_NUM];
+};
+
+/* common */
+u8* ml_set_mac_kde(u8 *buf, const unsigned char *addr);
+u8* ml_set_ml_link_kde(u8 *pos, u8 id, const unsigned char *addr,
+	const u8 *rsne, size_t rsne_len, const u8 *rsnxe, size_t rsnxe_len);
+int ml_parse_ies(const u8 *pos, size_t len, struct wpa_ml_ie_parse *ml);
+
+/* AP */
+int ml_group_init(struct hostapd_data *hapd, u8 mld_grp_idx, u8 link_id);
+int ml_group_deinit(struct hostapd_data *hapd);
+
+int ml_new_assoc_sta(struct wpa_state_machine *sm, const u8 *ie, size_t len);
+u8* ml_add_m1_kde(struct wpa_state_machine *sm, u8 *pos);
+int ml_process_m2_kde(struct wpa_state_machine *sm,
+		const u8 *key_data, size_t key_data_len);
+u8* ml_add_m3_kde(struct wpa_state_machine *sm, u8 *pos);
+int ml_process_m4_kde(struct wpa_state_machine *sm,
+		const u8 *key_data, size_t key_data_len);
+u8* ml_set_gtk_kde(struct wpa_state_machine *sm, u8 *pos,
+		   struct wpa_ml_link *link);
+u8* ml_set_ieee80211w_kde(struct wpa_state_machine *sm, u8 *pos,
+			  struct wpa_ml_link *link);
+u8* ml_add_rekey_kde(struct wpa_state_machine *sm, u8 *pos);
+int ml_rekey_gtk(struct wpa_state_machine *sm, struct wpa_eapol_ie_parse *kde);
+
+#ifdef CONFIG_SAE
+int ml_sae_process_auth(struct sae_data *sae, u16 auth_transaction,
+	const u8 *ies, size_t ies_len);
+int ml_sae_write_auth(struct hostapd_data *hapd,
+	struct sae_data *sae, struct wpabuf *buf);
+#endif
+
+
+
+#endif /* WPA_ML_H */
