kawaiifi 0.2.0

Wi-Fi scanning library for Linux, macOS, and Windows.
Documentation
use std::fmt::Display;

use deku::{DekuContainerWrite, DekuError, DekuRead, DekuWrite};
use num_enum::TryFromPrimitive;
use serde::{Deserialize, Serialize};

use crate::ChannelWidth;
use crate::ies::{BitRange, Field, IeId, ht_capabilities::SupportedMcsSet, write_bits_lsb0};

#[derive(Debug, Clone, PartialEq, Eq, Hash, DekuRead, DekuWrite, Serialize, Deserialize)]
pub struct HtOperation {
    #[deku(bytes = 1)]
    pub primary_channel: u8,
    pub ht_operation_information: HtOperationInformation,
    pub basic_ht_mcs_set: SupportedMcsSet,
}

impl HtOperation {
    pub const NAME: &'static str = "HT Operation";
    pub const ID: u8 = 61;
    pub const ID_EXT: Option<u8> = None;
    pub(crate) const IE_ID: IeId = IeId::new(Self::ID, Self::ID_EXT);
    pub const LENGTH: usize = 22;

    pub fn channel_width(&self) -> ChannelWidth {
        match self.ht_operation_information.sta_channel_width {
            SupportedChannelWidths::TwentyMhz => ChannelWidth::TwentyMhz,
            SupportedChannelWidths::Any => ChannelWidth::FortyMhz,
        }
    }

    pub fn summary(&self) -> String {
        format!(
            "Primary Channel: {}, Secondary Channel: {}",
            self.primary_channel, self.ht_operation_information.secondary_channel_offset
        )
    }

    pub fn fields(&self) -> Vec<Field> {
        vec![
            Field::builder()
                .title("Primary Channel")
                .value(self.primary_channel)
                .byte(self.primary_channel)
                .build(),
            self.ht_operation_information.to_field(),
            self.basic_ht_mcs_set.to_field("Basic HT-MCS Set"),
        ]
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DekuRead, DekuWrite, Serialize, Deserialize)]
#[deku(bit_order = "lsb")]
pub struct HtOperationInformation {
    #[deku(
        bits = 2,
        map = "|value: u8| SecondaryChannelOffset::try_from(value).map_err(|_| DekuError::Parse(\"Invalid SecondaryChannelOffset\".into()))",
        writer = "write_bits_lsb0(deku::writer, self.secondary_channel_offset as u8, 2)"
    )]
    pub secondary_channel_offset: SecondaryChannelOffset,
    #[deku(
        bits = 1,
        map = "|value: u8| SupportedChannelWidths::try_from(value).map_err(|_| DekuError::Parse(\"SupportedChannelWidths\".into()))",
        writer = "write_bits_lsb0(deku::writer, self.sta_channel_width as u8, 1)"
    )]
    pub sta_channel_width: SupportedChannelWidths,
    #[deku(bits = 1)]
    pub rifs_mode: bool,
    #[deku(bits = 4)]
    reserved_1: u8,
    #[deku(
        bits = 2,
        map = "|value: u8| HtProtection::try_from(value).map_err(|_| DekuError::Parse(\"Invalid HT Protection\".into()))",
        writer = "write_bits_lsb0(deku::writer, self.ht_protection as u8, 2)"
    )]
    pub ht_protection: HtProtection,
    #[deku(bits = 1)]
    pub nongreenfield_ht_stas_present: bool,
    #[deku(bits = 1)]
    reserved_2: bool,
    #[deku(bits = 1)]
    pub obss_non_ht_stas_present: bool,
    #[deku(bits = 8)]
    pub channel_center_frequency_segment_two: u8,
    #[deku(bits = 3)]
    reserved_3: u8,
    #[deku(bits = 6)]
    reserved_4: u8,
    #[deku(bits = 1)]
    pub dual_beacon: bool,
    #[deku(bits = 1)]
    pub dual_cts_protection: bool,
    #[deku(bits = 1)]
    pub stbc_beacon: bool,
    #[deku(bits = 7)]
    reserved_5: u8,
}

