root/include/linux/ieee80211-s1g.h
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * IEEE 802.11 S1G definitions
 *
 * Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen
 * <jkmaline@cc.hut.fi>
 * Copyright (c) 2002-2003, Jouni Malinen <jkmaline@cc.hut.fi>
 * Copyright (c) 2005, Devicescape Software, Inc.
 * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
 * Copyright (c) 2013 - 2014 Intel Mobile Communications GmbH
 * Copyright (c) 2016 - 2017 Intel Deutschland GmbH
 * Copyright (c) 2018 - 2025 Intel Corporation
 */

#ifndef LINUX_IEEE80211_S1G_H
#define LINUX_IEEE80211_S1G_H

#include <linux/types.h>
#include <linux/if_ether.h>

/* bits unique to S1G beacon frame control */
#define IEEE80211_S1G_BCN_NEXT_TBTT     0x100
#define IEEE80211_S1G_BCN_CSSID         0x200
#define IEEE80211_S1G_BCN_ANO           0x400

/* see 802.11ah-2016 9.9 NDP CMAC frames */
#define IEEE80211_S1G_1MHZ_NDP_BITS     25
#define IEEE80211_S1G_1MHZ_NDP_BYTES    4
#define IEEE80211_S1G_2MHZ_NDP_BITS     37
#define IEEE80211_S1G_2MHZ_NDP_BYTES    5

/**
 * ieee80211_is_s1g_beacon - check if IEEE80211_FTYPE_EXT &&
 * IEEE80211_STYPE_S1G_BEACON
 * @fc: frame control bytes in little-endian byteorder
 * Return: whether or not the frame is an S1G beacon
 */
static inline bool ieee80211_is_s1g_beacon(__le16 fc)
{
        return (fc & cpu_to_le16(IEEE80211_FCTL_FTYPE |
                                 IEEE80211_FCTL_STYPE)) ==
               cpu_to_le16(IEEE80211_FTYPE_EXT | IEEE80211_STYPE_S1G_BEACON);
}

/**
 * ieee80211_s1g_has_next_tbtt - check if IEEE80211_S1G_BCN_NEXT_TBTT
 * @fc: frame control bytes in little-endian byteorder
 * Return: whether or not the frame contains the variable-length
 *      next TBTT field
 */
static inline bool ieee80211_s1g_has_next_tbtt(__le16 fc)
{
        return ieee80211_is_s1g_beacon(fc) &&
                (fc & cpu_to_le16(IEEE80211_S1G_BCN_NEXT_TBTT));
}

/**
 * ieee80211_s1g_has_ano - check if IEEE80211_S1G_BCN_ANO
 * @fc: frame control bytes in little-endian byteorder
 * Return: whether or not the frame contains the variable-length
 *      ANO field
 */
static inline bool ieee80211_s1g_has_ano(__le16 fc)
{
        return ieee80211_is_s1g_beacon(fc) &&
                (fc & cpu_to_le16(IEEE80211_S1G_BCN_ANO));
}

/**
 * ieee80211_s1g_has_cssid - check if IEEE80211_S1G_BCN_CSSID
 * @fc: frame control bytes in little-endian byteorder
 * Return: whether or not the frame contains the variable-length
 *      compressed SSID field
 */
static inline bool ieee80211_s1g_has_cssid(__le16 fc)
{
        return ieee80211_is_s1g_beacon(fc) &&
                (fc & cpu_to_le16(IEEE80211_S1G_BCN_CSSID));
}

/**
 * enum ieee80211_s1g_chanwidth - S1G channel widths
 * These are defined in IEEE802.11-2016ah Table 10-20
 * as BSS Channel Width
 *
 * @IEEE80211_S1G_CHANWIDTH_1MHZ: 1MHz operating channel
 * @IEEE80211_S1G_CHANWIDTH_2MHZ: 2MHz operating channel
 * @IEEE80211_S1G_CHANWIDTH_4MHZ: 4MHz operating channel
 * @IEEE80211_S1G_CHANWIDTH_8MHZ: 8MHz operating channel
 * @IEEE80211_S1G_CHANWIDTH_16MHZ: 16MHz operating channel
 */
