kawaiifi 0.2.0

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

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

use crate::ies::{Field, IeId};

#[derive(Debug, Clone, PartialEq, Eq, Hash, DekuRead, DekuWrite, Serialize, Deserialize)]
#[deku(ctx = "len: usize")]
pub struct TimeAdvertisement {
    pub timing_capabilities: TimingCapabilities,
    #[deku(
        cond = "*timing_capabilities == TimingCapabilities::TimestampOffsetBasedOnUtc",
        bytes = 10
    )]
    pub time_value_ns: Option<i128>,
    #[deku(cond = "*timing_capabilities == TimingCapabilities::UtcTimeAtWhichTsfTimerIs0")]
    pub time_value: Option<TimeValue>,
    #[deku(
        cond = "*timing_capabilities == TimingCapabilities::TimestampOffsetBasedOnUtc || *timing_capabilities == TimingCapabilities::UtcTimeAtWhichTsfTimerIs0",
        bytes = 5
    )]
    pub time_error: Option<u64>,
    #[deku(
        cond = "*timing_capabilities == TimingCapabilities::UtcTimeAtWhichTsfTimerIs0",
        bytes = 1
    )]
    pub time_update_counter: Option<u8>,
    #[deku(
        cond = "matches!(*timing_capabilities, TimingCapabilities::Reserved(_))",
        count = "len.saturating_sub(1)"
    )]
    pub reserved: Option<Vec<u8>>,
}

impl TimeAdvertisement {
    pub const NAME: &'static str = "Time Advertisement";
    pub const ID: u8 = 69;
    pub const ID_EXT: Option<u8> = None;
    pub(crate) const IE_ID: IeId = IeId::new(Self::ID, Self::ID_EXT);

    pub fn summary(&self) -> String {
        self.timing_capabilities.to_string()
    }

    pub fn fields(&self) -> Vec<Field> {
        let mut fields = vec![
            Field::builder()
                .title("Timing Capabilities")
                .value(self.timing_capabilities)
                .bytes(self.timing_capabilities.to_bytes().unwrap_or_default())
                .build(),
        ];

        if let Some(time_value_ns) = self.time_value_ns {
            fields.push(
                Field::builder()
                    .title("Time Value")
                    .value(time_value_ns)
                    .units("ns")
                    .bytes(time_value_ns.to_le_bytes()[0..10].to_vec())
                    .build(),
            );
        }

        if let Some(time_value) = &self.time_value {
            fields.push(time_value.to_field());
        }

        if let Some(time_error) = self.time_error {
            match &self.timing_capabilities {
                TimingCapabilities::TimestampOffsetBasedOnUtc => {
                    fields.push(
                        Field::builder()
                            .title("Time Error")
                            .value(time_error)
                            .units("ns")
                            .bytes(time_error.to_le_bytes()[..5].to_vec())
                            .build(),
                    );
                }
                TimingCapabilities::UtcTimeAtWhichTsfTimerIs0 => {
                    fields.push(
                        Field::builder()
                            .title("Time Error")
                            .value(time_error)
                            .units("ms")
                            .bytes(time_error.to_le_bytes()[..5].to_vec())
                            .build(),
                    );
                }
                _ => (),
            };
        }

        if let Some(time_update_counter) = self.time_update_counter {
            fields.push(
                Field::builder()
                    .title("Time Update Counter")
                    .value(time_update_counter)
                    .byte(time_update_counter)
                    .build(),
            );
        }

        fields
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DekuRead, DekuWrite, Serialize, Deserialize)]
#[deku(id_type = "u8")]
#[repr(u8)]
pub enum TimingCapabilities {
    NoStandardizedExternalTimeSource = 0,
    TimestampOffsetBasedOnUtc = 1,
    UtcTimeAtWhichTsfTimerIs0 = 2,
    #[deku(id_pat = "_")]
    Reserved(u8),
}

impl Display for TimingCapabilities {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NoStandardizedExternalTimeSource => {
                write!(f, "No standardized external time source")
            }
            Self::TimestampOffsetBasedOnUtc => write!(f, "Timestamp offset based on UTC"),
            Self::UtcTimeAtWhichTsfTimerIs0 => write!(f, "UTC time at which the TSF timer is 0"),
            Self::Reserved(val) => write!(f, "Reserved ({})", val),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, DekuRead, DekuWrite, Serialize, Deserialize)]
pub struct TimeValue {
    #[deku(bytes = 2)]
    pub year: u16,
    #[deku(bytes = 1)]
    pub month: u8,
    #[deku(bytes = 1)]
    pub day_of_month: u8,
    #[deku(bytes = 1)]
    pub hours: u8,
    #[deku(bytes = 1)]
    pub minutes: u8,
    #[deku(bytes = 1)]
    pub seconds: u8,
    #[deku(bytes = 2)]
    pub milliseconds: u16,
    #[deku(bytes = 1)]
    reserved: u8,
}

impl TimeValue {
    pub fn to_field(&self) -> Field {
        Field::builder()
            .title("Time Value")
            .value("")
            .subfields([
                Field::builder()
                    .title("Year")
                    .value(self.year)
                    .bytes(self.year.to_le_bytes().to_vec())
                    .build(),
                Field::builder()
                    .title("Month")
                    .value(self.month)
                    .byte(self.month)
                    .build(),
                Field::builder()
                    .title("Day")
                    .value(self.day_of_month)
                    .byte(self.day_of_month)
                    .build(),
                Field::builder()
                    .title("Hours")
                    .value(self.hours)
                    .byte(self.hours)
                    .build(),
                Field::builder()
                    .title("Minutes")
                    .value(self.minutes)
                    .byte(self.minutes)
                    .build(),
                Field::builder()
                    .title("Seconds")
                    .value(self.seconds)
                    .byte(self.seconds)
                    .build(),
                Field::builder()
                    .title("Milliseconds")
                    .value(self.milliseconds)
                    .bytes(self.milliseconds.to_le_bytes().to_vec())
                    .build(),
                Field::builder()
                    .title("Reserved")
                    .value(self.reserved)
                    .byte(self.reserved)
                    .build(),
            ])
            .bytes(self.to_bytes().unwrap_or_default())
            .build()
    }
}