impl HtOperationInformation {
    pub fn to_field(&self) -> Field {
        let bytes = self.to_bytes().unwrap_or_default();
        Field::builder()
            .title("HT Operation Information")
            .value("")
            .subfields([
                Field::builder()
                    .title("Secondary Channel Offset")
                    .value(self.secondary_channel_offset)
                    .bits(BitRange::new(&bytes, 0, 2))
                    .build(),
                Field::builder()
                    .title("STA Channel Width")
                    .value(self.sta_channel_width)
                    .units("MHz")
                    .bits(BitRange::new(&bytes, 2, 1))
                    .build(),
                Field::builder()
                    .title("RIFS Mode")
                    .value(self.rifs_mode)
                    .bits(BitRange::new(&bytes, 3, 1))
                    .build(),
                Field::reserved(BitRange::new(&bytes, 4, 4)),
                Field::builder()
                    .title("HT Protection")
                    .value(self.ht_protection)
                    .bits(BitRange::new(&bytes, 8, 2))
                    .build(),
                Field::builder()
                    .title("Nongreenfield HT STAs Present")
                    .value(self.nongreenfield_ht_stas_present)
                    .bits(BitRange::new(&bytes, 10, 1))
                    .build(),
                Field::reserved(BitRange::new(&bytes, 11, 1)),
                Field::builder()
                    .title("OBSS Non-HT STAs Present")
                    .value(self.obss_non_ht_stas_present)
                    .bits(BitRange::new(&bytes, 12, 1))
                    .build(),
                Field::builder()
                    .title("Channel Center Frequency Segment 2")
                    .value(self.channel_center_frequency_segment_two)
                    .bits(BitRange::new(&bytes, 13, 8))
                    .build(),
                Field::reserved(BitRange::new(&bytes, 21, 3)),
                Field::reserved(BitRange::new(&bytes, 24, 6)),
                Field::builder()
                    .title("Dual Beacon")
                    .value(self.dual_beacon)
                    .bits(BitRange::new(&bytes, 30, 1))
                    .build(),
                Field::builder()
                    .title("Dual CTS Protection")
                    .value(self.dual_cts_protection)
                    .bits(BitRange::new(&bytes, 31, 1))
                    .build(),
                Field::builder()
                    .title("STBC Beacon")
                    .value(self.stbc_beacon)
                    .bits(BitRange::new(&bytes, 32, 1))
                    .build(),
                Field::reserved(BitRange::new(&bytes, 33, 7)),
            ])
            .bytes(bytes)
            .build()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Serialize, Deserialize)]
#[repr(u8)]
pub enum SecondaryChannelOffset {
    NoSecondary = 0,
    AbovePrimary = 1,
    BelowPrimary = 3,
}

impl Display for SecondaryChannelOffset {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NoSecondary => write!(f, "None"),
            Self::AbovePrimary => write!(f, "Above Primary"),
            Self::BelowPrimary => write!(f, "Below Primary"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Serialize, Deserialize)]
#[repr(u8)]
pub enum SupportedChannelWidths {
    TwentyMhz = 0,
    Any = 1,
}

impl Display for SupportedChannelWidths {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::TwentyMhz => write!(f, "20"),
            Self::Any => write!(f, "20/40"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Serialize, Deserialize)]
#[repr(u8)]
pub enum HtProtection {
    NoProtection = 0,
    NonmemberProtection,
    TwentyMhzProtection,
    NonHtMixed,
}

impl Display for HtProtection {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NoProtection => write!(f, "No Protection"),
            Self::NonmemberProtection => write!(f, "Nonmember Protection"),
            Self::TwentyMhzProtection => write!(f, "20 MHz Protection"),
            Self::NonHtMixed => write!(f, "Non-HT Mixed"),
        }
    }
}