enum ieee80211_s1g_chanwidth {
        IEEE80211_S1G_CHANWIDTH_1MHZ = 0,
        IEEE80211_S1G_CHANWIDTH_2MHZ = 1,
        IEEE80211_S1G_CHANWIDTH_4MHZ = 3,
        IEEE80211_S1G_CHANWIDTH_8MHZ = 7,
        IEEE80211_S1G_CHANWIDTH_16MHZ = 15,
};

/**
 * enum ieee80211_s1g_pri_chanwidth - S1G primary channel widths
 *      described in IEEE80211-2024 Table 10-39.
 *
 * @IEEE80211_S1G_PRI_CHANWIDTH_2MHZ: 2MHz primary channel
 * @IEEE80211_S1G_PRI_CHANWIDTH_1MHZ: 1MHz primary channel
 */
enum ieee80211_s1g_pri_chanwidth {
        IEEE80211_S1G_PRI_CHANWIDTH_2MHZ = 0,
        IEEE80211_S1G_PRI_CHANWIDTH_1MHZ = 1,
};

/**
 * struct ieee80211_s1g_bcn_compat_ie - S1G Beacon Compatibility element
 * @compat_info: Compatibility Information
 * @beacon_int: Beacon Interval
 * @tsf_completion: TSF Completion
 *
 * This structure represents the payload of the "S1G Beacon
 * Compatibility element" as described in IEEE Std 802.11-2020 section
 * 9.4.2.196.
 */
struct ieee80211_s1g_bcn_compat_ie {
        __le16 compat_info;
        __le16 beacon_int;
        __le32 tsf_completion;
} __packed;

/**
 * struct ieee80211_s1g_oper_ie - S1G Operation element
 * @ch_width: S1G Operation Information Channel Width
 * @oper_class: S1G Operation Information Operating Class
 * @primary_ch: S1G Operation Information Primary Channel Number
 * @oper_ch: S1G Operation Information  Channel Center Frequency
 * @basic_mcs_nss: Basic S1G-MCS and NSS Set
 *
 * This structure represents the payload of the "S1G Operation
 * element" as described in IEEE Std 802.11-2020 section 9.4.2.212.
 */
struct ieee80211_s1g_oper_ie {
        u8 ch_width;
        u8 oper_class;
        u8 primary_ch;
        u8 oper_ch;
        __le16 basic_mcs_nss;
} __packed;

/**
 * struct ieee80211_aid_response_ie - AID Response element
 * @aid: AID/Group AID
 * @switch_count: AID Switch Count
 * @response_int: AID Response Interval
 *
 * This structure represents the payload of the "AID Response element"
 * as described in IEEE Std 802.11-2020 section 9.4.2.194.
 */
struct ieee80211_aid_response_ie {
        __le16 aid;
        u8 switch_count;
        __le16 response_int;
} __packed;

struct ieee80211_s1g_cap {
        u8 capab_info[10];
        u8 supp_mcs_nss[5];
} __packed;

/**
 * ieee80211_s1g_optional_len - determine length of optional S1G beacon fields
 * @fc: frame control bytes in little-endian byteorder
 * Return: total length in bytes of the optional fixed-length fields
 *
 * S1G beacons may contain up to three optional fixed-length fields that
 * precede the variable-length elements. Whether these fields are present
 * is indicated by flags in the frame control field.
 *
 * From IEEE 802.11-2024 section 9.3.4.3:
 *  - Next TBTT field may be 0 or 3 bytes
 *  - Short SSID field may be 0 or 4 bytes
 *  - Access Network Options (ANO) field may be 0 or 1 byte
 */
static inline size_t
ieee80211_s1g_optional_len(__le16 fc)
{
        size_t len = 0;

        if (ieee80211_s1g_has_next_tbtt(fc))
                len += 3;

        if (ieee80211_s1g_has_cssid(fc))
                len += 4;

        if (ieee80211_s1g_has_ano(fc))
                len += 1;

        return len;
}

