Skip to main content

openipc_core/
ieee80211.rs

1use crate::channel::ChannelId;
2
3pub const IEEE80211_HEADER_LEN: usize = 24;
4pub const IEEE80211_FCS_LEN: usize = 4;
5pub const WFB_PREFIX: [u8; 2] = [0x57, 0x42];
6pub const QOS_DATA_FROM_STA_TO_DS: [u8; 2] = [0x08, 0x01];
7pub const SRC_MAC_CHANNEL_OFFSET: usize = 12;
8pub const DST_MAC_CHANNEL_OFFSET: usize = 18;
9pub const FRAME_SEQUENCE_OFFSET: usize = 22;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum FrameLayout {
13    WithFcs,
14    WithoutFcs,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum FrameError {
19    TooShort,
20    NotDataFrame,
21    InvalidWfbAddressMirror,
22    MissingPayload,
23}
24
25#[derive(Debug, Clone, Copy)]
26pub struct WifiFrame<'a> {
27    data: &'a [u8],
28    layout: FrameLayout,
29}
30
31impl<'a> WifiFrame<'a> {
32    pub fn parse(data: &'a [u8], layout: FrameLayout) -> Result<Self, FrameError> {
33        let min_len = match layout {
34            FrameLayout::WithFcs => IEEE80211_HEADER_LEN + IEEE80211_FCS_LEN + 1,
35            FrameLayout::WithoutFcs => IEEE80211_HEADER_LEN + 1,
36        };
37        if data.len() < min_len {
38            return Err(FrameError::TooShort);
39        }
40
41        let frame = Self { data, layout };
42        if !frame.is_qos_data_from_sta_to_ds() {
43            return Err(FrameError::NotDataFrame);
44        }
45        if !frame.has_mirrored_air_id_and_radio_port() {
46            return Err(FrameError::InvalidWfbAddressMirror);
47        }
48        if frame.payload().is_empty() {
49            return Err(FrameError::MissingPayload);
50        }
51        Ok(frame)
52    }
53
54    pub const fn raw(&self) -> &'a [u8] {
55        self.data
56    }
57
58    pub const fn layout(&self) -> FrameLayout {
59        self.layout
60    }
61
62    pub fn payload(&self) -> &'a [u8] {
63        match self.layout {
64            FrameLayout::WithFcs => {
65                &self.data[IEEE80211_HEADER_LEN..self.data.len() - IEEE80211_FCS_LEN]
66            }
67            FrameLayout::WithoutFcs => &self.data[IEEE80211_HEADER_LEN..],
68        }
69    }
70
71    pub fn nonce(&self) -> [u8; 8] {
72        let mut nonce = [0; 8];
73        nonce[0..4].copy_from_slice(&self.data[11..15]);
74        nonce[4..8].copy_from_slice(&self.data[17..21]);
75        nonce
76    }
77
78    pub fn matches_channel_id(&self, channel_id: ChannelId) -> bool {
79        let id = channel_id.to_be_bytes();
80        self.data[10..12] == WFB_PREFIX
81            && self.data[12..16] == id
82            && self.data[16..18] == WFB_PREFIX
83            && self.data[18..22] == id
84    }
85
86    pub fn channel_id(&self) -> Option<ChannelId> {
87        if self.data[10..12] != WFB_PREFIX || self.data[16..18] != WFB_PREFIX {
88            return None;
89        }
90        if self.data[12..16] != self.data[18..22] {
91            return None;
92        }
93        let mut bytes = [0; 4];
94        bytes.copy_from_slice(&self.data[12..16]);
95        Some(ChannelId::new(u32::from_be_bytes(bytes)))
96    }
97
98    fn is_qos_data_from_sta_to_ds(&self) -> bool {
99        self.data[0..2] == QOS_DATA_FROM_STA_TO_DS
100    }
101
102    fn has_mirrored_air_id_and_radio_port(&self) -> bool {
103        self.data[10] == self.data[16] && self.data[15] == self.data[21]
104    }
105}
106
107pub fn build_wfb_header(
108    channel_id: ChannelId,
109    sequence_control: [u8; 2],
110) -> [u8; IEEE80211_HEADER_LEN] {
111    build_wfb_header_with_frame_type(channel_id, sequence_control, QOS_DATA_FROM_STA_TO_DS[0])
112}
113
114pub fn build_wfb_header_with_frame_type(
115    channel_id: ChannelId,
116    sequence_control: [u8; 2],
117    frame_type: u8,
118) -> [u8; IEEE80211_HEADER_LEN] {
119    let id = channel_id.to_be_bytes();
120    [
121        frame_type,
122        0x01,
123        0x00,
124        0x00,
125        0xff,
126        0xff,
127        0xff,
128        0xff,
129        0xff,
130        0xff,
131        0x57,
132        0x42,
133        id[0],
134        id[1],
135        id[2],
136        id[3],
137        0x57,
138        0x42,
139        id[0],
140        id[1],
141        id[2],
142        id[3],
143        sequence_control[0],
144        sequence_control[1],
145    ]
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn parses_reference_wfb_frame_with_fcs() {
154        let channel = ChannelId::default_video();
155        let mut bytes = Vec::from(build_wfb_header(channel, [0x10, 0x00]));
156        bytes.extend_from_slice(&[1, 2, 3, 4]);
157        bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
158
159        let frame = WifiFrame::parse(&bytes, FrameLayout::WithFcs).unwrap();
160        assert!(frame.matches_channel_id(channel));
161        assert_eq!(frame.channel_id(), Some(channel));
162        assert_eq!(frame.payload(), &[1, 2, 3, 4]);
163        assert_eq!(
164            frame.nonce(),
165            [
166                0x42,
167                channel.to_be_bytes()[0],
168                channel.to_be_bytes()[1],
169                channel.to_be_bytes()[2],
170                0x42,
171                channel.to_be_bytes()[0],
172                channel.to_be_bytes()[1],
173                channel.to_be_bytes()[2]
174            ]
175        );
176    }
177
178    #[test]
179    fn rejects_short_frames() {
180        assert_eq!(
181            WifiFrame::parse(&[0x08, 0x01], FrameLayout::WithFcs).unwrap_err(),
182            FrameError::TooShort
183        );
184    }
185}