From b85be02700cee562b4b3e50ddd8ff39689a51291 Mon Sep 17 00:00:00 2001 From: Arne Beer Date: Sat, 11 Jan 2025 18:26:40 +0100 Subject: [PATCH] add: All the changes from AngryOxide --- CHANGELOG.md | 22 + examples/capture_example/Cargo.toml | 6 +- libwifi/Cargo.toml | 22 +- libwifi/benches/parse_beacon.rs | 4 +- libwifi/benches/parse_data.rs | 4 +- libwifi/src/frame/components/frame_control.rs | 13 +- libwifi/src/frame/components/header.rs | 56 +- libwifi/src/frame/components/mac_address.rs | 253 +++++- libwifi/src/frame/components/mod.rs | 8 +- .../src/frame/components/sequence_control.rs | 14 + libwifi/src/frame/components/station_info.rs | 815 +++++++++++++++++- libwifi/src/frame/control/rts_cts_ack.rs | 33 + libwifi/src/frame/data/data.rs | 133 +++ libwifi/src/frame/data/mod.rs | 12 + libwifi/src/frame/data/qos_data.rs | 387 +++++++++ libwifi/src/frame/management/action.rs | 94 +- libwifi/src/frame/management/association.rs | 126 +++ .../src/frame/management/authentication.rs | 170 ++++ libwifi/src/frame/management/beacon.rs | 23 + libwifi/src/frame/management/mod.rs | 11 +- libwifi/src/frame/management/probe.rs | 37 + libwifi/src/frame/mod.rs | 22 +- libwifi/src/frame_types.rs | 69 +- libwifi/src/lib.rs | 56 +- libwifi/src/parsers/components/mod.rs | 2 +- .../parsers/components/sequence_control.rs | 25 +- .../src/parsers/components/station_info.rs | 538 +++++++++++- libwifi/src/parsers/frame_types/data.rs | 250 +++++- libwifi/src/parsers/frame_types/management.rs | 158 +++- libwifi/tests/control_frames.rs | 10 +- libwifi/tests/data_frames.rs | 6 +- libwifi/tests/management_frames.rs | 6 +- libwifi_macros/Cargo.toml | 8 +- 33 files changed, 3255 insertions(+), 138 deletions(-) create mode 100644 libwifi/src/frame/management/authentication.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b561822..4571dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - unreleased + +This release encompasses a huge amount of backports from @Ragnt, which vendored `libwifi` in [AngryOxide](https://github.com/Ragnt/AngryOxide) and continued development over there. + +@Ragnt added a vast amount of additional features and parsers for data structures. +They allowed the backport of their changes and republished their vendored library under a permissable license [over here](https://github.com/Ragnt/libwifi). + +The most prominent features being: + +- Frame encoding. Parsed or constructed frames can now be brought back into byte representation. +- CRC Frame validation. +- More `MacAddress` helper functions and parsed formats. +- `MacAddressGlob` to match certain MacAddress spaces. +- Many more parsed `StationInfo` fields. +- Various parsers for new control and data frames, including + - `CTS` and `Ack` frames for the `RTS -> CTS -> Data -> ACK` flow. + - Complete `Data` frame parsing. + - `DataCfAck` `DataCfPoll`, `DataCfAckCfPoll`, `CfPoll` `CfAckCfPoll` + - `QosDataCfAck`, `QosDataCfPoll`, `QosDataCfAckCfPoll`, `QosCfPoll`, `QosCfAckCfPoll` +- Deauthentication reason parsing +- `ReassociationRequest`, `ReassociationResponse` + ## [0.3.1] - unreleased ### Changes diff --git a/examples/capture_example/Cargo.toml b/examples/capture_example/Cargo.toml index 5c98412..b420cf0 100644 --- a/examples/capture_example/Cargo.toml +++ b/examples/capture_example/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "capture_example" -version = "0.1.0" authors = ["Arne Beer "] edition = "2021" +name = "capture_example" +version = "0.1.0" [dependencies] anyhow = "1" better-panic = "0.3" -clap = { version = "4", features = ["derive", "cargo"] } +clap = { version = "4", features = ["cargo", "derive"] } libwifi = { path = "../../libwifi" } pcap = { version = "2", features = ['capture-stream'] } radiotap = "1.3" diff --git a/libwifi/Cargo.toml b/libwifi/Cargo.toml index 4e42e17..ec66fb1 100644 --- a/libwifi/Cargo.toml +++ b/libwifi/Cargo.toml @@ -1,31 +1,33 @@ [package] name = "libwifi" -description = "A library for parsing IEEE 802.11 frames and handling wifi interfaces." version = "0.3.1" -keywords = ["wifi", "802_11", "parser", "frame"] -readme = "README.md" +description = "A library for parsing IEEE 802.11 frames and handling wifi interfaces." +keywords = ["802_11", "frame", "parser", "wifi"] authors.workspace = true +edition.workspace = true homepage.workspace = true -repository.workspace = true license.workspace = true -edition.workspace = true +repository.workspace = true rust-version.workspace = true [[bench]] -name = "parse_beacon" harness = false +name = "parse_beacon" [[bench]] -name = "parse_data" harness = false +name = "parse_data" [dependencies] +byteorder = "1.5.0" +crc = "3.0.1" +enum_dispatch = "0.3" +libwifi_macros = { version = "0.0.2", path = "../libwifi_macros" } log = "0.4" nom = "7" -thiserror = "2.0" +rand = "0.8" strum_macros = "0.26" -enum_dispatch = "0.3" -libwifi_macros = { version = "0.0.2", path = "../libwifi_macros" } +thiserror = "2.0" [dev-dependencies] criterion = "0.5" diff --git a/libwifi/benches/parse_beacon.rs b/libwifi/benches/parse_beacon.rs index cb528de..76da48e 100644 --- a/libwifi/benches/parse_beacon.rs +++ b/libwifi/benches/parse_beacon.rs @@ -39,7 +39,7 @@ const BEACON_PAYLOAD: [u8; 272] = [ pub fn parse_beacon(crit: &mut Criterion) { let mut rng = thread_rng(); let random: u8 = rng.gen(); - let mut payload = BEACON_PAYLOAD.clone(); + let mut payload = BEACON_PAYLOAD; // Log raw byte throughput let mut group = crit.benchmark_group("parsers"); @@ -49,7 +49,7 @@ pub fn parse_beacon(crit: &mut Criterion) { group.bench_function("Parse beacon", |bencher| { bencher.iter(|| { payload[270] = random; - assert!(parse_frame(&BEACON_PAYLOAD).is_ok()) + assert!(parse_frame(&BEACON_PAYLOAD, false).is_ok()) }) }); group.finish() diff --git a/libwifi/benches/parse_data.rs b/libwifi/benches/parse_data.rs index ff4676c..0075ba0 100644 --- a/libwifi/benches/parse_data.rs +++ b/libwifi/benches/parse_data.rs @@ -20,7 +20,7 @@ pub fn parse_data(crit: &mut Criterion) { // Add some random variable to prevent aggressive compiler optimizations; let mut rng = thread_rng(); let random: u8 = rng.gen(); - let mut payload = DATA_PAYLOAD.clone(); + let mut payload = DATA_PAYLOAD; // Log raw byte throughput let mut group = crit.benchmark_group("parsers"); @@ -30,7 +30,7 @@ pub fn parse_data(crit: &mut Criterion) { group.bench_function("Parse data", |bencher| { bencher.iter(|| { payload[111] = random; - assert!(parse_frame(&payload).is_ok()); + assert!(parse_frame(&payload, false).is_ok()); }) }); group.finish() diff --git a/libwifi/src/frame/components/frame_control.rs b/libwifi/src/frame/components/frame_control.rs index 77ae5b1..f7716e2 100644 --- a/libwifi/src/frame/components/frame_control.rs +++ b/libwifi/src/frame/components/frame_control.rs @@ -39,7 +39,7 @@ pub struct FrameControl { pub frame_subtype: FrameSubType, pub flags: u8, } - +// impl FrameControl { pub fn to_ds(&self) -> bool { flag_is_set(self.flags, 0) @@ -72,6 +72,17 @@ impl FrameControl { pub fn order(&self) -> bool { flag_is_set(self.flags, 7) } + + pub fn encode(&self) -> [u8; 2] { + let protocol_version_bits = self.protocol_version & 0b11; // 2 bits + let frame_type_bits = (self.frame_type as u8 & 0b11) << 2; // 2 bits + let frame_subtype_bits = (self.frame_subtype.to_bytes() & 0b1111) << 4; // 4 bits + + let first_byte = frame_subtype_bits | frame_type_bits | protocol_version_bits; + let second_byte = self.flags; // Assuming flags fit into one byte + + [first_byte, second_byte] + } } #[cfg(test)] diff --git a/libwifi/src/frame/components/header.rs b/libwifi/src/frame/components/header.rs index 52bc318..93011be 100644 --- a/libwifi/src/frame/components/header.rs +++ b/libwifi/src/frame/components/header.rs @@ -36,6 +36,28 @@ pub struct ManagementHeader { pub sequence_control: SequenceControl, } +impl ManagementHeader { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Serialize frame control + bytes.extend_from_slice(&self.frame_control.encode()); + + // Serialize duration (2 bytes, big-endian) + bytes.extend_from_slice(&self.duration); + + // Serialize MAC addresses + bytes.extend_from_slice(&self.address_1.encode()); + bytes.extend_from_slice(&self.address_2.encode()); + bytes.extend_from_slice(&self.address_3.encode()); + + // Serialize sequence control + bytes.extend_from_slice(&self.sequence_control.encode()); + + bytes + } +} + /// Which address is used in which way, depends on a combination of /// - two flags in the FrameControl header. /// - the Type/Subtype constellation. @@ -104,7 +126,7 @@ impl Addresses for ManagementHeader { /// Representation of a data frame header. This format is used by all data frames! /// /// It's very similar to the format of the management header, but there are some slight -/// differences, since they allow a forth address and Quality of Service (QoS) data. +/// differences, since they allow a fourth address and Quality of Service (QoS) data. /// /// Structure: /// @@ -137,6 +159,38 @@ pub struct DataHeader { pub qos: Option<[u8; 2]>, } +impl DataHeader { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Serialize frame control + bytes.extend_from_slice(&self.frame_control.encode()); + + // Serialize duration (2 bytes) + bytes.extend_from_slice(&self.duration); + + // Serialize MAC addresses + bytes.extend_from_slice(&self.address_1.encode()); + bytes.extend_from_slice(&self.address_2.encode()); + bytes.extend_from_slice(&self.address_3.encode()); + + // Serialize sequence control + bytes.extend_from_slice(&self.sequence_control.encode()); + + // Serialize address 4 if present + if let Some(addr) = &self.address_4 { + bytes.extend_from_slice(&addr.encode()); + } + + // Serialize QoS if present + if let Some(qos) = &self.qos { + bytes.extend_from_slice(qos); + } + + bytes + } +} + impl Addresses for DataHeader { /// Return the mac address of the sender fn src(&self) -> Option<&MacAddress> { diff --git a/libwifi/src/frame/components/mac_address.rs b/libwifi/src/frame/components/mac_address.rs index c662219..164b71b 100644 --- a/libwifi/src/frame/components/mac_address.rs +++ b/libwifi/src/frame/components/mac_address.rs @@ -1,4 +1,7 @@ use std::fmt; +use std::hash::Hash; + +use rand::{thread_rng, Rng, RngCore}; /// This is our representation of a MAC-address /// @@ -10,10 +13,83 @@ use std::fmt; /// // -> true /// ``` /// -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, Eq, PartialEq, Copy, Ord, PartialOrd)] pub struct MacAddress(pub [u8; 6]); impl MacAddress { + pub fn from_vec(vec: Vec) -> Option { + if vec.len() == 6 { + let mut arr = [0u8; 6]; + for (place, element) in arr.iter_mut().zip(vec.iter()) { + *place = *element; + } + Some(MacAddress(arr)) + } else { + // Return None if the Vec is not exactly 6 bytes long + None + } + } + + /// Generate u64. + pub fn to_u64(&self) -> u64 { + let bytes = self.0; + (bytes[0] as u64) << 40 + | (bytes[1] as u64) << 32 + | (bytes[2] as u64) << 24 + | (bytes[3] as u64) << 16 + | (bytes[4] as u64) << 8 + | (bytes[5] as u64) + } + + /// Generate string with delimitters. + pub fn to_long_string(&self) -> String { + format!( + "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], + ) + } + + /// Generate random valid mac + pub fn random() -> Self { + loop { + let mac = MacAddress(generate_random_bytes(6).try_into().unwrap()); + if mac.is_real_device() { + return mac; + } + } + } + + pub fn broadcast() -> Self { + MacAddress([255, 255, 255, 255, 255, 255]) + } + + pub fn zeroed() -> Self { + MacAddress([0, 0, 0, 0, 0, 0]) + } + + /// Generate a random MAC address using the same OUI as the given MAC address + pub fn random_with_oui(other: &MacAddress) -> Self { + let mut rng = rand::thread_rng(); + let mut new_mac = other.0; + new_mac[3..6].fill_with(|| rng.gen()); + MacAddress(new_mac) + } + + /// Encode mac address for network. + pub fn encode(&self) -> [u8; 6] { + self.0 + } + + /// Check if this is a private address (locally set bit) + pub fn is_private(&self) -> bool { + self.0[0] & 0x02 != 0 + } + + /// Check if this is a multicast address + pub fn is_mcast(&self) -> bool { + self.0[0] % 2 == 1 + } + /// Check whether this MAC addresses the whole network. pub fn is_broadcast(&self) -> bool { self.0 == [255, 255, 255, 255, 255, 255] @@ -54,7 +130,8 @@ impl MacAddress { || self.is_broadcast() || self.is_ipv4_multicast() || self.is_groupcast() - || self.is_spanning_tree()) + || self.is_spanning_tree() + || self.is_mcast()) } } @@ -62,8 +139,8 @@ impl fmt::Display for MacAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", - self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] + "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], ) } } @@ -82,17 +159,52 @@ impl fmt::Display for MacParseError { impl std::error::Error for MacParseError {} +/// Parse a string mac representation. +/// +/// ``` +/// use libwifi::frame::components::MacAddress; +/// use std::str::FromStr; +/// +/// let mac = MacAddress([12, 157, 146, 197, 170, 127]); +/// +/// let colon = MacAddress::from_str("0c:9d:92:c5:aa:7f").unwrap(); +/// assert_eq!(colon, mac); +/// +/// let dash = MacAddress::from_str("0c-9d-92-c5-aa-7f").unwrap(); +/// assert_eq!(dash, mac); +/// +/// let no_delimiter = MacAddress::from_str("0c9d92c5aa7f").unwrap(); +/// assert_eq!(no_delimiter, mac); +/// ``` impl std::str::FromStr for MacAddress { type Err = MacParseError; fn from_str(input: &str) -> Result { let mut array = [0u8; 6]; - let bytes: Vec<&str> = input.split(|c| c == ':').collect(); + let input_lower = input.to_lowercase(); + // Check if the input contains colons, and split accordingly + let bytes: Vec<&str> = if input_lower.contains(':') { + input_lower.split(':').collect() + } else if input.contains('-') { + input_lower.split('-').collect() + } else if input_lower.len() == 12 { + // If the input doesn't contain colons and is 12 characters long + input_lower + .as_bytes() + .chunks(2) + .map(|chunk| std::str::from_utf8(chunk).unwrap_or("")) + .collect() + } else { + return Err(MacParseError::InvalidLength); + }; + + // Validate the number of bytes if bytes.len() != 6 { return Err(MacParseError::InvalidLength); } + // Parse each byte for (count, byte) in bytes.iter().enumerate() { array[count] = u8::from_str_radix(byte, 16).map_err(|_| MacParseError::InvalidDigit)?; } @@ -101,19 +213,128 @@ impl std::str::FromStr for MacAddress { } } -#[cfg(test)] -mod test { - use super::*; +pub fn generate_random_bytes(x: usize) -> Vec { + let mut rng = thread_rng(); + let length = x; + let mut bytes = vec![0u8; length]; + rng.fill_bytes(&mut bytes); + // Ensure the first byte is even + if !bytes.is_empty() { + bytes[0] &= 0xFE; // 0xFE is 11111110 in binary + } + + bytes +} - #[test] - fn test_broadcast() { - let mac = MacAddress([255, 255, 255, 255, 255, 255]); - assert!(mac.is_broadcast()) +// We need to be able to glob against mac addresses, so a MacAddressGlob will be a mac address that we can do a match against. + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct MacAddressGlob { + pattern: [u8; 6], + mask: [u8; 6], +} + +impl fmt::Display for MacAddressGlob { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for i in 0..6 { + if self.mask[i] == 0 { + write!(f, "*")?; + } else if self.mask[i] == 0xF0 { + write!(f, "{:x}*", self.pattern[i] >> 4)?; + } else { + write!(f, "{:02x}", self.pattern[i])?; + } + if i < 5 { + write!(f, ":")?; + } + } + Ok(()) } +} + +pub enum MacGlobParseError { + InvalidDigit, + InvalidLength, +} - #[test] - fn test_format() { - let mac = MacAddress([12, 157, 146, 197, 170, 127]); - assert_eq!("0c:9d:92:c5:aa:7f", mac.to_string()) +impl fmt::Display for MacGlobParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MacGlobParseError::InvalidDigit => write!(f, "Invalid hex digit in pattern"), + MacGlobParseError::InvalidLength => write!(f, "Pattern is too long"), + } + } +} + +impl MacAddressGlob { + pub fn new(pattern: &str) -> Result { + let normalized_pattern = pattern.to_lowercase().replace(&[':', '-', '.'][..], ""); + + if normalized_pattern.len() > 12 { + return Err(MacGlobParseError::InvalidLength); + } + + let mut pattern_bytes = [0u8; 6]; + let mut mask_bytes = [0u8; 6]; + + for (i, chunk) in normalized_pattern.as_bytes().chunks(2).enumerate() { + if i >= 6 { + return Err(MacGlobParseError::InvalidLength); + } + let (pattern_byte, mask_byte) = match chunk { + [b'*'] => (0x00, 0x00), + [high, b'*'] => { + let high_nibble = match high { + b'0'..=b'9' => high - b'0', + b'a'..=b'f' => high - b'a' + 10, + _ => return Err(MacGlobParseError::InvalidDigit), + }; + (high_nibble << 4, 0xF0) + } + [high, low] => { + let high_nibble = match high { + b'0'..=b'9' => high - b'0', + b'a'..=b'f' => high - b'a' + 10, + _ => return Err(MacGlobParseError::InvalidDigit), + }; + let low_nibble = match low { + b'0'..=b'9' => low - b'0', + b'a'..=b'f' => low - b'a' + 10, + _ => return Err(MacGlobParseError::InvalidDigit), + }; + (high_nibble << 4 | low_nibble, 0xFF) + } + _ => return Err(MacGlobParseError::InvalidDigit), + }; + pattern_bytes[i] = pattern_byte; + mask_bytes[i] = mask_byte; + } + + // Handle patterns that are not full 6 bytes by filling remaining bytes with wildcards + for i in (normalized_pattern.len() / 2)..6 { + pattern_bytes[i] = 0x00; + mask_bytes[i] = 0x00; + } + + Ok(Self { + pattern: pattern_bytes, + mask: mask_bytes, + }) + } + + pub fn from_mac_address(mac: &MacAddress) -> Self { + Self { + pattern: mac.0, + mask: [0xFF; 6], + } + } + + pub fn matches(&self, mac: &MacAddress) -> bool { + for i in 0..6 { + if (mac.0[i] & self.mask[i]) != (self.pattern[i] & self.mask[i]) { + return false; + } + } + true } } diff --git a/libwifi/src/frame/components/mod.rs b/libwifi/src/frame/components/mod.rs index 208db61..b9180ca 100644 --- a/libwifi/src/frame/components/mod.rs +++ b/libwifi/src/frame/components/mod.rs @@ -8,4 +8,10 @@ pub use frame_control::FrameControl; pub use header::*; pub use mac_address::*; pub use sequence_control::SequenceControl; -pub use station_info::StationInfo; +pub use station_info::{ + AudioDevices, Cameras, Category, ChannelSwitchAnnouncment, ChannelSwitchMode, Computers, + Displays, DockingDevices, GamingDevices, HTInformation, InputDevices, MultimediaDevices, + NetworkInfrastructure, PrintersEtAl, RsnAkmSuite, RsnCipherSuite, RsnInformation, StationInfo, + Storage, SupportedRate, Telephone, VendorSpecificInfo, WpaAkmSuite, WpaCipherSuite, + WpaInformation, WpsInformation, WpsSetupState, +}; diff --git a/libwifi/src/frame/components/sequence_control.rs b/libwifi/src/frame/components/sequence_control.rs index c527f82..7dc3bfb 100644 --- a/libwifi/src/frame/components/sequence_control.rs +++ b/libwifi/src/frame/components/sequence_control.rs @@ -5,3 +5,17 @@ pub struct SequenceControl { /// The 12 bit sequence number from a sequence control field. pub sequence_number: u16, } + +impl SequenceControl { + pub fn encode(&self) -> [u8; 2] { + // The sequence number occupies the upper 12 bits + let sequence_number_bits = (self.sequence_number & 0x0FFF) << 4; + // The fragment number occupies the lower 4 bits + let fragment_number_bits = self.fragment_number & 0x0F; + + let combined = sequence_number_bits | fragment_number_bits as u16; + + // Convert to two bytes in little-endian format + [combined as u8, (combined >> 8) as u8] + } +} diff --git a/libwifi/src/frame/components/station_info.rs b/libwifi/src/frame/components/station_info.rs index a61035d..d50a989 100644 --- a/libwifi/src/frame/components/station_info.rs +++ b/libwifi/src/frame/components/station_info.rs @@ -1,3 +1,5 @@ +use std::fmt::{self, Display}; + #[derive(Clone, Debug, Default)] /// StationInfo is used to parse and store variable length fields that are often sent /// with management frames. @@ -13,14 +15,815 @@ /// Since we cannot handle all all those elements, the bytes of all unhandled elements will /// be saved in the `data` field under the respectiv element id. pub struct StationInfo { - /// The transmission rates that are supported by the station. - /// Empty if no rates were transmitted. - pub supported_rates: Vec, - /// If the sender included a SSID, it will be in here. + pub supported_rates: Vec, + pub extended_supported_rates: Option>, pub ssid: Option, - /// This map contains all fields that aren't explicitly parsed by us. + pub ssid_length: Option, + pub ds_parameter_set: Option, + pub tim: Option>, + pub country_info: Option>, + pub power_constraint: Option, + pub ht_capabilities: Option>, + pub ht_information: Option, + pub vht_capabilities: Option>, + pub rsn_information: Option, + pub wpa_info: Option, + pub wps_info: Option, + pub vendor_specific: Vec, + pub extended_capabilities: Option>, + pub channel_switch: Option, + /// Contains all fields that aren't explicitly parsed by us. /// The format is Vec<(FieldId, PayloadBytes)>. /// - /// Please consider to create a PR, if you write a parser for a new field :). + /// Fields may occur multiple times. pub data: Vec<(u8, Vec)>, } + +impl StationInfo { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode SSID (if present) + if let Some(ssid) = &self.ssid { + bytes.push(0); // ID + bytes.push(ssid.len() as u8); // Length of SSID + bytes.extend_from_slice(ssid.as_bytes()); // SSID as bytes + } + + if !self.supported_rates.is_empty() { + // Encode Supported Rates + bytes.push(1); // ID + bytes.push(self.supported_rates.len() as u8); + for rate in &self.supported_rates { + // Convert rate from Mbps to 500 kbps units and then to a byte + let rate_byte = (rate.rate * 2.0) as u8; + let rate_byte_with_flag = rate_byte | 0x80; // Setting MSB + if rate.mandatory { + bytes.push(rate_byte_with_flag); + } else { + bytes.push(rate_byte); + } + } + } + + if let Some(ext_rates) = &self.extended_supported_rates { + // Encode Supported Rates + bytes.push(50); // ID + bytes.push(ext_rates.len() as u8); + for rate in ext_rates { + // Convert rate from Mbps to 500 kbps units and then to a byte + let rate_byte = (rate.rate * 2.0) as u8; + let rate_byte_with_flag = rate_byte | 0x80; // Setting MSB + if rate.mandatory { + bytes.push(rate_byte_with_flag); + } else { + bytes.push(rate_byte); + } + } + } + + // Encode DS Parameter Set (if present) + if let Some(ds_param) = self.ds_parameter_set { + bytes.push(3); // DS Parameter Set tag number + bytes.push(1); // Length is always 1 byte + bytes.push(ds_param); + } + + // Encode TIM (if present) - Tag Number: 5 + if let Some(tim) = &self.tim { + bytes.push(5); // TIM tag number + bytes.push(tim.len() as u8); // Length of TIM + bytes.extend(tim); + } + + // Encode Country Info (if present) - Tag Number: 7 + if let Some(country_info) = &self.country_info { + bytes.push(7); // Country Info tag number + bytes.push(country_info.len() as u8); // Length of Country Info + bytes.extend(country_info); + } + + // Encode Power Constraint (if present) - Tag Number: 32 + if let Some(power_constraint) = self.power_constraint { + bytes.push(32); // Power Constraint tag number + bytes.push(1); // Length is always 1 byte + bytes.push(power_constraint); + } + + // Encode HT Capabilities (if present) - Tag Number: 45 + if let Some(ht_capabilities) = &self.ht_capabilities { + bytes.push(45); // HT Capabilities tag number + bytes.push(ht_capabilities.len() as u8); // Length of HT Capabilities + bytes.extend(ht_capabilities); + } + + // Encode HT Information (if present) - Tag Number: 61 + if let Some(ht_info) = &self.ht_information { + let ht_info_data = ht_info.encode(); + bytes.push(61); // HT Capabilities tag number + bytes.push(ht_info_data.len() as u8); // Length of HT Capabilities + bytes.extend(ht_info_data); + } + + // Encode VHT Capabilities (if present) - Tag Number: 191 + if let Some(vht_capabilities) = &self.vht_capabilities { + bytes.push(191); // VHT Capabilities tag number + bytes.push(vht_capabilities.len() as u8); // Length of VHT Capabilities + bytes.extend(vht_capabilities); + } + + // Encode RSN Information (if present) - Tag Number: 48 + if let Some(rsn_info) = &self.rsn_information { + bytes.push(48); // RSN Information tag number + let rsn_encoded = rsn_info.encode(); + bytes.push(rsn_encoded.len() as u8); // Length of RSN Information + bytes.extend(rsn_encoded); + } + + // Encode WPA Information (if present) - This is usually vendor-specific + // WPA Information uses the vendor-specific tag number (221) with the specific OUI for WPA + if let Some(wpa_info) = &self.wpa_info { + bytes.push(221); // Vendor-Specific tag number + let wpa_encoded = wpa_info.encode(); + bytes.push(wpa_encoded.len() as u8); // Length of WPA Information + bytes.extend(wpa_encoded); + } + + // Encode Vendor Specific Info + for vendor_info in &self.vendor_specific { + bytes.push(vendor_info.element_id); + bytes.push(vendor_info.length); + bytes.extend_from_slice(&vendor_info.oui); + bytes.push(vendor_info.oui_type); + bytes.extend(&vendor_info.data); + } + + // Encode Extended Capabilities (if present) + if let Some(ext_caps) = &self.extended_capabilities { + bytes.push(127); + bytes.push(ext_caps.len() as u8); + bytes.extend(ext_caps); + } + + if let Some(chan_switch) = &self.channel_switch { + let encoded = chan_switch.encode(); + bytes.push(37); + bytes.push(encoded.len() as u8); + bytes.extend(encoded); + } + + // Encode additional data + for (id, data) in &self.data { + bytes.push(*id); + bytes.push(data.len() as u8); + bytes.extend(data); + } + + bytes + } + + // Get the SSID from the station_info + pub fn ssid(&self) -> String { + match &self.ssid { + Some(ssid) if !ssid.is_empty() => ssid.clone(), + Some(_) if self.ssid_length.is_some_and(|s| s > 0) => { + format!("", self.ssid_length.unwrap_or(0)) + } + Some(_) => "".to_string(), + None => "".to_string(), + } + } + + // Handle ESSID where it could be empty (return Option instead of String) + pub fn essid(&self) -> Option { + match &self.ssid { + Some(ssid) if !ssid.is_empty() => Some(ssid.clone()), + Some(_) if self.ssid_length.is_some_and(|s| s > 0) => { + Some(format!("", self.ssid_length.unwrap_or(0))) + } + Some(_) => Some("".to_string()), + None => None, + } + } + + // Get the channel this station is broadcasting on. + pub fn channel(&self) -> Option { + if let Some(ds) = self.ds_parameter_set { + Some(ds) + } else { + self.ht_information + .as_ref() + .map(|ht_info| ht_info.primary_channel) + } + } + + // Get the WPA information + pub fn wpa_info(&self) -> Option<&WpaInformation> { + self.wpa_info.as_ref() + } +} + +#[derive(Clone, Debug)] +pub struct SupportedRate { + pub mandatory: bool, + pub rate: f32, +} + +pub enum Category { + Computer(Computers), + InputDevice(InputDevices), + PrintersScannersFaxCopier(PrintersEtAl), + Camera(Cameras), + Storage(Storage), + NetworkInfrastructure(NetworkInfrastructure), + Displays(Displays), + MultimediaDevices(MultimediaDevices), + GamingDevices(GamingDevices), + Telephone(Telephone), + AudioDevices(AudioDevices), + DockingDevices(DockingDevices), + Others, +} + +impl fmt::Display for Category { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Category::Computer(sub) => write!(f, "{}", sub), + Category::InputDevice(sub) => write!(f, "{}", sub), + Category::PrintersScannersFaxCopier(sub) => { + write!(f, "{}", sub) + } + Category::Camera(sub) => write!(f, "{}", sub), + Category::Storage(sub) => write!(f, "{}", sub), + Category::NetworkInfrastructure(sub) => write!(f, "{}", sub), + Category::Displays(sub) => write!(f, "{}", sub), + Category::MultimediaDevices(sub) => write!(f, "{}", sub), + Category::GamingDevices(sub) => write!(f, "{}", sub), + Category::Telephone(sub) => write!(f, "{}", sub), + Category::AudioDevices(sub) => write!(f, "{}", sub), + Category::DockingDevices(sub) => write!(f, "{}", sub), + Category::Others => write!(f, "Others"), + } + } +} + +pub enum Computers { + PC, + Server, + MediaCenter, + UltraMobilePC, + Notebook, + Desktop, + MID, + Netbook, + Tablet, + Ultrabook, +} + +pub enum InputDevices { + Keyboard, + Mouse, + Joystick, + Trackball, + GamingController, + Remote, + Touchscreen, + BiometricReader, + BarcodeReader, +} + +pub enum PrintersEtAl { + Printer, + Scanner, + Fax, + Copier, + AllInOne, +} + +pub enum Cameras { + DigitalCamera, + VideoCamera, + Webcam, + SecurityCamera, +} + +pub enum Storage { + NAS, +} + +pub enum NetworkInfrastructure { + AP, + Router, + Switch, + Gateway, + Bridge, +} + +pub enum Displays { + Television, + ElectronicPictureFrame, + Projector, + Monitor, +} + +pub enum MultimediaDevices { + DAR, + PVR, + MCX, + SetTopBox, + MediaServer, + ProtableVideoPlayer, +} + +pub enum GamingDevices { + Xbox, + Xbox360, + Playstation, + GameConsole, + PortableGamingDevice, +} + +pub enum Telephone { + WindowsMobile, + PhoneSingleMode, + PhoneDualMode, + SmartphoneSingleMode, + SmartphoneDualMode, +} + +pub enum AudioDevices { + AutioTunerReceiver, + Speakers, + PortableMusicPlayer, + Headset, + Headphones, + Microphone, + HomeTheaterSystems, +} + +pub enum DockingDevices { + ComputerDockingStation, + MediaKiosk, +} + +impl fmt::Display for Computers { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Computers::PC => write!(f, "PC"), + Computers::Server => write!(f, "Server"), + Computers::MediaCenter => write!(f, "Media Center"), + Computers::UltraMobilePC => write!(f, "Ultra Mobile PC"), + Computers::Notebook => write!(f, "Notebook"), + Computers::Desktop => write!(f, "Desktop"), + Computers::MID => write!(f, "Mobile Internet Device"), + Computers::Netbook => write!(f, "Netbook"), + Computers::Tablet => write!(f, "Tablet"), + Computers::Ultrabook => write!(f, "Ultrabook"), + } + } +} + +impl fmt::Display for InputDevices { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InputDevices::Keyboard => write!(f, "Keyboard"), + InputDevices::Mouse => write!(f, "Mouse"), + InputDevices::Joystick => write!(f, "Joystick"), + InputDevices::Trackball => write!(f, "Trackball"), + InputDevices::GamingController => write!(f, "Gaming Controller"), + InputDevices::Remote => write!(f, "Input Remote"), + InputDevices::Touchscreen => write!(f, "Touchscreen"), + InputDevices::BiometricReader => write!(f, "Biometric Reader"), + InputDevices::BarcodeReader => write!(f, "Barcode Reader"), + } + } +} + +impl fmt::Display for PrintersEtAl { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PrintersEtAl::Printer => write!(f, "Printer"), + PrintersEtAl::Scanner => write!(f, "Scanner"), + PrintersEtAl::Fax => write!(f, "Fax Machine"), + PrintersEtAl::Copier => write!(f, "Copier"), + PrintersEtAl::AllInOne => write!(f, "All-In-One Printer"), + } + } +} + +impl fmt::Display for Cameras { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Cameras::DigitalCamera => write!(f, "Digital Camera"), + Cameras::VideoCamera => write!(f, "Video Camera"), + Cameras::Webcam => write!(f, "Webcam"), + Cameras::SecurityCamera => write!(f, "Security Camera"), + } + } +} + +impl fmt::Display for Storage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Storage::NAS => write!(f, "NAS"), + } + } +} + +impl fmt::Display for NetworkInfrastructure { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + NetworkInfrastructure::AP => write!(f, "Access Point"), + NetworkInfrastructure::Router => write!(f, "Router"), + NetworkInfrastructure::Switch => write!(f, "Network Switch"), + NetworkInfrastructure::Gateway => write!(f, "Network Gateway"), + NetworkInfrastructure::Bridge => write!(f, "Network Bridge"), + } + } +} + +impl fmt::Display for Displays { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Displays::Television => write!(f, "Television"), + Displays::ElectronicPictureFrame => write!(f, "Electronic Picture Frame"), + Displays::Projector => write!(f, "Projector"), + Displays::Monitor => write!(f, "Monitor"), + } + } +} + +impl fmt::Display for MultimediaDevices { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + MultimediaDevices::DAR => write!(f, "Digital Audio Recorder"), + MultimediaDevices::PVR => write!(f, "Personal Video Recorder"), + MultimediaDevices::MCX => write!(f, "Media Center Extender"), + MultimediaDevices::SetTopBox => write!(f, "Set-Top Box"), + MultimediaDevices::MediaServer => write!(f, "Media Server"), + MultimediaDevices::ProtableVideoPlayer => write!(f, "Portable Video Player"), + } + } +} + +impl fmt::Display for GamingDevices { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + GamingDevices::Xbox => write!(f, "Xbox"), + GamingDevices::Xbox360 => write!(f, "Xbox 360"), + GamingDevices::Playstation => write!(f, "Playstation"), + GamingDevices::GameConsole => write!(f, "Game Console"), + GamingDevices::PortableGamingDevice => write!(f, "Portable Gaming Device"), + } + } +} + +impl fmt::Display for Telephone { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Telephone::WindowsMobile => write!(f, "Windows Mobile"), + Telephone::PhoneSingleMode => write!(f, "Phone Single Mode"), + Telephone::PhoneDualMode => write!(f, "Phone Dual Mode"), + Telephone::SmartphoneSingleMode => write!(f, "Smartphone Single Mode"), + Telephone::SmartphoneDualMode => write!(f, "Smartphone Dual Mode"), + } + } +} + +impl fmt::Display for AudioDevices { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AudioDevices::AutioTunerReceiver => write!(f, "Audio Tuner Receiver"), + AudioDevices::Speakers => write!(f, "Speakers"), + AudioDevices::PortableMusicPlayer => write!(f, "Portable Music Player"), + AudioDevices::Headset => write!(f, "Headset"), + AudioDevices::Headphones => write!(f, "Headphones"), + AudioDevices::Microphone => write!(f, "Microphone"), + AudioDevices::HomeTheaterSystems => write!(f, "Home Theater Systems"), + } + } +} + +impl fmt::Display for DockingDevices { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DockingDevices::ComputerDockingStation => write!(f, "Computer Docking Station"), + DockingDevices::MediaKiosk => write!(f, "Media Kiosk"), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct VendorSpecificInfo { + pub element_id: u8, + pub length: u8, + pub oui: [u8; 3], + pub oui_type: u8, + pub data: Vec, +} + +impl VendorSpecificInfo { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.push(self.element_id); + bytes.push(self.length); + bytes.extend_from_slice(&self.oui); + bytes.push(self.oui_type); + bytes.extend(&self.data); + + bytes + } +} + +#[derive(Clone, Debug, Default)] +pub struct WpsInformation { + pub setup_state: WpsSetupState, + pub manufacturer: String, + pub model: String, + pub model_number: String, + pub serial_number: String, + pub primary_device_type: String, + pub device_name: String, +} + +impl WpsInformation { + pub fn update_with(&mut self, other: &WpsInformation) { + if other.setup_state != WpsSetupState::NotConfigured { + self.setup_state = other.setup_state; + } + + if !other.manufacturer.is_empty() { + self.manufacturer = other.manufacturer.clone(); + } + + if !other.model.is_empty() { + self.model = other.model.clone(); + } + if !other.model_number.is_empty() { + self.model_number = other.model_number.clone(); + } + if !other.serial_number.is_empty() { + self.serial_number = other.serial_number.clone(); + } + + if !other.primary_device_type.is_empty() { + self.primary_device_type = other.primary_device_type.clone(); + } + + if !other.device_name.is_empty() { + self.device_name = other.device_name.clone(); + } + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum WpsSetupState { + #[default] + NotConfigured = 0x01, + Configured = 0x02, +} + +impl Display for WpsSetupState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let output = match self { + WpsSetupState::NotConfigured => "Not Configured", + WpsSetupState::Configured => "Configured", + }; + write!(f, "{output}") + } +} + +#[derive(Clone, Debug, Default)] +pub struct WpaInformation { + pub version: u16, + pub multicast_cipher_suite: WpaCipherSuite, + pub unicast_cipher_suites: Vec, + pub akm_suites: Vec, +} + +impl WpaInformation { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode version + bytes.extend_from_slice(&self.version.to_le_bytes()); + + // Encode Multicast Cipher Suite + bytes.extend(self.multicast_cipher_suite.encode()); + + // Encode Unicast Cipher Suites + bytes.extend_from_slice(&(self.unicast_cipher_suites.len() as u16).to_le_bytes()); + for suite in &self.unicast_cipher_suites { + bytes.extend(suite.encode()); + } + + // Encode AKM Suites + bytes.extend_from_slice(&(self.akm_suites.len() as u16).to_le_bytes()); + for suite in &self.akm_suites { + bytes.extend(suite.encode()); + } + + bytes + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub enum WpaCipherSuite { + Wep40, + Wep104, + Tkip, + #[default] + Ccmp, + Unknown(Vec), +} + +impl WpaCipherSuite { + pub fn encode(&self) -> Vec { + match self { + WpaCipherSuite::Wep40 => vec![0x00, 0x50, 0xF2, 0x01], + WpaCipherSuite::Wep104 => vec![0x00, 0x50, 0xF2, 0x05], + WpaCipherSuite::Tkip => vec![0x00, 0x50, 0xF2, 0x02], + WpaCipherSuite::Ccmp => vec![0x00, 0x50, 0xF2, 0x04], + WpaCipherSuite::Unknown(data) => data.clone(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub enum WpaAkmSuite { + #[default] + Psk, // Typically represented as 00-50-F2-1 + Eap, // Typically represented as 00-50-F2-2 + Unknown(Vec), // For any unrecognized AKM suites +} + +impl WpaAkmSuite { + pub fn encode(&self) -> Vec { + match self { + WpaAkmSuite::Psk => vec![0x00, 0x50, 0xF2, 0x01], + WpaAkmSuite::Eap => vec![0x00, 0x50, 0xF2, 0x02], + WpaAkmSuite::Unknown(data) => data.clone(), + } + } +} + +// Define the RsnInformation struct to hold the parsed data +#[derive(Clone, Debug, Default)] +pub struct RsnInformation { + pub version: u16, + pub group_cipher_suite: RsnCipherSuite, + pub pairwise_cipher_suites: Vec, + pub akm_suites: Vec, + // RSN Capabilities Flags + pub pre_auth: bool, + pub no_pairwise: bool, + pub ptksa_replay_counter: u8, + pub gtksa_replay_counter: u8, + pub mfp_required: bool, + pub mfp_capable: bool, + pub joint_multi_band_rsna: bool, + pub peerkey_enabled: bool, + pub extended_key_id: bool, + pub ocvc: bool, +} + +impl RsnInformation { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode version + bytes.extend_from_slice(&self.version.to_le_bytes()); + + // Encode Group Cipher Suite + bytes.extend(self.group_cipher_suite.encode()); + + // Encode Pairwise Cipher Suites + bytes.extend_from_slice(&(self.pairwise_cipher_suites.len() as u16).to_le_bytes()); + for suite in &self.pairwise_cipher_suites { + bytes.extend(suite.encode()); + } + + // Encode AKM Suites + bytes.extend_from_slice(&(self.akm_suites.len() as u16).to_le_bytes()); + for suite in &self.akm_suites { + bytes.extend(suite.encode()); + } + + // Encode RSN Capabilities + let mut rsn_capabilities: u16 = 0; + rsn_capabilities |= self.pre_auth as u16; + rsn_capabilities |= (self.no_pairwise as u16) << 1; + rsn_capabilities |= ((self.ptksa_replay_counter & 0x03) as u16) << 2; + rsn_capabilities |= ((self.gtksa_replay_counter & 0x03) as u16) << 3; + rsn_capabilities |= (self.mfp_required as u16) << 6; + rsn_capabilities |= (self.mfp_capable as u16) << 7; + rsn_capabilities |= (self.joint_multi_band_rsna as u16) << 8; + rsn_capabilities |= (self.peerkey_enabled as u16) << 9; + rsn_capabilities |= (self.extended_key_id as u16) << 13; + rsn_capabilities |= (self.ocvc as u16) << 14; + + bytes.extend_from_slice(&rsn_capabilities.to_le_bytes()); + + bytes + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub enum RsnAkmSuite { + #[default] + PSK, + EAP, + PSKFT, + EAPFT, + SAE, + SUITEBEAP256, + PSK256, + EAP256, + Unknown(Vec), +} + +impl RsnAkmSuite { + pub fn encode(&self) -> Vec { + match self { + RsnAkmSuite::EAP => vec![0x00, 0x0F, 0xAC, 0x01], + RsnAkmSuite::PSK => vec![0x00, 0x0F, 0xAC, 0x02], + RsnAkmSuite::EAPFT => vec![0x00, 0x0F, 0xAC, 0x03], + RsnAkmSuite::PSKFT => vec![0x00, 0x0F, 0xAC, 0x04], + RsnAkmSuite::EAP256 => vec![0x00, 0x0F, 0xAC, 0x05], + RsnAkmSuite::PSK256 => vec![0x00, 0x0F, 0xAC, 0x06], + RsnAkmSuite::SAE => vec![0x00, 0x0F, 0xAC, 0x08], + RsnAkmSuite::SUITEBEAP256 => vec![0x00, 0x0F, 0xAC, 0x0b], + RsnAkmSuite::Unknown(data) => data.clone(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub enum RsnCipherSuite { + None, + WEP, + TKIP, + WRAP, + #[default] + CCMP, + WEP104, + Unknown(Vec), +} + +impl RsnCipherSuite { + pub fn encode(&self) -> Vec { + match self { + RsnCipherSuite::None => vec![0x00, 0x0F, 0xAC, 0x00], + RsnCipherSuite::WEP => vec![0x00, 0x0F, 0xAC, 0x01], + RsnCipherSuite::TKIP => vec![0x00, 0x0F, 0xAC, 0x02], + RsnCipherSuite::WRAP => vec![0x00, 0x0F, 0xAC, 0x03], + RsnCipherSuite::CCMP => vec![0x00, 0x0F, 0xAC, 0x04], + RsnCipherSuite::WEP104 => vec![0x00, 0x0F, 0xAC, 0x05], + RsnCipherSuite::Unknown(data) => data.clone(), + } + } +} + +#[derive(Debug, Clone)] +pub struct HTInformation { + pub primary_channel: u8, + pub other_data: Vec, // TODO +} + +impl HTInformation { + pub fn encode(&self) -> Vec { + let mut data: Vec = Vec::new(); + data.push(self.primary_channel); + data.extend(self.other_data.clone()); + data + } +} + +#[derive(Debug, Clone)] +pub struct ChannelSwitchAnnouncment { + pub mode: ChannelSwitchMode, + pub new_channel: u8, + pub count: u8, +} + +impl ChannelSwitchAnnouncment { + pub fn encode(&self) -> Vec { + vec![self.mode.clone() as u8, self.new_channel, self.count] + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ChannelSwitchMode { + Restrict = 1, + Unrestricted = 0, +} + +impl ChannelSwitchMode { + pub fn from_u8(value: u8) -> ChannelSwitchMode { + match value { + 1 => ChannelSwitchMode::Restrict, + _ => ChannelSwitchMode::Unrestricted, + } + } +} diff --git a/libwifi/src/frame/control/rts_cts_ack.rs b/libwifi/src/frame/control/rts_cts_ack.rs index 749a980..33df949 100644 --- a/libwifi/src/frame/control/rts_cts_ack.rs +++ b/libwifi/src/frame/control/rts_cts_ack.rs @@ -41,6 +41,22 @@ pub struct Cts { pub duration: [u8; 2], pub destination: MacAddress, } +impl Cts { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Serialize frame_control + bytes.extend_from_slice(&self.frame_control.encode()); + + // Serialize duration (2 bytes) + bytes.extend_from_slice(&self.duration); + + // Serialize destination (MacAddress) + bytes.extend_from_slice(&self.destination.encode()); + + bytes + } +} impl Addresses for Cts { fn src(&self) -> Option<&MacAddress> { @@ -82,3 +98,20 @@ impl Addresses for Ack { None } } + +impl Ack { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Serialize frame_control + bytes.extend_from_slice(&self.frame_control.encode()); + + // Serialize duration (2 bytes) + bytes.extend_from_slice(&self.duration); + + // Serialize destination (MacAddress) + bytes.extend_from_slice(&self.destination.encode()); + + bytes + } +} diff --git a/libwifi/src/frame/data/data.rs b/libwifi/src/frame/data/data.rs index 495669b..82cd7c5 100644 --- a/libwifi/src/frame/data/data.rs +++ b/libwifi/src/frame/data/data.rs @@ -2,13 +2,146 @@ use libwifi_macros::AddressHeader; use crate::frame::components::*; +use super::{DataFrame, EapolKey, NullDataFrame}; + #[derive(Clone, Debug, AddressHeader)] pub struct Data { pub header: DataHeader, + pub eapol_key: Option, + pub data: Vec, +} + +impl Data { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Serialize header + bytes.extend_from_slice(&self.header.encode()); + + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + + // Serialize EAPOL key if present + if let Some(eapol_key) = &self.eapol_key { + bytes.extend(eapol_llc_header); + bytes.extend(eapol_key.encode().unwrap()); // Unwrap the result + } + + // Append data + bytes.extend_from_slice(&self.data); + + bytes + } +} + +impl DataFrame for Data { + fn header(&self) -> &DataHeader { + &self.header + } + fn eapol_key(&self) -> &Option { + &self.eapol_key + } + fn data(&self) -> &Vec { + &self.data + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct DataCfAck { + pub header: DataHeader, + pub eapol_key: Option, pub data: Vec, } +impl DataFrame for DataCfAck { + fn header(&self) -> &DataHeader { + &self.header + } + fn eapol_key(&self) -> &Option { + &self.eapol_key + } + fn data(&self) -> &Vec { + &self.data + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct DataCfPoll { + pub header: DataHeader, + pub eapol_key: Option, + pub data: Vec, +} + +impl DataFrame for DataCfPoll { + fn header(&self) -> &DataHeader { + &self.header + } + fn eapol_key(&self) -> &Option { + &self.eapol_key + } + fn data(&self) -> &Vec { + &self.data + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct DataCfAckCfPoll { + pub header: DataHeader, + pub eapol_key: Option, + pub data: Vec, +} + +impl DataFrame for DataCfAckCfPoll { + fn header(&self) -> &DataHeader { + &self.header + } + fn eapol_key(&self) -> &Option { + &self.eapol_key + } + fn data(&self) -> &Vec { + &self.data + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct CfAck { + pub header: DataHeader, +} + +impl NullDataFrame for CfAck { + fn header(&self) -> &DataHeader { + &self.header + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct CfPoll { + pub header: DataHeader, +} + +impl NullDataFrame for CfPoll { + fn header(&self) -> &DataHeader { + &self.header + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct CfAckCfPoll { + pub header: DataHeader, +} + +impl NullDataFrame for CfAckCfPoll { + fn header(&self) -> &DataHeader { + &self.header + } +} + #[derive(Clone, Debug, AddressHeader)] pub struct NullData { pub header: DataHeader, } + +impl NullDataFrame for NullData { + fn header(&self) -> &DataHeader { + &self.header + } +} diff --git a/libwifi/src/frame/data/mod.rs b/libwifi/src/frame/data/mod.rs index ca911de..97da0b5 100644 --- a/libwifi/src/frame/data/mod.rs +++ b/libwifi/src/frame/data/mod.rs @@ -4,3 +4,15 @@ mod qos_data; pub use data::*; pub use qos_data::*; + +use super::components::DataHeader; + +pub trait DataFrame { + fn header(&self) -> &DataHeader; + fn eapol_key(&self) -> &Option; + fn data(&self) -> &Vec; +} + +pub trait NullDataFrame { + fn header(&self) -> &DataHeader; +} diff --git a/libwifi/src/frame/data/qos_data.rs b/libwifi/src/frame/data/qos_data.rs index 1279627..67270bd 100644 --- a/libwifi/src/frame/data/qos_data.rs +++ b/libwifi/src/frame/data/qos_data.rs @@ -2,13 +2,400 @@ use libwifi_macros::AddressHeader; use crate::frame::components::*; +use byteorder::{BigEndian, WriteBytesExt}; +use std::{ + io::{self, Write}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use super::{DataFrame, NullDataFrame}; + #[derive(Clone, Debug, AddressHeader)] pub struct QosData { pub header: DataHeader, + pub eapol_key: Option, + pub data: Vec, +} + +impl DataFrame for QosData { + fn header(&self) -> &DataHeader { + &self.header + } + fn eapol_key(&self) -> &Option { + &self.eapol_key + } + fn data(&self) -> &Vec { + &self.data + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct QosDataCfAck { + pub header: DataHeader, + pub eapol_key: Option, + pub data: Vec, +} + +impl DataFrame for QosDataCfAck { + fn header(&self) -> &DataHeader { + &self.header + } + fn eapol_key(&self) -> &Option { + &self.eapol_key + } + fn data(&self) -> &Vec { + &self.data + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct QosDataCfPoll { + pub header: DataHeader, + pub eapol_key: Option, + pub data: Vec, +} + +impl DataFrame for QosDataCfPoll { + fn header(&self) -> &DataHeader { + &self.header + } + fn eapol_key(&self) -> &Option { + &self.eapol_key + } + fn data(&self) -> &Vec { + &self.data + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct QosDataCfAckCfPoll { + pub header: DataHeader, + pub eapol_key: Option, pub data: Vec, } +impl DataFrame for QosDataCfAckCfPoll { + fn header(&self) -> &DataHeader { + &self.header + } + fn eapol_key(&self) -> &Option { + &self.eapol_key + } + fn data(&self) -> &Vec { + &self.data + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct QosCfPoll { + pub header: DataHeader, +} + +impl NullDataFrame for QosCfPoll { + fn header(&self) -> &DataHeader { + &self.header + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct QosCfAckCfPoll { + pub header: DataHeader, +} + +impl NullDataFrame for QosCfAckCfPoll { + fn header(&self) -> &DataHeader { + &self.header + } +} + #[derive(Clone, Debug, AddressHeader)] pub struct QosNull { pub header: DataHeader, } + +impl NullDataFrame for QosNull { + fn header(&self) -> &DataHeader { + &self.header + } +} + +#[derive(Clone, Debug)] +pub struct KeyInformation { + pub descriptor_version: u8, + pub key_type: bool, + pub key_index: u8, + pub install: bool, + pub key_ack: bool, + pub key_mic: bool, + pub secure: bool, + pub error: bool, + pub request: bool, + pub encrypted_key_data: bool, + pub smk_message: bool, +} + +#[derive(Clone, Debug)] +pub struct EapolKey { + pub protocol_version: u8, + pub timestamp: SystemTime, + pub packet_type: u8, + pub packet_length: u16, + pub descriptor_type: u8, + pub key_information: u16, + pub key_length: u16, + pub replay_counter: u64, + pub key_nonce: [u8; 32], + pub key_iv: [u8; 16], + pub key_rsc: u64, + pub key_id: u64, + pub key_mic: [u8; 16], + pub key_data_length: u16, + pub key_data: Vec, +} + +impl Default for EapolKey { + fn default() -> EapolKey { + EapolKey { + protocol_version: 0, + timestamp: UNIX_EPOCH, + packet_type: 0, + packet_length: 0, + descriptor_type: 0, + key_information: 0, + key_length: 0, + replay_counter: 0, + key_nonce: [0; 32], + key_iv: [0; 16], + key_rsc: 0, + key_id: 0, + key_mic: [0; 16], + key_data_length: 0, + key_data: Vec::new(), + } + } +} + +impl EapolKey { + pub fn to_bytes(&self) -> io::Result> { + let mut bytes = Vec::new(); + + bytes.write_u8(self.protocol_version)?; + bytes.write_u8(self.packet_type)?; + bytes.write_u16::(self.packet_length)?; + bytes.write_u8(self.descriptor_type)?; + bytes.write_u16::(self.key_information)?; + bytes.write_u16::(self.key_length)?; + bytes.write_u64::(self.replay_counter)?; + bytes.write_all(&self.key_nonce)?; + bytes.write_all(&self.key_iv)?; + bytes.write_u64::(self.key_rsc)?; + bytes.write_u64::(self.key_id)?; + bytes.write_all(&self.key_mic)?; + bytes.write_u16::(self.key_data_length)?; + bytes.write_all(&self.key_data)?; + + Ok(bytes) + } + + pub fn encode(&self) -> Result, std::io::Error> { + let key_data_length = self.key_data.len() as u16; + + // Calculate the packet length dynamically + let packet_length = 1 // descriptor_type + + 2 // key_information + + 2 // key_length + + 8 // replay_counter + + 32 + + 16 + + 8 // key_rsc + + 8 // key_id + + 16 + + 2 // key_data_length + + key_data_length; + + let mut buf = Vec::new(); + buf.write_u8(self.protocol_version)?; + buf.write_u8(self.packet_type)?; + buf.write_u16::(packet_length)?; + buf.write_u8(self.descriptor_type)?; + buf.write_u16::(self.key_information)?; + buf.write_u16::(self.key_length)?; + buf.write_u64::(self.replay_counter)?; + buf.extend_from_slice(&self.key_nonce); + buf.extend_from_slice(&self.key_iv); + buf.write_u64::(self.key_rsc)?; + buf.write_u64::(self.key_id)?; + buf.extend_from_slice(&self.key_mic); + buf.write_u16::(key_data_length)?; + buf.extend_from_slice(&self.key_data); + Ok(buf) + } + + pub fn parse_key_information(&self) -> KeyInformation { + KeyInformation { + descriptor_version: (self.key_information & 0x0007) as u8, // Bits 0-2 + key_type: (self.key_information & 0x0008) != 0, // Bit 3 + key_index: ((self.key_information & 0x0030) >> 4) as u8, // Bits 4-5 + install: (self.key_information & 0x0040) != 0, // Bit 6 + key_ack: (self.key_information & 0x0080) != 0, // Bit 7 + key_mic: (self.key_information & 0x0100) != 0, // Bit 8 + secure: (self.key_information & 0x0200) != 0, // Bit 9 + error: (self.key_information & 0x0400) != 0, // Bit 10 + request: (self.key_information & 0x0800) != 0, // Bit 11 + encrypted_key_data: (self.key_information & 0x1000) != 0, // Bit 12 + smk_message: (self.key_information & 0x2000) != 0, // Bit 13 + } + } + + pub fn determine_key_type(&self) -> MessageType { + /* + 00000001 00001010 + xxx..... ........ Reserved + ...0.... ........ Key Data Not Encrypted + ....0... ........ No Request to initiate Handshake + .....0.. ........ No Error + ......0. ........ Not Secure + .......0 ........ Message contains Key MIC + ........ 1....... No Key ACK + ........ .0...... Install: 802.1X component shall not configure the temporal key + ........ ..xx.... Reserved + ........ ....1... Key Type: Pairwise Key + ........ .....010 Vers: HMAC-SHA1-128 is the EAPOL-Key MIC / NIST AES key wrap is the EAPOL-key enc + */ + + const KEY_TYPE: u16 = 1 << 3; + // Define the bit masks for the relevant bits in the key_information field + const KEY_ACK: u16 = 1 << 7; + const KEY_MIC: u16 = 1 << 8; + const SECURE: u16 = 1 << 9; + const INSTALL: u16 = 1 << 6; + /* println!( + "KEY ACK: {:?} KEY MIC: {:?} SECURE: {:?} INSTALL: {:?} ", + (key_information & KEY_ACK) != 0, + (key_information & KEY_MIC) != 0, + (key_information & SECURE) != 0, + (key_information & INSTALL) != 0 + ); */ + + if self.key_information & KEY_TYPE != 0 { + match self.key_information { + // Check for Message 1 of 4-way handshake + // KEY_ACK == 1 + ki if ki & KEY_ACK != 0 + && ki & KEY_MIC == 0 + && ki & SECURE == 0 + && ki & INSTALL == 0 => + { + MessageType::Message1 + } + // Check for Message 2 of 4-way handshake + // KEY_MIC == 1 + ki if ki & KEY_ACK == 0 + && ki & KEY_MIC != 0 + && ki & SECURE == 0 + && ki & INSTALL == 0 => + { + MessageType::Message2 + } + // Check for Message 3 of 4-way handshake + // KEY_ACK & KEY_MIC & SECURE & AND INSTALL == 1 + ki if ki & KEY_ACK != 0 + && ki & KEY_MIC != 0 + && ki & SECURE != 0 + && ki & INSTALL != 0 => + { + MessageType::Message3 + } + // Check for Message 4 of 4-way handshake + // KEY MIC & KEY SECURE == 1 + ki if ki & KEY_ACK == 0 + && ki & KEY_MIC != 0 + && ki & SECURE != 0 + && ki & INSTALL == 0 => + { + MessageType::Message4 + } + // Other cases, such as Group Key Handshake, or unrecognized/invalid key information + _ => MessageType::Error, + } + } else { + MessageType::GTK + } + } + + pub fn pmkid(&self) -> Option { + if self.determine_key_type() != MessageType::Message1 { + return None; + } + + // Define the RSN Suite OUI for PMKID validation + let rsnsuiteoui: [u8; 3] = [0x00, 0x0f, 0xac]; + + // Check for PMKID presence and validity + if self.key_data_length as usize == 22 { + // Extract PMKID from the key data + let pmkid = Pmkid::from_bytes(&self.key_data); + + if pmkid.oui == rsnsuiteoui + && pmkid.len == 0x14 + && pmkid.oui_type == 4 + && pmkid.pmkid.iter().any(|&x| x != 0) + { + return Some(pmkid); + } + } + None + } +} + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum MessageType { + Message1, + Message2, + Message3, + Message4, + GTK, + Error, +} + +impl std::fmt::Display for MessageType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MessageType::Message1 => write!(f, "Message 1"), + MessageType::Message2 => write!(f, "Message 2"), + MessageType::Message3 => write!(f, "Message 3"), + MessageType::Message4 => write!(f, "Message 4"), + MessageType::GTK => write!(f, "Group Temporal Key"), + MessageType::Error => write!(f, "Unknown Message"), + } + } +} + +// PMKID struct definition +#[derive(Debug, Clone, Copy)] +pub struct Pmkid { + pub id: u8, + pub len: u8, + pub oui: [u8; 3], + pub oui_type: u8, + pub pmkid: [u8; 16], +} + +// PMKID struct conversion implementation +impl Pmkid { + fn from_bytes(bytes: &[u8]) -> Self { + // Ensure the slice has the correct length + if bytes.len() != 22 { + panic!("Invalid PMKID data length"); + } + let mut pmkid = Pmkid { + id: bytes[0], + len: bytes[1], + oui: [bytes[2], bytes[3], bytes[4]], + oui_type: bytes[5], + pmkid: [0; 16], + }; + pmkid.pmkid.copy_from_slice(&bytes[6..]); + pmkid + } +} diff --git a/libwifi/src/frame/management/action.rs b/libwifi/src/frame/management/action.rs index 19cfd7a..e2c0212 100644 --- a/libwifi/src/frame/management/action.rs +++ b/libwifi/src/frame/management/action.rs @@ -2,10 +2,19 @@ use libwifi_macros::AddressHeader; use crate::frame::components::*; -enum ActionCategory { +#[derive(Clone, Debug, AddressHeader)] +pub struct Action { + pub header: ManagementHeader, + pub category: ActionCategory, + pub action: u8, + pub station_info: StationInfo, +} + +#[derive(Clone, Debug)] +pub enum ActionCategory { SpectrumManagement, Qos, - Dls, + Reserved, BlockAck, Public, RadioMeasurement, @@ -13,20 +22,85 @@ enum ActionCategory { HighThroughput, SaQuery, ProtectedDualOfPublicAction, - Reserved, + Wnm, + UnprotectedWNM, + Tdls, + Mesh, + Multihop, + SelfProtected, + Dmg, + FastSessionTransfer, + RobustAVStreaming, + UnprotectedDMG, + Vht, + UnprotectedS1G, + S1G, + FlowControl, + ControlResponseMCSNegotiation, + Fils, + Cdmg, + Dmmg, + Glk, VendorSpecificProtected, VendorSpecific, Error, } -enum Action { +impl Action { + pub fn encode(&self) -> Vec { + let mut encoded: Vec = Vec::new(); + // Encode the ManagementHeader + encoded.extend(self.header.encode()); + + // Encode the ActionCategory and action + encoded.push(self.category.clone() as u8); + encoded.push(self.action); + + // Encode StationInfo if necessary + encoded.extend(self.station_info.encode()); + + encoded + } } -#[derive(Clone, Debug, AddressHeader)] -pub struct Action { - pub header: ManagementHeader, - pub category: , - pub action: , - pub station_info: StationInfo, +impl From for ActionCategory { + fn from(value: u8) -> Self { + match value { + 0 => ActionCategory::SpectrumManagement, + 1 => ActionCategory::Qos, + 2 => ActionCategory::Reserved, + 3 => ActionCategory::BlockAck, + 4 => ActionCategory::Public, + 5 => ActionCategory::RadioMeasurement, + 6 => ActionCategory::FastBssTransition, + 7 => ActionCategory::HighThroughput, + 8 => ActionCategory::SaQuery, + 9 => ActionCategory::ProtectedDualOfPublicAction, + 10 => ActionCategory::Wnm, + 11 => ActionCategory::UnprotectedWNM, + 12 => ActionCategory::Tdls, + 13 => ActionCategory::Mesh, + 14 => ActionCategory::Multihop, + 15 => ActionCategory::SelfProtected, + 16 => ActionCategory::Dmg, + 17 => ActionCategory::Reserved, + 18 => ActionCategory::FastSessionTransfer, + 19 => ActionCategory::RobustAVStreaming, + 20 => ActionCategory::UnprotectedDMG, + 21 => ActionCategory::Vht, + 22 => ActionCategory::UnprotectedS1G, + 23 => ActionCategory::S1G, + 24 => ActionCategory::FlowControl, + 25 => ActionCategory::ControlResponseMCSNegotiation, + 26 => ActionCategory::Fils, + 27 => ActionCategory::Cdmg, + 28 => ActionCategory::Dmmg, + 29 => ActionCategory::Glk, + 30..=125 => ActionCategory::Reserved, + 126 => ActionCategory::VendorSpecificProtected, + 127 => ActionCategory::VendorSpecific, + 128..=255 => ActionCategory::Error, + } + } } diff --git a/libwifi/src/frame/management/association.rs b/libwifi/src/frame/management/association.rs index 1e264c6..c9fde5f 100644 --- a/libwifi/src/frame/management/association.rs +++ b/libwifi/src/frame/management/association.rs @@ -2,6 +2,8 @@ use libwifi_macros::AddressHeader; use crate::frame::components::*; +use super::DeauthenticationReason; + #[derive(Clone, Debug, AddressHeader)] pub struct AssociationRequest { pub header: ManagementHeader, @@ -10,6 +12,26 @@ pub struct AssociationRequest { pub station_info: StationInfo, } +impl AssociationRequest { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode the ManagementHeader + bytes.extend(self.header.encode()); + + // Encode Capability Info + bytes.extend_from_slice(&self.capability_info.to_le_bytes()); + + // Encode Beacon Interval + bytes.extend_from_slice(&self.beacon_interval.to_le_bytes()); + + // Encode Station Info + bytes.extend(self.station_info.encode()); + + bytes + } +} + #[derive(Clone, Debug, AddressHeader)] pub struct AssociationResponse { pub header: ManagementHeader, @@ -18,3 +40,107 @@ pub struct AssociationResponse { pub association_id: u16, pub station_info: StationInfo, } + +impl AssociationResponse { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode the ManagementHeader + bytes.extend(self.header.encode()); + + // Encode Capability Info + bytes.extend_from_slice(&self.capability_info.to_le_bytes()); + + // Encode Status Code + bytes.extend_from_slice(&self.status_code.to_le_bytes()); + + // Encode Association ID + bytes.extend_from_slice(&self.association_id.to_le_bytes()); + + // Encode Station Info + bytes.extend(self.station_info.encode()); + + bytes + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct ReassociationRequest { + pub header: ManagementHeader, + pub capability_info: u16, + pub listen_interval: u16, + pub current_ap_address: MacAddress, // MAC address of the current AP + pub station_info: StationInfo, +} + +impl ReassociationRequest { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode the ManagementHeader + bytes.extend(self.header.encode()); + + // Encode Capability Info + bytes.extend_from_slice(&self.capability_info.to_le_bytes()); + + // Encode Listen Interval + bytes.extend_from_slice(&self.listen_interval.to_le_bytes()); + + // Encode Current AP Address + bytes.extend_from_slice(&self.current_ap_address.encode()); + + // Encode Station Info + bytes.extend(self.station_info.encode()); + + bytes + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct ReassociationResponse { + pub header: ManagementHeader, + pub capability_info: u16, + pub status_code: u16, + pub association_id: u16, + pub station_info: StationInfo, +} + +impl ReassociationResponse { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode the ManagementHeader + bytes.extend(self.header.encode()); + + // Encode Capability Info + bytes.extend_from_slice(&self.capability_info.to_le_bytes()); + + // Encode Status Code + bytes.extend_from_slice(&self.status_code.to_le_bytes()); + + // Encode Association ID + bytes.extend_from_slice(&self.association_id.to_le_bytes()); + + bytes + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct Disassociation { + pub header: ManagementHeader, + pub reason_code: DeauthenticationReason, +} + +impl Disassociation { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode the ManagementHeader + bytes.extend(self.header.encode()); + + // Encode Reason Code + bytes.extend_from_slice(&(self.reason_code.clone() as u16).to_ne_bytes()); + + bytes + } +} diff --git a/libwifi/src/frame/management/authentication.rs b/libwifi/src/frame/management/authentication.rs new file mode 100644 index 0000000..ebcb668 --- /dev/null +++ b/libwifi/src/frame/management/authentication.rs @@ -0,0 +1,170 @@ +use crate::frame::components::*; +use libwifi_macros::AddressHeader; + +pub const DEAUTHENTICATION_REASON_MAX: u8 = 46; + +#[derive(Clone, Debug, AddressHeader)] +pub struct Authentication { + pub header: ManagementHeader, + pub auth_algorithm: u16, + pub auth_seq: u16, + pub status_code: u16, + pub challenge_text: Option>, + pub station_info: Option, +} + +impl Authentication { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + // Serialize the ManagementHeader + bytes.extend_from_slice(&self.header.encode()); + + // Serialize auth_algorithm (2 bytes) + bytes.extend_from_slice(&self.auth_algorithm.to_ne_bytes()); + + // Serialize auth_seq (2 bytes) + bytes.extend_from_slice(&self.auth_seq.to_ne_bytes()); + + // Serialize status_code (2 bytes) + bytes.extend_from_slice(&self.status_code.to_ne_bytes()); + + // Serialize challenge_text (if present) + if let Some(ref text) = self.challenge_text { + bytes.extend_from_slice(text); + } + + if let Some(info) = &self.station_info { + bytes.extend_from_slice(&info.encode()); + } + + bytes + } +} + +#[derive(Clone, Debug, AddressHeader)] +pub struct Deauthentication { + pub header: ManagementHeader, + pub reason_code: DeauthenticationReason, +} + +impl Deauthentication { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + // Serialize the ManagementHeader + bytes.extend_from_slice(&self.header.encode()); + + // Serialize reason_code (2 bytes) + bytes.extend_from_slice(&(self.reason_code.clone() as u16).to_ne_bytes()); + + bytes + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum DeauthenticationReason { + UnspecifiedReason = 1, + PreviousAuthenticationNoLongerValid = 2, + DeauthenticatedBecauseSTAIsLeaving = 3, + DisassociatedDueToInactivity = 4, + DisassociatedBecauseAPUnableToHandleAllSTAs = 5, + Class2FrameReceivedFromNonauthenticatedSTA = 6, + Class3FrameReceivedFromNonassociatedSTA = 7, + DisassociatedBecauseSTALeavingBSS = 8, + STARequestingReassociationNotAuthenticated = 9, + DisassociatedBecauseOfPowerCapability = 10, + DisassociatedBecauseOfSupportedChannels = 11, + InvalidInformationElement = 13, + MICFailure = 14, + FourWayHandshakeTimeout = 15, + GroupKeyHandshakeTimeout = 16, + InformationElementInFourWayHandshakeDifferent = 17, + InvalidGroupCipher = 18, + InvalidPairwiseCipher = 19, + InvalidAKMP = 20, + UnsupportedRSNInformationElementVersion = 21, + InvalidRSNInformationElementCapabilities = 22, + IEEE8021XAuthenticationFailed = 23, + CipherSuiteRejectedBecauseOfSecurityPolicy = 24, + TDLSUnreachable = 25, + TDLSUnspecifiedReason = 26, + TDLSRejected = 27, + TDLSRequestedTearDown = 28, + TDLSChannelSwitching = 30, + UnauthorizedAccessPoint = 31, + PriorAuthenticationValid = 32, + ExternalServiceRequirements = 33, + InvalidFTActionFrameCount = 34, + InvalidPMKID = 35, + InvalidMDE = 36, + InvalidFTE = 37, + SMECancelsAuthentication = 38, + PeerUnreachable = 39, + PeerDeauthenticatedForListenIntervalTooLarge = 41, + DisassociatedForReasonUnspecified = 42, + PeerDeauthenticatedForReasonUnspecified = 43, + DisassociatedForSensorStation = 44, + DisassociatedForPoorChannelConditions = 45, + DisassociatedForBSSTransitionManagement = 46, + DeauthenticatedForReasonUnspecified = 47, + SessionInformationUnavailable = 48, + DisassociatedForSCPRequestUnsuccessful = 49, + DeauthenticatedForSCPRequestUnsuccessful = 50, + DisassociatedDueToPoorRSSI = 71, + Unknown, +} + +impl DeauthenticationReason { + pub fn from_code(code: u16) -> Self { + match code { + 1 => DeauthenticationReason::UnspecifiedReason, + 2 => DeauthenticationReason::PreviousAuthenticationNoLongerValid, + 3 => DeauthenticationReason::DeauthenticatedBecauseSTAIsLeaving, + 4 => DeauthenticationReason::DisassociatedDueToInactivity, + 5 => DeauthenticationReason::DisassociatedBecauseAPUnableToHandleAllSTAs, + 6 => DeauthenticationReason::Class2FrameReceivedFromNonauthenticatedSTA, + 7 => DeauthenticationReason::Class3FrameReceivedFromNonassociatedSTA, + 8 => DeauthenticationReason::DisassociatedBecauseSTALeavingBSS, + 9 => DeauthenticationReason::STARequestingReassociationNotAuthenticated, + 10 => DeauthenticationReason::DisassociatedBecauseOfPowerCapability, + 11 => DeauthenticationReason::DisassociatedBecauseOfSupportedChannels, + 13 => DeauthenticationReason::InvalidInformationElement, + 14 => DeauthenticationReason::MICFailure, + 15 => DeauthenticationReason::FourWayHandshakeTimeout, + 16 => DeauthenticationReason::GroupKeyHandshakeTimeout, + 17 => DeauthenticationReason::InformationElementInFourWayHandshakeDifferent, + 18 => DeauthenticationReason::InvalidGroupCipher, + 19 => DeauthenticationReason::InvalidPairwiseCipher, + 20 => DeauthenticationReason::InvalidAKMP, + 21 => DeauthenticationReason::UnsupportedRSNInformationElementVersion, + 22 => DeauthenticationReason::InvalidRSNInformationElementCapabilities, + 23 => DeauthenticationReason::IEEE8021XAuthenticationFailed, + 24 => DeauthenticationReason::CipherSuiteRejectedBecauseOfSecurityPolicy, + 25 => DeauthenticationReason::TDLSUnreachable, + 26 => DeauthenticationReason::TDLSUnspecifiedReason, + 27 => DeauthenticationReason::TDLSRejected, + 28 => DeauthenticationReason::TDLSRequestedTearDown, + 30 => DeauthenticationReason::TDLSChannelSwitching, + 31 => DeauthenticationReason::UnauthorizedAccessPoint, + 32 => DeauthenticationReason::PriorAuthenticationValid, + 33 => DeauthenticationReason::ExternalServiceRequirements, + 34 => DeauthenticationReason::InvalidFTActionFrameCount, + 35 => DeauthenticationReason::InvalidPMKID, + 36 => DeauthenticationReason::InvalidMDE, + 37 => DeauthenticationReason::InvalidFTE, + 38 => DeauthenticationReason::SMECancelsAuthentication, + 39 => DeauthenticationReason::PeerUnreachable, + 41 => DeauthenticationReason::PeerDeauthenticatedForListenIntervalTooLarge, + 42 => DeauthenticationReason::DisassociatedForReasonUnspecified, + 43 => DeauthenticationReason::PeerDeauthenticatedForReasonUnspecified, + 44 => DeauthenticationReason::DisassociatedForSensorStation, + 45 => DeauthenticationReason::DisassociatedForPoorChannelConditions, + 46 => DeauthenticationReason::DisassociatedForBSSTransitionManagement, + 47 => DeauthenticationReason::DeauthenticatedForReasonUnspecified, + 48 => DeauthenticationReason::SessionInformationUnavailable, + 49 => DeauthenticationReason::DisassociatedForSCPRequestUnsuccessful, + 50 => DeauthenticationReason::DeauthenticatedForSCPRequestUnsuccessful, + 71 => DeauthenticationReason::DisassociatedDueToPoorRSSI, + _ => DeauthenticationReason::Unknown, + } + } +} diff --git a/libwifi/src/frame/management/beacon.rs b/libwifi/src/frame/management/beacon.rs index b1eee60..8406388 100644 --- a/libwifi/src/frame/management/beacon.rs +++ b/libwifi/src/frame/management/beacon.rs @@ -10,3 +10,26 @@ pub struct Beacon { pub capability_info: u16, pub station_info: StationInfo, } + +impl Beacon { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode the ManagementHeader + bytes.extend(self.header.encode()); + + // Encode Timestamp + bytes.extend_from_slice(&self.timestamp.to_le_bytes()); + + // Encode Beacon Interval + bytes.extend_from_slice(&self.beacon_interval.to_le_bytes()); + + // Encode Capability Info + bytes.extend_from_slice(&self.capability_info.to_le_bytes()); + + // Encode Station Info + bytes.extend(self.station_info.encode()); + + bytes + } +} diff --git a/libwifi/src/frame/management/mod.rs b/libwifi/src/frame/management/mod.rs index ee72347..9b6d048 100644 --- a/libwifi/src/frame/management/mod.rs +++ b/libwifi/src/frame/management/mod.rs @@ -1,7 +1,16 @@ +mod action; mod association; +mod authentication; mod beacon; mod probe; -pub use association::{AssociationRequest, AssociationResponse}; +pub use action::{Action, ActionCategory}; +pub use association::{ + AssociationRequest, AssociationResponse, Disassociation, ReassociationRequest, + ReassociationResponse, +}; +pub use authentication::{ + Authentication, Deauthentication, DeauthenticationReason, DEAUTHENTICATION_REASON_MAX, +}; pub use beacon::Beacon; pub use probe::{ProbeRequest, ProbeResponse}; diff --git a/libwifi/src/frame/management/probe.rs b/libwifi/src/frame/management/probe.rs index cbcffaf..62cb951 100644 --- a/libwifi/src/frame/management/probe.rs +++ b/libwifi/src/frame/management/probe.rs @@ -8,6 +8,20 @@ pub struct ProbeRequest { pub station_info: StationInfo, } +impl ProbeRequest { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode the ManagementHeader + bytes.extend(self.header.encode()); + + // Encode Station Info + bytes.extend(self.station_info.encode()); + + bytes + } +} + #[derive(Clone, Debug, AddressHeader)] pub struct ProbeResponse { pub header: ManagementHeader, @@ -16,3 +30,26 @@ pub struct ProbeResponse { pub capability_info: u16, pub station_info: StationInfo, } + +impl ProbeResponse { + pub fn encode(&self) -> Vec { + let mut bytes = Vec::new(); + + // Encode the ManagementHeader + bytes.extend(self.header.encode()); + + // Encode Timestamp + bytes.extend_from_slice(&self.timestamp.to_le_bytes()); + + // Encode Beacon Interval + bytes.extend_from_slice(&self.beacon_interval.to_le_bytes()); + + // Encode Capability Info + bytes.extend_from_slice(&self.capability_info.to_le_bytes()); + + // Encode Station Info + bytes.extend(self.station_info.encode()); + + bytes + } +} diff --git a/libwifi/src/frame/mod.rs b/libwifi/src/frame/mod.rs index 0f4557d..94c14e4 100644 --- a/libwifi/src/frame/mod.rs +++ b/libwifi/src/frame/mod.rs @@ -3,6 +3,7 @@ use enum_dispatch::enum_dispatch; /// Contains structs representing recurring sets of structured data. /// For instance, MAC-Addresses, default headers, etc. pub mod components; + /// Control frame structs mod control; /// Data frames structs @@ -27,6 +28,13 @@ pub enum Frame { ProbeResponse(ProbeResponse), AssociationRequest(AssociationRequest), AssociationResponse(AssociationResponse), + ReassociationRequest(ReassociationRequest), + ReassociationResponse(ReassociationResponse), + Action(Action), + + // Authentication + Authentication(Authentication), + Deauthentication(Deauthentication), // Control Frames Rts(Rts), @@ -37,7 +45,19 @@ pub enum Frame { // Data Frames Data(Data), - NullData(NullData), QosData(QosData), + DataCfAck(DataCfAck), + DataCfPoll(DataCfPoll), + DataCfAckCfPoll(DataCfAckCfPoll), + CfAck(CfAck), + CfPoll(CfPoll), + CfAckCfPoll(CfAckCfPoll), + QosDataCfAck(QosDataCfAck), + QosDataCfPoll(QosDataCfPoll), + QosDataCfAckCfPoll(QosDataCfAckCfPoll), + // No eapol + QosCfPoll(QosCfPoll), + QosCfAckCfPoll(QosCfAckCfPoll), QosNull(QosNull), + NullData(NullData), } diff --git a/libwifi/src/frame_types.rs b/libwifi/src/frame_types.rs index 429a2f9..48a1407 100644 --- a/libwifi/src/frame_types.rs +++ b/libwifi/src/frame_types.rs @@ -9,6 +9,24 @@ pub enum FrameType { Unknown, } +pub enum ManagementSubTypes { + AssociationRequest, + AssociationResponse, + ReassociationRequest, + ReassociationResponse, + ProbeRequest, + ProbeResponse, + TimingAdvertisement, + Reserved, + Beacon, + Atim, + Disassociation, + Authentication, + Deauthentication, + Action, + ActionNoAck, +} + /// Enum with all frame subtypes. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display)] pub enum FrameSubType { @@ -62,8 +80,8 @@ pub enum FrameSubType { QosCfAckCfPoll, // Special subtypes - Reserved, Unhandled, + Reserved, } impl FrameSubType { @@ -79,4 +97,53 @@ impl FrameSubType { | FrameSubType::QosCfAckCfPoll, ) } + + pub fn to_bytes(&self) -> u8 { + match self { + FrameSubType::AssociationRequest => 0, + FrameSubType::AssociationResponse => 1, + FrameSubType::ReassociationRequest => 2, + FrameSubType::ReassociationResponse => 3, + FrameSubType::ProbeRequest => 4, + FrameSubType::ProbeResponse => 5, + FrameSubType::TimingAdvertisement => 6, + FrameSubType::Beacon => 8, + FrameSubType::Atim => 9, + FrameSubType::Disassociation => 10, + FrameSubType::Authentication => 11, + FrameSubType::Deauthentication => 12, + FrameSubType::Action => 13, + FrameSubType::ActionNoAck => 14, + FrameSubType::Trigger => 2, + FrameSubType::Tack => 3, + FrameSubType::BeamformingReportPoll => 4, + FrameSubType::NdpAnnouncement => 5, + FrameSubType::ControlFrameExtension => 6, + FrameSubType::ControlWrapper => 7, + FrameSubType::BlockAckRequest => 8, + FrameSubType::BlockAck => 9, + FrameSubType::PsPoll => 10, + FrameSubType::Rts => 11, + FrameSubType::Cts => 12, + FrameSubType::Ack => 13, + FrameSubType::CfEnd => 14, + FrameSubType::CfEndCfAck => 15, + FrameSubType::Data => 0, + FrameSubType::DataCfAck => 1, + FrameSubType::DataCfPoll => 2, + FrameSubType::DataCfAckCfPoll => 3, + FrameSubType::NullData => 4, + FrameSubType::CfAck => 5, + FrameSubType::CfPoll => 6, + FrameSubType::CfAckCfPoll => 7, + FrameSubType::QosData => 8, + FrameSubType::QosDataCfAck => 9, + FrameSubType::QosDataCfPoll => 10, + FrameSubType::QosDataCfAckCfPoll => 11, + FrameSubType::QosNull => 12, + FrameSubType::QosCfPoll => 14, + FrameSubType::QosCfAckCfPoll => 15, + _ => 255, + } + } } diff --git a/libwifi/src/lib.rs b/libwifi/src/lib.rs index 71196cd..6fb626e 100644 --- a/libwifi/src/lib.rs +++ b/libwifi/src/lib.rs @@ -5,7 +5,7 @@ pub mod frame; /// Enums representing frame types and frame subtypes. mod frame_types; /// [nom] parsers for internal usage. -mod parsers; +pub mod parsers; /// All traits used or provided by this library. mod traits; @@ -17,16 +17,40 @@ pub use crate::frame::Frame; pub use crate::frame_types::*; pub use crate::traits::*; +use crc::{Crc, CRC_32_ISO_HDLC}; + +// CRC algorithm for FCS calculation +const CRC_32: Crc = Crc::::new(&CRC_32_ISO_HDLC); + /// Parse IEE 802.11 frames from raw bytes. /// -/// This function doesn't do FCS checks. These need to be done separately. -pub fn parse_frame(input: &[u8]) -> Result { +/// If `fcs_included` is set to `true`, it will be assumed that a CRC checksum exists +/// and it will be used to verify the payload before parsing starts. +pub fn parse_frame(input: &[u8], fcs_included: bool) -> Result { + if fcs_included { + if input.len() < 4 { + return Err(Error::Incomplete("Incomplete".to_string())); + } + + // Split the input into frame data and FCS + let (frame_data, fcs_bytes) = input.split_at(input.len() - 4); + + // Calculate the CRC over the frame data + let crc = CRC_32.checksum(frame_data); + + // Convert the last 4 bytes (FCS) to a u32 -- this needs to be little endian I guess? + let fcs = u32::from_le_bytes([fcs_bytes[0], fcs_bytes[1], fcs_bytes[2], fcs_bytes[3]]); + + // Verify the FCS + if crc != fcs { + return Err(Error::Incomplete(format!( + "(FCS) mismatch {:02x} {:02x}", + crc, fcs + ))); + } + } + let (input, frame_control) = parse_frame_control(input)?; - //println!( - // "Type/Subtype: {:?}, {:?}", - // frame_control.frame_type, frame_control.frame_subtype - //); - //println!("Payload bytes: {:?}", &input); // Check which kind of frame sub-type we got match frame_control.frame_subtype { @@ -36,6 +60,11 @@ pub fn parse_frame(input: &[u8]) -> Result { FrameSubType::ProbeResponse => parse_probe_response(frame_control, input), FrameSubType::AssociationRequest => parse_association_request(frame_control, input), FrameSubType::AssociationResponse => parse_association_response(frame_control, input), + FrameSubType::ReassociationRequest => parse_reassociation_request(frame_control, input), + FrameSubType::ReassociationResponse => parse_reassociation_response(frame_control, input), + FrameSubType::Authentication => parse_authentication_frame(frame_control, input), + FrameSubType::Deauthentication => parse_deauthentication_frame(frame_control, input), + FrameSubType::Action => parse_action(frame_control, input), // Control FrameSubType::Rts => parse_rts(frame_control, input), @@ -49,6 +78,17 @@ pub fn parse_frame(input: &[u8]) -> Result { FrameSubType::NullData => parse_null_data(frame_control, input), FrameSubType::QosData => parse_qos_data(frame_control, input), FrameSubType::QosNull => parse_qos_null(frame_control, input), + FrameSubType::DataCfAck => parse_data_cf_ack(frame_control, input), + FrameSubType::DataCfPoll => parse_data_cf_poll(frame_control, input), + FrameSubType::DataCfAckCfPoll => parse_data_cf_ack_cf_poll(frame_control, input), + FrameSubType::CfAck => parse_cf_ack(frame_control, input), + FrameSubType::CfPoll => parse_cf_poll(frame_control, input), + FrameSubType::CfAckCfPoll => parse_cf_ack_cf_poll(frame_control, input), + FrameSubType::QosDataCfAck => parse_qos_data_cf_ack(frame_control, input), + FrameSubType::QosDataCfPoll => parse_qos_data_cf_poll(frame_control, input), + FrameSubType::QosDataCfAckCfPoll => parse_qos_data_cf_ack_cf_poll(frame_control, input), + FrameSubType::QosCfPoll => parse_qos_cf_poll(frame_control, input), + FrameSubType::QosCfAckCfPoll => parse_qos_cf_ack_cf_poll(frame_control, input), _ => Err(Error::UnhandledFrameSubtype(frame_control, input.to_vec())), } } diff --git a/libwifi/src/parsers/components/mod.rs b/libwifi/src/parsers/components/mod.rs index 409beba..f288394 100644 --- a/libwifi/src/parsers/components/mod.rs +++ b/libwifi/src/parsers/components/mod.rs @@ -11,7 +11,7 @@ mod station_info; pub use frame_control::parse_frame_control; pub use header::*; pub use sequence_control::parse_sequence_control; -pub use station_info::parse_station_info; +pub use station_info::{parse_rsn_information, parse_station_info}; /// Parse mac addresses. /// Just take 6 bytes, clone them and create a new MacAddress struct from those bytes. diff --git a/libwifi/src/parsers/components/sequence_control.rs b/libwifi/src/parsers/components/sequence_control.rs index 8154ec2..d1253b8 100644 --- a/libwifi/src/parsers/components/sequence_control.rs +++ b/libwifi/src/parsers/components/sequence_control.rs @@ -1,16 +1,23 @@ -use nom::complete::take; -use nom::error::Error; -use nom::sequence::tuple; -use nom::{bits, IResult}; +use nom::bytes::complete::take; +use nom::IResult; use crate::frame::components::SequenceControl; -/// Parse and return the [ManagementHeader] from a given payload. pub fn parse_sequence_control(input: &[u8]) -> IResult<&[u8], SequenceControl> { - let (remaining, (fragment_number, sequence_number)) = - bits::<_, (u8, u16), Error<(&[u8], usize)>, _, _>(tuple((take(4usize), take(12usize))))( - input, - )?; + // Read exactly 2 bytes (16 bits) + let (remaining, sequence_control_bytes) = take(2usize)(input)?; + + // Ensure that we have exactly two bytes + let byte1 = sequence_control_bytes[0]; + let byte2 = sequence_control_bytes[1]; + + // Extract fragment number (lower 4 bits of byte1) + let fragment_number = byte1 & 0b00001111; + + // Extract the 12-bit sequence number: + // - The upper 4 bits from byte1, shifted right by 4 bits + // - The entire byte2 shifted left to fill the remaining 8 bits + let sequence_number = ((byte1 as u16 & 0b11110000) >> 4) | ((byte2 as u16) << 4); Ok(( remaining, diff --git a/libwifi/src/parsers/components/station_info.rs b/libwifi/src/parsers/components/station_info.rs index fa82e1f..6c2214b 100644 --- a/libwifi/src/parsers/components/station_info.rs +++ b/libwifi/src/parsers/components/station_info.rs @@ -1,9 +1,16 @@ +#![allow(dead_code)] use nom::bytes::complete::take; use nom::number::complete::u8 as get_u8; use nom::sequence::tuple; use nom::IResult; -use crate::frame::components::StationInfo; +use crate::frame::components::{ + AudioDevices, Cameras, Category, ChannelSwitchAnnouncment, ChannelSwitchMode, Computers, + Displays, DockingDevices, GamingDevices, HTInformation, InputDevices, MultimediaDevices, + NetworkInfrastructure, PrintersEtAl, RsnAkmSuite, RsnCipherSuite, RsnInformation, StationInfo, + Storage, SupportedRate, Telephone, VendorSpecificInfo, WpaAkmSuite, WpaCipherSuite, + WpaInformation, WpsInformation, WpsSetupState, +}; /// Parse variable length and variable field information. /// The general structure of the data looks like this: @@ -24,54 +31,505 @@ pub fn parse_station_info(mut input: &[u8]) -> IResult<&[u8], StationInfo> { let mut data; loop { (input, (element_id, length)) = tuple((get_u8, get_u8))(input)?; - //println!("Element id {}, Length: {}", element_id, length); (input, data) = take(length)(input)?; - //println!("Extracted data: {:?}", data); - - match element_id { - 0 => { - let mut ssid = String::from_utf8_lossy(data).to_string(); - // Remove null chars. Some APs seem to enjoy sending those. - ssid = ssid.replace('\0', " "); - station_info.ssid = Some(ssid); - } - 1 => station_info.supported_rates = parse_supported_rates(data), - _ => { - station_info.data.push((element_id, data.to_vec())); - } - }; + if !data.is_empty() { + match element_id { + 0 => { + let ssid = String::from_utf8_lossy(data).to_string(); + station_info.ssid = Some(ssid); + station_info.ssid_length = Some(length as usize); + } + 1 => station_info.supported_rates = parse_supported_rates(data), + 3 => station_info.ds_parameter_set = Some(data[0]), + 5 => station_info.tim = Some(data.to_vec()), + 7 => station_info.country_info = Some(data.to_vec()), + 32 => station_info.power_constraint = Some(data[0]), + 37 => station_info.channel_switch = parse_channel_switch(data), + 45 => station_info.ht_capabilities = Some(data.to_vec()), + 48 => { + if let Ok(rsn_info) = parse_rsn_information(data) { + station_info.rsn_information = Some(rsn_info) + } + } + 50 => station_info.extended_supported_rates = Some(parse_supported_rates(data)), + 61 => { + if let Ok(ht_info) = parse_ht_information(data) { + station_info.ht_information = Some(ht_info) + } + } + 191 => station_info.vht_capabilities = Some(data.to_vec()), + 221 => { + // Vendor-specific tag + if data.len() >= 4 { + // Minimum length for OUI and OUI Type + let oui = [data[0], data[1], data[2]]; + let oui_type = data[3]; + let vendor_data = data[4..].to_vec(); + + if oui == [0x00, 0x50, 0xf2] && oui_type == 1 { + // Specific parsing for WPA Information Element + station_info.wpa_info = + Some(parse_wpa_information(&vendor_data).unwrap()); + } else if oui == [0x00, 0x50, 0xf2] && oui_type == 4 { + // Specific parsing for WPS Information Element + station_info.wps_info = parse_wps_information(&vendor_data).ok(); + } else { + let vendor_specific_info = VendorSpecificInfo { + element_id, + length, + oui, + oui_type, + data: vendor_data, + }; + station_info.vendor_specific.push(vendor_specific_info); + } + } + } + _ => { + station_info.data.push((element_id, data.to_vec())); + } + }; - if input.len() <= 4 { - break; + if input.len() <= 4 { + break; + } } } Ok((input, station_info)) } -/// This is used in the ProbeResponse frame. -/// It indicates which transmission rates (in Mbps) are supported by this AP. -fn parse_supported_rates(input: &[u8]) -> Vec { - let mut rates: Vec = Vec::new(); - for rate in input { - match rate { - 0x82 => rates.push(1.0), - 0x84 => rates.push(2.0), - 0x8b => rates.push(5.5), - 0x0c => rates.push(6.0), - 0x12 => rates.push(9.0), - 0x96 => rates.push(11.0), - 0x18 => rates.push(12.0), - 0x24 => rates.push(18.0), - 0x2c => rates.push(22.0), - 0x30 => rates.push(24.0), - 0x42 => rates.push(33.0), - 0x48 => rates.push(36.0), - 0x60 => rates.push(48.0), - 0x6c => rates.push(54.0), - _ => continue, +fn parse_wpa_information(data: &[u8]) -> Result { + if data.len() < 10 { + return Err("WPA Information data too short"); + } + + let version = u16::from_le_bytes([data[0], data[1]]); + if version != 1 { + return Err("Unsupported WPA version"); + } + + let multicast_cipher_suite = parse_cipher_suite(&data[2..6]); + let unicast_cipher_suite_count = u16::from_le_bytes([data[6], data[7]]) as usize; + let mut offset = 8; + + if data.len() < offset + 4 * unicast_cipher_suite_count { + return Err("WPA Information data too short for unicast cipher suites"); + } + + let mut unicast_cipher_suites = Vec::new(); + for _ in 0..unicast_cipher_suite_count { + let cipher_suite = parse_cipher_suite(&data[offset..offset + 4]); + unicast_cipher_suites.push(cipher_suite); + offset += 4; + } + + if data.len() < offset + 2 { + return Err("WPA Information data too short for AKM suite count"); + } + + let akm_suite_count = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize; + offset += 2; + + if data.len() < offset + 4 * akm_suite_count { + return Err("WPA Information data too short for AKM suites"); + } + + let mut akm_suites = Vec::new(); + for _ in 0..akm_suite_count { + let akm_suite = parse_wpa_akm_suite(&data[offset..offset + 4]); + akm_suites.push(akm_suite); + offset += 4; + } + + Ok(WpaInformation { + version, + multicast_cipher_suite, + unicast_cipher_suites, + akm_suites, + }) +} + +fn parse_ht_information(data: &[u8]) -> Result { + if data.is_empty() { + return Err("WPA Information data too short"); + } + + Ok(HTInformation { + primary_channel: data[0], + other_data: data[1..].to_vec(), + }) +} + +fn parse_wps_information(data: &[u8]) -> Result { + let mut wps_info = WpsInformation::default(); + let mut offset = 0; + + while offset < data.len() { + if offset + 4 > data.len() { + return Err("Invalid WPS data length"); + } + + let element_type = u16::from_be_bytes([data[offset], data[offset + 1]]); + let element_length = u16::from_be_bytes([data[offset + 2], data[offset + 3]]) as usize; + offset += 4; + + if offset + element_length > data.len() { + return Err("Invalid WPS data length for element"); + } + + match element_type { + 0x1057 => { + wps_info.setup_state = match data[offset] { + 0x01 => WpsSetupState::NotConfigured, + 0x02 => WpsSetupState::Configured, + _ => return Err("Invalid WPS Setup State"), + }; + } + 0x1021 => { + wps_info.manufacturer = + parse_string_from_bytes(&data[offset..offset + element_length])?; + } + 0x1023 => { + wps_info.model = parse_string_from_bytes(&data[offset..offset + element_length])?; + } + 0x1024 => { + wps_info.model_number = + parse_string_from_bytes(&data[offset..offset + element_length])?; + } + 0x1042 => { + wps_info.serial_number = + parse_string_from_bytes(&data[offset..offset + element_length])?; + } + 0x1054 => { + let device_type_data = data[offset..offset + element_length].to_vec(); + if device_type_data.len() >= 8 { + let oui = device_type_data[2..6].to_vec(); + if oui == [0x00, 0x50, 0xf2, 0x04] { + let category = device_type_data[0..2].to_vec(); + let subcategory = device_type_data[6..8].to_vec(); + if let Some(cat) = bytes_to_category(category, subcategory) { + wps_info.primary_device_type = cat.to_string(); + } else { + "".clone_into(&mut wps_info.primary_device_type); + } + } + } else { + "".clone_into(&mut wps_info.primary_device_type); + } + } + 0x1011 => { + wps_info.device_name = + parse_string_from_bytes(&data[offset..offset + element_length])?; + } + _ => {} // Unknown or unhandled type + } + + offset += element_length; + } + + Ok(wps_info) +} + +fn bytes_to_category(catbytes: Vec, subbytes: Vec) -> Option { + if catbytes.len() == 2 { + let value = u16::from(catbytes[0]) << 8 | u16::from(catbytes[1]); + let subvalue = u16::from(subbytes[0]) << 8 | u16::from(subbytes[1]); + match value { + 0x0001 => match subvalue { + 0x0001 => Some(Category::Computer(Computers::PC)), + 0x0002 => Some(Category::Computer(Computers::Server)), + 0x0003 => Some(Category::Computer(Computers::MediaCenter)), + 0x0004 => Some(Category::Computer(Computers::UltraMobilePC)), + 0x0005 => Some(Category::Computer(Computers::Notebook)), + 0x0006 => Some(Category::Computer(Computers::Desktop)), + 0x0007 => Some(Category::Computer(Computers::MID)), + 0x0008 => Some(Category::Computer(Computers::Netbook)), + 0x0009 => Some(Category::Computer(Computers::Tablet)), + 0x000a => Some(Category::Computer(Computers::Ultrabook)), + _ => None, + }, + 0x0002 => match subvalue { + 0x0001 => Some(Category::InputDevice(InputDevices::Keyboard)), + 0x0002 => Some(Category::InputDevice(InputDevices::Mouse)), + 0x0003 => Some(Category::InputDevice(InputDevices::Joystick)), + 0x0004 => Some(Category::InputDevice(InputDevices::Trackball)), + 0x0005 => Some(Category::InputDevice(InputDevices::GamingController)), + 0x0006 => Some(Category::InputDevice(InputDevices::Remote)), + 0x0007 => Some(Category::InputDevice(InputDevices::Touchscreen)), + 0x0008 => Some(Category::InputDevice(InputDevices::BiometricReader)), + 0x0009 => Some(Category::InputDevice(InputDevices::BarcodeReader)), + _ => None, + }, + 0x0003 => match subvalue { + 0x0001 => Some(Category::PrintersScannersFaxCopier(PrintersEtAl::Printer)), + 0x0002 => Some(Category::PrintersScannersFaxCopier(PrintersEtAl::Scanner)), + 0x0003 => Some(Category::PrintersScannersFaxCopier(PrintersEtAl::Fax)), + 0x0004 => Some(Category::PrintersScannersFaxCopier(PrintersEtAl::Copier)), + 0x0005 => Some(Category::PrintersScannersFaxCopier(PrintersEtAl::AllInOne)), + _ => None, + }, + 0x0004 => match subvalue { + 0x0001 => Some(Category::Camera(Cameras::DigitalCamera)), + 0x0002 => Some(Category::Camera(Cameras::VideoCamera)), + 0x0003 => Some(Category::Camera(Cameras::Webcam)), + 0x0004 => Some(Category::Camera(Cameras::SecurityCamera)), + _ => None, + }, + 0x0005 => Some(Category::Storage(Storage::NAS)), + 0x0006 => match subvalue { + 0x0001 => Some(Category::NetworkInfrastructure(NetworkInfrastructure::AP)), + 0x0002 => Some(Category::NetworkInfrastructure( + NetworkInfrastructure::Router, + )), + 0x0003 => Some(Category::NetworkInfrastructure( + NetworkInfrastructure::Switch, + )), + 0x0004 => Some(Category::NetworkInfrastructure( + NetworkInfrastructure::Gateway, + )), + 0x0005 => Some(Category::NetworkInfrastructure( + NetworkInfrastructure::Bridge, + )), + _ => None, + }, + 0x0007 => match subvalue { + 0x0001 => Some(Category::Displays(Displays::Television)), + 0x0002 => Some(Category::Displays(Displays::ElectronicPictureFrame)), + 0x0003 => Some(Category::Displays(Displays::Projector)), + 0x0004 => Some(Category::Displays(Displays::Monitor)), + _ => None, + }, + 0x0008 => match subvalue { + 0x0001 => Some(Category::MultimediaDevices(MultimediaDevices::DAR)), + 0x0002 => Some(Category::MultimediaDevices(MultimediaDevices::PVR)), + 0x0003 => Some(Category::MultimediaDevices(MultimediaDevices::MCX)), + 0x0004 => Some(Category::MultimediaDevices(MultimediaDevices::SetTopBox)), + 0x0005 => Some(Category::MultimediaDevices(MultimediaDevices::MediaServer)), + 0x0006 => Some(Category::MultimediaDevices( + MultimediaDevices::ProtableVideoPlayer, + )), + _ => None, + }, + 0x0009 => match subvalue { + 0x0001 => Some(Category::GamingDevices(GamingDevices::Xbox)), + 0x0002 => Some(Category::GamingDevices(GamingDevices::Xbox360)), + 0x0003 => Some(Category::GamingDevices(GamingDevices::Playstation)), + 0x0004 => Some(Category::GamingDevices(GamingDevices::GameConsole)), + 0x0005 => Some(Category::GamingDevices(GamingDevices::PortableGamingDevice)), + _ => None, + }, + 0x000a => match subvalue { + 0x0001 => Some(Category::Telephone(Telephone::WindowsMobile)), + 0x0002 => Some(Category::Telephone(Telephone::PhoneSingleMode)), + 0x0003 => Some(Category::Telephone(Telephone::PhoneDualMode)), + 0x0004 => Some(Category::Telephone(Telephone::SmartphoneSingleMode)), + 0x0005 => Some(Category::Telephone(Telephone::SmartphoneDualMode)), + _ => None, + }, + 0x000b => match subvalue { + 0x0001 => Some(Category::AudioDevices(AudioDevices::AutioTunerReceiver)), + 0x0002 => Some(Category::AudioDevices(AudioDevices::Speakers)), + 0x0003 => Some(Category::AudioDevices(AudioDevices::PortableMusicPlayer)), + 0x0004 => Some(Category::AudioDevices(AudioDevices::Headset)), + 0x0005 => Some(Category::AudioDevices(AudioDevices::Headphones)), + 0x0006 => Some(Category::AudioDevices(AudioDevices::Microphone)), + 0x0007 => Some(Category::AudioDevices(AudioDevices::HomeTheaterSystems)), + _ => None, + }, + 0x000c => match subvalue { + 0x0001 => Some(Category::DockingDevices( + DockingDevices::ComputerDockingStation, + )), + 0x0002 => Some(Category::DockingDevices(DockingDevices::MediaKiosk)), + _ => None, + }, + _ => None, + } + } else { + None + } +} + +fn bytes_to_subcategory(category: &Category, bytes: Vec) -> Option { + if bytes.len() == 2 { + let value = u16::from(bytes[0]) << 8 | u16::from(bytes[1]); + match category { + Category::Computer(_) => match value { + 0x0001 => Some(Computers::PC.to_string()), + _ => None, + }, + _ => None, } + } else { + None + } +} + +fn parse_device_type_data(device_type_data: &[u8]) -> Option<(Vec, Vec, Vec)> { + if device_type_data.len() >= 8 { + let category = device_type_data[0..2].to_vec(); + let oui = device_type_data[2..6].to_vec(); + let subcategory = device_type_data[6..8].to_vec(); + + Some((category, oui, subcategory)) + } else { + None + } +} + +fn parse_string_from_bytes(data: &[u8]) -> Result { + match std::str::from_utf8(data) { + Ok(s) => Ok(s.to_string()), + Err(_) => Err("Invalid UTF-8 string"), + } +} + +pub fn parse_rsn_information(data: &[u8]) -> Result { + if data.len() < 10 { + return Err("RSN Information data too short"); + } + + let version = u16::from_ne_bytes([data[0], data[1]]); + if version != 1 { + return Err("Unsupported RSN version"); + } + + let group_cipher_suite = parse_group_cipher_suite(&data[2..6]); + let pairwise_cipher_suite_count = u16::from_ne_bytes([data[6], data[7]]) as usize; + let mut offset = 8; + + let mut pairwise_cipher_suites = Vec::new(); + for _ in 0..pairwise_cipher_suite_count { + let suite = parse_pairwise_cipher_suite(&data[offset..offset + 4]); + pairwise_cipher_suites.push(suite); + offset += 4; + } + + let akm_suite_count = u16::from_ne_bytes([data[offset], data[offset + 1]]) as usize; + offset += 2; + + let mut akm_suites = Vec::new(); + for _ in 0..akm_suite_count { + let suite = parse_akm_suite(&data[offset..offset + 4]); + akm_suites.push(suite); + offset += 4; + } + + if data.len() >= offset + 2 { + let rsn_capabilities = u16::from_ne_bytes([data[offset], data[offset + 1]]); + + let pre_auth = (rsn_capabilities & (1 << 0)) != 0; + let no_pairwise = (rsn_capabilities & (1 << 1)) != 0; + let ptksa_replay_counter = ((rsn_capabilities >> 2) & 0x03) as u8; // Extract 2 bits starting at position 2 + let gtksa_replay_counter = ((rsn_capabilities >> 4) & 0x03) as u8; // Extract 2 bits starting at position 4 + let mfp_required = (rsn_capabilities & (1 << 6)) != 0; + let mfp_capable = (rsn_capabilities & (1 << 7)) != 0; + let joint_multi_band_rsna = (rsn_capabilities & (1 << 8)) != 0; + let peerkey_enabled = (rsn_capabilities & (1 << 9)) != 0; + let extended_key_id = (rsn_capabilities & (1 << 13)) != 0; + let ocvc = (rsn_capabilities & (1 << 14)) != 0; + + Ok(RsnInformation { + version, + group_cipher_suite, + pairwise_cipher_suites, + akm_suites, + pre_auth, + no_pairwise, + ptksa_replay_counter, + gtksa_replay_counter, + mfp_required, + mfp_capable, + joint_multi_band_rsna, + peerkey_enabled, + extended_key_id, + ocvc, + }) + } else { + Err("RSN Information data too short for RSN Capabilities") + } +} + +pub fn parse_channel_switch(data: &[u8]) -> Option { + if data.len() < 3 { + return None; + } + + let mode = ChannelSwitchMode::from_u8(data[0]); + let new_channel = data[1]; + let count = data[2]; + + Some(ChannelSwitchAnnouncment { + mode, + new_channel, + count, + }) +} + +fn parse_cipher_suite(data: &[u8]) -> WpaCipherSuite { + match data { + [0x00, 0x50, 0xF2, 0x01] => WpaCipherSuite::Wep40, + [0x00, 0x50, 0xF2, 0x05] => WpaCipherSuite::Wep104, + [0x00, 0x50, 0xF2, 0x02] => WpaCipherSuite::Tkip, + [0x00, 0x50, 0xF2, 0x04] => WpaCipherSuite::Ccmp, + _ => WpaCipherSuite::Unknown(data.to_vec()), } +} + +fn parse_wpa_akm_suite(data: &[u8]) -> WpaAkmSuite { + match data { + [0x00, 0x50, 0xF2, 0x01] => WpaAkmSuite::Psk, + [0x00, 0x50, 0xF2, 0x02] => WpaAkmSuite::Eap, + _ => WpaAkmSuite::Unknown(data.to_vec()), + } +} + +fn parse_group_cipher_suite(data: &[u8]) -> RsnCipherSuite { + match data { + [0x00, 0x0F, 0xAC, 0x00] => RsnCipherSuite::None, + [0x00, 0x0F, 0xAC, 0x01] => RsnCipherSuite::WEP, + [0x00, 0x0F, 0xAC, 0x02] => RsnCipherSuite::TKIP, + [0x00, 0x0F, 0xAC, 0x03] => RsnCipherSuite::WRAP, + [0x00, 0x0F, 0xAC, 0x04] => RsnCipherSuite::CCMP, + [0x00, 0x0F, 0xAC, 0x05] => RsnCipherSuite::WEP104, + _ => RsnCipherSuite::Unknown(data.to_vec()), + } +} + +fn parse_pairwise_cipher_suite(data: &[u8]) -> RsnCipherSuite { + match data { + [0x00, 0x0F, 0xAC, 0x00] => RsnCipherSuite::None, + [0x00, 0x0F, 0xAC, 0x01] => RsnCipherSuite::WEP, + [0x00, 0x0F, 0xAC, 0x02] => RsnCipherSuite::TKIP, + [0x00, 0x0F, 0xAC, 0x03] => RsnCipherSuite::WRAP, + [0x00, 0x0F, 0xAC, 0x04] => RsnCipherSuite::CCMP, + [0x00, 0x0F, 0xAC, 0x05] => RsnCipherSuite::WEP104, + _ => RsnCipherSuite::Unknown(data.to_vec()), + } +} + +fn parse_akm_suite(data: &[u8]) -> RsnAkmSuite { + match data { + [0x00, 0x0F, 0xAC, 0x01] => RsnAkmSuite::EAP, + [0x00, 0x0F, 0xAC, 0x02] => RsnAkmSuite::PSK, + [0x00, 0x0F, 0xAC, 0x03] => RsnAkmSuite::EAPFT, + [0x00, 0x0F, 0xAC, 0x04] => RsnAkmSuite::PSKFT, + [0x00, 0x0F, 0xAC, 0x05] => RsnAkmSuite::EAP256, + [0x00, 0x0F, 0xAC, 0x06] => RsnAkmSuite::PSK256, + [0x00, 0x0F, 0xAC, 0x08] => RsnAkmSuite::SAE, + [0x00, 0x0F, 0xAC, 0x0b] => RsnAkmSuite::SUITEBEAP256, + _ => RsnAkmSuite::Unknown(data.to_vec()), + } +} - rates +fn parse_supported_rates(input: &[u8]) -> Vec { + input + .iter() + .map(|&data| { + let rate = (data & 0x7F) as f32 / 2.0; + let mandatory = (data & 0x80) != 0; + SupportedRate { mandatory, rate } + }) + .collect() } diff --git a/libwifi/src/parsers/frame_types/data.rs b/libwifi/src/parsers/frame_types/data.rs index 54c1000..424ff3c 100644 --- a/libwifi/src/parsers/frame_types/data.rs +++ b/libwifi/src/parsers/frame_types/data.rs @@ -1,16 +1,34 @@ +use std::time::SystemTime; + use crate::error::Error; use crate::frame::components::FrameControl; use crate::frame::*; use crate::parsers::parse_data_header; +use nom::{ + bytes::complete::take, + number::complete::{be_u16, be_u64, le_u8}, +}; /// Parse a [Data] frame. pub fn parse_data(frame_control: FrameControl, input: &[u8]) -> Result { let (remaining, header) = parse_data_header(frame_control, input)?; - Ok(Frame::Data(Data { - header, - data: remaining.into(), - })) + // Check for EAPOL LLC header + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + if remaining.starts_with(&eapol_llc_header) { + let eapol_key = parse_eapol_key(&remaining[eapol_llc_header.len()..])?; + Ok(Frame::Data(Data { + header, + eapol_key: Some(eapol_key), + data: Vec::new(), // No other data if EAPOL-Key frame is present + })) + } else { + Ok(Frame::Data(Data { + header, + eapol_key: None, + data: remaining.to_vec(), + })) + } } /// Parse a [NullData] frame. @@ -24,10 +42,22 @@ pub fn parse_null_data(frame_control: FrameControl, input: &[u8]) -> Result Result { let (remaining, header) = parse_data_header(frame_control, input)?; - Ok(Frame::QosData(QosData { - header, - data: remaining.into(), - })) + // Check for EAPOL LLC header + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + if remaining.starts_with(&eapol_llc_header) { + let eapol_key = parse_eapol_key(&remaining[eapol_llc_header.len()..])?; + Ok(Frame::QosData(QosData { + header, + eapol_key: Some(eapol_key), + data: Vec::new(), // No other data if EAPOL-Key frame is present + })) + } else { + Ok(Frame::QosData(QosData { + header, + eapol_key: None, + data: remaining.to_vec(), + })) + } } /// Parse a [QosNull] frame. @@ -36,3 +66,207 @@ pub fn parse_qos_null(frame_control: FrameControl, input: &[u8]) -> Result Result { + let (remaining, header) = parse_data_header(frame_control, input)?; + + // Check for EAPOL LLC header + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + if remaining.starts_with(&eapol_llc_header) { + let eapol_key = parse_eapol_key(&remaining[eapol_llc_header.len()..])?; + Ok(Frame::DataCfAck(DataCfAck { + header, + eapol_key: Some(eapol_key), + data: Vec::new(), // No other data if EAPOL-Key frame is present + })) + } else { + Ok(Frame::DataCfAck(DataCfAck { + header, + eapol_key: None, + data: remaining.to_vec(), + })) + } +} + +// DataCfPoll +pub fn parse_data_cf_poll(frame_control: FrameControl, input: &[u8]) -> Result { + let (remaining, header) = parse_data_header(frame_control, input)?; + + // Check for EAPOL LLC header + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + if remaining.starts_with(&eapol_llc_header) { + let eapol_key = parse_eapol_key(&remaining[eapol_llc_header.len()..])?; + Ok(Frame::DataCfPoll(DataCfPoll { + header, + eapol_key: Some(eapol_key), + data: Vec::new(), // No other data if EAPOL-Key frame is present + })) + } else { + Ok(Frame::DataCfPoll(DataCfPoll { + header, + eapol_key: None, + data: remaining.to_vec(), + })) + } +} + +// DataCfAckCfPoll +pub fn parse_data_cf_ack_cf_poll( + frame_control: FrameControl, + input: &[u8], +) -> Result { + let (remaining, header) = parse_data_header(frame_control, input)?; + + // Check for EAPOL LLC header + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + if remaining.starts_with(&eapol_llc_header) { + let eapol_key = parse_eapol_key(&remaining[eapol_llc_header.len()..])?; + Ok(Frame::DataCfAckCfPoll(DataCfAckCfPoll { + header, + eapol_key: Some(eapol_key), + data: Vec::new(), // No other data if EAPOL-Key frame is present + })) + } else { + Ok(Frame::DataCfAckCfPoll(DataCfAckCfPoll { + header, + eapol_key: None, + data: remaining.to_vec(), + })) + } +} + +// CfAck +pub fn parse_cf_ack(frame_control: FrameControl, input: &[u8]) -> Result { + let (_, header) = parse_data_header(frame_control, input)?; + Ok(Frame::CfAck(CfAck { header })) +} + +// CfPoll +pub fn parse_cf_poll(frame_control: FrameControl, input: &[u8]) -> Result { + let (_, header) = parse_data_header(frame_control, input)?; + Ok(Frame::CfPoll(CfPoll { header })) +} + +// CfAckCfPoll +pub fn parse_cf_ack_cf_poll(frame_control: FrameControl, input: &[u8]) -> Result { + let (_, header) = parse_data_header(frame_control, input)?; + Ok(Frame::CfAckCfPoll(CfAckCfPoll { header })) +} + +// QosDataCfAck +pub fn parse_qos_data_cf_ack(frame_control: FrameControl, input: &[u8]) -> Result { + let (remaining, header) = parse_data_header(frame_control, input)?; + + // Check for EAPOL LLC header + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + if remaining.starts_with(&eapol_llc_header) { + let eapol_key = parse_eapol_key(&remaining[eapol_llc_header.len()..])?; + Ok(Frame::QosDataCfAck(QosDataCfAck { + header, + eapol_key: Some(eapol_key), + data: Vec::new(), // No other data if EAPOL-Key frame is present + })) + } else { + Ok(Frame::QosDataCfAck(QosDataCfAck { + header, + eapol_key: None, + data: remaining.to_vec(), + })) + } +} + +// QosDataCfPoll +pub fn parse_qos_data_cf_poll(frame_control: FrameControl, input: &[u8]) -> Result { + let (remaining, header) = parse_data_header(frame_control, input)?; + + // Check for EAPOL LLC header + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + if remaining.starts_with(&eapol_llc_header) { + let eapol_key = parse_eapol_key(&remaining[eapol_llc_header.len()..])?; + Ok(Frame::QosDataCfPoll(QosDataCfPoll { + header, + eapol_key: Some(eapol_key), + data: Vec::new(), // No other data if EAPOL-Key frame is present + })) + } else { + Ok(Frame::QosDataCfPoll(QosDataCfPoll { + header, + eapol_key: None, + data: remaining.to_vec(), + })) + } +} + +// QosDataCfAckCfPoll +pub fn parse_qos_data_cf_ack_cf_poll( + frame_control: FrameControl, + input: &[u8], +) -> Result { + let (remaining, header) = parse_data_header(frame_control, input)?; + + // Check for EAPOL LLC header + let eapol_llc_header: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x88, 0x8e]; + if remaining.starts_with(&eapol_llc_header) { + let eapol_key = parse_eapol_key(&remaining[eapol_llc_header.len()..])?; + Ok(Frame::QosDataCfAckCfPoll(QosDataCfAckCfPoll { + header, + eapol_key: Some(eapol_key), + data: Vec::new(), // No other data if EAPOL-Key frame is present + })) + } else { + Ok(Frame::QosDataCfAckCfPoll(QosDataCfAckCfPoll { + header, + eapol_key: None, + data: remaining.to_vec(), + })) + } +} + +// QosCfPoll +pub fn parse_qos_cf_poll(frame_control: FrameControl, input: &[u8]) -> Result { + let (_, header) = parse_data_header(frame_control, input)?; + Ok(Frame::QosCfPoll(QosCfPoll { header })) +} + +// QosCfAckCfPoll +pub fn parse_qos_cf_ack_cf_poll(frame_control: FrameControl, input: &[u8]) -> Result { + let (_, header) = parse_data_header(frame_control, input)?; + Ok(Frame::QosCfAckCfPoll(QosCfAckCfPoll { header })) +} + +/// Parse a [EapolKey] Frame +pub fn parse_eapol_key(input: &[u8]) -> Result { + let (input, protocol_version) = le_u8(input)?; + let (input, packet_type) = le_u8(input)?; + let (input, packet_length) = be_u16(input)?; + let (input, descriptor_type) = le_u8(input)?; + let (input, key_information) = be_u16(input)?; + let (input, key_length) = be_u16(input)?; + let (input, replay_counter) = be_u64(input)?; + let (input, key_nonce) = take(32usize)(input)?; + let (input, key_iv) = take(16usize)(input)?; + let (input, key_rsc) = be_u64(input)?; + let (input, key_id) = be_u64(input)?; + let (input, key_mic) = take(16usize)(input)?; + let (input, key_data_length) = be_u16(input)?; + let (_, key_data) = take(key_data_length as usize)(input)?; + + Ok(EapolKey { + protocol_version, + timestamp: SystemTime::now(), + packet_type, + packet_length, + descriptor_type, + key_information, + key_length, + replay_counter, + key_nonce: key_nonce.try_into().expect("Slice with incorrect length"), + key_iv: key_iv.try_into().expect("Slice with incorrect length"), + key_rsc, + key_id, + key_mic: key_mic.try_into().expect("Slice with incorrect length"), + key_data_length, + key_data: key_data.to_vec(), + }) +} diff --git a/libwifi/src/parsers/frame_types/management.rs b/libwifi/src/parsers/frame_types/management.rs index f52a6e1..60ed287 100644 --- a/libwifi/src/parsers/frame_types/management.rs +++ b/libwifi/src/parsers/frame_types/management.rs @@ -1,10 +1,12 @@ -use nom::number::complete::{le_u16, le_u64}; +use nom::bytes::complete::take; +use nom::number::complete::{le_u16, le_u64, le_u8}; + use nom::sequence::tuple; use crate::error::Error; use crate::frame::components::FrameControl; use crate::frame::*; -use crate::parsers::{parse_management_header, parse_station_info}; +use crate::parsers::{parse_mac, parse_management_header, parse_station_info}; /// Parse an [AssociationRequest] frame. /// @@ -29,6 +31,74 @@ pub fn parse_association_request( })) } +/// Parse an [Authentication] frame. +/// +/// The general structure is: +/// - ManagementHeader +/// - Authentication Algorithm Number +/// - Authentication Transaction Sequence Number +/// - Status Code +/// - Challenge Text (optional, dynamic length) +pub fn parse_authentication_frame( + frame_control: FrameControl, + input: &[u8], +) -> Result { + let (input, header) = parse_management_header(frame_control, input)?; + + // Parse the fixed fields + let (input, auth_algorithm) = le_u16(input)?; + let (input, auth_seq) = le_u16(input)?; + let (input, status_code) = le_u16(input)?; + + let mut challenge_text = None; + let mut station_info = None; + + if auth_algorithm == 1 && (auth_seq == 2 || auth_seq == 3) { + // Parse the optional challenge text + if !input.is_empty() { + let (input, length) = le_u16(input)?; + let (_input, text) = take(length)(input)?; + challenge_text = Some(text.to_vec()); + }; + } else { + // Parse station info (extended capabilities) if present + if !input.is_empty() { + if let Ok((_input, info)) = parse_station_info(input) { + station_info = Some(info); + } + } + } + + Ok(Frame::Authentication(Authentication { + header, + auth_algorithm, + auth_seq, + status_code, + challenge_text, + station_info, + })) +} + +/// Parse a [Deauthentication] frame. +/// +/// The general structure is: +/// - ManagementHeader +/// - Reason Code +pub fn parse_deauthentication_frame( + frame_control: FrameControl, + input: &[u8], +) -> Result { + let (input, header) = parse_management_header(frame_control, input)?; + + // Parse the reason code + let (_, reason_code) = le_u16(input)?; + + Ok(Frame::Deauthentication(Deauthentication { + header, + reason_code: DeauthenticationReason::from_code(reason_code), + })) +} + /// Parse an [AssociationResponse] frame. /// /// The general structure is: @@ -54,6 +124,58 @@ pub fn parse_association_response( })) } +/// Parse a [ReassociationRequest] frame. +/// +/// The general structure is: +/// - ManagementHeader +/// - Capability info +/// - Listen interval +/// - Current AP address (MAC address) +/// - Dynamic fields (like StationInfo) +pub fn parse_reassociation_request( + frame_control: FrameControl, + input: &[u8], +) -> Result { + let (input, header) = parse_management_header(frame_control, input)?; + let (input, (capability_info, listen_interval)) = tuple((le_u16, le_u16))(input)?; + + let (input, current_ap_address) = parse_mac(input)?; + let (_, station_info) = parse_station_info(input)?; + + Ok(Frame::ReassociationRequest(ReassociationRequest { + header, + capability_info, + listen_interval, + current_ap_address, + station_info, + })) +} + +/// Parse a [ReassociationResponse] frame. +/// +/// The general structure is: +/// - ManagementHeader +/// - Capability info +/// - Status code +/// - Association id +pub fn parse_reassociation_response( + frame_control: FrameControl, + input: &[u8], +) -> Result { + let (input, header) = parse_management_header(frame_control, input)?; + let (_, (capability_info, status_code, association_id)) = + tuple((le_u16, le_u16, le_u16))(input)?; + let (_input, station_info) = parse_station_info(input)?; + + Ok(Frame::ReassociationResponse(ReassociationResponse { + header, + capability_info, + status_code, + association_id, + station_info, + })) +} + /// Parse a [Beacon] frame. /// /// The general structure is: @@ -63,6 +185,7 @@ pub fn parse_association_response( /// - Dynamic fields pub fn parse_beacon(frame_control: FrameControl, input: &[u8]) -> Result { let (input, header) = parse_management_header(frame_control, input)?; + let (_, (timestamp, beacon_interval, capability_info, station_info)) = tuple((le_u64, le_u16, le_u16, parse_station_info))(input)?; @@ -110,3 +233,34 @@ pub fn parse_probe_response(frame_control: FrameControl, input: &[u8]) -> Result station_info, })) } + +/// Parse an [Action] frame. +/// +/// The general structure is: +/// - ManagementHeader +/// - Category (indicating the type of action, e.g., spectrum management, QoS) +/// - Action (specific action within the category) +/// - Dynamic fields (vary depending on the category and action) +pub fn parse_action(frame_control: FrameControl, input: &[u8]) -> Result { + let (input, header) = parse_management_header(frame_control, input)?; + + // Parsing the category field (1 byte) + let (input, category) = le_u8(input)?; + + // Parsing the action field (1 byte) + let (input, action) = le_u8(input)?; + + // Parsing the dynamic fields (depends on category and action) + let (_, station_info) = parse_station_info(input)?; + + // Assuming `StationInfo` is part of dynamic fields and its parsing + // is handled inside `parse_dynamic_fields` + // ... + + Ok(Frame::Action(Action { + header, + category: category.into(), // Convert to enum variant if needed + action, // Convert to enum variant if needed + station_info, // Assuming this comes from dynamic fields + })) +} diff --git a/libwifi/tests/control_frames.rs b/libwifi/tests/control_frames.rs index 40f85d8..ba04e46 100644 --- a/libwifi/tests/control_frames.rs +++ b/libwifi/tests/control_frames.rs @@ -10,7 +10,7 @@ fn test_rts() { 20, 125, 218, 170, 84, 81, // Second Address ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::Rts(_))); } @@ -23,7 +23,7 @@ fn test_cts() { 224, 62, 68, 8, 195, 239, // First Address ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::Cts(_))); } @@ -36,7 +36,7 @@ fn test_ack() { 104, 217, 60, 214, 195, 239, // First Address ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::Ack(_))); } @@ -52,7 +52,7 @@ fn test_single_tid_compressed_block_ack_request() { 160, 15, // Starting sequence number of the single TID ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::BlockAckRequest(_))); @@ -73,7 +73,7 @@ fn test_compressed_bitmap_block_ack() { 1, 0, 0, 0, 0, 0, 0, 0, // BlockAck Bitmap ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::BlockAck(_))); diff --git a/libwifi/tests/data_frames.rs b/libwifi/tests/data_frames.rs index fd3db9a..22cebfa 100644 --- a/libwifi/tests/data_frames.rs +++ b/libwifi/tests/data_frames.rs @@ -18,7 +18,7 @@ fn test_data() { 178, 230, 66, ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::Data(_))); } @@ -50,7 +50,7 @@ fn test_qos_data() { 20, 115, 79, 168, 50, 132, 160, 219, 152, 184, 110, 181, 105, 4, 153, 182, 129, 58, 87, 72, 110, 194, 217, 192, 151, 89, 181, 161, 122, 249, 129, 201, 75, 6, 32, 158, 213, 21, 168, ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::QosData(_))); } @@ -66,7 +66,7 @@ fn test_qos_null() { 0, 0, // QoS Header ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::QosNull(_))); } diff --git a/libwifi/tests/management_frames.rs b/libwifi/tests/management_frames.rs index 836afa4..08a7ca3 100644 --- a/libwifi/tests/management_frames.rs +++ b/libwifi/tests/management_frames.rs @@ -36,7 +36,7 @@ fn test_beacon() { 47, 0, ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::Beacon(_))); @@ -61,7 +61,7 @@ fn test_probe_request() { 0, 0, 0, 0, 0, 191, 12, 178, 97, 128, 51, 254, 255, 134, 1, 254, 255, 134, 1, ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::ProbeRequest(_))); } @@ -96,7 +96,7 @@ fn test_probe_response() { 24, 0, 80, 242, 2, 1, 1, 132, 0, 3, 164, 0, 0, 39, 164, 0, 0, 66, 67, 94, 0, 98, 50, 47, 0, ]; - let frame = parse_frame(&payload).expect("Payload should be valid"); + let frame = parse_frame(&payload, false).expect("Payload should be valid"); println!("{frame:?}"); assert!(matches!(frame, Frame::ProbeResponse(_))); diff --git a/libwifi_macros/Cargo.toml b/libwifi_macros/Cargo.toml index 523f2c6..bbc6700 100644 --- a/libwifi_macros/Cargo.toml +++ b/libwifi_macros/Cargo.toml @@ -1,18 +1,18 @@ [package] name = "libwifi_macros" -version = "0.0.2" description = "A library providing macros for the libwifi crate." +version = "0.0.2" authors.workspace = true +edition.workspace = true homepage.workspace = true -repository.workspace = true license.workspace = true -edition.workspace = true +repository.workspace = true rust-version.workspace = true [lib] proc-macro = true [dependencies] -syn = "2" proc-macro2 = "1" quote = "1" +syn = "2"