/* S1G Capabilities Information field */
#define IEEE80211_S1G_CAPABILITY_LEN    15

#define S1G_CAP0_S1G_LONG       BIT(0)
#define S1G_CAP0_SGI_1MHZ       BIT(1)
#define S1G_CAP0_SGI_2MHZ       BIT(2)
#define S1G_CAP0_SGI_4MHZ       BIT(3)
#define S1G_CAP0_SGI_8MHZ       BIT(4)
#define S1G_CAP0_SGI_16MHZ      BIT(5)
#define S1G_CAP0_SUPP_CH_WIDTH  GENMASK(7, 6)

#define S1G_SUPP_CH_WIDTH_2     0
#define S1G_SUPP_CH_WIDTH_4     1
#define S1G_SUPP_CH_WIDTH_8     2
#define S1G_SUPP_CH_WIDTH_16    3
#define S1G_SUPP_CH_WIDTH_MAX(cap) ((1 << FIELD_GET(S1G_CAP0_SUPP_CH_WIDTH, \
                                                    cap[0])) << 1)

#define S1G_CAP1_RX_LDPC        BIT(0)
#define S1G_CAP1_TX_STBC        BIT(1)
#define S1G_CAP1_RX_STBC        BIT(2)
#define S1G_CAP1_SU_BFER        BIT(3)
#define S1G_CAP1_SU_BFEE        BIT(4)
#define S1G_CAP1_BFEE_STS       GENMASK(7, 5)

#define S1G_CAP2_SOUNDING_DIMENSIONS    GENMASK(2, 0)
#define S1G_CAP2_MU_BFER                BIT(3)
#define S1G_CAP2_MU_BFEE                BIT(4)
#define S1G_CAP2_PLUS_HTC_VHT           BIT(5)
#define S1G_CAP2_TRAVELING_PILOT        GENMASK(7, 6)

#define S1G_CAP3_RD_RESPONDER           BIT(0)
#define S1G_CAP3_HT_DELAYED_BA          BIT(1)
#define S1G_CAP3_MAX_MPDU_LEN           BIT(2)
#define S1G_CAP3_MAX_AMPDU_LEN_EXP      GENMASK(4, 3)
#define S1G_CAP3_MIN_MPDU_START         GENMASK(7, 5)

#define S1G_CAP4_UPLINK_SYNC    BIT(0)
#define S1G_CAP4_DYNAMIC_AID    BIT(1)
#define S1G_CAP4_BAT            BIT(2)
#define S1G_CAP4_TIME_ADE       BIT(3)
#define S1G_CAP4_NON_TIM        BIT(4)
#define S1G_CAP4_GROUP_AID      BIT(5)
#define S1G_CAP4_STA_TYPE       GENMASK(7, 6)

#define S1G_CAP5_CENT_AUTH_CONTROL      BIT(0)
#define S1G_CAP5_DIST_AUTH_CONTROL      BIT(1)
#define S1G_CAP5_AMSDU                  BIT(2)
#define S1G_CAP5_AMPDU                  BIT(3)
#define S1G_CAP5_ASYMMETRIC_BA          BIT(4)
#define S1G_CAP5_FLOW_CONTROL           BIT(5)
#define S1G_CAP5_SECTORIZED_BEAM        GENMASK(7, 6)

#define S1G_CAP6_OBSS_MITIGATION        BIT(0)
#define S1G_CAP6_FRAGMENT_BA            BIT(1)
#define S1G_CAP6_NDP_PS_POLL            BIT(2)
#define S1G_CAP6_RAW_OPERATION          BIT(3)
#define S1G_CAP6_PAGE_SLICING           BIT(4)
#define S1G_CAP6_TXOP_SHARING_IMP_ACK   BIT(5)
#define S1G_CAP6_VHT_LINK_ADAPT         GENMASK(7, 6)

