/* * Copyright (c) 2021, The Linux Foundation. All rights reserved. * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * DOC: contains MLO manager util api's */ #include #include #include #include #include #include "wlan_scan_api.h" #include "qdf_types.h" #include "utils_mlo.h" #include "wlan_mlo_mgr_cmn.h" #include "wlan_utility.h" #ifdef WLAN_FEATURE_11BE_MLO static uint8_t *util_find_eid(uint8_t eid, uint8_t *frame, qdf_size_t len) { if (!frame) return NULL; while (len >= MIN_IE_LEN && len >= frame[TAG_LEN_POS] + MIN_IE_LEN) { if (frame[ID_POS] == eid) return frame; len -= frame[TAG_LEN_POS] + MIN_IE_LEN; frame += frame[TAG_LEN_POS] + MIN_IE_LEN; } return NULL; } static uint8_t *util_find_extn_eid(uint8_t eid, uint8_t extn_eid, uint8_t *frame, qdf_size_t len) { if (!frame) return NULL; while (len > MIN_IE_LEN && len >= frame[TAG_LEN_POS] + MIN_IE_LEN) { if ((frame[ID_POS] == eid) && (frame[ELEM_ID_EXTN_POS] == extn_eid)) return frame; len -= frame[TAG_LEN_POS] + MIN_IE_LEN; frame += frame[TAG_LEN_POS] + MIN_IE_LEN; } return NULL; } static QDF_STATUS util_parse_multi_link_ctrl(uint8_t *mlieseqpayload, qdf_size_t mlieseqpayloadlen, uint8_t **link_info, qdf_size_t *link_info_len) { qdf_size_t parsed_payload_len; uint16_t mlcontrol; uint16_t presence_bm; uint16_t cinfo_len = 0; uint16_t exp_cinfo_len = 0; /* This helper returns the location(s) and length(s) of (sub)field(s) * inferable after parsing the Multi Link element Control field. These * location(s) and length(s) is/are in reference to the payload section * of the Multi Link element (after defragmentation, if applicable). * Here, the payload is the point after the element ID extension of the * Multi Link element, and includes the payloads of all subsequent * fragments (if any) but not the headers of those fragments. * * Currently, the helper returns the location and length of the Link * Info field in the Multi Link element sequence. Other (sub)field(s) * can be added later as required. */ if (!mlieseqpayload) { mlo_err("ML seq payload pointer is NULL"); return QDF_STATUS_E_NULL_VALUE; } if (!mlieseqpayloadlen) { mlo_err("ML seq payload len is 0"); return QDF_STATUS_E_INVAL; } if (mlieseqpayloadlen < WLAN_ML_CTRL_SIZE) { mlo_err_rl("ML seq payload len %zu < ML Control size %u", mlieseqpayloadlen, WLAN_ML_CTRL_SIZE); return QDF_STATUS_E_PROTO; } parsed_payload_len = 0; qdf_mem_copy(&mlcontrol, mlieseqpayload, WLAN_ML_CTRL_SIZE); mlcontrol = qdf_le16_to_cpu(mlcontrol); parsed_payload_len += WLAN_ML_CTRL_SIZE; presence_bm = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_BV_CINFO_LENGTH_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for common info length size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_BV_CINFO_LENGTH_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } cinfo_len = *(mlieseqpayload + parsed_payload_len); parsed_payload_len += WLAN_ML_BV_CINFO_LENGTH_SIZE; if (mlieseqpayloadlen < (parsed_payload_len + QDF_MAC_ADDR_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for MAC address size %u after parsed payload len %zu.", mlieseqpayloadlen, QDF_MAC_ADDR_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += QDF_MAC_ADDR_SIZE; /* Check if Link ID info is present */ if (presence_bm & WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P) { if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_BV_CINFO_LINKIDINFO_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for Link ID info size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_BV_CINFO_LINKIDINFO_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_BV_CINFO_LINKIDINFO_SIZE; } /* Check if BSS parameter change count is present */ if (presence_bm & WLAN_ML_BV_CTRL_PBM_BSSPARAMCHANGECNT_P) { if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_BSSPARAMCHNGCNT_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for BSS parameter change count size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_BSSPARAMCHNGCNT_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_BSSPARAMCHNGCNT_SIZE; } /* Check if Medium Sync Delay Info is present */ if (presence_bm & WLAN_ML_BV_CTRL_PBM_MEDIUMSYNCDELAYINFO_P) { if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_BV_CINFO_MEDMSYNCDELAYINFO_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for Medium Sync Delay Info size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_BV_CINFO_MEDMSYNCDELAYINFO_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_BV_CINFO_MEDMSYNCDELAYINFO_SIZE; } /* Check if EML cap is present */ if (presence_bm & WLAN_ML_BV_CTRL_PBM_EMLCAP_P) { if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_BV_CINFO_EMLCAP_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for EML cap size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_BV_CINFO_EMLCAP_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_BV_CINFO_EMLCAP_SIZE; } /* Check if MLD cap is present */ if (presence_bm & WLAN_ML_BV_CTRL_PBM_MLDCAPANDOP_P) { if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_BV_CINFO_MLDCAPANDOP_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for MLD cap size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_BV_CINFO_MLDCAPANDOP_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_BV_CINFO_MLDCAPANDOP_SIZE; } /* Check if MLD ID is present */ if (presence_bm & WLAN_ML_BV_CTRL_PBM_MLDID_P) { if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_BV_CINFO_MLDID_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for MLD ID size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_BV_CINFO_MLDID_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_BV_CINFO_MLDID_SIZE; } exp_cinfo_len = parsed_payload_len - WLAN_ML_CTRL_SIZE; if (cinfo_len != exp_cinfo_len) { mlo_err_rl("ML seq common info len %u doesn't match with expected common info len %u", cinfo_len, exp_cinfo_len); return QDF_STATUS_E_PROTO; } if (link_info_len) { *link_info_len = mlieseqpayloadlen - parsed_payload_len; mlo_debug("link_info_len:%zu, parsed_payload_len:%zu", *link_info_len, parsed_payload_len); } if (mlieseqpayloadlen == parsed_payload_len) { mlo_debug("No Link Info field present"); if (link_info) *link_info = NULL; return QDF_STATUS_SUCCESS; } if (link_info) *link_info = mlieseqpayload + parsed_payload_len; return QDF_STATUS_SUCCESS; } static QDF_STATUS util_parse_prv_multi_link_ctrl(uint8_t *mlieseqpayload, qdf_size_t mlieseqpayloadlen, uint8_t **link_info, qdf_size_t *link_info_len) { qdf_size_t parsed_payload_len; uint16_t mlcontrol; uint16_t presence_bm; uint16_t cinfo_len = 0; uint16_t exp_cinfo_len = 0; /* This helper returns the location(s) and length(s) of (sub)field(s) * inferable after parsing the Multi Link element Control field. These * location(s) and length(s) is/are in reference to the payload section * of the Multi Link element (after defragmentation, if applicable). * Here, the payload is the point after the element ID extension of the * Multi Link element, and includes the payloads of all subsequent * fragments (if any) but not the headers of those fragments. * * Currently, the helper returns the location and length of the Link * Info field in the Multi Link element sequence. Other (sub)field(s) * can be added later as required. */ if (!mlieseqpayload) { mlo_err("ML seq payload pointer is NULL"); return QDF_STATUS_E_NULL_VALUE; } if (!mlieseqpayloadlen) { mlo_err("ML seq payload len is 0"); return QDF_STATUS_E_INVAL; } if (mlieseqpayloadlen < WLAN_ML_CTRL_SIZE) { mlo_err_rl("ML seq payload len %zu < ML Control size %u", mlieseqpayloadlen, WLAN_ML_CTRL_SIZE); return QDF_STATUS_E_PROTO; } parsed_payload_len = 0; qdf_mem_copy(&mlcontrol, mlieseqpayload, WLAN_ML_CTRL_SIZE); mlcontrol = qdf_le16_to_cpu(mlcontrol); parsed_payload_len += WLAN_ML_CTRL_SIZE; presence_bm = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_PRV_CINFO_LENGTH_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for common info length size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_PRV_CINFO_LENGTH_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } cinfo_len = *(mlieseqpayload + parsed_payload_len); parsed_payload_len += WLAN_ML_PRV_CINFO_LENGTH_SIZE; /* Check if MLD ID is present */ if (presence_bm & WLAN_ML_PRV_CTRL_PBM_MLDID_P) { if (mlieseqpayloadlen < (parsed_payload_len + WLAN_ML_PRV_CINFO_MLDID_SIZE)) { mlo_err_rl("ML seq payload len %zu insufficient for MLD ID size %u after parsed payload len %zu.", mlieseqpayloadlen, WLAN_ML_PRV_CINFO_MLDID_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_PRV_CINFO_MLDID_SIZE; } exp_cinfo_len = parsed_payload_len - WLAN_ML_CTRL_SIZE; if (cinfo_len != exp_cinfo_len) { mlo_err_rl("ML seq common info len %u doesn't match with expected common info len %u", cinfo_len, exp_cinfo_len); return QDF_STATUS_E_PROTO; } if (link_info_len) { *link_info_len = mlieseqpayloadlen - parsed_payload_len; mlo_debug("link_info_len:%zu, parsed_payload_len:%zu", *link_info_len, parsed_payload_len); } if (mlieseqpayloadlen == parsed_payload_len) { mlo_debug("No Link Info field present"); if (link_info) *link_info = NULL; return QDF_STATUS_SUCCESS; } if (link_info) *link_info = mlieseqpayload + parsed_payload_len; return QDF_STATUS_SUCCESS; } static QDF_STATUS util_parse_bvmlie_perstaprofile_stactrl(uint8_t *subelempayload, qdf_size_t subelempayloadlen, uint8_t *linkid, uint16_t *beaconinterval, bool *is_beaconinterval_valid, uint64_t *tsfoffset, bool *is_tsfoffset_valid, bool *is_complete_profile, bool *is_macaddr_valid, struct qdf_mac_addr *macaddr, bool is_staprof_reqd, uint8_t **staprof, qdf_size_t *staprof_len) { qdf_size_t parsed_payload_len = 0; uint16_t stacontrol; uint8_t completeprofile; uint8_t nstrlppresent; enum wlan_ml_bv_linfo_perstaprof_stactrl_nstrbmsz nstrbmsz; /* This helper returns the location(s) and where required, the length(s) * of (sub)field(s) inferable after parsing the STA Control field in the * per-STA profile subelement. These location(s) and length(s) is/are in * reference to the payload section of the per-STA profile subelement * (after defragmentation, if applicable). Here, the payload is the * point after the subelement length in the subelement, and includes the * payloads of all subsequent fragments (if any) but not the headers of * those fragments. * * Currently, the helper returns the link ID, MAC address, and STA * profile. More (sub)fields can be added when required. */ if (!subelempayload) { mlo_err("Pointer to subelement payload is NULL"); return QDF_STATUS_E_NULL_VALUE; } if (!subelempayloadlen) { mlo_err("Length of subelement payload is zero"); return QDF_STATUS_E_INVAL; } if (subelempayloadlen < WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_SIZE) { mlo_err_rl("Subelement payload length %zu octets is smaller than STA control field of per-STA profile subelement %u octets", subelempayloadlen, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_SIZE); return QDF_STATUS_E_PROTO; } parsed_payload_len = 0; qdf_mem_copy(&stacontrol, subelempayload, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_SIZE); stacontrol = le16toh(stacontrol); parsed_payload_len += WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_SIZE; if (linkid) { *linkid = QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_LINKID_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_LINKID_BITS); } /* Check if this a complete profile */ completeprofile = QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_CMPLTPROF_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_CMPLTPROF_BITS); if (completeprofile && is_complete_profile) *is_complete_profile = true; /* Check STA Info Length */ if (subelempayloadlen < parsed_payload_len + WLAN_ML_BV_LINFO_PERSTAPROF_STAINFO_LENGTH_SIZE) { mlo_err_rl("Length of subelement payload %zu octets not sufficient to contain STA Info Length of size %u octets after parsed payload length of %zu octets.", subelempayloadlen, WLAN_ML_BV_LINFO_PERSTAPROF_STAINFO_LENGTH_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_BV_LINFO_PERSTAPROF_STAINFO_LENGTH_SIZE; if (is_macaddr_valid) *is_macaddr_valid = false; /* Check STA MAC address present bit */ if (QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_MACADDRP_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_MACADDRP_BITS)) { if (subelempayloadlen < (parsed_payload_len + QDF_MAC_ADDR_SIZE)) { mlo_err_rl("Length of subelement payload %zu octets not sufficient to contain MAC address of size %u octets after parsed payload length of %zu octets.", subelempayloadlen, QDF_MAC_ADDR_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } if (macaddr) { qdf_mem_copy(macaddr->bytes, subelempayload + parsed_payload_len, QDF_MAC_ADDR_SIZE); mlo_nofl_debug("Copied MAC address: " QDF_MAC_ADDR_FMT, subelempayload + parsed_payload_len); if (is_macaddr_valid) *is_macaddr_valid = true; } parsed_payload_len += QDF_MAC_ADDR_SIZE; } /* Check Beacon Interval present bit */ if (QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_BCNINTP_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_BCNINTP_BITS)) { if (subelempayloadlen < (parsed_payload_len + WLAN_BEACONINTERVAL_LEN)) { mlo_err_rl("Length of subelement payload %zu octets not sufficient to contain Beacon Interval of size %u octets after parsed payload length of %zu octets.", subelempayloadlen, WLAN_BEACONINTERVAL_LEN, parsed_payload_len); return QDF_STATUS_E_PROTO; } if (beaconinterval) { qdf_mem_copy(beaconinterval, subelempayload + parsed_payload_len, WLAN_BEACONINTERVAL_LEN); *beaconinterval = qdf_le16_to_cpu(*beaconinterval); if (is_beaconinterval_valid) *is_beaconinterval_valid = true; } parsed_payload_len += WLAN_BEACONINTERVAL_LEN; } /* Check TSF Offset present bit */ if (QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_TSFOFFSETP_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_TSFOFFSETP_BITS)) { if (!completeprofile) { mlo_err_rl("TSF offset is expected only for complete profiles"); return QDF_STATUS_E_PROTO; } if (subelempayloadlen < (parsed_payload_len + WLAN_ML_TSF_OFFSET_SIZE)) { mlo_err_rl("Length of subelement payload %zu octets not sufficient to contain TSF Offset of size %u octets after parsed payload length of %zu octets.", subelempayloadlen, WLAN_ML_TSF_OFFSET_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } if (tsfoffset) { qdf_mem_copy(tsfoffset, subelempayload + parsed_payload_len, WLAN_TIMESTAMP_LEN); *tsfoffset = qdf_le64_to_cpu(*tsfoffset); if (is_tsfoffset_valid) *is_tsfoffset_valid = true; } parsed_payload_len += WLAN_ML_TSF_OFFSET_SIZE; } /* Check DTIM Info present bit */ if (QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_DTIMINFOP_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_DTIMINFOP_BITS)) { if (subelempayloadlen < (parsed_payload_len + sizeof(struct wlan_ml_bv_linfo_perstaprof_stainfo_dtiminfo))) { mlo_err_rl("Length of subelement payload %zu octets not sufficient to contain DTIM Info of size %zu octets after parsed payload length of %zu octets.", subelempayloadlen, sizeof(struct wlan_ml_bv_linfo_perstaprof_stainfo_dtiminfo), parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += sizeof(struct wlan_ml_bv_linfo_perstaprof_stainfo_dtiminfo); } /* Check NTSR Link pair present bit */ nstrlppresent = QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRLINKPRP_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRLINKPRP_BITS); if (completeprofile && nstrlppresent) { /* Check NTSR Bitmap Size bit */ nstrbmsz = QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_BITS); if (nstrbmsz == WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_1_OCTET) { if (subelempayloadlen < (parsed_payload_len + 1)) { mlo_err_rl("Length of subelement payload %zu octets not sufficient to contain NTSR Bitmap of size 1 octet after parsed payload length of %zu octets.", subelempayloadlen, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += 1; } else if (nstrbmsz == WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_2_OCTETS) { if (subelempayloadlen < (parsed_payload_len + 2)) { mlo_err_rl("Length of subelement payload %zu octets not sufficient to contain NTSR Bitmap of size 2 octets after parsed payload length of %zu octets.", subelempayloadlen, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += 2; } else { /* Though an invalid value cannot occur if only 1 bit is * used, we check for it in a generic manner in case the * number of bits is increased in the future. */ mlo_err_rl("Invalid NSTR Bitmap size %u", nstrbmsz); return QDF_STATUS_E_PROTO; } } /* Check BSS Parameters Change Count Present bit */ if (QDF_GET_BITS(stacontrol, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_BSSPARAMCHNGCNTP_IDX, WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_BSSPARAMCHNGCNTP_BITS)) { if (subelempayloadlen < (parsed_payload_len + WLAN_ML_BSSPARAMCHNGCNT_SIZE)) { mlo_err_rl("Length of subelement payload %zu octets not sufficient to contain BSS Parameters Change Count of size %u octets after parsed payload length of %zu octets.", subelempayloadlen, WLAN_ML_BSSPARAMCHNGCNT_SIZE, parsed_payload_len); return QDF_STATUS_E_PROTO; } parsed_payload_len += WLAN_ML_BSSPARAMCHNGCNT_SIZE; } /* Note: Some implementation versions of hostapd/wpa_supplicant may * provide a per-STA profile without STA profile. Let the caller * indicate whether a STA profile is required to be found. This may be * revisited as upstreaming progresses. */ if (!is_staprof_reqd) return QDF_STATUS_SUCCESS; if (subelempayloadlen == parsed_payload_len) { mlo_err_rl("Subelement payload length %zu == parsed payload length %zu. Unable to get STA profile.", subelempayloadlen, parsed_payload_len); return QDF_STATUS_E_PROTO; } if (staprof_len) *staprof_len = subelempayloadlen - parsed_payload_len; if (staprof) *staprof = subelempayload + parsed_payload_len; return QDF_STATUS_SUCCESS; } static QDF_STATUS util_parse_prvmlie_perstaprofile_stactrl(uint8_t *subelempayload, qdf_size_t subelempayloadlen, uint8_t *linkid, bool is_staprof_reqd, uint8_t **staprof, qdf_size_t *staprof_len) { qdf_size_t parsed_payload_len = 0; uint16_t stacontrol; uint8_t completeprofile; /* This helper returns the location(s) and where required, the length(s) * of (sub)field(s) inferable after parsing the STA Control field in the * per-STA profile subelement. These location(s) and length(s) is/are in * reference to the payload section of the per-STA profile subelement * (after defragmentation, if applicable). Here, the payload is the * point after the subelement length in the subelement, and includes the * payloads of all subsequent fragments (if any) but not the headers of * those fragments. * * Currently, the helper returns the link ID, MAC address, and STA * profile. More (sub)fields can be added when required. */ if (!subelempayload) { mlo_err("Pointer to subelement payload is NULL"); return QDF_STATUS_E_NULL_VALUE; } if (!subelempayloadlen) { mlo_err("Length of subelement payload is zero"); return QDF_STATUS_E_INVAL; } if (subelempayloadlen < WLAN_ML_PRV_LINFO_PERSTAPROF_STACTRL_SIZE) { mlo_err_rl("Subelement payload length %zu octets is smaller than STA control field of per-STA profile subelement %u octets", subelempayloadlen, WLAN_ML_PRV_LINFO_PERSTAPROF_STACTRL_SIZE); return QDF_STATUS_E_PROTO; } parsed_payload_len = 0; qdf_mem_copy(&stacontrol, subelempayload, WLAN_ML_PRV_LINFO_PERSTAPROF_STACTRL_SIZE); stacontrol = qdf_le16_to_cpu(stacontrol); parsed_payload_len += WLAN_ML_PRV_LINFO_PERSTAPROF_STACTRL_SIZE; if (linkid) { *linkid = QDF_GET_BITS(stacontrol, WLAN_ML_PRV_LINFO_PERSTAPROF_STACTRL_LINKID_IDX, WLAN_ML_PRV_LINFO_PERSTAPROF_STACTRL_LINKID_BITS); } /* Check if this a complete profile */ completeprofile = QDF_GET_BITS(stacontrol, WLAN_ML_PRV_LINFO_PERSTAPROF_STACTRL_CMPLTPROF_IDX, WLAN_ML_PRV_LINFO_PERSTAPROF_STACTRL_CMPLTPROF_BITS); /* Note: Some implementation versions of hostapd/wpa_supplicant may * provide a per-STA profile without STA profile. Let the caller * indicate whether a STA profile is required to be found. This may be * revisited as upstreaming progresses. */ if (!is_staprof_reqd) return QDF_STATUS_SUCCESS; if (subelempayloadlen == parsed_payload_len) { mlo_err_rl("Subelement payload length %zu == parsed payload length %zu. Unable to get STA profile.", subelempayloadlen, parsed_payload_len); return QDF_STATUS_E_PROTO; } if (staprof_len) *staprof_len = subelempayloadlen - parsed_payload_len; if (staprof) *staprof = subelempayload + parsed_payload_len; return QDF_STATUS_SUCCESS; } static uint8_t *util_get_successorfrag(uint8_t *currie, uint8_t *frame, qdf_size_t len) { uint8_t *nextie; if (!currie || !frame || !len) return NULL; if ((currie + MIN_IE_LEN) > (frame + len)) return NULL; /* Check whether there is sufficient space in the frame for the current * IE, plus at least another MIN_IE_LEN bytes for the IE header of a * fragment (if present) that would come just after the current IE. */ if ((currie + MIN_IE_LEN + currie[TAG_LEN_POS] + MIN_IE_LEN) > (frame + len)) return NULL; nextie = currie + currie[TAG_LEN_POS] + MIN_IE_LEN; /* Check whether there is sufficient space in the frame for the next IE */ if ((nextie + MIN_IE_LEN + nextie[TAG_LEN_POS]) > (frame + len)) return NULL; if (nextie[ID_POS] != WLAN_ELEMID_FRAGMENT) return NULL; return nextie; } static QDF_STATUS util_parse_partner_info_from_linkinfo(uint8_t *linkinfo, qdf_size_t linkinfo_len, struct mlo_partner_info *partner_info) { uint8_t linkid; struct qdf_mac_addr macaddr; bool is_macaddr_valid; uint8_t *linkinfo_currpos; qdf_size_t linkinfo_remlen; bool is_subelemfragseq; uint8_t subelemid; qdf_size_t subelemseqtotallen; qdf_size_t subelemseqpayloadlen; qdf_size_t defragpayload_len; QDF_STATUS ret; /* This helper function parses partner info from the per-STA profiles * present (if any) in the Link Info field in the payload of a Multi * Link element (after defragmentation if required). The caller should * pass a copy of the payload so that inline defragmentation of * subelements can be carried out if required. The subelement * defragmentation (if applicable) in this Control Path helper is * required for maintainability, accuracy and eliminating current and * future per-field-access multi-level fragment boundary checks and * adjustments, given the complex format of Multi Link elements. It is * also most likely to be required mainly at the client side. */ if (!linkinfo) { mlo_err("linkinfo is NULL"); return QDF_STATUS_E_NULL_VALUE; } if (!linkinfo_len) { mlo_err("linkinfo_len is zero"); return QDF_STATUS_E_NULL_VALUE; } if (!partner_info) { mlo_err("ML partner info is NULL"); return QDF_STATUS_E_NULL_VALUE; } partner_info->num_partner_links = 0; linkinfo_currpos = linkinfo; linkinfo_remlen = linkinfo_len; while (linkinfo_remlen) { if (linkinfo_remlen < sizeof(struct subelem_header)) { mlo_err_rl("Remaining length in link info %zu octets is smaller than subelement header length %zu octets", linkinfo_remlen, sizeof(struct subelem_header)); return QDF_STATUS_E_PROTO; } subelemid = linkinfo_currpos[ID_POS]; is_subelemfragseq = false; subelemseqtotallen = 0; subelemseqpayloadlen = 0; ret = wlan_get_subelem_fragseq_info(WLAN_ML_LINFO_SUBELEMID_FRAGMENT, linkinfo_currpos, linkinfo_remlen, &is_subelemfragseq, &subelemseqtotallen, &subelemseqpayloadlen); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (is_subelemfragseq) { if (!subelemseqpayloadlen) { mlo_err_rl("Subelement fragment sequence payload is reported as 0, investigate"); return QDF_STATUS_E_FAILURE; } mlo_debug("Subelement fragment sequence found with payload len %zu", subelemseqpayloadlen); ret = wlan_defrag_subelem_fragseq(true, WLAN_ML_LINFO_SUBELEMID_FRAGMENT, linkinfo_currpos, linkinfo_remlen, NULL, 0, &defragpayload_len); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (defragpayload_len != subelemseqpayloadlen) { mlo_err_rl("Length of defragmented payload %zu octets is not equal to length of subelement fragment sequence payload %zu octets", defragpayload_len, subelemseqpayloadlen); return QDF_STATUS_E_FAILURE; } /* Adjust linkinfo_remlen to reflect removal of all * subelement headers except the header of the lead * subelement. */ linkinfo_remlen -= (subelemseqtotallen - subelemseqpayloadlen - sizeof(struct subelem_header)); } else { if (linkinfo_remlen < (sizeof(struct subelem_header) + linkinfo_currpos[TAG_LEN_POS])) { mlo_err_rl("Remaining length in link info %zu octets is smaller than total size of current subelement %zu octets", linkinfo_remlen, sizeof(struct subelem_header) + linkinfo_currpos[TAG_LEN_POS]); return QDF_STATUS_E_PROTO; } subelemseqpayloadlen = linkinfo_currpos[TAG_LEN_POS]; } if (subelemid == WLAN_ML_LINFO_SUBELEMID_PERSTAPROFILE) { is_macaddr_valid = false; ret = util_parse_bvmlie_perstaprofile_stactrl(linkinfo_currpos + sizeof(struct subelem_header), subelemseqpayloadlen, &linkid, NULL, NULL, NULL, NULL, NULL, &is_macaddr_valid, &macaddr, false, NULL, NULL); if (QDF_IS_STATUS_ERROR(ret)) { return ret; } if (is_macaddr_valid) { if (partner_info->num_partner_links >= QDF_ARRAY_SIZE(partner_info->partner_link_info)) { mlo_err_rl("Insufficient size %zu of array for partner link info", QDF_ARRAY_SIZE(partner_info->partner_link_info)); return QDF_STATUS_E_NOMEM; } partner_info->partner_link_info[partner_info->num_partner_links].link_id = linkid; qdf_mem_copy(&partner_info->partner_link_info[partner_info->num_partner_links].link_addr, &macaddr, sizeof(partner_info->partner_link_info[partner_info->num_partner_links].link_addr)); partner_info->num_partner_links++; } else { mlo_warn_rl("MAC address not found in STA Info field of per-STA profile with link ID %u", linkid); } } linkinfo_remlen -= (sizeof(struct subelem_header) + subelemseqpayloadlen); linkinfo_currpos += (sizeof(struct subelem_header) + subelemseqpayloadlen); } mlo_debug("Number of ML partner links found=%u", partner_info->num_partner_links); return QDF_STATUS_SUCCESS; } static QDF_STATUS util_parse_probereq_info_from_linkinfo(uint8_t *linkinfo, qdf_size_t linkinfo_len, struct mlo_probereq_info *probereq_info) { uint8_t linkid; uint8_t *linkinfo_currpos; qdf_size_t linkinfo_remlen; bool is_subelemfragseq; uint8_t subelemid; qdf_size_t subelemseqtotallen; qdf_size_t subelemseqpayloadlen; qdf_size_t defragpayload_len; QDF_STATUS ret; /* This helper function parses probe request info from the per-STA prof * present (if any) in the Link Info field in the payload of a Multi * Link element (after defragmentation if required). The caller should * pass a copy of the payload so that inline defragmentation of * subelements can be carried out if required. The subelement * defragmentation (if applicable) in this Control Path helper is * required for maintainability, accuracy and eliminating current and * future per-field-access multi-level fragment boundary checks and * adjustments, given the complex format of Multi Link elements. It is * also most likely to be required mainly at the client side. */ if (!linkinfo) { mlo_err("linkinfo is NULL"); return QDF_STATUS_E_NULL_VALUE; } if (!linkinfo_len) { mlo_err("linkinfo_len is zero"); return QDF_STATUS_E_NULL_VALUE; } if (!probereq_info) { mlo_err("ML probe req info is NULL"); return QDF_STATUS_E_NULL_VALUE; } probereq_info->num_links = 0; linkinfo_currpos = linkinfo; linkinfo_remlen = linkinfo_len; while (linkinfo_remlen) { if (linkinfo_remlen < sizeof(struct subelem_header)) { mlo_err_rl("Remaining length in link info %zu octets is smaller than subelement header length %zu octets", linkinfo_remlen, sizeof(struct subelem_header)); return QDF_STATUS_E_PROTO; } subelemid = linkinfo_currpos[ID_POS]; is_subelemfragseq = false; subelemseqtotallen = 0; subelemseqpayloadlen = 0; ret = wlan_get_subelem_fragseq_info(WLAN_ML_LINFO_SUBELEMID_FRAGMENT, linkinfo_currpos, linkinfo_remlen, &is_subelemfragseq, &subelemseqtotallen, &subelemseqpayloadlen); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (is_subelemfragseq) { if (!subelemseqpayloadlen) { mlo_err_rl("Subelement fragment sequence payload is reported as 0, investigate"); return QDF_STATUS_E_FAILURE; } mlo_debug("Subelement fragment sequence found with payload len %zu", subelemseqpayloadlen); ret = wlan_defrag_subelem_fragseq(true, WLAN_ML_LINFO_SUBELEMID_FRAGMENT, linkinfo_currpos, linkinfo_remlen, NULL, 0, &defragpayload_len); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (defragpayload_len != subelemseqpayloadlen) { mlo_err_rl("Length of defragmented payload %zu octets is not equal to length of subelement fragment sequence payload %zu octets", defragpayload_len, subelemseqpayloadlen); return QDF_STATUS_E_FAILURE; } /* Adjust linkinfo_remlen to reflect removal of all * subelement headers except the header of the lead * subelement. */ linkinfo_remlen -= (subelemseqtotallen - subelemseqpayloadlen - sizeof(struct subelem_header)); } else { if (linkinfo_remlen < (sizeof(struct subelem_header) + linkinfo_currpos[TAG_LEN_POS])) { mlo_err_rl("Remaining length in link info %zu octets is smaller than total size of current subelement %zu octets", linkinfo_remlen, sizeof(struct subelem_header) + linkinfo_currpos[TAG_LEN_POS]); return QDF_STATUS_E_PROTO; } subelemseqpayloadlen = linkinfo_currpos[TAG_LEN_POS]; } if (subelemid == WLAN_ML_LINFO_SUBELEMID_PERSTAPROFILE) { ret = util_parse_prvmlie_perstaprofile_stactrl(linkinfo_currpos + sizeof(struct subelem_header), subelemseqpayloadlen, &linkid, false, NULL, NULL); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (probereq_info->num_links >= QDF_ARRAY_SIZE(probereq_info->link_id)) { mlo_err_rl("Insufficient size %zu of array for probe req link id", QDF_ARRAY_SIZE(probereq_info->link_id)); return QDF_STATUS_E_NOMEM; } probereq_info->link_id[probereq_info->num_links] = linkid; probereq_info->num_links++; } linkinfo_remlen -= (sizeof(struct subelem_header) + subelemseqpayloadlen); linkinfo_currpos += (sizeof(struct subelem_header) + subelemseqpayloadlen); } mlo_debug("Number of ML probe request links found=%u", probereq_info->num_links); return QDF_STATUS_SUCCESS; } 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 functionality 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_elemextlist_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 functionality 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) { qdf_size_t reportingsta_ie_size; if (!reportingsta_ie) { mlo_err("Pointer to reporting STA IE is NULL"); return QDF_STATUS_E_NULL_VALUE; } 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[ID_POS] == WLAN_ELEMID_VENDOR) && (reportingsta_ie_size < (PAYLOAD_START_POS + OUI_LEN))) { mlo_err_rl("Total length %zu of element for reporting STA is smaller than minimum required to access vendor EID %u", reportingsta_ie_size, PAYLOAD_START_POS + OUI_LEN); 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; } #ifdef CONN_MGR_ADV_FEATURE /** * util_add_mlie_for_prb_rsp_gen - Add the basic variant Multi-Link element * when generating link specific probe response. * @reportingsta_ie: Pointer to the reportingsta ie * @reportingsta_ie_len: Length for reporting sta ie * @plink_frame_currpos: Pointer to Link frame current pos * @plink_frame_currlen: Current length of link frame. * @linkid: Link Id value * * Add the basic variant Multi-Link element when * generating link specific probe response. * * Return: QDF_STATUS_SUCCESS in the case of success, QDF_STATUS value giving * the reason for error in the case of failure */ static QDF_STATUS util_add_mlie_for_prb_rsp_gen(const uint8_t *reportingsta_ie, qdf_size_t reportingsta_ie_len, uint8_t **plink_frame_currpos, qdf_size_t *plink_frame_currlen, uint8_t linkid) { uint8_t mlie_len = 0; uint8_t common_info_len = 0; struct wlan_ie_multilink ml_ie_ff; uint16_t mlcontrol; uint16_t presencebm; uint8_t *mlie_frame = NULL; uint8_t link_id_offset = sizeof(struct wlan_ie_multilink) + QDF_MAC_ADDR_SIZE + WLAN_ML_BV_CINFO_LENGTH_SIZE; uint8_t *link_frame_currpos = *plink_frame_currpos; qdf_size_t link_frame_currlen = *plink_frame_currlen; QDF_STATUS status = QDF_STATUS_SUCCESS; status = util_get_mlie_common_info_len((uint8_t *)reportingsta_ie, reportingsta_ie_len, &common_info_len); if (QDF_IS_STATUS_ERROR(status)) { mlo_err("Failed while parsing the common info length"); return status; } /* common info len + bvmlie fixed fields */ mlie_len = common_info_len + sizeof(struct wlan_ie_multilink); mlo_debug_rl("mlie_len %d, common_info_len %d, link_id_offset %d", mlie_len, common_info_len, link_id_offset); mlie_frame = qdf_mem_malloc(mlie_len); if (!mlie_frame) return QDF_STATUS_E_NOMEM; /* Copy ml ie fixed fields */ qdf_mem_copy(&ml_ie_ff, reportingsta_ie, sizeof(struct wlan_ie_multilink)); ml_ie_ff.elem_len = mlie_len - sizeof(struct ie_header); mlcontrol = qdf_le16_to_cpu(ml_ie_ff.mlcontrol); presencebm = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); qdf_set_bit(WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P, (unsigned long *)&presencebm); QDF_SET_BITS(ml_ie_ff.mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS, presencebm); qdf_mem_copy(mlie_frame, &ml_ie_ff, sizeof(struct wlan_ie_multilink)); qdf_mem_copy(mlie_frame + sizeof(struct wlan_ie_multilink), reportingsta_ie + sizeof(struct wlan_ie_multilink), mlie_len - sizeof(struct wlan_ie_multilink)); if (linkid == 0xFF) { qdf_mem_free(mlie_frame); mlo_err("Link id is invalid"); return QDF_STATUS_E_INVAL; } mlie_frame[link_id_offset] = (mlie_frame[link_id_offset] & ~0x0f) | (linkid & 0x0f); qdf_mem_copy(link_frame_currpos, mlie_frame, mlie_len); mlo_debug("Add mlie for link id %d", linkid); QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_PE, QDF_TRACE_LEVEL_DEBUG, mlie_frame, mlie_len); link_frame_currpos += mlie_len; link_frame_currlen += mlie_len; *plink_frame_currpos = link_frame_currpos; *plink_frame_currlen = link_frame_currlen; qdf_mem_free(mlie_frame); return QDF_STATUS_SUCCESS; } #else static QDF_STATUS util_add_mlie_for_prb_rsp_gen(const uint8_t *reportingsta_ie, qdf_size_t reportingsta_ie_len, uint8_t **plink_frame_currpos, qdf_size_t *plink_frame_currlen, uint8_t linkid) { return QDF_STATUS_SUCCESS; } #endif #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 #define MLO_LINKSPECIFIC_PROBE_RESP_FC0 0x50 #define MLO_LINKSPECIFIC_PROBE_RESP_FC1 0x00 static QDF_STATUS util_gen_link_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/Multi-Link element fragment sequence */ uint8_t *mlieseq; /* Total length of Multi-Link element sequence (including fragments if * any) */ qdf_size_t mlieseqlen; /* Variant (i.e. type) of the Multi-Link element */ enum wlan_ml_variant variant; /* Length of the payload of the Multi-Link element (inclusive of * fragment payloads if any) without IE headers and element ID extension */ qdf_size_t mlieseqpayloadlen; /* Pointer to copy of the payload of the Multi-Link element (inclusive * of fragment payloads if any) without IE headers and element ID * extension */ uint8_t *mlieseqpayload_copy; /* Pointer to start of Link Info within the copy of the payload of the * Multi-Link element */ uint8_t *link_info; /* 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; /* Pointer to per-STA profile */ uint8_t *persta_prof; /* Length of the containing buffer which starts with the per-STA profile */ qdf_size_t persta_prof_bufflen; /* Other variables for temporary purposes */ /* Variable into which API for determining fragment information will * indicate whether the element is the start of a fragment sequence or * not. */ bool is_elemfragseq; /* De-fragmented payload length returned by API for element * defragmentation. */ qdf_size_t defragpayload_len; /* Variable into which API for determining fragment information will * indicate whether the subelement is the start of a fragment sequence * or not. */ bool is_subelemfragseq; /* Total length of the subelement fragment sequence, inclusive of * subelement header and the headers of fragments if any. */ qdf_size_t subelemseqtotallen; /* Total length of the subelement fragment sequence payload, excluding * subelement header and fragment headers if any. */ qdf_size_t subelemseqpayloadlen; /* Pointer to Beacon interval in STA info field */ uint16_t beaconinterval; /* Whether Beacon interval value valid */ bool is_beaconinterval_valid; /* TSF timer of the reporting AP */ uint64_t tsf; /* TSF offset of the reproted AP */ uint64_t tsfoffset; /* TSF offset value valid */ bool is_tsfoffset_valid; /* If Complete Profile or not*/ bool is_completeprofile; qdf_size_t tmplen; QDF_STATUS ret; uint8_t linkid = 0xFF; 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) && (subtype != WLAN_FC0_STYPE_PROBE_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 if (subtype == WLAN_FC0_STYPE_PROBE_RESP) { frame_iesection_offset = WLAN_PROBE_RESP_IES_OFFSET; qdf_mem_copy(&tsf, frame, WLAN_TIMESTAMP_LEN); tsf = qdf_le64_to_cpu(tsf); } 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; mlieseq = NULL; mlieseqlen = 0; ret = util_find_mlie(frame_iesection, frame_iesection_len, &mlieseq, &mlieseqlen); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (!mlieseq) { /* 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; } if (mlieseqlen < sizeof(struct wlan_ie_multilink)) { mlo_err_rl("Multi-Link element sequence length %zu octets is smaller than required for the fixed portion of Multi-Link element (%zu octets)", mlieseqlen, sizeof(struct wlan_ie_multilink)); return QDF_STATUS_E_PROTO; } ret = util_get_mlie_variant(mlieseq, 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; } mlieseqpayloadlen = 0; tmplen = 0; is_elemfragseq = false; ret = wlan_get_elem_fragseq_info(mlieseq, mlieseqlen, &is_elemfragseq, &tmplen, &mlieseqpayloadlen); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (is_elemfragseq) { if (tmplen != mlieseqlen) { mlo_err_rl("Mismatch in values of element fragment sequence total length. Val per frag info determination: %zu octets, val per Multi-Link element search: %zu octets", tmplen, mlieseqlen); return QDF_STATUS_E_FAILURE; } if (!mlieseqpayloadlen) { mlo_err_rl("Multi-Link element fragment sequence payload is reported as 0, investigate"); return QDF_STATUS_E_FAILURE; } mlo_debug("Multi-Link element fragment sequence found with payload len %zu", mlieseqpayloadlen); } else { if (mlieseqlen > (sizeof(struct ie_header) + WLAN_MAX_IE_LEN)) { mlo_err_rl("Expected presence of valid fragment sequence since Multi-Link element sequence length %zu octets is larger than frag threshold of %zu octets, however no valid fragment sequence found", mlieseqlen, sizeof(struct ie_header) + WLAN_MAX_IE_LEN); return QDF_STATUS_E_FAILURE; } mlieseqpayloadlen = mlieseqlen - (sizeof(struct ie_header) + 1); } mlieseqpayload_copy = qdf_mem_malloc(mlieseqpayloadlen); if (!mlieseqpayload_copy) { mlo_err_rl("Could not allocate memory for Multi-Link element payload copy"); return QDF_STATUS_E_NOMEM; } if (is_elemfragseq) { ret = wlan_defrag_elem_fragseq(false, mlieseq, mlieseqlen, mlieseqpayload_copy, mlieseqpayloadlen, &defragpayload_len); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } if (defragpayload_len != mlieseqpayloadlen) { mlo_err_rl("Length of de-fragmented payload %zu octets is not equal to length of Multi-Link element fragment sequence payload %zu octets", defragpayload_len, mlieseqpayloadlen); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_FAILURE; } } else { qdf_mem_copy(mlieseqpayload_copy, mlieseq + sizeof(struct ie_header) + 1, mlieseqpayloadlen); } link_info = NULL; link_info_len = 0; ret = util_parse_multi_link_ctrl(mlieseqpayload_copy, mlieseqpayloadlen, &link_info, &link_info_len); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } /* 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 (!link_info) { mlo_err_rl("Unable to successfully obtain Link Info"); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_PROTO; } mlo_debug("Dumping hex of link info after parsing Multi-Link element control"); QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_MLO, QDF_TRACE_LEVEL_DEBUG, link_info, link_info_len); /* 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. */ persta_prof = link_info; persta_prof_bufflen = link_info_len; is_subelemfragseq = false; subelemseqtotallen = 0; subelemseqpayloadlen = 0; ret = wlan_get_subelem_fragseq_info(WLAN_ML_LINFO_SUBELEMID_FRAGMENT, persta_prof, persta_prof_bufflen, &is_subelemfragseq, &subelemseqtotallen, &subelemseqpayloadlen); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } if (is_subelemfragseq) { if (!subelemseqpayloadlen) { mlo_err_rl("Subelement fragment sequence payload is reported as 0, investigate"); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_FAILURE; } mlo_debug("Subelement fragment sequence found with payload len %zu", subelemseqpayloadlen); ret = wlan_defrag_subelem_fragseq(true, WLAN_ML_LINFO_SUBELEMID_FRAGMENT, persta_prof, persta_prof_bufflen, NULL, 0, &defragpayload_len); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } if (defragpayload_len != subelemseqpayloadlen) { mlo_err_rl("Length of defragmented payload %zu octets is not equal to length of subelement fragment sequence payload %zu octets", defragpayload_len, subelemseqpayloadlen); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_FAILURE; } } else { if (persta_prof_bufflen < (sizeof(struct subelem_header) + persta_prof[TAG_LEN_POS])) { mlo_err_rl("Length of buffer containing per-STA profile %zu octets is smaller than total size of current subelement %zu octets", persta_prof_bufflen, sizeof(struct subelem_header) + persta_prof[TAG_LEN_POS]); return QDF_STATUS_E_PROTO; } subelemseqpayloadlen = persta_prof[TAG_LEN_POS]; } sta_prof_remlen = 0; sta_prof_currpos = NULL; is_reportedmacaddr_valid = false; is_beaconinterval_valid = false; is_completeprofile = false; is_tsfoffset_valid = false; /* Parse per-STA profile */ ret = util_parse_bvmlie_perstaprofile_stactrl(persta_prof + sizeof(struct subelem_header), subelemseqpayloadlen, &linkid, &beaconinterval, &is_beaconinterval_valid, &tsfoffset, &is_tsfoffset_valid, &is_completeprofile, &is_reportedmacaddr_valid, &reportedmacaddr, true, &sta_prof_currpos, &sta_prof_remlen); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } if (subtype == WLAN_FC0_STYPE_PROBE_RESP && !is_completeprofile) { mlo_err("Complete profile information is not present in per-STA profile of probe response frame"); return QDF_STATUS_E_NOSUPPORT; } /* We double check for a NULL STA Profile, though the helper function * above would have taken care of this. We need to get a non-NULL STA * profile, 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("STA profile is NULL"); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_PROTO; } /* 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(mlieseqpayload_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("Insufficient 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(mlieseqpayload_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(mlieseqpayload_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("Insufficient 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(mlieseqpayload_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 Capability 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("Insufficient 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(mlieseqpayload_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("Insufficient 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(mlieseqpayload_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 if (subtype == WLAN_FC0_STYPE_ASSOC_RESP || subtype == WLAN_FC0_STYPE_REASSOC_RESP) { /* 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(mlieseqpayload_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("Insufficient 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(mlieseqpayload_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("Insufficient 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(mlieseqpayload_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); } else if (subtype == WLAN_FC0_STYPE_PROBE_RESP) { /* This is a probe response */ mlo_debug("Populating fixed fields for probe response in link specific frame"); if ((link_frame_maxsize - link_frame_currlen) < WLAN_TIMESTAMP_LEN) { mlo_err("Insufficient space in link specific frame for Timestamp Info field. Required: %u octets, available: %zu octets", WLAN_TIMESTAMP_LEN, (link_frame_maxsize - link_frame_currlen)); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_NOMEM; } /* Per spec 11be_D2.1.1, the TSF Offset subfield of the STA Info * field indicates the offset (Toffset)between the TSF timer of * the reported AP (TA) and the TSF timer of the reporting * AP (TB) and is encoded as a 2s complement signed integer * with units of 2 µs. Toffset is calculated as * Toffset= Floor((TA – TB)/2). */ if (is_tsfoffset_valid) tsf += tsfoffset * 2; qdf_mem_copy(link_frame_currpos, &tsf, WLAN_TIMESTAMP_LEN); link_frame_currpos += WLAN_TIMESTAMP_LEN; link_frame_currlen += WLAN_TIMESTAMP_LEN; mlo_debug("Added Timestamp Info field (%u octets) to link specific frame", WLAN_TIMESTAMP_LEN); if (!is_beaconinterval_valid) { mlo_err_rl("Beacon interval information not present in STA info field of per-STA profile"); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_PROTO; } /* Beacon Interval information copy this from * the STA info field. */ if ((link_frame_maxsize - link_frame_currlen) < WLAN_BEACONINTERVAL_LEN) { mlo_err("Insufficient space in link specific frame for Beacon Interval Info field. Required: %u octets, available: %zu octets", WLAN_BEACONINTERVAL_LEN, (link_frame_maxsize - link_frame_currlen)); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_NOMEM; } qdf_mem_copy(link_frame_currpos, &beaconinterval, WLAN_BEACONINTERVAL_LEN); link_frame_currpos += WLAN_BEACONINTERVAL_LEN; link_frame_currlen += WLAN_BEACONINTERVAL_LEN; mlo_debug("Added Beacon Interval Info field (%u octets) to link specific frame", WLAN_BEACONINTERVAL_LEN); 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(mlieseqpayload_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("Insufficient 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(mlieseqpayload_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 Capability Info field (%u octets) to link specific frame", WLAN_CAPABILITYINFO_LEN); sta_prof_currpos += WLAN_CAPABILITYINFO_LEN; sta_prof_remlen -= WLAN_CAPABILITYINFO_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(mlieseqpayload_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) || (subtype == WLAN_FC0_STYPE_PROBE_RESP)) { /* 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 in reporting STA of the frame."); qdf_mem_free(mlieseqpayload_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(mlieseqpayload_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(mlieseqpayload_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(mlieseqpayload_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; /* Add BV ML IE for link specific probe response */ if (subtype == WLAN_FC0_STYPE_PROBE_RESP) { ret = util_add_mlie_for_prb_rsp_gen(reportingsta_ie, reportingsta_ie[TAG_LEN_POS], &link_frame_currpos, &link_frame_currlen, linkid); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } } 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(mlieseqpayload_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(mlieseqpayload_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("Insufficient 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("Insufficient 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(mlieseqpayload_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 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. */ 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(mlieseqpayload_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 Vendor IE also presents in STA profile, * then ignore the Vendor IE which is for * reporting STA. It only needs to copy Vendor * IE from STA profile to link specific frame. * The copy happens when going through the * remaining IEs. */ ; } 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 (sta_prof_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) { mlo_err_rl("Insufficient 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("Insufficient 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(mlieseqpayload_copy); return QDF_STATUS_E_NOMEM; } } } 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(mlieseqpayload_copy); return ret; } reportingsta_ie_size = reportingsta_ie[TAG_LEN_POS] + MIN_IE_LEN; } /* 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(mlieseqpayload_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("Insufficient 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("Insufficient 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(mlieseqpayload_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 if (subtype == WLAN_FC0_STYPE_PROBE_RESP) { 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_PROBE_RESP_FC0; link_frame_hdr->i_fc[1] = MLO_LINKSPECIFIC_PROBE_RESP_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; } mlo_debug("subtype:%u addr3:" QDF_MAC_ADDR_FMT " addr2:" QDF_MAC_ADDR_FMT " addr1:" QDF_MAC_ADDR_FMT, subtype, QDF_MAC_ADDR_REF(link_frame_hdr->i_addr3), QDF_MAC_ADDR_REF(link_frame_hdr->i_addr2), QDF_MAC_ADDR_REF(link_frame_hdr->i_addr1)); /* Seq num not used so not populated */ qdf_mem_free(mlieseqpayload_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_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_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_gen_link_probe_rsp(uint8_t *frame, qdf_size_t frame_len, 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_reqrsp_cmn(frame, frame_len, WLAN_FC0_STYPE_PROBE_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) { uint8_t *bufboundary; uint8_t *ieseq; qdf_size_t ieseqlen; uint8_t *currie; uint8_t *successorfrag; if (!buf || !buflen || !mlieseq || !mlieseqlen) return QDF_STATUS_E_NULL_VALUE; *mlieseq = NULL; *mlieseqlen = 0; /* Find Multi-Link element. In case a fragment sequence is present, * this element will be the leading fragment. */ ieseq = util_find_extn_eid(WLAN_ELEMID_EXTN_ELEM, WLAN_EXTN_ELEMID_MULTI_LINK, buf, buflen); /* Even if the element is not found, we have successfully examined the * buffer. The caller will be provided a NULL value for the starting of * the Multi-Link element. Hence, we return success. */ if (!ieseq) return QDF_STATUS_SUCCESS; bufboundary = buf + buflen; if ((ieseq + MIN_IE_LEN) > bufboundary) return QDF_STATUS_E_INVAL; ieseqlen = MIN_IE_LEN + ieseq[TAG_LEN_POS]; if (ieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_PROTO; if ((ieseq + ieseqlen) > bufboundary) return QDF_STATUS_E_INVAL; /* In the next sequence of checks, if there is no space in the buffer * for another element after the Multi-Link element/element fragment * sequence, it could indicate an issue since non-MLO EHT elements * would be expected to follow the Multi-Link element/element fragment * sequence. However, this is outside of the purview of this function, * hence we ignore it. */ currie = ieseq; successorfrag = util_get_successorfrag(currie, buf, buflen); /* Fragmentation definitions as of IEEE802.11be D1.0 and * IEEE802.11REVme D0.2 are applied. Only the case where Multi-Link * element is present in a buffer from the core frame is considered. * Future changes to fragmentation, cases where the Multi-Link element * is present in a subelement, etc. to be reflected here if applicable * as and when the rules evolve. */ while (successorfrag) { /* We should not be seeing a successor fragment if the length * of the current IE is lesser than the max. */ if (currie[TAG_LEN_POS] != WLAN_MAX_IE_LEN) return QDF_STATUS_E_PROTO; if (successorfrag[TAG_LEN_POS] == 0) return QDF_STATUS_E_PROTO; ieseqlen += (MIN_IE_LEN + successorfrag[TAG_LEN_POS]); currie = successorfrag; successorfrag = util_get_successorfrag(currie, buf, buflen); } *mlieseq = ieseq; *mlieseqlen = ieseqlen; return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_mlie_common_info_len(uint8_t *mlieseq, qdf_size_t mlieseqlen, uint8_t *commoninfo_len) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant variant; uint16_t mlcontrol; if (!mlieseq || !mlieseqlen || !commoninfo_len) return QDF_STATUS_E_NULL_VALUE; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if (mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM || mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK) return QDF_STATUS_E_INVAL; mlcontrol = qdf_le16_to_cpu(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_BASIC) return QDF_STATUS_E_INVAL; /* Common Info starts at mlieseq + sizeof(struct wlan_ie_multilink). * Check if there is sufficient space in the buffer for the Common Info * Length and MLD MAC address. */ if ((sizeof(struct wlan_ie_multilink) + WLAN_ML_BV_CINFO_LENGTH_SIZE + QDF_MAC_ADDR_SIZE) > mlieseqlen) return QDF_STATUS_E_PROTO; *commoninfo_len = *(mlieseq + sizeof(struct wlan_ie_multilink)); return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_bvmlie_bssparamchangecnt(uint8_t *mlieseq, qdf_size_t mlieseqlen, bool *bssparamchangecntfound, uint8_t *bssparamchangecnt) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant variant; uint16_t mlcontrol; uint16_t presencebitmap; uint8_t *commoninfo; qdf_size_t commoninfolen; if (!mlieseq || !mlieseqlen || !bssparamchangecntfound || !bssparamchangecnt) return QDF_STATUS_E_NULL_VALUE; *bssparamchangecntfound = false; *bssparamchangecnt = 0; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if (mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM || mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK) return QDF_STATUS_E_INVAL; mlcontrol = qdf_le16_to_cpu(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_BASIC) return QDF_STATUS_E_NOSUPPORT; presencebitmap = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); commoninfo = mlieseq + sizeof(struct wlan_ie_multilink); commoninfolen = WLAN_ML_BV_CINFO_LENGTH_SIZE; commoninfolen += QDF_MAC_ADDR_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P) { commoninfolen += WLAN_ML_BV_CINFO_LINKIDINFO_SIZE; if ((sizeof(struct wlan_ie_multilink) + commoninfolen) > mlieseqlen) return QDF_STATUS_E_PROTO; } if (presencebitmap & WLAN_ML_BV_CTRL_PBM_BSSPARAMCHANGECNT_P) { *bssparamchangecntfound = true; *bssparamchangecnt = *(commoninfo + commoninfolen); } return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_mlie_variant(uint8_t *mlieseq, qdf_size_t mlieseqlen, int *variant) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant var; uint16_t mlcontrol; if (!mlieseq || !mlieseqlen || !variant) return QDF_STATUS_E_NULL_VALUE; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if ((mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM) || (mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK)) return QDF_STATUS_E_INVAL; mlcontrol = le16toh(mlie_fixed->mlcontrol); var = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (var >= WLAN_ML_VARIANT_INVALIDSTART) return QDF_STATUS_E_PROTO; *variant = var; return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_bvmlie_eml_cap(uint8_t *mlieseq, qdf_size_t mlieseqlen, bool *eml_cap_found, uint16_t *eml_cap) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant variant; uint16_t mlcontrol; uint8_t eml_cap_offset; uint8_t commoninfo_len; uint16_t presencebitmap; if (!mlieseq || !mlieseqlen || !eml_cap_found || !eml_cap) return QDF_STATUS_E_NULL_VALUE; *eml_cap = 0; *eml_cap_found = false; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if ((mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM) || (mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK)) return QDF_STATUS_E_INVAL; mlcontrol = qdf_le16_to_cpu(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_BASIC) return QDF_STATUS_E_INVAL; presencebitmap = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); /* eml_cap_offset stores the offset of EML Capabilities within * Common Info */ eml_cap_offset = WLAN_ML_BV_CINFO_LENGTH_SIZE + QDF_MAC_ADDR_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P) eml_cap_offset += WLAN_ML_BV_CINFO_LINKIDINFO_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_BSSPARAMCHANGECNT_P) eml_cap_offset += WLAN_ML_BSSPARAMCHNGCNT_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_MEDIUMSYNCDELAYINFO_P) eml_cap_offset += WLAN_ML_BV_CINFO_MEDMSYNCDELAYINFO_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_EMLCAP_P) { /* Common Info starts at * mlieseq + sizeof(struct wlan_ie_multilink). * Check if there is sufficient space in the buffer for * the Common Info Length. */ if (mlieseqlen < (sizeof(struct wlan_ie_multilink) + WLAN_ML_BV_CINFO_LENGTH_SIZE)) return QDF_STATUS_E_PROTO; /* Check if the value indicated in the Common Info Length * subfield is sufficient to access the EML capabilities. */ commoninfo_len = *(mlieseq + sizeof(struct wlan_ie_multilink)); if (commoninfo_len < (eml_cap_offset + WLAN_ML_BV_CINFO_EMLCAP_SIZE)) return QDF_STATUS_E_PROTO; /* Common Info starts at mlieseq + sizeof(struct * wlan_ie_multilink). Check if there is sufficient space in * Common Info for the EML capability. */ if (mlieseqlen < (sizeof(struct wlan_ie_multilink) + eml_cap_offset + WLAN_ML_BV_CINFO_EMLCAP_SIZE)) return QDF_STATUS_E_PROTO; *eml_cap_found = true; *eml_cap = qdf_le16_to_cpu(*(uint16_t *)(mlieseq + sizeof(struct wlan_ie_multilink) + eml_cap_offset)); } return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_bvmlie_msd_cap(uint8_t *mlieseq, qdf_size_t mlieseqlen, bool *msd_cap_found, uint16_t *msd_cap) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant variant; uint16_t mlcontrol; uint8_t msd_cap_offset; uint8_t commoninfo_len; uint16_t presencebitmap; if (!mlieseq || !mlieseqlen || !msd_cap_found || !msd_cap) return QDF_STATUS_E_NULL_VALUE; *msd_cap = 0; *msd_cap_found = false; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if ((mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM) || (mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK)) return QDF_STATUS_E_INVAL; mlcontrol = qdf_le16_to_cpu(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_BASIC) return QDF_STATUS_E_INVAL; presencebitmap = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); /* msd_cap_offset stores the offset of MSD capabilities within * Common Info */ msd_cap_offset = WLAN_ML_BV_CINFO_LENGTH_SIZE + QDF_MAC_ADDR_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P) msd_cap_offset += WLAN_ML_BV_CINFO_LINKIDINFO_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_BSSPARAMCHANGECNT_P) msd_cap_offset += WLAN_ML_BSSPARAMCHNGCNT_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_MEDIUMSYNCDELAYINFO_P) { /* Common Info starts at * mlieseq + sizeof(struct wlan_ie_multilink). * Check if there is sufficient space in the buffer for * the Common Info Length. */ if (mlieseqlen < (sizeof(struct wlan_ie_multilink) + WLAN_ML_BV_CINFO_LENGTH_SIZE)) return QDF_STATUS_E_PROTO; /* Check if the value indicated in the Common Info Length * subfield is sufficient to access the MSD capabilities. */ commoninfo_len = *(mlieseq + sizeof(struct wlan_ie_multilink)); if (commoninfo_len < (msd_cap_offset + WLAN_ML_BV_CINFO_MEDMSYNCDELAYINFO_SIZE)) return QDF_STATUS_E_PROTO; /* Common Info starts at mlieseq + sizeof(struct * wlan_ie_multilink). Check if there is sufficient space in * Common Info for the MSD capability. */ if (mlieseqlen < (sizeof(struct wlan_ie_multilink) + msd_cap_offset + WLAN_ML_BV_CINFO_MEDMSYNCDELAYINFO_SIZE)) return QDF_STATUS_E_PROTO; *msd_cap_found = true; *msd_cap = qdf_le16_to_cpu(*(uint16_t *)(mlieseq + sizeof(struct wlan_ie_multilink) + msd_cap_offset)); } else { mlo_debug("MSD caps not found in assoc rsp"); } return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_bvmlie_mldmacaddr(uint8_t *mlieseq, qdf_size_t mlieseqlen, struct qdf_mac_addr *mldmacaddr) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant variant; uint16_t mlcontrol; uint8_t commoninfo_len; if (!mlieseq || !mlieseqlen || !mldmacaddr) return QDF_STATUS_E_NULL_VALUE; qdf_mem_zero(mldmacaddr, sizeof(*mldmacaddr)); if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if ((mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM) || (mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK)) return QDF_STATUS_E_INVAL; mlcontrol = qdf_le16_to_cpu(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_BASIC) return QDF_STATUS_E_INVAL; /* Common Info starts at mlieseq + sizeof(struct wlan_ie_multilink). * Check if there is sufficient space in the buffer for the Common Info * Length and MLD MAC address. */ if ((sizeof(struct wlan_ie_multilink) + WLAN_ML_BV_CINFO_LENGTH_SIZE + QDF_MAC_ADDR_SIZE) > mlieseqlen) return QDF_STATUS_E_PROTO; /* Check if the value indicated in the Common Info Length subfield is * sufficient to access the MLD MAC address. */ commoninfo_len = *(mlieseq + sizeof(struct wlan_ie_multilink)); if (commoninfo_len < (WLAN_ML_BV_CINFO_LENGTH_SIZE + QDF_MAC_ADDR_SIZE)) return QDF_STATUS_E_PROTO; qdf_mem_copy(mldmacaddr->bytes, mlieseq + sizeof(struct wlan_ie_multilink) + WLAN_ML_BV_CINFO_LENGTH_SIZE, QDF_MAC_ADDR_SIZE); return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_bvmlie_primary_linkid(uint8_t *mlieseq, qdf_size_t mlieseqlen, bool *linkidfound, uint8_t *linkid) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant variant; uint16_t mlcontrol; uint16_t presencebitmap; uint8_t *commoninfo; qdf_size_t commoninfolen; uint8_t *linkidinfo; if (!mlieseq || !mlieseqlen || !linkidfound || !linkid) return QDF_STATUS_E_NULL_VALUE; *linkidfound = false; *linkid = 0; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if ((mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM) || (mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK)) return QDF_STATUS_E_INVAL; mlcontrol = le16toh(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_BASIC) return QDF_STATUS_E_INVAL; presencebitmap = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); commoninfo = mlieseq + sizeof(struct wlan_ie_multilink); commoninfolen = 0; commoninfolen += WLAN_ML_BV_CINFO_LENGTH_SIZE; if ((sizeof(struct wlan_ie_multilink) + commoninfolen) > mlieseqlen) return QDF_STATUS_E_PROTO; commoninfolen += QDF_MAC_ADDR_SIZE; if ((sizeof(struct wlan_ie_multilink) + commoninfolen) > mlieseqlen) return QDF_STATUS_E_PROTO; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P) { linkidinfo = commoninfo + commoninfolen; commoninfolen += WLAN_ML_BV_CINFO_LINKIDINFO_SIZE; if ((sizeof(struct wlan_ie_multilink) + commoninfolen) > mlieseqlen) return QDF_STATUS_E_PROTO; *linkidfound = true; *linkid = QDF_GET_BITS(linkidinfo[0], WLAN_ML_BV_CINFO_LINKIDINFO_LINKID_IDX, WLAN_ML_BV_CINFO_LINKIDINFO_LINKID_BITS); } return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_bvmlie_mldcap(uint8_t *mlieseq, qdf_size_t mlieseqlen, bool *mldcapfound, uint16_t *mldcap) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant variant; uint16_t mlcontrol; uint16_t presencebitmap; uint8_t *commoninfo; uint8_t commoninfo_len; qdf_size_t mldcap_offset; if (!mlieseq || !mlieseqlen || !mldcapfound || !mldcap) return QDF_STATUS_E_NULL_VALUE; *mldcapfound = false; *mldcap = 0; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if (mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM || mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK) return QDF_STATUS_E_INVAL; mlcontrol = qdf_le16_to_cpu(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_BASIC) return QDF_STATUS_E_NOSUPPORT; presencebitmap = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); commoninfo = mlieseq + sizeof(struct wlan_ie_multilink); commoninfo_len = *(mlieseq + sizeof(struct wlan_ie_multilink)); /* mldcap_offset stores the offset of MLD Capabilities within * Common Info */ mldcap_offset = WLAN_ML_BV_CINFO_LENGTH_SIZE; mldcap_offset += QDF_MAC_ADDR_SIZE; if (presencebitmap & WLAN_ML_BV_CTRL_PBM_LINKIDINFO_P) { mldcap_offset += WLAN_ML_BV_CINFO_LINKIDINFO_SIZE; if ((sizeof(struct wlan_ie_multilink) + mldcap_offset) > mlieseqlen) return QDF_STATUS_E_PROTO; } if (presencebitmap & WLAN_ML_BV_CTRL_PBM_BSSPARAMCHANGECNT_P) { mldcap_offset += WLAN_ML_BSSPARAMCHNGCNT_SIZE; if ((sizeof(struct wlan_ie_multilink) + mldcap_offset) > mlieseqlen) return QDF_STATUS_E_PROTO; } if (presencebitmap & WLAN_ML_BV_CTRL_PBM_MEDIUMSYNCDELAYINFO_P) { mldcap_offset += WLAN_ML_BV_CINFO_MEDMSYNCDELAYINFO_SIZE; if ((sizeof(struct wlan_ie_multilink) + mldcap_offset) > mlieseqlen) return QDF_STATUS_E_PROTO; } if (presencebitmap & WLAN_ML_BV_CTRL_PBM_EMLCAP_P) { mldcap_offset += WLAN_ML_BV_CINFO_EMLCAP_SIZE; if ((sizeof(struct wlan_ie_multilink) + mldcap_offset) > mlieseqlen) return QDF_STATUS_E_PROTO; } if (presencebitmap & WLAN_ML_BV_CTRL_PBM_MLDCAPANDOP_P) { /* Check if the value indicated in the Common Info Length * subfield is sufficient to access the MLD capabilities. */ if (commoninfo_len < (mldcap_offset + WLAN_ML_BV_CINFO_MLDCAPANDOP_SIZE)) return QDF_STATUS_E_PROTO; if ((sizeof(struct wlan_ie_multilink) + mldcap_offset + WLAN_ML_BV_CINFO_MLDCAPANDOP_SIZE) > mlieseqlen) return QDF_STATUS_E_PROTO; *mldcap = qdf_le16_to_cpu(*((uint16_t *)(commoninfo + mldcap_offset))); *mldcapfound = true; } return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_bvmlie_persta_partner_info(uint8_t *mlieseq, qdf_size_t mlieseqlen, struct mlo_partner_info *partner_info) { struct wlan_ie_multilink *mlie_fixed; uint16_t mlcontrol; enum wlan_ml_variant variant; uint8_t *linkinfo; qdf_size_t linkinfo_len; struct mlo_partner_info pinfo = {0}; qdf_size_t mlieseqpayloadlen; uint8_t *mlieseqpayload_copy; bool is_elemfragseq; qdf_size_t defragpayload_len; qdf_size_t tmplen; QDF_STATUS ret; if (!mlieseq) { mlo_err("Pointer to Multi-Link element sequence is NULL"); return QDF_STATUS_E_NULL_VALUE; } if (!mlieseqlen) { mlo_err("Length of Multi-Link element sequence is zero"); return QDF_STATUS_E_INVAL; } if (!partner_info) { mlo_err("partner_info is NULL"); return QDF_STATUS_E_NULL_VALUE; } partner_info->num_partner_links = 0; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) { mlo_err_rl("Multi-Link element sequence length %zu octets is smaller than required for the fixed portion of Multi-Link element (%zu octets)", mlieseqlen, sizeof(struct wlan_ie_multilink)); return QDF_STATUS_E_INVAL; } mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if ((mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM) || (mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK)) { mlo_err("The element is not a Multi-Link element"); return QDF_STATUS_E_INVAL; } mlcontrol = le16toh(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_BASIC) { mlo_err("The variant value %u does not correspond to Basic Variant value %u", variant, WLAN_ML_VARIANT_BASIC); return QDF_STATUS_E_INVAL; } mlieseqpayloadlen = 0; tmplen = 0; is_elemfragseq = false; ret = wlan_get_elem_fragseq_info(mlieseq, mlieseqlen, &is_elemfragseq, &tmplen, &mlieseqpayloadlen); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (is_elemfragseq) { if (tmplen != mlieseqlen) { mlo_err_rl("Mismatch in values of element fragment sequence total length. Val per frag info determination: %zu octets, val passed as arg: %zu octets", tmplen, mlieseqlen); return QDF_STATUS_E_INVAL; } if (!mlieseqpayloadlen) { mlo_err_rl("Multi-Link element fragment sequence payload is reported as 0, investigate"); return QDF_STATUS_E_FAILURE; } mlo_debug("Multi-Link element fragment sequence found with payload len %zu", mlieseqpayloadlen); } else { if (mlieseqlen > (sizeof(struct ie_header) + WLAN_MAX_IE_LEN)) { mlo_err_rl("Expected presence of valid fragment sequence since Multi-Link element sequence length %zu octets is larger than frag threshold of %zu octets, however no valid fragment sequence found", mlieseqlen, sizeof(struct ie_header) + WLAN_MAX_IE_LEN); return QDF_STATUS_E_FAILURE; } mlieseqpayloadlen = mlieseqlen - (sizeof(struct ie_header) + 1); } mlieseqpayload_copy = qdf_mem_malloc(mlieseqpayloadlen); if (!mlieseqpayload_copy) { mlo_err_rl("Could not allocate memory for Multi-Link element payload copy"); return QDF_STATUS_E_NOMEM; } if (is_elemfragseq) { ret = wlan_defrag_elem_fragseq(false, mlieseq, mlieseqlen, mlieseqpayload_copy, mlieseqpayloadlen, &defragpayload_len); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } if (defragpayload_len != mlieseqpayloadlen) { mlo_err_rl("Length of de-fragmented payload %zu octets is not equal to length of Multi-Link element fragment sequence payload %zu octets", defragpayload_len, mlieseqpayloadlen); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_FAILURE; } } else { qdf_mem_copy(mlieseqpayload_copy, mlieseq + sizeof(struct ie_header) + 1, mlieseqpayloadlen); } linkinfo = NULL; linkinfo_len = 0; ret = util_parse_multi_link_ctrl(mlieseqpayload_copy, mlieseqpayloadlen, &linkinfo, &linkinfo_len); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } /* In case Link Info is absent, the number of partner links will remain * zero. */ if (!linkinfo) { qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_SUCCESS; } ret = util_parse_partner_info_from_linkinfo(linkinfo, linkinfo_len, &pinfo); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } qdf_mem_copy(partner_info, &pinfo, sizeof(*partner_info)); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_prvmlie_persta_link_id(uint8_t *mlieseq, qdf_size_t mlieseqlen, struct mlo_probereq_info *probereq_info) { struct wlan_ie_multilink *mlie_fixed; uint16_t mlcontrol; enum wlan_ml_variant variant; uint8_t *linkinfo; qdf_size_t linkinfo_len; struct mlo_probereq_info pinfo = {0}; qdf_size_t mlieseqpayloadlen; uint8_t *mlieseqpayload_copy; bool is_elemfragseq; qdf_size_t defragpayload_len; qdf_size_t tmplen; QDF_STATUS ret; if (!mlieseq) { mlo_err("Pointer to Multi-Link element sequence is NULL"); return QDF_STATUS_E_NULL_VALUE; } if (!mlieseqlen) { mlo_err("Length of Multi-Link element sequence is zero"); return QDF_STATUS_E_INVAL; } if (!probereq_info) { mlo_err("probe request_info is NULL"); return QDF_STATUS_E_NULL_VALUE; } probereq_info->num_links = 0; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) { mlo_err_rl("Multi-Link element sequence length %zu octets is smaller than required for the fixed portion of Multi-Link element (%zu octets)", mlieseqlen, sizeof(struct wlan_ie_multilink)); return QDF_STATUS_E_INVAL; } mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if ((mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM) || (mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK)) { mlo_err("The element is not a Multi-Link element"); return QDF_STATUS_E_INVAL; } mlcontrol = qdf_le16_to_cpu(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_PROBEREQ) { mlo_err("The variant value %u does not correspond to Probe Request Variant value %u", variant, WLAN_ML_VARIANT_PROBEREQ); return QDF_STATUS_E_INVAL; } mlieseqpayloadlen = 0; tmplen = 0; is_elemfragseq = false; ret = wlan_get_elem_fragseq_info(mlieseq, mlieseqlen, &is_elemfragseq, &tmplen, &mlieseqpayloadlen); if (QDF_IS_STATUS_ERROR(ret)) return ret; if (is_elemfragseq) { if (tmplen != mlieseqlen) { mlo_err_rl("Mismatch in values of element fragment sequence total length. Val per frag info determination: %zu octets, val passed as arg: %zu octets", tmplen, mlieseqlen); return QDF_STATUS_E_INVAL; } if (!mlieseqpayloadlen) { mlo_err_rl("Multi-Link element fragment sequence payload is reported as 0, investigate"); return QDF_STATUS_E_FAILURE; } mlo_debug("Multi-Link element fragment sequence found with payload len %zu", mlieseqpayloadlen); } else { if (mlieseqlen > (sizeof(struct ie_header) + WLAN_MAX_IE_LEN)) { mlo_err_rl("Expected presence of valid fragment sequence since Multi-Link element sequence length %zu octets is larger than frag threshold of %zu octets, however no valid fragment sequence found", mlieseqlen, sizeof(struct ie_header) + WLAN_MAX_IE_LEN); return QDF_STATUS_E_FAILURE; } mlieseqpayloadlen = mlieseqlen - (sizeof(struct ie_header) + 1); } mlieseqpayload_copy = qdf_mem_malloc(mlieseqpayloadlen); if (!mlieseqpayload_copy) { mlo_err_rl("Could not allocate memory for Multi-Link element payload copy"); return QDF_STATUS_E_NOMEM; } if (is_elemfragseq) { ret = wlan_defrag_elem_fragseq(false, mlieseq, mlieseqlen, mlieseqpayload_copy, mlieseqpayloadlen, &defragpayload_len); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } if (defragpayload_len != mlieseqpayloadlen) { mlo_err_rl("Length of de-fragmented payload %zu octets is not equal to length of Multi-Link element fragment sequence payload %zu octets", defragpayload_len, mlieseqpayloadlen); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_E_FAILURE; } } else { qdf_mem_copy(mlieseqpayload_copy, mlieseq + sizeof(struct ie_header) + 1, mlieseqpayloadlen); } linkinfo = NULL; linkinfo_len = 0; ret = util_parse_prv_multi_link_ctrl(mlieseqpayload_copy, mlieseqpayloadlen, &linkinfo, &linkinfo_len); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } /* In case Link Info is absent, the number of links will remain * zero. */ if (!linkinfo) { qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_SUCCESS; } ret = util_parse_probereq_info_from_linkinfo(linkinfo, linkinfo_len, &pinfo); if (QDF_IS_STATUS_ERROR(ret)) { qdf_mem_free(mlieseqpayload_copy); return ret; } qdf_mem_copy(probereq_info, &pinfo, sizeof(*probereq_info)); qdf_mem_free(mlieseqpayload_copy); return QDF_STATUS_SUCCESS; } QDF_STATUS util_get_prvmlie_mldid(uint8_t *mlieseq, qdf_size_t mlieseqlen, bool *mldidfound, uint8_t *mldid) { struct wlan_ie_multilink *mlie_fixed; enum wlan_ml_variant variant; uint16_t mlcontrol; uint16_t presencebitmap; uint8_t *commoninfo; qdf_size_t commoninfolen; if (!mlieseq || !mlieseqlen || !mldidfound || !mldid) return QDF_STATUS_E_NULL_VALUE; *mldidfound = false; *mldid = 0; if (mlieseqlen < sizeof(struct wlan_ie_multilink)) return QDF_STATUS_E_INVAL; mlie_fixed = (struct wlan_ie_multilink *)mlieseq; if (mlie_fixed->elem_id != WLAN_ELEMID_EXTN_ELEM || mlie_fixed->elem_id_ext != WLAN_EXTN_ELEMID_MULTI_LINK) return QDF_STATUS_E_INVAL; mlcontrol = qdf_le16_to_cpu(mlie_fixed->mlcontrol); variant = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_TYPE_IDX, WLAN_ML_CTRL_TYPE_BITS); if (variant != WLAN_ML_VARIANT_PROBEREQ) return QDF_STATUS_E_NOSUPPORT; presencebitmap = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX, WLAN_ML_CTRL_PBM_BITS); commoninfo = mlieseq + sizeof(struct wlan_ie_multilink); commoninfolen = WLAN_ML_PRV_CINFO_LENGTH_SIZE; if (presencebitmap & WLAN_ML_PRV_CTRL_PBM_MLDID_P) { if ((sizeof(struct wlan_ie_multilink) + commoninfolen + WLAN_ML_PRV_CINFO_MLDID_SIZE) > mlieseqlen) return QDF_STATUS_E_PROTO; *mldid = *((uint8_t *)(commoninfo + commoninfolen)); commoninfolen += WLAN_ML_PRV_CINFO_MLDID_SIZE; *mldidfound = true; } return QDF_STATUS_SUCCESS; } #endif