
Add support for Multi-Link element defragmentation in the common handler for generating link specific (re)association request/response, as well as in the API for getting per-STA partner link information. CRs-Fixed: 3116412 Change-Id: I237c593b44ec3a7c3347ccb95633cec362ba5411
2329 lines
72 KiB
C
2329 lines
72 KiB
C
/*
|
|
* 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 <wlan_cmn.h>
|
|
#include <wlan_mlo_mgr_sta.h>
|
|
#include <wlan_cm_public_struct.h>
|
|
#include <wlan_mlo_mgr_main.h>
|
|
#include <wlan_cm_api.h>
|
|
#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 presencebm;
|
|
|
|
/* 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;
|
|
|
|
presencebm = QDF_GET_BITS(mlcontrol, WLAN_ML_CTRL_PBM_IDX,
|
|
WLAN_ML_CTRL_PBM_BITS);
|
|
|
|
/* Check if MLD MAC address is present */
|
|
if (presencebm & WLAN_ML_BV_CTRL_PBM_MLDMACADDR_P) {
|
|
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 (presencebm & 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 (presencebm & WLAN_ML_BV_CTRL_PBM_BSSPARAMCHANGECNT_P) {
|
|
if (mlieseqpayloadlen <
|
|
(parsed_payload_len +
|
|
WLAN_ML_BV_CINFO_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_BV_CINFO_BSSPARAMCHNGCNT_SIZE,
|
|
parsed_payload_len);
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
parsed_payload_len += WLAN_ML_BV_CINFO_BSSPARAMCHNGCNT_SIZE;
|
|
}
|
|
|
|
/* Check if Medium Sync Delay Info is present */
|
|
if (presencebm & 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 (presencebm & 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 (presencebm & WLAN_ML_BV_CTRL_PBM_MLDCAP_P) {
|
|
if (mlieseqpayloadlen <
|
|
(parsed_payload_len +
|
|
WLAN_ML_BV_CINFO_MLDCAP_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_MLDCAP_SIZE,
|
|
parsed_payload_len);
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
parsed_payload_len += WLAN_ML_BV_CINFO_MLDCAP_SIZE;
|
|
}
|
|
|
|
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
|
|
uint8_t *util_parse_bvmlie_perstaprofile(uint8_t *subelement,
|
|
qdf_size_t len,
|
|
bool is_staprof_reqd,
|
|
qdf_size_t *staprof_len,
|
|
uint8_t *linkid,
|
|
bool *is_macaddr_valid,
|
|
struct qdf_mac_addr *macaddr)
|
|
{
|
|
qdf_size_t subelement_len = 0;
|
|
struct wlan_ml_bv_linfo_perstaprof *perstaprof_fixed;
|
|
uint16_t stacontrol;
|
|
uint8_t completeprofile;
|
|
uint8_t nstrlppresent;
|
|
enum wlan_ml_bv_linfo_perstaprof_stactrl_nstrbmsz nstrbmsz;
|
|
|
|
if (!subelement) {
|
|
mlo_err("Pointer to subelement is NULL");
|
|
return NULL;
|
|
}
|
|
|
|
if (!len) {
|
|
mlo_err("Length is zero");
|
|
return NULL;
|
|
}
|
|
|
|
if (subelement[0] != WLAN_ML_BV_LINFO_SUBELEMID_PERSTAPROFILE) {
|
|
mlo_err_rl("Pointer to subelement does not point to per-STA profile");
|
|
return NULL;
|
|
}
|
|
|
|
if (len < sizeof(struct wlan_ml_bv_linfo_perstaprof)) {
|
|
mlo_err_rl("len %zu octets is smaller than that required for the fixed portion of per-STA profile (%zu octets)",
|
|
len, sizeof(struct wlan_ml_bv_linfo_perstaprof));
|
|
return NULL;
|
|
}
|
|
|
|
perstaprof_fixed = (struct wlan_ml_bv_linfo_perstaprof *)subelement;
|
|
|
|
subelement_len = sizeof(*perstaprof_fixed);
|
|
|
|
stacontrol = le16toh(perstaprof_fixed->stacontrol);
|
|
|
|
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);
|
|
|
|
/* We increment the measured length of the per-STA profile by checking
|
|
* for the presence of individual fields. We validate this length
|
|
* against the total length of the sublement only at the end, except in
|
|
* cases where we are actually about to access a given field.
|
|
*/
|
|
|
|
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 (macaddr) {
|
|
/* Explicityly check if the length is sufficient to hold
|
|
* the STA MAC address, since we are about to attempt to
|
|
* access the STA MAC address.
|
|
*/
|
|
if (len < (subelement_len + QDF_MAC_ADDR_SIZE)) {
|
|
mlo_err_rl("len %zu octets is smaller than min size of per-STA profile required to accommodate STA MAC address (%zu octets)",
|
|
len,
|
|
(subelement_len + QDF_MAC_ADDR_SIZE));
|
|
return NULL;
|
|
}
|
|
|
|
qdf_mem_copy(macaddr->bytes,
|
|
subelement + subelement_len,
|
|
QDF_MAC_ADDR_SIZE);
|
|
|
|
mlo_nofl_debug("Copied MAC address: " QDF_MAC_ADDR_FMT,
|
|
subelement + subelement_len);
|
|
|
|
if (is_macaddr_valid)
|
|
*is_macaddr_valid = true;
|
|
}
|
|
|
|
subelement_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))
|
|
subelement_len += WLAN_BEACONINTERVAL_LEN;
|
|
|
|
/* 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))
|
|
subelement_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) {
|
|
subelement_len += 1;
|
|
} else if (nstrbmsz == WLAN_ML_BV_LINFO_PERSTAPROF_STACTRL_NSTRBMSZ_2_OCTETS) {
|
|
subelement_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 NULL;
|
|
}
|
|
}
|
|
|
|
/* 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) {
|
|
if (len < subelement_len) {
|
|
mlo_err_rl("len %zu < subelement_len %zu",
|
|
len,
|
|
subelement_len);
|
|
return NULL;
|
|
}
|
|
|
|
return &subelement[subelement_len - 1];
|
|
}
|
|
|
|
if (len <= subelement_len) {
|
|
mlo_err_rl("len %zu <= subelement_len %zu",
|
|
len,
|
|
subelement_len);
|
|
return NULL;
|
|
}
|
|
|
|
if (staprof_len)
|
|
*staprof_len = len - subelement_len;
|
|
|
|
return &subelement[subelement_len];
|
|
}
|
|
|
|
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;
|
|
|
|
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 *currpos;
|
|
qdf_size_t currlen;
|
|
uint8_t *endofstainfo;
|
|
|
|
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;
|
|
currpos = linkinfo;
|
|
currlen = linkinfo_len;
|
|
|
|
while (currlen) {
|
|
if (currlen < MIN_IE_LEN) {
|
|
mlo_err_rl("currlen %zu is smaller than minimum IE length %u",
|
|
currlen, MIN_IE_LEN);
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if (currlen < (MIN_IE_LEN + currpos[TAG_LEN_POS])) {
|
|
mlo_err_rl("currlen %zu is smaller than length of current IE %u",
|
|
currlen, MIN_IE_LEN + currpos[TAG_LEN_POS]);
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if (currpos[ID_POS] ==
|
|
WLAN_ML_BV_LINFO_SUBELEMID_PERSTAPROFILE) {
|
|
is_macaddr_valid = false;
|
|
|
|
/* Per-STA profile fragmentation support may be added
|
|
* once support for this is introduced in the standard.
|
|
*/
|
|
endofstainfo =
|
|
util_parse_bvmlie_perstaprofile(currpos,
|
|
currlen,
|
|
false,
|
|
NULL,
|
|
&linkid,
|
|
&is_macaddr_valid,
|
|
&macaddr);
|
|
|
|
if (!endofstainfo) {
|
|
mlo_err_rl("Error in parsing per-STA profile");
|
|
return QDF_STATUS_E_EMPTY;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
currlen -= (MIN_IE_LEN + currpos[TAG_LEN_POS]);
|
|
currpos += (MIN_IE_LEN + currpos[TAG_LEN_POS]);
|
|
}
|
|
|
|
mlo_debug("Number of ML partner links found=%u",
|
|
partner_info->num_partner_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 funtionality provided by this helper may be combined with
|
|
* other, older non-inheritance parsing helper functionality and exposed
|
|
* as a common API as part of future efforts once the older
|
|
* functionality can be made generic.
|
|
*/
|
|
|
|
if (!buff) {
|
|
mlo_err("Pointer to buffer for IEs is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (!buff_len) {
|
|
mlo_err("IE buffer length is zero");
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
if (!ninherit_elemlist) {
|
|
mlo_err("Pointer to Non-Inheritance element ID list array is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (!ninherit_elemlist_len) {
|
|
mlo_err("Pointer to Non-Inheritance element ID list array length is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (!ninherit_elemextlist) {
|
|
mlo_err("Pointer to Non-Inheritance element ID extension list array is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (!ninherit_elemextlist_len) {
|
|
mlo_err("Pointer to Non-Inheritance element ID extension list array length is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
ninherit_ie = NULL;
|
|
*ninherit_elemlist_len = 0;
|
|
*ninherit_elemlist = NULL;
|
|
*ninherit_elemextlist_len = 0;
|
|
*ninherit_elemextlist = NULL;
|
|
|
|
ninherit_ie =
|
|
(uint8_t *)util_find_extn_eid(WLAN_ELEMID_EXTN_ELEM,
|
|
WLAN_EXTN_ELEMID_NONINHERITANCE,
|
|
buff,
|
|
buff_len);
|
|
|
|
if (ninherit_ie) {
|
|
if ((ninherit_ie + TAG_LEN_POS) > (buff + buff_len - 1)) {
|
|
mlo_err_rl("Position of length field of Non-Inheritance element would exceed IE buffer boundary");
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if ((ninherit_ie + ninherit_ie[TAG_LEN_POS] + MIN_IE_LEN) >
|
|
(buff + buff_len)) {
|
|
mlo_err_rl("Non-Inheritance element with total length %u would exceed IE buffer boundary",
|
|
ninherit_ie[TAG_LEN_POS] + MIN_IE_LEN);
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if ((ninherit_ie[TAG_LEN_POS] + MIN_IE_LEN) <
|
|
MIN_NONINHERITANCEELEM_LEN) {
|
|
mlo_err_rl("Non-Inheritance element size %u is smaller than the minimum required %u",
|
|
ninherit_ie[TAG_LEN_POS] + MIN_IE_LEN,
|
|
MIN_NONINHERITANCEELEM_LEN);
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
/* Track the number of unparsed octets, excluding the IE header.
|
|
*/
|
|
unparsed_len = ninherit_ie[TAG_LEN_POS];
|
|
|
|
/* Mark the element ID extension as parsed */
|
|
unparsed_len--;
|
|
|
|
*ninherit_elemlist_len = ninherit_ie[ELEM_ID_LIST_LEN_POS];
|
|
unparsed_len--;
|
|
|
|
/* While checking if the Non-Inheritance element ID list length
|
|
* exceeds the remaining unparsed IE space, we factor in one
|
|
* octet for the element extension ID list length and subtract
|
|
* this from the unparsed IE space.
|
|
*/
|
|
if (*ninherit_elemlist_len > (unparsed_len - 1)) {
|
|
mlo_err_rl("Non-Inheritance element ID list length %zu exceeds remaining unparsed IE space, minus an octet for element extension ID list length %zu",
|
|
*ninherit_elemlist_len, unparsed_len - 1);
|
|
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if (*ninherit_elemlist_len != 0) {
|
|
*ninherit_elemlist = ninherit_ie + ELEM_ID_LIST_POS;
|
|
unparsed_len -= *ninherit_elemlist_len;
|
|
}
|
|
|
|
*ninherit_elemextlist_len =
|
|
ninherit_ie[ELEM_ID_LIST_LEN_POS + *ninherit_elemlist_len + 1];
|
|
unparsed_len--;
|
|
|
|
if (*ninherit_elemextlist_len > unparsed_len) {
|
|
mlo_err_rl("Non-Inheritance element ID extension list length %zu exceeds remaining unparsed IE space %zu",
|
|
*ninherit_elemextlist_len, unparsed_len);
|
|
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if (*ninherit_elemextlist_len != 0) {
|
|
*ninherit_elemextlist = ninherit_ie +
|
|
ELEM_ID_LIST_LEN_POS + (*ninherit_elemlist_len)
|
|
+ 2;
|
|
unparsed_len -= *ninherit_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 funtionality provided by this helper may be combined with
|
|
* other, older non-inheritance parsing helper functionality and exposed
|
|
* as a common API as part of future efforts once the older
|
|
* functionality can be made generic.
|
|
*/
|
|
|
|
/* Except for is_in_noninheritlist and ie, other pointer arguments are
|
|
* permitted to be NULL if they are inapplicable. If they are
|
|
* applicable, they will be checked to ensure they are not NULL.
|
|
*/
|
|
|
|
if (!is_in_noninheritlist) {
|
|
mlo_err("NULL pointer to flag that indicates if element is in a Non-Inheritance list");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
/* If ninherit_elemlist_len and ninherit_elemextlist_len are both zero
|
|
* as checked soon in this function, we won't be accessing the IE.
|
|
* However, we still check right-away if the pointer to the IE is
|
|
* non-NULL and whether the total IE length is sane enough to access the
|
|
* element ID and if applicable, the element ID extension, since it
|
|
* doesn't make sense to set the flag in is_in_noninheritlist for a NULL
|
|
* IE pointer or an IE whose total length is not sane enough to
|
|
* distinguish the identity of the IE.
|
|
*/
|
|
if (!ie) {
|
|
mlo_err("NULL pointer to IE");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (total_ie_len < (ID_POS + 1)) {
|
|
mlo_err("Total IE length %zu is smaller than minimum required to access element ID %u",
|
|
total_ie_len, ID_POS + 1);
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
if ((ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
|
|
(total_ie_len < (IDEXT_POS + 1))) {
|
|
mlo_err("Total IE length %zu is smaller than minimum required to access element ID extension %u",
|
|
total_ie_len, IDEXT_POS + 1);
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
*is_in_noninheritlist = false;
|
|
|
|
/* If both the Non-Inheritance element list and Non-Inheritance element
|
|
* ID extension list are empty, then return success since we can
|
|
* conclude immediately that the given element does not occur in any
|
|
* Non-Inheritance list. The is_in_noninheritlist remains set to false
|
|
* as required.
|
|
*/
|
|
if (!ninherit_elemlist_len && !ninherit_elemextlist_len)
|
|
return QDF_STATUS_SUCCESS;
|
|
|
|
if (ie[ID_POS] != WLAN_ELEMID_EXTN_ELEM) {
|
|
if (!ninherit_elemlist_len)
|
|
return QDF_STATUS_SUCCESS;
|
|
|
|
if (!ninherit_elemlist) {
|
|
mlo_err("NULL pointer to Non-Inheritance element ID list though length of element ID list is %zu",
|
|
ninherit_elemlist_len);
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
for (i = 0; i < ninherit_elemlist_len; i++) {
|
|
if (ie[ID_POS] == ninherit_elemlist[i]) {
|
|
*is_in_noninheritlist = true;
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
}
|
|
} else {
|
|
if (!ninherit_elemextlist_len)
|
|
return QDF_STATUS_SUCCESS;
|
|
|
|
if (!ninherit_elemextlist) {
|
|
mlo_err("NULL pointer to Non-Inheritance element ID extension list though length of element ID extension list is %zu",
|
|
ninherit_elemextlist_len);
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
for (i = 0; i < ninherit_elemextlist_len; i++) {
|
|
if (ie[IDEXT_POS] == ninherit_elemextlist[i]) {
|
|
*is_in_noninheritlist = true;
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
|
|
static inline
|
|
QDF_STATUS util_validate_reportingsta_ie(const uint8_t *reportingsta_ie,
|
|
const uint8_t *frame_iesection,
|
|
const qdf_size_t frame_iesection_len)
|
|
{
|
|
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 + reportingsta_ie_size) >
|
|
(frame_iesection + frame_iesection_len)) {
|
|
if (reportingsta_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
|
|
mlo_err_rl("Total size %zu octets of element with element ID %u element ID extension %u for reporting STA would exceed frame IE section boundary",
|
|
reportingsta_ie_size,
|
|
reportingsta_ie[ID_POS],
|
|
reportingsta_ie[IDEXT_POS]);
|
|
} else {
|
|
mlo_err_rl("Total size %zu octets of element with element ID %u for reporting STA would exceed frame IE section boundary",
|
|
reportingsta_ie_size,
|
|
reportingsta_ie[ID_POS]);
|
|
}
|
|
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
|
|
static inline
|
|
QDF_STATUS util_validate_sta_prof_ie(const uint8_t *sta_prof_ie,
|
|
const uint8_t *sta_prof_iesection,
|
|
const qdf_size_t sta_prof_iesection_len)
|
|
{
|
|
qdf_size_t sta_prof_ie_size;
|
|
|
|
if (!sta_prof_ie) {
|
|
mlo_err("Pointer to STA profile IE is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (!sta_prof_iesection) {
|
|
mlo_err("Pointer to start of IE section in STA profile is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (!sta_prof_iesection_len) {
|
|
mlo_err("Length of IE section in STA profile is zero");
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
if ((sta_prof_ie + ID_POS) > (sta_prof_iesection +
|
|
sta_prof_iesection_len - 1)) {
|
|
mlo_err_rl("Position of element ID field of STA profile element would exceed STA profile IE section boundary");
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if ((sta_prof_ie + TAG_LEN_POS) > (sta_prof_iesection +
|
|
sta_prof_iesection_len - 1)) {
|
|
mlo_err_rl("Position of length field of element with element ID %u in STA profile would exceed STA profile IE section boundary",
|
|
sta_prof_ie[ID_POS]);
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if ((sta_prof_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
|
|
((sta_prof_ie + IDEXT_POS) > (sta_prof_iesection +
|
|
sta_prof_iesection_len - 1))) {
|
|
mlo_err_rl("Position of element ID extension field of element would exceed STA profile IE section boundary");
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
sta_prof_ie_size = sta_prof_ie[TAG_LEN_POS] + MIN_IE_LEN;
|
|
|
|
if ((sta_prof_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) &&
|
|
(sta_prof_ie_size < (IDEXT_POS + 1))) {
|
|
mlo_err_rl("Total length %zu of STA profile element is smaller than minimum required to access element ID extension %u",
|
|
sta_prof_ie_size, IDEXT_POS + 1);
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
if ((sta_prof_ie + sta_prof_ie_size) >
|
|
(sta_prof_iesection + sta_prof_iesection_len)) {
|
|
if (sta_prof_ie[ID_POS] == WLAN_ELEMID_EXTN_ELEM) {
|
|
mlo_err_rl("Total size %zu octets of element with element ID %u element ID extension %u in STA profile would exceed STA profile IE section boundary",
|
|
sta_prof_ie_size,
|
|
sta_prof_ie[ID_POS],
|
|
sta_prof_ie[IDEXT_POS]);
|
|
} else {
|
|
mlo_err_rl("Total size %zu octets of element with element ID %u in STA profile would exceed STA profile IE section boundary",
|
|
sta_prof_ie_size,
|
|
sta_prof_ie[ID_POS]);
|
|
}
|
|
|
|
return QDF_STATUS_E_PROTO;
|
|
}
|
|
|
|
return QDF_STATUS_SUCCESS;
|
|
}
|
|
|
|
#define MLO_LINKSPECIFIC_ASSOC_REQ_FC0 0x00
|
|
#define MLO_LINKSPECIFIC_ASSOC_REQ_FC1 0x00
|
|
#define MLO_LINKSPECIFIC_ASSOC_RESP_FC0 0x10
|
|
#define MLO_LINKSPECIFIC_ASSOC_RESP_FC1 0x00
|
|
|
|
static
|
|
QDF_STATUS util_gen_link_assoc_reqrsp_cmn(uint8_t *frame, qdf_size_t frame_len,
|
|
uint8_t subtype,
|
|
struct qdf_mac_addr link_addr,
|
|
uint8_t *link_frame,
|
|
qdf_size_t link_frame_maxsize,
|
|
qdf_size_t *link_frame_len)
|
|
{
|
|
/* Please see documentation for util_gen_link_assoc_req() and
|
|
* util_gen_link_assoc_resp() for information on the inputs to and
|
|
* output from this helper, since those APIs are essentially wrappers
|
|
* over this helper.
|
|
*/
|
|
|
|
/* Pointer to Multi-Link element/Multi-Link element fragment sequence */
|
|
uint8_t *mlieseq;
|
|
/* Total length of Multi-Link element sequence (including fragements if
|
|
* any)
|
|
*/
|
|
qdf_size_t mlieseqlen;
|
|
/* Variant (i.e. type) of the Multi-Link element */
|
|
enum wlan_ml_variant variant;
|
|
|
|
/* 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;
|
|
|
|
/* 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;
|
|
|
|
qdf_size_t tmplen;
|
|
QDF_STATUS ret;
|
|
|
|
if (!frame) {
|
|
mlo_err("Pointer to original frame is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (!frame_len) {
|
|
mlo_err("Length of original frame is zero");
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
if ((subtype != WLAN_FC0_STYPE_ASSOC_REQ) &&
|
|
(subtype != WLAN_FC0_STYPE_REASSOC_REQ) &&
|
|
(subtype != WLAN_FC0_STYPE_ASSOC_RESP) &&
|
|
(subtype != WLAN_FC0_STYPE_REASSOC_RESP)) {
|
|
mlo_err("802.11 frame subtype %u is invalid", subtype);
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
if (!link_frame) {
|
|
mlo_err("Pointer to secondary link specific frame is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
if (!link_frame_maxsize) {
|
|
mlo_err("Maximum size of secondary link specific frame is zero");
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
if (!link_frame_len) {
|
|
mlo_err("Pointer to populated length of secondary link specific frame is NULL");
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
}
|
|
|
|
frame_iesection_offset = 0;
|
|
|
|
if (subtype == WLAN_FC0_STYPE_ASSOC_REQ) {
|
|
frame_iesection_offset = WLAN_ASSOC_REQ_IES_OFFSET;
|
|
} else if (subtype == WLAN_FC0_STYPE_REASSOC_REQ) {
|
|
frame_iesection_offset = WLAN_REASSOC_REQ_IES_OFFSET;
|
|
} else {
|
|
/* This is a (re)association response */
|
|
frame_iesection_offset = WLAN_ASSOC_RSP_IES_OFFSET;
|
|
}
|
|
|
|
if (frame_len < frame_iesection_offset) {
|
|
/* The caller is supposed to have confirmed that this is a valid
|
|
* frame containing a Multi-Link element. Hence we treat this as
|
|
* a case of invalid argument being passed to us.
|
|
*/
|
|
mlo_err("Frame length %zu is smaller than the IE section offset %zu for subtype %u",
|
|
frame_len, frame_iesection_offset, subtype);
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
frame_iesection_len = frame_len - frame_iesection_offset;
|
|
|
|
if (frame_iesection_len == 0) {
|
|
/* The caller is supposed to have confirmed that this is a valid
|
|
* frame containing a Multi-Link element. Hence we treat this as
|
|
* a case of invalid argument being passed to us.
|
|
*/
|
|
mlo_err("No space left in frame for IE section");
|
|
return QDF_STATUS_E_INVAL;
|
|
}
|
|
|
|
frame_iesection = frame + frame_iesection_offset;
|
|
|
|
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);
|
|
|
|
sta_prof_remlen = 0;
|
|
is_reportedmacaddr_valid = false;
|
|
|
|
/* Note: We may have a future change to skip subelements which are not
|
|
* Per-STA Profile, handle more than two links in MLO, handle cases
|
|
* where we unexpectedly find more Per-STA Profiles than expected, etc.
|
|
*/
|
|
|
|
/* Parse per-STA profile */
|
|
sta_prof_currpos = util_parse_bvmlie_perstaprofile(link_info,
|
|
link_info_len,
|
|
true,
|
|
&sta_prof_remlen,
|
|
NULL,
|
|
&is_reportedmacaddr_valid,
|
|
&reportedmacaddr);
|
|
|
|
/* If we do not successfully find a STA Profile, we return an error.
|
|
* This is because we need to get at least the expected fixed fields,
|
|
* even if there is an (improbable) total inheritance.
|
|
*/
|
|
if (!sta_prof_currpos) {
|
|
mlo_err_rl("Unable to find STA profile");
|
|
qdf_mem_free(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("Insufficent space in link specific frame for 802.11 header. Required: %u octets, available: %zu octets",
|
|
WLAN_MAC_HDR_LEN_3A, link_frame_maxsize);
|
|
|
|
qdf_mem_free(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("Insufficent space in link specific frame for Capability Info field. Required: %u octets, available: %zu octets",
|
|
WLAN_CAPABILITYINFO_LEN,
|
|
(link_frame_maxsize - link_frame_currlen));
|
|
|
|
qdf_mem_free(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 Capablity Info field (%u octets) to link specific frame",
|
|
WLAN_CAPABILITYINFO_LEN);
|
|
|
|
sta_prof_currpos += WLAN_CAPABILITYINFO_LEN;
|
|
sta_prof_remlen -= WLAN_CAPABILITYINFO_LEN;
|
|
|
|
/* Listen Interval is common between all links. Copy this from
|
|
* the reporting section of the frame.
|
|
*/
|
|
|
|
if ((link_frame_maxsize - link_frame_currlen) <
|
|
WLAN_LISTENINTERVAL_LEN) {
|
|
mlo_err("Insufficent space in link specific frame for Listen Interval field. Required: %u octets, available: %zu octets",
|
|
WLAN_LISTENINTERVAL_LEN,
|
|
(link_frame_maxsize - link_frame_currlen));
|
|
|
|
qdf_mem_free(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("Insufficent space in link specific frame for current AP address. Required: %u octets, available: %zu octets",
|
|
QDF_MAC_ADDR_SIZE,
|
|
(link_frame_maxsize -
|
|
link_frame_currlen));
|
|
|
|
qdf_mem_free(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 {
|
|
/* 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("Insufficent space in link specific frame for Capability Info and Status Code fields. Required: %u octets, available: %zu octets",
|
|
WLAN_CAPABILITYINFO_LEN + WLAN_STATUSCODE_LEN,
|
|
(link_frame_maxsize - link_frame_currlen));
|
|
|
|
qdf_mem_free(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("Insufficent space in link specific frame for AID field. Required: %u octets, available: %zu octets",
|
|
WLAN_AID_LEN,
|
|
(link_frame_maxsize - link_frame_currlen));
|
|
|
|
qdf_mem_free(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);
|
|
}
|
|
|
|
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)) {
|
|
/* Sanity check that the SSID element is present for the
|
|
* reporting STA. There is no stipulation in the standard for
|
|
* the STA profile in this regard, so we do not check the STA
|
|
* profile for the SSID element.
|
|
*/
|
|
if (!reportingsta_ie) {
|
|
mlo_err_rl("SSID element not found for reporting STA for (re)association request.");
|
|
qdf_mem_free(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;
|
|
|
|
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("Insufficent space in link specific frame for IE with element ID : %u extension element ID : %u. Required: %zu octets, available: %zu octets",
|
|
reportingsta_ie[ID_POS],
|
|
reportingsta_ie[IDEXT_POS],
|
|
reportingsta_ie_size,
|
|
link_frame_maxsize -
|
|
link_frame_currlen);
|
|
} else {
|
|
mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
|
|
reportingsta_ie[ID_POS],
|
|
reportingsta_ie_size,
|
|
link_frame_maxsize -
|
|
link_frame_currlen);
|
|
}
|
|
|
|
qdf_mem_free(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 (!qdf_mem_cmp(reportingsta_ie +
|
|
PAYLOAD_START_POS,
|
|
sta_prof_ie +
|
|
PAYLOAD_START_POS,
|
|
OUI_LEN)) {
|
|
/* Same vendor IE, copy from STA profile
|
|
*/
|
|
if ((link_frame_currpos +
|
|
sta_prof_ie_size) <=
|
|
(link_frame +
|
|
link_frame_maxsize)) {
|
|
qdf_mem_copy(link_frame_currpos,
|
|
sta_prof_ie,
|
|
sta_prof_ie_size);
|
|
|
|
link_frame_currpos +=
|
|
sta_prof_ie_size;
|
|
link_frame_currlen +=
|
|
sta_prof_ie_size;
|
|
|
|
mlo_debug("Vendor IE (%zu octets) for reporting STA also present in STA profile. Copied IE from STA profile to link specific frame",
|
|
sta_prof_ie_size);
|
|
|
|
sta_prof_ie[0] = 0;
|
|
} else {
|
|
mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
|
|
sta_prof_ie[ID_POS],
|
|
sta_prof_ie_size,
|
|
link_frame_maxsize -
|
|
link_frame_currlen);
|
|
|
|
qdf_mem_free(mlieseqpayload_copy);
|
|
return QDF_STATUS_E_NOMEM;
|
|
}
|
|
} else {
|
|
if ((link_frame_currpos +
|
|
reportingsta_ie_size) <=
|
|
(link_frame +
|
|
link_frame_maxsize)) {
|
|
qdf_mem_copy(link_frame_currpos,
|
|
reportingsta_ie,
|
|
reportingsta_ie_size);
|
|
|
|
link_frame_currpos +=
|
|
reportingsta_ie_size;
|
|
link_frame_currlen +=
|
|
reportingsta_ie_size;
|
|
|
|
mlo_debug("Vendor IE (%zu octets) present for reporting STA but not present in STA profile. Copied IE from reporting frame to link specific frame",
|
|
reportingsta_ie_size);
|
|
} else {
|
|
mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
|
|
reportingsta_ie[ID_POS],
|
|
reportingsta_ie_size,
|
|
link_frame_maxsize -
|
|
link_frame_currlen);
|
|
|
|
qdf_mem_free(mlieseqpayload_copy);
|
|
return QDF_STATUS_E_NOMEM;
|
|
}
|
|
}
|
|
} 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("Insufficent space in link specific frame for IE with element ID : %u extension element ID : %u. Required: %zu octets, available: %zu octets",
|
|
sta_prof_ie[ID_POS],
|
|
sta_prof_ie[IDEXT_POS],
|
|
sta_prof_ie_size,
|
|
link_frame_maxsize -
|
|
link_frame_currlen);
|
|
} else {
|
|
mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
|
|
sta_prof_ie[ID_POS],
|
|
sta_prof_ie_size,
|
|
link_frame_maxsize -
|
|
link_frame_currlen);
|
|
}
|
|
|
|
qdf_mem_free(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("Insufficent space in link specific frame for IE with element ID : %u extension element ID : %u. Required: %zu octets, available: %zu octets",
|
|
sta_prof_ie[ID_POS],
|
|
sta_prof_ie[IDEXT_POS],
|
|
sta_prof_ie_size,
|
|
link_frame_maxsize -
|
|
link_frame_currlen);
|
|
} else {
|
|
mlo_err_rl("Insufficent space in link specific frame for IE with element ID : %u. Required: %zu octets, available: %zu octets",
|
|
sta_prof_ie[ID_POS],
|
|
sta_prof_ie_size,
|
|
link_frame_maxsize -
|
|
link_frame_currlen);
|
|
}
|
|
|
|
qdf_mem_free(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 {
|
|
/* This is a (re)association response */
|
|
|
|
qdf_mem_copy(link_frame_hdr->i_addr3, reportedmacaddr.bytes,
|
|
QDF_MAC_ADDR_SIZE);
|
|
qdf_mem_copy(link_frame_hdr->i_addr2, reportedmacaddr.bytes,
|
|
QDF_MAC_ADDR_SIZE);
|
|
qdf_mem_copy(link_frame_hdr->i_addr1, &link_addr,
|
|
QDF_MAC_ADDR_SIZE);
|
|
|
|
link_frame_hdr->i_fc[0] = MLO_LINKSPECIFIC_ASSOC_RESP_FC0;
|
|
link_frame_hdr->i_fc[1] = MLO_LINKSPECIFIC_ASSOC_RESP_FC1;
|
|
}
|
|
|
|
/* Seq num not used so not populated */
|
|
|
|
qdf_mem_free(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_assoc_reqrsp_cmn(frame, frame_len,
|
|
(isreassoc ? WLAN_FC0_STYPE_REASSOC_REQ :
|
|
WLAN_FC0_STYPE_ASSOC_REQ),
|
|
link_addr, link_frame, link_frame_maxsize,
|
|
link_frame_len);
|
|
}
|
|
|
|
QDF_STATUS
|
|
util_gen_link_assoc_rsp(uint8_t *frame, qdf_size_t frame_len, bool isreassoc,
|
|
struct qdf_mac_addr link_addr,
|
|
uint8_t *link_frame,
|
|
qdf_size_t link_frame_maxsize,
|
|
qdf_size_t *link_frame_len)
|
|
{
|
|
return util_gen_link_assoc_reqrsp_cmn(frame, frame_len,
|
|
(isreassoc ? WLAN_FC0_STYPE_REASSOC_RESP :
|
|
WLAN_FC0_STYPE_ASSOC_RESP),
|
|
link_addr, link_frame, link_frame_maxsize,
|
|
link_frame_len);
|
|
}
|
|
|
|
QDF_STATUS
|
|
util_find_mlie(uint8_t *buf, qdf_size_t buflen, uint8_t **mlieseq,
|
|
qdf_size_t *mlieseqlen)
|
|
{
|
|
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_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_mldmacaddr(uint8_t *mlieseq, qdf_size_t mlieseqlen,
|
|
bool *mldmacaddrfound,
|
|
struct qdf_mac_addr *mldmacaddr)
|
|
{
|
|
struct wlan_ie_multilink *mlie_fixed;
|
|
enum wlan_ml_variant variant;
|
|
uint16_t mlcontrol;
|
|
uint16_t presencebitmap;
|
|
|
|
if (!mlieseq || !mlieseqlen || !mldmacaddrfound || !mldmacaddr)
|
|
return QDF_STATUS_E_NULL_VALUE;
|
|
|
|
*mldmacaddrfound = false;
|
|
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 = 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);
|
|
|
|
if (presencebitmap & WLAN_ML_BV_CTRL_PBM_MLDMACADDR_P) {
|
|
/* Common Info starts at mlieseq + sizeof(struct
|
|
* wlan_ie_multilink). Check if there is sufficient space in
|
|
* Common Info for the MLD MAC address.
|
|
*/
|
|
if ((sizeof(struct wlan_ie_multilink) + QDF_MAC_ADDR_SIZE) >
|
|
mlieseqlen)
|
|
return QDF_STATUS_E_PROTO;
|
|
|
|
*mldmacaddrfound = true;
|
|
qdf_mem_copy(mldmacaddr->bytes,
|
|
mlieseq + sizeof(struct wlan_ie_multilink),
|
|
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;
|
|
|
|
if (presencebitmap & WLAN_ML_BV_CTRL_PBM_MLDMACADDR_P) {
|
|
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_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;
|
|
}
|
|
#endif
|