#define S1G_CAP7_TACK_AS_PS_POLL                BIT(0)
#define S1G_CAP7_DUP_1MHZ                       BIT(1)
#define S1G_CAP7_MCS_NEGOTIATION                BIT(2)
#define S1G_CAP7_1MHZ_CTL_RESPONSE_PREAMBLE     BIT(3)
#define S1G_CAP7_NDP_BFING_REPORT_POLL          BIT(4)
#define S1G_CAP7_UNSOLICITED_DYN_AID            BIT(5)
#define S1G_CAP7_SECTOR_TRAINING_OPERATION      BIT(6)
#define S1G_CAP7_TEMP_PS_MODE_SWITCH            BIT(7)

#define S1G_CAP8_TWT_GROUPING   BIT(0)
#define S1G_CAP8_BDT            BIT(1)
#define S1G_CAP8_COLOR          GENMASK(4, 2)
#define S1G_CAP8_TWT_REQUEST    BIT(5)
#define S1G_CAP8_TWT_RESPOND    BIT(6)
#define S1G_CAP8_PV1_FRAME      BIT(7)

#define S1G_CAP9_LINK_ADAPT_PER_CONTROL_RESPONSE BIT(0)

#define S1G_OPER_CH_WIDTH_PRIMARY       BIT(0)
#define S1G_OPER_CH_WIDTH_OPER          GENMASK(4, 1)
#define S1G_OPER_CH_PRIMARY_LOCATION    BIT(5)

#define S1G_2M_PRIMARY_LOCATION_LOWER   0
#define S1G_2M_PRIMARY_LOCATION_UPPER   1

#define LISTEN_INT_USF  GENMASK(15, 14)
#define LISTEN_INT_UI   GENMASK(13, 0)

#define IEEE80211_MAX_USF       FIELD_MAX(LISTEN_INT_USF)
#define IEEE80211_MAX_UI        FIELD_MAX(LISTEN_INT_UI)

/* S1G encoding types */
#define IEEE80211_S1G_TIM_ENC_MODE_BLOCK        0
#define IEEE80211_S1G_TIM_ENC_MODE_SINGLE       1
#define IEEE80211_S1G_TIM_ENC_MODE_OLB          2

enum ieee80211_s1g_actioncode {
        WLAN_S1G_AID_SWITCH_REQUEST,
        WLAN_S1G_AID_SWITCH_RESPONSE,
        WLAN_S1G_SYNC_CONTROL,
        WLAN_S1G_STA_INFO_ANNOUNCE,
        WLAN_S1G_EDCA_PARAM_SET,
        WLAN_S1G_EL_OPERATION,
        WLAN_S1G_TWT_SETUP,
        WLAN_S1G_TWT_TEARDOWN,
        WLAN_S1G_SECT_GROUP_ID_LIST,
        WLAN_S1G_SECT_ID_FEEDBACK,
        WLAN_S1G_TWT_INFORMATION = 11,
};

/**
 * ieee80211_is_s1g_short_beacon - check if frame is an S1G short beacon
 * @fc: frame control bytes in little-endian byteorder
 * @variable: pointer to the beacon frame elements
 * @variable_len: length of the frame elements
 * Return: whether or not the frame is an S1G short beacon. As per
 *      IEEE80211-2024 11.1.3.10.1, The S1G beacon compatibility element shall
 *      always be present as the first element in beacon frames generated at a
 *      TBTT (Target Beacon Transmission Time), so any frame not containing
 *      this element must have been generated at a TSBTT (Target Short Beacon
 *      Transmission Time) that is not a TBTT. Additionally, short beacons are
 *      prohibited from containing the S1G beacon compatibility element as per
 *      IEEE80211-2024 9.3.4.3 Table 9-76, so if we have an S1G beacon with
 *      either no elements or the first element is not the beacon compatibility
 *      element, we have a short beacon.
 */
static inline bool ieee80211_is_s1g_short_beacon(__le16 fc, const u8 *variable,
                                                 size_t variable_len)
{
        if (!ieee80211_is_s1g_beacon(fc))
                return false;

        /*
         * If the frame does not contain at least 1 element (this is perfectly
         * valid in a short beacon) and is an S1G beacon, we have a short
         * beacon.
         */
        if (variable_len < 2)
                return true;

        return variable[0] != WLAN_EID_S1G_BCN_COMPAT;
}

