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    pub fn new(start: usize, end: usize) -> Self {
106        Self {
107            index: LayerIndex::new(LayerKind::Dot11, start, end),
108        }
109    }
110
111    /// Validate the buffer has enough data for a RadioTap header.
112    pub fn validate(buf: &[u8], offset: usize) -> Result<(), FieldError> {
113        if buf.len() < offset + RADIOTAP_MIN_HEADER_LEN {
114            return Err(FieldError::BufferTooShort {
115                offset,
116                need: RADIOTAP_MIN_HEADER_LEN,
117                have: buf.len().saturating_sub(offset),
118            });
119        }
120        Ok(())
121    }
122
123    // ========================================================================
124    // Fixed header fields
125    // ========================================================================
126
127    /// RadioTap version (always 0).
128    #[inline]
129    pub fn version(&self, buf: &[u8]) -> Result<u8, FieldError> {
130        let off = self.index.start + offsets::VERSION;
131        if buf.len() <= off {
132            return Err(FieldError::BufferTooShort {
133                offset: off,
134                need: 1,
135                have: buf.len(),
136            });
137        }
138        Ok(buf[off])
139    }
140
141    /// Padding byte.
142    #[inline]
143    pub fn pad(&self, buf: &[u8]) -> Result<u8, FieldError> {
144        let off = self.index.start + offsets::PAD;
145        if buf.len() <= off {
146            return Err(FieldError::BufferTooShort {
147                offset: off,
148                need: 1,
149                have: buf.len(),
150            });
151        }
152        Ok(buf[off])
153    }
154
155    /// Total header length (little-endian u16).
156    #[inline]
157    pub fn header_length(&self, buf: &[u8]) -> Result<u16, FieldError> {
158        let off = self.index.start + offsets::LENGTH;
159        if buf.len() < off + 2 {
160            return Err(FieldError::BufferTooShort {
161                offset: off,
162                need: 2,
163                have: buf.len(),
164            });
165        }
166        Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
167    }
168
169    /// Present flags bitmask (little-endian u32).
170    #[inline]
171    pub fn present(&self, buf: &[u8]) -> Result<u32, FieldError> {
172        let off = self.index.start + offsets::PRESENT;
173        if buf.len() < off + 4 {
174            return Err(FieldError::BufferTooShort {
175                offset: off,
176                need: 4,
177                have: buf.len(),
178            });
179        }
180        Ok(u32::from_le_bytes([
181            buf[off],
182            buf[off + 1],
183            buf[off + 2],
184            buf[off + 3],
185        ]))
186    }
187
188    /// Check if a specific present field bit is set.
189    #[inline]
190    pub fn has_field(&self, buf: &[u8], bit: u32) -> bool {
191        self.present(buf)
192            .map(|p| p & (1 << bit) != 0)
193            .unwrap_or(false)
194    }
195
196    /// Check if the FCS flag is set (indicating the payload includes FCS).
197    pub fn has_fcs(&self, buf: &[u8]) -> bool {
198        if self.has_field(buf, radiotap_present::FLAGS) {
199            if let Ok(fields) = self.parse_fields(buf) {
200                if let Some(flags) = fields.flags {
201                    return flags & super::types::radiotap_flags::FCS != 0;
202                }
203            }
204        }
205        false
206    }
207
208    // ========================================================================
209    // Field parsing
210    // ========================================================================
211
212    /// Count the number of present bitmask words (including extended).
213    fn count_present_words(&self, buf: &[u8]) -> usize {
214        let mut count = 1;
215        let mut off = self.index.start + offsets::PRESENT;
216
217        loop {
218            if buf.len() < off + 4 {
219                break;
220            }
221            let word = u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]]);
222            if word & (1 << radiotap_present::EXT) == 0 {
223                break;
224            }
225            count += 1;
226            off += 4;
227        }
228        count
229    }
230
231    /// Parse all present fields from the RadioTap header.
232    pub fn parse_fields(&self, buf: &[u8]) -> Result<RadioTapFields, FieldError> {
233        let present = self.present(buf)?;
234        let header_len = self.header_length(buf)? as usize;
235        let start = self.index.start;
236
237        if buf.len() < start + header_len {
238            return Err(FieldError::BufferTooShort {
239                offset: start,
240                need: header_len,
241                have: buf.len().saturating_sub(start),
242            });
243        }
244
245        let num_present_words = self.count_present_words(buf);
246        // Data starts after all present words
247        let mut pos = start + 4 + (num_present_words * 4);
248        let end = start + header_len;
249        let mut fields = RadioTapFields::default();
250
251        // Iterate through present bits and parse each field
252        for bit in 0..28u32 {
253            if present & (1 << bit) == 0 {
254                continue;
255            }
256
257            let (size, align) = field_alignment(bit);
258            if size == 0 {
259                continue;
260            }
261
262            // Apply alignment
263            if align > 1 {
264                let base = start + 4 + (num_present_words * 4);
265                let relative = pos - base;
266                let _padding = (align - (relative % align)) % align;
267                // Also need to align relative to the start of header
268                let abs_padding = (align - ((pos - start) % align)) % align;
269                pos += abs_padding;
270            }
271
272            if pos + size > end {
273                break;
274            }
275
276            match bit {
277                radiotap_present::TSFT => {
278                    fields.tsft = Some(u64::from_le_bytes([
279                        buf[pos],
280                        buf[pos + 1],
281                        buf[pos + 2],
282                        buf[pos + 3],
283                        buf[pos + 4],
284                        buf[pos + 5],
285                        buf[pos + 6],
286                        buf[pos + 7],
287                    ]));
288                }
289                radiotap_present::FLAGS => {
290                    fields.flags = Some(buf[pos]);
291                }
292                radiotap_present::RATE => {
293                    fields.rate = Some(buf[pos]);
294                }
295                radiotap_present::CHANNEL => {
296                    fields.channel_freq = Some(u16::from_le_bytes([buf[pos], buf[pos + 1]]));
297                    fields.channel_flags = Some(u16::from_le_bytes([buf[pos + 2], buf[pos + 3]]));
298                }
299                radiotap_present::DBM_ANT_SIGNAL => {
300                    fields.dbm_ant_signal = Some(buf[pos] as i8);
301                }
302                radiotap_present::DBM_ANT_NOISE => {
303                    fields.dbm_ant_noise = Some(buf[pos] as i8);
304                }
305                radiotap_present::LOCK_QUALITY => {
306                    fields.lock_quality = Some(u16::from_le_bytes([buf[pos], buf[pos + 1]]));
307                }
308                radiotap_present::ANTENNA => {
309                    fields.antenna = Some(buf[pos]);
310                }
311                radiotap_present::DB_ANT_SIGNAL => {
312                    fields.db_ant_signal = Some(buf[pos]);
313                }
314                radiotap_present::DB_ANT_NOISE => {
315                    fields.db_ant_noise = Some(buf[pos]);
316                }
317                radiotap_present::RX_FLAGS => {
318                    fields.rx_flags = Some(u16::from_le_bytes([buf[pos], buf[pos + 1]]));
319                }
320                radiotap_present::TX_FLAGS => {
321                    fields.tx_flags = Some(u16::from_le_bytes([buf[pos], buf[pos + 1]]));
322                }
323                radiotap_present::MCS => {
324                    fields.mcs_known = Some(buf[pos]);
325                    fields.mcs_flags = Some(buf[pos + 1]);
326                    fields.mcs_index = Some(buf[pos + 2]);
327                }
328                radiotap_present::A_MPDU => {
329                    fields.a_mpdu_ref = Some(u32::from_le_bytes([
330                        buf[pos],
331                        buf[pos + 1],
332                        buf[pos + 2],
333                        buf[pos + 3],
334                    ]));
335                    fields.a_mpdu_flags = Some(u32::from_le_bytes([
336                        buf[pos + 4],
337                        buf[pos + 5],
338                        buf[pos + 6],
339                        buf[pos + 7],
340                    ]));
341                }
342                _ => {}
343            }
344
345            pos += size;
346        }
347
348        Ok(fields)
349    }
350}
351
352impl Layer for RadioTapLayer {
353    fn kind(&self) -> LayerKind {
354        LayerKind::Dot11
355    }
356
357    fn summary(&self, buf: &[u8]) -> String {
358        let len = self
359            .header_length(buf)
360            .map(|l| l.to_string())
361            .unwrap_or_else(|_| "?".to_string());
362        let present = self
363            .present(buf)
364            .map(|p| format!("{:#010x}", p))
365            .unwrap_or_else(|_| "?".to_string());
366        format!("RadioTap len={} present={}", len, 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    pub fn new() -> Self {
417        Self::default()
418    }
419
420    pub fn tsft(mut self, tsft: u64) -> Self {
421        self.tsft = Some(tsft);
422        self
423    }
424
425    pub fn flags(mut self, flags: u8) -> Self {
426        self.flags = Some(flags);
427        self
428    }
429
430    pub fn rate(mut self, rate: u8) -> Self {
431        self.rate = Some(rate);
432        self
433    }
434
435    pub fn channel(mut self, freq: u16, flags: u16) -> Self {
436        self.channel_freq = Some(freq);
437        self.channel_flags = Some(flags);
438        self
439    }
440
441    pub fn dbm_ant_signal(mut self, signal: i8) -> Self {
442        self.dbm_ant_signal = Some(signal);
443        self
444    }
445
446    pub fn dbm_ant_noise(mut self, noise: i8) -> Self {
447        self.dbm_ant_noise = Some(noise);
448        self
449    }
450
451    pub fn antenna(mut self, antenna: u8) -> Self {
452        self.antenna = Some(antenna);
453        self
454    }
455
456    /// Build the RadioTap header bytes.
457    pub fn build(&self) -> Vec<u8> {
458        let mut present: u32 = 0;
459        let mut fields_data = Vec::new();
460
461        // Build fields in order of present bit position
462        if let Some(tsft) = self.tsft {
463            present |= 1 << radiotap_present::TSFT;
464            // TSFT requires 8-byte alignment
465            while (8 + fields_data.len()) % 8 != 0 {
466                fields_data.push(0);
467            }
468            fields_data.extend_from_slice(&tsft.to_le_bytes());
469        }
470
471        if let Some(flags) = self.flags {
472            present |= 1 << radiotap_present::FLAGS;
473            fields_data.push(flags);
474        }
475
476        if let Some(rate) = self.rate {
477            present |= 1 << radiotap_present::RATE;
478            fields_data.push(rate);
479        }
480
481        if self.channel_freq.is_some() {
482            present |= 1 << radiotap_present::CHANNEL;
483            // Channel requires 2-byte alignment
484            let header_fixed = 8; // version(1) + pad(1) + len(2) + present(4)
485            let current = header_fixed + fields_data.len();
486            if current % 2 != 0 {
487                fields_data.push(0);
488            }
489            fields_data.extend_from_slice(&self.channel_freq.unwrap_or(0).to_le_bytes());
490            fields_data.extend_from_slice(&self.channel_flags.unwrap_or(0).to_le_bytes());
491        }
492
493        if let Some(signal) = self.dbm_ant_signal {
494            present |= 1 << radiotap_present::DBM_ANT_SIGNAL;
495            fields_data.push(signal as u8);
496        }
497
498        if let Some(noise) = self.dbm_ant_noise {
499            present |= 1 << radiotap_present::DBM_ANT_NOISE;
500            fields_data.push(noise as u8);
501        }
502
503        if let Some(antenna) = self.antenna {
504            present |= 1 << radiotap_present::ANTENNA;
505            fields_data.push(antenna);
506        }
507
508        let total_len = (8 + fields_data.len()) as u16;
509
510        let mut out = Vec::with_capacity(total_len as usize);
511        out.push(0); // version
512        out.push(0); // pad
513        out.extend_from_slice(&total_len.to_le_bytes());
514        out.extend_from_slice(&present.to_le_bytes());
515        out.extend_from_slice(&fields_data);
516
517        out
518    }
519}
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524
525    /// Build a minimal RadioTap header: version=0, pad=0, len=8, present=0.
526    fn make_minimal_radiotap() -> Vec<u8> {
527        vec![
528            0x00, // version
529            0x00, // pad
530            0x08, 0x00, // len = 8 (LE)
531            0x00, 0x00, 0x00, 0x00, // present = 0
532        ]
533    }
534
535    /// Build a RadioTap header with Flags + Rate + Channel + dBm_AntSignal.
536    fn make_radiotap_with_fields() -> Vec<u8> {
537        // present: Flags(1) | Rate(2) | Channel(3) | dBm_AntSignal(5) => bits 1,2,3,5
538        // = 0x0000002E
539        let present: u32 = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5);
540        let mut buf = Vec::new();
541        buf.push(0x00); // version
542        buf.push(0x00); // pad
543
544        // Fields in order:
545        // Flags(1B) | Rate(1B) | Channel(2B freq + 2B flags) | dBm_AntSignal(1B)
546        // Total = 8 + 1 + 1 + 4 + 1 = 15, but channel needs 2-byte alignment.
547        // After flags(1) + rate(1) = 2 bytes relative, already aligned for channel.
548        let fields_len = 1 + 1 + 4 + 1; // 7 bytes of fields
549        let total_len: u16 = 8 + fields_len;
550        buf.extend_from_slice(&total_len.to_le_bytes());
551        buf.extend_from_slice(&present.to_le_bytes());
552
553        // Flags
554        buf.push(0x10); // FCS flag
555        // Rate
556        buf.push(0x0C); // 6 Mbps
557        // Channel (2-byte aligned already since offset is 8+2=10, which is even)
558        buf.extend_from_slice(&2437u16.to_le_bytes()); // freq=2437 (channel 6)
559        buf.extend_from_slice(&0x00A0u16.to_le_bytes()); // flags: OFDM+2GHz
560        // dBm_AntSignal
561        buf.push(0xCE_u8); // -50 dBm as i8
562
563        buf
564    }
565
566    #[test]
567    fn test_minimal_radiotap() {
568        let buf = make_minimal_radiotap();
569        let layer = RadioTapLayer::new(0, buf.len());
570
571        assert_eq!(layer.version(&buf).unwrap(), 0);
572        assert_eq!(layer.pad(&buf).unwrap(), 0);
573        assert_eq!(layer.header_length(&buf).unwrap(), 8);
574        assert_eq!(layer.present(&buf).unwrap(), 0);
575    }
576
577    #[test]
578    fn test_radiotap_with_fields() {
579        let buf = make_radiotap_with_fields();
580        let layer = RadioTapLayer::new(0, buf.len());
581
582        assert_eq!(layer.version(&buf).unwrap(), 0);
583        let present = layer.present(&buf).unwrap();
584        assert!(present & (1 << radiotap_present::FLAGS) != 0);
585        assert!(present & (1 << radiotap_present::RATE) != 0);
586        assert!(present & (1 << radiotap_present::CHANNEL) != 0);
587
588        let fields = layer.parse_fields(&buf).unwrap();
589        assert_eq!(fields.flags, Some(0x10));
590        assert_eq!(fields.rate, Some(0x0C));
591        assert_eq!(fields.channel_freq, Some(2437));
592        assert_eq!(fields.dbm_ant_signal, Some(-50));
593    }
594
595    #[test]
596    fn test_has_fcs() {
597        let buf = make_radiotap_with_fields();
598        let layer = RadioTapLayer::new(0, buf.len());
599        assert!(layer.has_fcs(&buf)); // Flags has FCS bit set
600    }
601
602    #[test]
603    fn test_header_len_trait() {
604        let buf = make_radiotap_with_fields();
605        let layer = RadioTapLayer::new(0, buf.len());
606        assert_eq!(layer.header_len(&buf), 15);
607    }
608
609    #[test]
610    fn test_summary() {
611        let buf = make_radiotap_with_fields();
612        let layer = RadioTapLayer::new(0, buf.len());
613        let s = layer.summary(&buf);
614        assert!(s.contains("RadioTap"));
615        assert!(s.contains("len="));
616    }
617
618    #[test]
619    fn test_builder_minimal() {
620        let header = RadioTapBuilder::new().build();
621        assert_eq!(header.len(), 8);
622        assert_eq!(header[0], 0); // version
623        assert_eq!(u16::from_le_bytes([header[2], header[3]]), 8); // len
624        assert_eq!(
625            u32::from_le_bytes([header[4], header[5], header[6], header[7]]),
626            0
627        ); // present
628    }
629
630    #[test]
631    fn test_builder_with_fields() {
632        let header = RadioTapBuilder::new()
633            .flags(0x10)
634            .rate(12)
635            .channel(2437, 0x00A0)
636            .dbm_ant_signal(-50)
637            .build();
638
639        let layer = RadioTapLayer::new(0, header.len());
640        assert_eq!(layer.version(&header).unwrap(), 0);
641        let present = layer.present(&header).unwrap();
642        assert!(present & (1 << radiotap_present::FLAGS) != 0);
643        assert!(present & (1 << radiotap_present::RATE) != 0);
644        assert!(present & (1 << radiotap_present::CHANNEL) != 0);
645        assert!(present & (1 << radiotap_present::DBM_ANT_SIGNAL) != 0);
646
647        let fields = layer.parse_fields(&header).unwrap();
648        assert_eq!(fields.flags, Some(0x10));
649        assert_eq!(fields.rate, Some(12));
650        assert_eq!(fields.channel_freq, Some(2437));
651        assert_eq!(fields.dbm_ant_signal, Some(-50));
652    }
653
654    #[test]
655    fn test_roundtrip_builder_parse() {
656        let header = RadioTapBuilder::new()
657            .flags(0x02) // short preamble
658            .rate(22) // 11 Mbps
659            .antenna(1)
660            .build();
661
662        let layer = RadioTapLayer::new(0, header.len());
663        let fields = layer.parse_fields(&header).unwrap();
664        assert_eq!(fields.flags, Some(0x02));
665        assert_eq!(fields.rate, Some(22));
666        assert_eq!(fields.antenna, Some(1));
667    }
668
669    #[test]
670    fn test_extended_present_words() {
671        // Build a header with EXT bit set in present
672        let mut buf = vec![0u8; 16];
673        buf[0] = 0x00; // version
674        buf[1] = 0x00; // pad
675        buf[2] = 0x10; // len = 16
676        buf[3] = 0x00;
677        // present word 1: EXT bit set (bit 31)
678        let present1: u32 = 1 << 31;
679        buf[4..8].copy_from_slice(&present1.to_le_bytes());
680        // present word 2: no EXT
681        buf[8..12].copy_from_slice(&0u32.to_le_bytes());
682        // 4 bytes of padding/data
683        buf[12..16].copy_from_slice(&[0; 4]);
684
685        let layer = RadioTapLayer::new(0, buf.len());
686        assert_eq!(layer.count_present_words(&buf), 2);
687    }
688}