Skip to main content

stackforge_core/layer/dot11/
radiotap.rs

1//! `RadioTap` header implementation for IEEE 802.11 frames.
2//!
3//! `RadioTap` is a de facto standard for 802.11 frame injection and reception.
4//! It provides a flexible, extensible header format that precedes the actual
5//! 802.11 frame and carries metadata like signal strength, channel, and rate.
6//!
7//! Reference: <https://www.radiotap.org/>
8
9use crate::layer::field::FieldError;
10use crate::layer::{Layer, LayerIndex, LayerKind};
11
12use super::types::radiotap_present;
13
14/// Minimum `RadioTap` header length: version(1) + pad(1) + len(2) + present(4) = 8 bytes.
15pub const RADIOTAP_MIN_HEADER_LEN: usize = 8;
16
17/// `RadioTap` header field offsets.
18pub mod offsets {
19    pub const VERSION: usize = 0;
20    pub const PAD: usize = 1;
21    pub const LENGTH: usize = 2;
22    pub const PRESENT: usize = 4;
23}
24
25/// Alignment requirements for `RadioTap` fields (size, alignment).
26/// Returns (`field_size`, `required_alignment`) for a given present bit.
27fn field_alignment(bit: u32) -> (usize, usize) {
28    match bit {
29        radiotap_present::TSFT => (8, 8),
30        radiotap_present::FLAGS => (1, 1),
31        radiotap_present::RATE => (1, 1),
32        radiotap_present::CHANNEL => (4, 2), // freq(2) + flags(2)
33        radiotap_present::FHSS => (2, 1),
34        radiotap_present::DBM_ANT_SIGNAL => (1, 1),
35        radiotap_present::DBM_ANT_NOISE => (1, 1),
36        radiotap_present::LOCK_QUALITY => (2, 2),
37        radiotap_present::TX_ATTENUATION => (2, 2),
38        radiotap_present::DB_TX_ATTENUATION => (2, 2),
39        radiotap_present::DBM_TX_POWER => (1, 1),
40        radiotap_present::ANTENNA => (1, 1),
41        radiotap_present::DB_ANT_SIGNAL => (1, 1),
42        radiotap_present::DB_ANT_NOISE => (1, 1),
43        radiotap_present::RX_FLAGS => (2, 2),
44        radiotap_present::TX_FLAGS => (2, 2),
45        radiotap_present::MCS => (3, 1), // known(1) + flags(1) + mcs(1)
46        radiotap_present::A_MPDU => (8, 4), // ref(4) + flags(4)
47        radiotap_present::VHT => (12, 2), // known(2)+flags(1)+bw(1)+mcs_nss(4x1)+coding(1)+group(1)+partial_aid(2)
48        radiotap_present::HE => (12, 2),  // 6x u16
49        _ => (0, 1),
50    }
51}
52
53/// Parsed `RadioTap` field values.
54#[derive(Debug, Clone, Default)]
55pub struct RadioTapFields {
56    /// TSFT (MAC timestamp in microseconds).
57    pub tsft: Option<u64>,
58    /// Flags byte.
59    pub flags: Option<u8>,
60    /// Rate in units of 500 Kbps.
61    pub rate: Option<u8>,
62    /// Channel frequency in MHz.
63    pub channel_freq: Option<u16>,
64    /// Channel flags.
65    pub channel_flags: Option<u16>,
66    /// Antenna signal in dBm.
67    pub dbm_ant_signal: Option<i8>,
68    /// Antenna noise in dBm.
69    pub dbm_ant_noise: Option<i8>,
70    /// Lock quality (signal quality).
71    pub lock_quality: Option<u16>,
72    /// Antenna index.
73    pub antenna: Option<u8>,
74    /// Antenna signal in dB.
75    pub db_ant_signal: Option<u8>,
76    /// Antenna noise in dB.
77    pub db_ant_noise: Option<u8>,
78    /// RX flags.
79    pub rx_flags: Option<u16>,
80    /// TX flags.
81    pub tx_flags: Option<u16>,
82    /// MCS known bitmask.
83    pub mcs_known: Option<u8>,
84    /// MCS flags.
85    pub mcs_flags: Option<u8>,
86    /// MCS index.
87    pub mcs_index: Option<u8>,
88    /// A-MPDU reference number.
89    pub a_mpdu_ref: Option<u32>,
90    /// A-MPDU flags.
91    pub a_mpdu_flags: Option<u32>,
92}
93
94/// `RadioTap` layer - a zero-copy view into the `RadioTap` header.
95///
96/// The `RadioTap` header has a variable length indicated by the `it_len` field.
97/// Present fields are indicated by a bitmask in `it_present`.
98#[derive(Debug, Clone)]
99pub struct RadioTapLayer {
100    pub index: LayerIndex,
101}
102
103impl RadioTapLayer {
104    /// Create a new `RadioTapLayer` from start/end offsets.
105    #[must_use]
106    pub fn new(start: usize, end: usize) -> Self {
107        Self {
108            index: LayerIndex::new(LayerKind::Dot11, start, end),
109        }
110    }
111
112    /// Validate the buffer has enough data for a `RadioTap` header.
113    pub fn validate(buf: &[u8], offset: usize) -> Result<(), FieldError> {
114        if buf.len() < offset + RADIOTAP_MIN_HEADER_LEN {
115            return Err(FieldError::BufferTooShort {
116                offset,
117                need: RADIOTAP_MIN_HEADER_LEN,
118                have: buf.len().saturating_sub(offset),
119            });
120        }
121        Ok(())
122    }
123
124    // ========================================================================
125    // Fixed header fields
126    // ========================================================================
127
128    /// `RadioTap` version (always 0).
129    #[inline]
130    pub fn version(&self, buf: &[u8]) -> Result<u8, FieldError> {
131        let off = self.index.start + offsets::VERSION;
132        if buf.len() <= off {
133            return Err(FieldError::BufferTooShort {
134                offset: off,
135                need: 1,
136                have: buf.len(),
137            });
138        }
139        Ok(buf[off])
140    }
141
142    /// Padding byte.
143    #[inline]
144    pub fn pad(&self, buf: &[u8]) -> Result<u8, FieldError> {
145        let off = self.index.start + offsets::PAD;
146        if buf.len() <= off {
147            return Err(FieldError::BufferTooShort {
148                offset: off,
149                need: 1,
150                have: buf.len(),
151            });
152        }
153        Ok(buf[off])
154    }
155
156    /// Total header length (little-endian u16).
157    #[inline]
158    pub fn header_length(&self, buf: &[u8]) -> Result<u16, FieldError> {
159        let off = self.index.start + offsets::LENGTH;
160        if buf.len() < off + 2 {
161            return Err(FieldError::BufferTooShort {
162                offset: off,
163                need: 2,
164                have: buf.len(),
165            });
166        }
167        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
168    }
169
170    /// Present flags bitmask (little-endian u32).
171    #[inline]
172    pub fn present(&self, buf: &[u8]) -> Result<u32, FieldError> {
173        let off = self.index.start + offsets::PRESENT;
174        if buf.len() < off + 4 {
175            return Err(FieldError::BufferTooShort {
176                offset: off,
177                need: 4,
178                have: buf.len(),
179            });
180        }
181        Ok(u32::from_le_bytes([
182            buf[off],
183            buf[off + 1],
184            buf[off + 2],
185            buf[off + 3],
186        ]))
187    }
188
189    /// Check if a specific present field bit is set.
190    #[inline]
191    #[must_use]
192    pub fn has_field(&self, buf: &[u8], bit: u32) -> bool {
193        self.present(buf)
194            .map(|p| p & (1 << bit) != 0)
195            .unwrap_or(false)
196    }
197
198    /// Check if the FCS flag is set (indicating the payload includes FCS).
199    #[must_use]
200    pub fn has_fcs(&self, buf: &[u8]) -> bool {
201        if self.has_field(buf, radiotap_present::FLAGS)
202            && let Ok(fields) = self.parse_fields(buf)
203            && let Some(flags) = fields.flags
204        {
205            return flags & super::types::radiotap_flags::FCS != 0;
206        }
207        false
208    }
209
210    // ========================================================================
211    // Field parsing
212    // ========================================================================
213
214    /// Count the number of present bitmask words (including extended).
215    fn count_present_words(&self, buf: &[u8]) -> usize {
216        let mut count = 1;
217        let mut off = self.index.start + offsets::PRESENT;
218
219        loop {
220            if buf.len() < off + 4 {
221                break;
222            }
223            let word = u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]]);
224            if word & (1 << radiotap_present::EXT) == 0 {
225                break;
226            }
227            count += 1;
228            off += 4;
229        }
230        count
231    }
232
233    /// Parse all present fields from the `RadioTap` header.
234    pub fn parse_fields(&self, buf: &[u8]) -> Result<RadioTapFields, FieldError> {
235        let present = self.present(buf)?;
236        let header_len = self.header_length(buf)? as usize;
237        let start = self.index.start;
238
239        if buf.len() < start + header_len {
240            return Err(FieldError::BufferTooShort {
241                offset: start,
242                need: header_len,
243                have: buf.len().saturating_sub(start),
244            });
245        }
246
247        let num_present_words = self.count_present_words(buf);
248        // Data starts after all present words
249        let mut pos = start + 4 + (num_present_words * 4);
250        let end = start + header_len;
251        let mut fields = RadioTapFields::default();
252
253        // Iterate through present bits and parse each field
254        for bit in 0..28u32 {
255            if present & (1 << bit) == 0 {
256                continue;
257            }
258
259            let (size, align) = field_alignment(bit);
260            if size == 0 {
261                continue;
262            }
263
264            // Apply alignment
265            if align > 1 {
266                let base = start + 4 + (num_present_words * 4);
267                let relative = pos - base;
268                let _padding = (align - (relative % align)) % align;
269                // Also need to align relative to the start of header
270                let abs_padding = (align - ((pos - start) % align)) % align;
271                pos += abs_padding;
272            }
273
274            if pos + size > end {
275                break;
276            }
277
278            match bit {
279                radiotap_present::TSFT => {
280                    fields.tsft = Some(u64::from_le_bytes([
281                        buf[pos],
282                        buf[pos + 1],
283                        buf[pos + 2],
284                        buf[pos + 3],
285                        buf[pos + 4],
286                        buf[pos + 5],
287                        buf[pos + 6],
288                        buf[pos + 7],
289                    ]));
290                },
291                radiotap_present::FLAGS => {
292                    fields.flags = Some(buf[pos]);
293                },
294                radiotap_present::RATE => {
295                    fields.rate = Some(buf[pos]);
296                },
297                radiotap_present::CHANNEL => {
298                    fields.channel_freq = Some(u16::from_le_bytes([buf[pos], buf[pos + 1]]));
299                    fields.channel_flags = Some(u16::from_le_bytes([buf[pos + 2], buf[pos + 3]]));
300                },
301                radiotap_present::DBM_ANT_SIGNAL => {
302                    fields.dbm_ant_signal = Some(buf[pos] as i8);
303                },
304                radiotap_present::DBM_ANT_NOISE => {
305                    fields.dbm_ant_noise = Some(buf[pos] as i8);
306                },
307                radiotap_present::LOCK_QUALITY => {
308                    fields.lock_quality = Some(u16::from_le_bytes([buf[pos], buf[pos + 1]]));
309                },
310                radiotap_present::ANTENNA => {
311                    fields.antenna = Some(buf[pos]);
312                },
313                radiotap_present::DB_ANT_SIGNAL => {
314                    fields.db_ant_signal = Some(buf[pos]);
315                },
316                radiotap_present::DB_ANT_NOISE => {
317                    fields.db_ant_noise = Some(buf[pos]);
318                },
319                radiotap_present::RX_FLAGS => {
320                    fields.rx_flags = Some(u16::from_le_bytes([buf[pos], buf[pos + 1]]));
321                },
322                radiotap_present::TX_FLAGS => {
323                    fields.tx_flags = Some(u16::from_le_bytes([buf[pos], buf[pos + 1]]));
324                },
325                radiotap_present::MCS => {
326                    fields.mcs_known = Some(buf[pos]);
327                    fields.mcs_flags = Some(buf[pos + 1]);
328                    fields.mcs_index = Some(buf[pos + 2]);
329                },
330                radiotap_present::A_MPDU => {
331                    fields.a_mpdu_ref = Some(u32::from_le_bytes([
332                        buf[pos],
333                        buf[pos + 1],
334                        buf[pos + 2],
335                        buf[pos + 3],
336                    ]));
337                    fields.a_mpdu_flags = Some(u32::from_le_bytes([
338                        buf[pos + 4],
339                        buf[pos + 5],
340                        buf[pos + 6],
341                        buf[pos + 7],
342                    ]));
343                },
344                _ => {},
345            }
346
347            pos += size;
348        }
349
350        Ok(fields)
351    }
352}
353
354impl Layer for RadioTapLayer {
355    fn kind(&self) -> LayerKind {
356        LayerKind::Dot11
357    }
358
359    fn summary(&self, buf: &[u8]) -> String {
360        let len = self
361            .header_length(buf)
362            .map_or_else(|_| "?".to_string(), |l| l.to_string());
363        let present = self
364            .present(buf)
365            .map_or_else(|_| "?".to_string(), |p| format!("{p:#010x}"));
366        format!("RadioTap len={len} present={present}")
367    }
368
369    fn header_len(&self, buf: &[u8]) -> usize {
370        self.header_length(buf)
371            .map(|v| v as usize)
372            .unwrap_or(RADIOTAP_MIN_HEADER_LEN)
373    }
374
375    fn field_names(&self) -> &'static [&'static str] {
376        &[
377            "version",
378            "pad",
379            "len",
380            "present",
381            "mac_timestamp",
382            "Flags",
383            "Rate",
384            "ChannelFrequency",
385            "ChannelFlags",
386            "dBm_AntSignal",
387            "dBm_AntNoise",
388            "Antenna",
389        ]
390    }
391}
392
393/// Builder for constructing `RadioTap` headers.
394#[derive(Debug, Clone, Default)]
395pub struct RadioTapBuilder {
396    /// TSFT timestamp.
397    pub tsft: Option<u64>,
398    /// Flags.
399    pub flags: Option<u8>,
400    /// Rate (in units of 500 Kbps).
401    pub rate: Option<u8>,
402    /// Channel frequency (MHz).
403    pub channel_freq: Option<u16>,
404    /// Channel flags.
405    pub channel_flags: Option<u16>,
406    /// dBm antenna signal.
407    pub dbm_ant_signal: Option<i8>,
408    /// dBm antenna noise.
409    pub dbm_ant_noise: Option<i8>,
410    /// Antenna index.
411    pub antenna: Option<u8>,
412}
413
414impl RadioTapBuilder {
415    /// Create a new `RadioTapBuilder`.
416    #[must_use]
417    pub fn new() -> Self {
418        Self::default()
419    }
420
421    #[must_use]
422    pub fn tsft(mut self, tsft: u64) -> Self {
423        self.tsft = Some(tsft);
424        self
425    }
426
427    #[must_use]
428    pub fn flags(mut self, flags: u8) -> Self {
429        self.flags = Some(flags);
430        self
431    }
432
433    #[must_use]
434    pub fn rate(mut self, rate: u8) -> Self {
435        self.rate = Some(rate);
436        self
437    }
438
439    #[must_use]
440    pub fn channel(mut self, freq: u16, flags: u16) -> Self {
441        self.channel_freq = Some(freq);
442        self.channel_flags = Some(flags);
443        self
444    }
445
446    #[must_use]
447    pub fn dbm_ant_signal(mut self, signal: i8) -> Self {
448        self.dbm_ant_signal = Some(signal);
449        self
450    }
451
452    #[must_use]
453    pub fn dbm_ant_noise(mut self, noise: i8) -> Self {
454        self.dbm_ant_noise = Some(noise);
455        self
456    }
457
458    #[must_use]
459    pub fn antenna(mut self, antenna: u8) -> Self {
460        self.antenna = Some(antenna);
461        self
462    }
463
464    /// Build the `RadioTap` header bytes.
465    #[must_use]
466    pub fn build(&self) -> Vec<u8> {
467        let mut present: u32 = 0;
468        let mut fields_data = Vec::new();
469
470        // Build fields in order of present bit position
471        if let Some(tsft) = self.tsft {
472            present |= 1 << radiotap_present::TSFT;
473            // TSFT requires 8-byte alignment
474            while (8 + fields_data.len()) % 8 != 0 {
475                fields_data.push(0);
476            }
477            fields_data.extend_from_slice(&tsft.to_le_bytes());
478        }
479
480        if let Some(flags) = self.flags {
481            present |= 1 << radiotap_present::FLAGS;
482            fields_data.push(flags);
483        }
484
485        if let Some(rate) = self.rate {
486            present |= 1 << radiotap_present::RATE;
487            fields_data.push(rate);
488        }
489
490        if self.channel_freq.is_some() {
491            present |= 1 << radiotap_present::CHANNEL;
492            // Channel requires 2-byte alignment
493            let header_fixed = 8; // version(1) + pad(1) + len(2) + present(4)
494            let current = header_fixed + fields_data.len();
495            if current % 2 != 0 {
496                fields_data.push(0);
497            }
498            fields_data.extend_from_slice(&self.channel_freq.unwrap_or(0).to_le_bytes());
499            fields_data.extend_from_slice(&self.channel_flags.unwrap_or(0).to_le_bytes());
500        }
501
502        if let Some(signal) = self.dbm_ant_signal {
503            present |= 1 << radiotap_present::DBM_ANT_SIGNAL;
504            fields_data.push(signal as u8);
505        }
506
507        if let Some(noise) = self.dbm_ant_noise {
508            present |= 1 << radiotap_present::DBM_ANT_NOISE;
509            fields_data.push(noise as u8);
510        }
511
512        if let Some(antenna) = self.antenna {
513            present |= 1 << radiotap_present::ANTENNA;
514            fields_data.push(antenna);
515        }
516
517        let total_len = (8 + fields_data.len()) as u16;
518
519        let mut out = Vec::with_capacity(total_len as usize);
520        out.push(0); // version
521        out.push(0); // pad
522        out.extend_from_slice(&total_len.to_le_bytes());
523        out.extend_from_slice(&present.to_le_bytes());
524        out.extend_from_slice(&fields_data);
525
526        out
527    }
528}
529
530#[cfg(test)]
531mod tests {
532    use super::*;
533
534    /// Build a minimal RadioTap header: version=0, pad=0, len=8, present=0.
535    fn make_minimal_radiotap() -> Vec<u8> {
536        vec![
537            0x00, // version
538            0x00, // pad
539            0x08, 0x00, // len = 8 (LE)
540            0x00, 0x00, 0x00, 0x00, // present = 0
541        ]
542    }
543
544    /// Build a RadioTap header with Flags + Rate + Channel + dBm_AntSignal.
545    fn make_radiotap_with_fields() -> Vec<u8> {
546        // present: Flags(1) | Rate(2) | Channel(3) | dBm_AntSignal(5) => bits 1,2,3,5
547        // = 0x0000002E
548        let present: u32 = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5);
549        let mut buf = Vec::new();
550        buf.push(0x00); // version
551        buf.push(0x00); // pad
552
553        // Fields in order:
554        // Flags(1B) | Rate(1B) | Channel(2B freq + 2B flags) | dBm_AntSignal(1B)
555        // Total = 8 + 1 + 1 + 4 + 1 = 15, but channel needs 2-byte alignment.
556        // After flags(1) + rate(1) = 2 bytes relative, already aligned for channel.
557        let fields_len = 1 + 1 + 4 + 1; // 7 bytes of fields
558        let total_len: u16 = 8 + fields_len;
559        buf.extend_from_slice(&total_len.to_le_bytes());
560        buf.extend_from_slice(&present.to_le_bytes());
561
562        // Flags
563        buf.push(0x10); // FCS flag
564        // Rate
565        buf.push(0x0C); // 6 Mbps
566        // Channel (2-byte aligned already since offset is 8+2=10, which is even)
567        buf.extend_from_slice(&2437u16.to_le_bytes()); // freq=2437 (channel 6)
568        buf.extend_from_slice(&0x00A0u16.to_le_bytes()); // flags: OFDM+2GHz
569        // dBm_AntSignal
570        buf.push(0xCE_u8); // -50 dBm as i8
571
572        buf
573    }
574
575    #[test]
576    fn test_minimal_radiotap() {
577        let buf = make_minimal_radiotap();
578        let layer = RadioTapLayer::new(0, buf.len());
579
580        assert_eq!(layer.version(&buf).unwrap(), 0);
581        assert_eq!(layer.pad(&buf).unwrap(), 0);
582        assert_eq!(layer.header_length(&buf).unwrap(), 8);
583        assert_eq!(layer.present(&buf).unwrap(), 0);
584    }
585
586    #[test]
587    fn test_radiotap_with_fields() {
588        let buf = make_radiotap_with_fields();
589        let layer = RadioTapLayer::new(0, buf.len());
590
591        assert_eq!(layer.version(&buf).unwrap(), 0);
592        let present = layer.present(&buf).unwrap();
593        assert!(present & (1 << radiotap_present::FLAGS) != 0);
594        assert!(present & (1 << radiotap_present::RATE) != 0);
595        assert!(present & (1 << radiotap_present::CHANNEL) != 0);
596
597        let fields = layer.parse_fields(&buf).unwrap();
598        assert_eq!(fields.flags, Some(0x10));
599        assert_eq!(fields.rate, Some(0x0C));
600        assert_eq!(fields.channel_freq, Some(2437));
601        assert_eq!(fields.dbm_ant_signal, Some(-50));
602    }
603
604    #[test]
605    fn test_has_fcs() {
606        let buf = make_radiotap_with_fields();
607        let layer = RadioTapLayer::new(0, buf.len());
608        assert!(layer.has_fcs(&buf)); // Flags has FCS bit set
609    }
610
611    #[test]
612    fn test_header_len_trait() {
613        let buf = make_radiotap_with_fields();
614        let layer = RadioTapLayer::new(0, buf.len());
615        assert_eq!(layer.header_len(&buf), 15);
616    }
617
618    #[test]
619    fn test_summary() {
620        let buf = make_radiotap_with_fields();
621        let layer = RadioTapLayer::new(0, buf.len());
622        let s = layer.summary(&buf);
623        assert!(s.contains("RadioTap"));
624        assert!(s.contains("len="));
625    }
626
627    #[test]
628    fn test_builder_minimal() {
629        let header = RadioTapBuilder::new().build();
630        assert_eq!(header.len(), 8);
631        assert_eq!(header[0], 0); // version
632        assert_eq!(u16::from_le_bytes([header[2], header[3]]), 8); // len
633        assert_eq!(
634            u32::from_le_bytes([header[4], header[5], header[6], header[7]]),
635            0
636        ); // present
637    }
638
639    #[test]
640    fn test_builder_with_fields() {
641        let header = RadioTapBuilder::new()
642            .flags(0x10)
643            .rate(12)
644            .channel(2437, 0x00A0)
645            .dbm_ant_signal(-50)
646            .build();
647
648        let layer = RadioTapLayer::new(0, header.len());
649        assert_eq!(layer.version(&header).unwrap(), 0);
650        let present = layer.present(&header).unwrap();
651        assert!(present & (1 << radiotap_present::FLAGS) != 0);
652        assert!(present & (1 << radiotap_present::RATE) != 0);
653        assert!(present & (1 << radiotap_present::CHANNEL) != 0);
654        assert!(present & (1 << radiotap_present::DBM_ANT_SIGNAL) != 0);
655
656        let fields = layer.parse_fields(&header).unwrap();
657        assert_eq!(fields.flags, Some(0x10));
658        assert_eq!(fields.rate, Some(12));
659        assert_eq!(fields.channel_freq, Some(2437));
660        assert_eq!(fields.dbm_ant_signal, Some(-50));
661    }
662
663    #[test]
664    fn test_roundtrip_builder_parse() {
665        let header = RadioTapBuilder::new()
666            .flags(0x02) // short preamble
667            .rate(22) // 11 Mbps
668            .antenna(1)
669            .build();
670
671        let layer = RadioTapLayer::new(0, header.len());
672        let fields = layer.parse_fields(&header).unwrap();
673        assert_eq!(fields.flags, Some(0x02));
674        assert_eq!(fields.rate, Some(22));
675        assert_eq!(fields.antenna, Some(1));
676    }
677
678    #[test]
679    fn test_extended_present_words() {
680        // Build a header with EXT bit set in present
681        let mut buf = vec![0u8; 16];
682        buf[0] = 0x00; // version
683        buf[1] = 0x00; // pad
684        buf[2] = 0x10; // len = 16
685        buf[3] = 0x00;
686        // present word 1: EXT bit set (bit 31)
687        let present1: u32 = 1 << 31;
688        buf[4..8].copy_from_slice(&present1.to_le_bytes());
689        // present word 2: no EXT
690        buf[8..12].copy_from_slice(&0u32.to_le_bytes());
691        // 4 bytes of padding/data
692        buf[12..16].copy_from_slice(&[0; 4]);
693
694        let layer = RadioTapLayer::new(0, buf.len());
695        assert_eq!(layer.count_present_words(&buf), 2);
696    }
697}