struct s1g_tim_aid {
        u16 aid;
        u8 target_blk; /* Target block index */
        u8 target_subblk; /* Target subblock index */
        u8 target_subblk_bit; /* Target subblock bit */
};

struct s1g_tim_enc_block {
        u8 enc_mode;
        bool inverse;
        const u8 *ptr;
        u8 len;

        /*
         * For an OLB encoded block that spans multiple blocks, this
         * is the offset into the span described by that encoded block.
         */
        u8 olb_blk_offset;
};

/*
 * Helper routines to quickly extract the length of an encoded block. Validation
 * is also performed to ensure the length extracted lies within the TIM.
 */

static inline int ieee80211_s1g_len_bitmap(const u8 *ptr, const u8 *end)
{
        u8 blkmap;
        u8 n_subblks;

        if (ptr >= end)
                return -EINVAL;

        blkmap = *ptr;
        n_subblks = hweight8(blkmap);

        if (ptr + 1 + n_subblks > end)
                return -EINVAL;

        return 1 + n_subblks;
}

static inline int ieee80211_s1g_len_single(const u8 *ptr, const u8 *end)
{
        return (ptr + 1 > end) ? -EINVAL : 1;
}

static inline int ieee80211_s1g_len_olb(const u8 *ptr, const u8 *end)
{
        if (ptr >= end)
                return -EINVAL;

        return (ptr + 1 + *ptr > end) ? -EINVAL : 1 + *ptr;
}

/*
 * Enumerate all encoded blocks until we find the encoded block that describes
 * our target AID. OLB is a special case as a single encoded block can describe
 * multiple blocks as a single encoded block.
 */
static inline int ieee80211_s1g_find_target_block(struct s1g_tim_enc_block *enc,
                                                  const struct s1g_tim_aid *aid,
                                                  const u8 *ptr, const u8 *end)
{
        /* need at least block-control octet */
        while (ptr + 1 <= end) {
                u8 ctrl = *ptr++;
                u8 mode = ctrl & 0x03;
                bool contains, inverse = ctrl & BIT(2);
                u8 span, blk_off = ctrl >> 3;
                int len;

                switch (mode) {
                case IEEE80211_S1G_TIM_ENC_MODE_BLOCK:
                        len = ieee80211_s1g_len_bitmap(ptr, end);
                        contains = blk_off == aid->target_blk;
                        break;
                case IEEE80211_S1G_TIM_ENC_MODE_SINGLE:
                        len = ieee80211_s1g_len_single(ptr, end);
                        contains = blk_off == aid->target_blk;
                        break;
                case IEEE80211_S1G_TIM_ENC_MODE_OLB:
                        len = ieee80211_s1g_len_olb(ptr, end);
                        /*
                         * An OLB encoded block can describe more then one
                         * block, meaning an encoded OLB block can span more
                         * then a single block.
                         */
                        if (len > 0) {
                                /* Minus one for the length octet */
                                span = DIV_ROUND_UP(len - 1, 8);
                                /*
                                 * Check if our target block lies within the
                                 * block span described by this encoded block.
                                 */
                                contains = (aid->target_blk >= blk_off) &&
                                           (aid->target_blk < blk_off + span);
                        }
                        break;
                default:
                        return -EOPNOTSUPP;
                }

                if (len < 0)
                        return len;

                if (contains) {
                        enc->enc_mode = mode;
                        enc->inverse = inverse;
                        enc->ptr = ptr;
                        enc->len = (u8)len;
                        enc->olb_blk_offset = blk_off;
                        return 0;
                }

                ptr += len;
        }

        return -ENOENT;
}

static inline bool ieee80211_s1g_parse_bitmap(struct s1g_tim_enc_block *enc,
                                              struct s1g_tim_aid *aid)
{
        const u8 *ptr = enc->ptr;
        u8 blkmap = *ptr++;

