Skip to main content

openipc_core/
ieee80211.rs

1use crate::channel::ChannelId;
2
3/// Length of the 802.11 data header used by OpenIPC/WFB frames.
4pub const IEEE80211_HEADER_LEN: usize = 24;
5/// Length of the optional 802.11 frame check sequence.
6pub const IEEE80211_FCS_LEN: usize = 4;
7/// Marker bytes mirrored in the OpenIPC/WFB source and destination addresses.
8pub const WFB_PREFIX: [u8; 2] = [0x57, 0x42];
9/// Frame-control bytes for QoS data from station to distribution system.
10pub const QOS_DATA_FROM_STA_TO_DS: [u8; 2] = [0x08, 0x01];
11/// Offset of the channel-id bytes in the source-address mirror.
12pub const SRC_MAC_CHANNEL_OFFSET: usize = 12;
13/// Offset of the channel-id bytes in the destination-address mirror.
14pub const DST_MAC_CHANNEL_OFFSET: usize = 18;
15/// Offset of the 802.11 sequence-control bytes.
16pub const FRAME_SEQUENCE_OFFSET: usize = 22;
17
18/// Whether received 802.11 frames include their trailing FCS bytes.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum FrameLayout {
21    /// Frames still include the 4-byte FCS.
22    WithFcs,
23    /// Frames have already had the FCS stripped.
24    WithoutFcs,
25}
26
27/// Reason an 802.11 frame could not be interpreted as an OpenIPC/WFB frame.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum FrameError {
30    /// The buffer is too short for the selected [`FrameLayout`].
31    TooShort,
32    /// The frame-control bytes are not the expected QoS data shape.
33    NotDataFrame,
34    /// The mirrored OpenIPC address/channel fields are malformed.
35    InvalidWfbAddressMirror,
36    /// No payload remains after the 802.11 header and optional FCS.
37    MissingPayload,
38}
39
40/// Borrowed view of an OpenIPC/WFB 802.11 data frame.
41#[derive(Debug, Clone, Copy)]
42pub struct WifiFrame<'a> {
43    data: &'a [u8],
44    layout: FrameLayout,
45}
46
47impl<'a> WifiFrame<'a> {
48    /// Parse and validate a borrowed 802.11 frame.
49    pub fn parse(data: &'a [u8], layout: FrameLayout) -> Result<Self, FrameError> {
50        let min_len = match layout {
51            FrameLayout::WithFcs => IEEE80211_HEADER_LEN + IEEE80211_FCS_LEN + 1,
52            FrameLayout::WithoutFcs => IEEE80211_HEADER_LEN + 1,
53        };
54        if data.len() < min_len {
55            return Err(FrameError::TooShort);
56        }
57
58        let frame = Self { data, layout };
59        if !frame.is_qos_data_from_sta_to_ds() {
60            return Err(FrameError::NotDataFrame);
61        }
62        if !frame.has_mirrored_air_id_and_radio_port() {
63            return Err(FrameError::InvalidWfbAddressMirror);
64        }
65        if frame.payload().is_empty() {
66            return Err(FrameError::MissingPayload);
67        }
68        Ok(frame)
69    }
70
71    /// Return the original frame bytes.
72    pub const fn raw(&self) -> &'a [u8] {
73        self.data
74    }
75
76    /// Return the frame layout used during parsing.
77    pub const fn layout(&self) -> FrameLayout {
78        self.layout
79    }
80
81    /// Return the WFB forwarder payload without the 802.11 header or FCS.
82    pub fn payload(&self) -> &'a [u8] {
83        match self.layout {
84            FrameLayout::WithFcs => {
85                &self.data[IEEE80211_HEADER_LEN..self.data.len() - IEEE80211_FCS_LEN]
86            }
87            FrameLayout::WithoutFcs => &self.data[IEEE80211_HEADER_LEN..],
88        }
89    }
90
91    /// Build the 8-byte nonce input mirrored from the WFB address fields.
92    pub fn nonce(&self) -> [u8; 8] {
93        let mut nonce = [0; 8];
94        nonce[0..4].copy_from_slice(&self.data[11..15]);
95        nonce[4..8].copy_from_slice(&self.data[17..21]);
96        nonce
97    }
98
99    /// Return true when both mirrored address fields match `channel_id`.
100    pub fn matches_channel_id(&self, channel_id: ChannelId) -> bool {
101        let id = channel_id.to_be_bytes();
102        self.data[10..12] == WFB_PREFIX
103            && self.data[12..16] == id
104            && self.data[16..18] == WFB_PREFIX
105            && self.data[18..22] == id
106    }
107
108    /// Extract the mirrored channel id when the WFB address fields agree.
109    pub fn channel_id(&self) -> Option<ChannelId> {
110        if self.data[10..12] != WFB_PREFIX || self.data[16..18] != WFB_PREFIX {
111            return None;
112        }
113        if self.data[12..16] != self.data[18..22] {
114            return None;
115        }
116        let mut bytes = [0; 4];
117        bytes.copy_from_slice(&self.data[12..16]);
118        Some(ChannelId::new(u32::from_be_bytes(bytes)))
119    }
120
121    fn is_qos_data_from_sta_to_ds(&self) -> bool {
122        self.data[0..2] == QOS_DATA_FROM_STA_TO_DS
123    }
124
125    fn has_mirrored_air_id_and_radio_port(&self) -> bool {
126        self.data[10] == self.data[16] && self.data[15] == self.data[21]
127    }
128}
129
130/// Build the standard OpenIPC/WFB QoS data 802.11 header.
131pub fn build_wfb_header(
132    channel_id: ChannelId,
133    sequence_control: [u8; 2],
134) -> [u8; IEEE80211_HEADER_LEN] {
135    build_wfb_header_with_frame_type(channel_id, sequence_control, QOS_DATA_FROM_STA_TO_DS[0])
136}
137
138/// Build an OpenIPC/WFB 802.11 header with an explicit first frame-control byte.
139pub fn build_wfb_header_with_frame_type(
140    channel_id: ChannelId,
141    sequence_control: [u8; 2],
142    frame_type: u8,
143) -> [u8; IEEE80211_HEADER_LEN] {
144    let id = channel_id.to_be_bytes();
145    [
146        frame_type,
147        0x01,
148        0x00,
149        0x00,
150        0xff,
151        0xff,
152        0xff,
153        0xff,
154        0xff,
155        0xff,
156        0x57,
157        0x42,
158        id[0],
159        id[1],
160        id[2],
161        id[3],
162        0x57,
163        0x42,
164        id[0],
165        id[1],
166        id[2],
167        id[3],
168        sequence_control[0],
169        sequence_control[1],
170    ]
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn parses_reference_wfb_frame_with_fcs() {
179        let channel = ChannelId::default_video();
180        let mut bytes = Vec::from(build_wfb_header(channel, [0x10, 0x00]));
181        bytes.extend_from_slice(&[1, 2, 3, 4]);
182        bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
183
184        let frame = WifiFrame::parse(&bytes, FrameLayout::WithFcs).unwrap();
185        assert!(frame.matches_channel_id(channel));
186        assert_eq!(frame.channel_id(), Some(channel));
187        assert_eq!(frame.payload(), &[1, 2, 3, 4]);
188        assert_eq!(
189            frame.nonce(),
190            [
191                0x42,
192                channel.to_be_bytes()[0],
193                channel.to_be_bytes()[1],
194                channel.to_be_bytes()[2],
195                0x42,
196                channel.to_be_bytes()[0],
197                channel.to_be_bytes()[1],
198                channel.to_be_bytes()[2]
199            ]
200        );
201    }
202
203    #[test]
204    fn rejects_short_frames() {
205        assert_eq!(
206            WifiFrame::parse(&[0x08, 0x01], FrameLayout::WithFcs).unwrap_err(),
207            FrameError::TooShort
208        );
209    }
210}