        /*
         * If our block bitmap does not contain a set bit that corresponds
         * to our AID, it could mean a variety of things depending on if
         * the encoding mode is inverted or not.
         *
         * 1. If inverted, it means the entire subblock is present and hence
         *    our AID has been set.
         * 2. If not inverted, it means our subblock is not present and hence
         *    it is all zero meaning our AID is not set.
         */
        if (!(blkmap & BIT(aid->target_subblk)))
                return enc->inverse;

        /*
         * Increment ptr by the number of set subblocks that appear before our
         * target subblock. If our target subblock is 0, do nothing as ptr
         * already points to our target subblock.
         */
        if (aid->target_subblk)
                ptr += hweight8(blkmap & GENMASK(aid->target_subblk - 1, 0));

        return !!(*ptr & BIT(aid->target_subblk_bit)) ^ enc->inverse;
}

static inline bool ieee80211_s1g_parse_single(struct s1g_tim_enc_block *enc,
                                              struct s1g_tim_aid *aid)
{
        /*
         * Single AID mode describes, as the name suggests, a single AID
         * within the block described by the encoded block. The octet
         * contains the 6 LSBs of the AID described in the block. The other
         * 2 bits are reserved. When inversed, every single AID described
         * by the current block have buffered traffic except for the AID
         * described in the single AID octet.
         */
        return ((*enc->ptr & 0x3f) == (aid->aid & 0x3f)) ^ enc->inverse;
}

static inline bool ieee80211_s1g_parse_olb(struct s1g_tim_enc_block *enc,
                                           struct s1g_tim_aid *aid)
{
        const u8 *ptr = enc->ptr;
        u8 blk_len = *ptr++;
        /*
         * Given an OLB encoded block that describes multiple blocks,
         * calculate the offset into the span. Then calculate the
         * subblock location normally.
         */
        u16 span_offset = aid->target_blk - enc->olb_blk_offset;
        u16 subblk_idx = span_offset * 8 + aid->target_subblk;

        if (subblk_idx >= blk_len)
                return enc->inverse;

        return !!(ptr[subblk_idx] & BIT(aid->target_subblk_bit)) ^ enc->inverse;
}

/*
 * An S1G PVB has 3 non optional encoding types, each that can be inverted.
 * An S1G PVB is constructed with zero or more encoded block subfields. Each
 * encoded block represents a single "block" of AIDs (64), and each encoded
 * block can contain one of the 3 encoding types alongside a single bit for
 * whether the bits should be inverted.
 *
 * As the standard makes no guarantee about the ordering of encoded blocks,
 * we must parse every encoded block in the worst case scenario given an
 * AID that lies within the last block.
 */
static inline bool ieee80211_s1g_check_tim(const struct ieee80211_tim_ie *tim,
                                           u8 tim_len, u16 aid)
{
        int err;
        struct s1g_tim_aid target_aid;
        struct s1g_tim_enc_block enc_blk;

        if (tim_len < 3)
                return false;

        target_aid.aid = aid;
        target_aid.target_blk = (aid >> 6) & 0x1f;
        target_aid.target_subblk = (aid >> 3) & 0x7;
        target_aid.target_subblk_bit = aid & 0x7;

        /*
         * Find our AIDs target encoded block and fill &enc_blk with the
         * encoded blocks information. If no entry is found or an error
         * occurs return false.
         */
        err = ieee80211_s1g_find_target_block(&enc_blk, &target_aid,
                                              tim->virtual_map,
                                              (const u8 *)tim + tim_len + 2);
        if (err)
                return false;

        switch (enc_blk.enc_mode) {
        case IEEE80211_S1G_TIM_ENC_MODE_BLOCK:
                return ieee80211_s1g_parse_bitmap(&enc_blk, &target_aid);
        case IEEE80211_S1G_TIM_ENC_MODE_SINGLE:
                return ieee80211_s1g_parse_single(&enc_blk, &target_aid);
        case IEEE80211_S1G_TIM_ENC_MODE_OLB:
                return ieee80211_s1g_parse_olb(&enc_blk, &target_aid);
        default:
                return false;
        }
}

#endif /* LINUX_IEEE80211_S1G_H */