Skip to main content

stackforge_core/layer/l2tp/
mod.rs

1//! L2TP (Layer 2 Tunneling Protocol) layer implementation.
2//!
3//! Implements L2TPv2 as defined in RFC 2661.
4//!
5//! ## Header Format
6//!
7//! ```text
8//! Bits:  0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15
9//!        T L X X S X X O | P X  X  X  X  Ver(4 bits)
10//! ```
11//!
12//! Where:
13//! - T (bit 0):   Message type: 0=data, 1=control
14//! - L (bit 1):   Length bit; if set, Length field is present
15//! - S (bit 4):   Sequence bit; if set, Ns and Nr fields are present
16//! - O (bit 6):   Offset bit; if set, Offset Size/Pad fields are present
17//! - P (bit 8):   Priority (data messages only)
18//! - Ver (12-15): Must be 2 for L2TPv2
19//!
20//! Fields present in order:
21//! 1. Flags+Version (2 bytes, always)
22//! 2. Length (2 bytes, only if L=1)
23//! 3. Tunnel ID (2 bytes, always)
24//! 4. Session ID (2 bytes, always)
25//! 5. Ns (2 bytes, only if S=1)
26//! 6. Nr (2 bytes, only if S=1)
27//! 7. Offset Size (2 bytes, only if O=1)
28//! 8. Offset Pad (variable, only if O=1)
29
30pub mod builder;
31
32pub use builder::L2tpBuilder;
33
34use crate::layer::field::{FieldError, FieldValue};
35use crate::layer::{Layer, LayerIndex, LayerKind};
36
37/// Minimum L2TP header: flags+version (2) + tunnel_id (2) + session_id (2) = 6 bytes.
38pub const L2TP_MIN_HEADER_LEN: usize = 6;
39
40/// L2TP version number (bits 12-15 of the flags word).
41pub const L2TP_VERSION: u8 = 2;
42
43/// L2TP UDP port.
44pub const L2TP_PORT: u16 = 1701;
45
46/// Field names exported for Python/generic access.
47pub static L2TP_FIELD_NAMES: &[&str] = &[
48    "flags",
49    "version",
50    "msg_type",
51    "length",
52    "tunnel_id",
53    "session_id",
54    "ns",
55    "nr",
56    "offset_size",
57];
58
59/// L2TP layer — a zero-copy view into a packet buffer.
60#[derive(Debug, Clone)]
61pub struct L2tpLayer {
62    pub index: LayerIndex,
63}
64
65impl L2tpLayer {
66    /// Create a new L2TP layer from a layer index.
67    pub fn new(index: LayerIndex) -> Self {
68        Self { index }
69    }
70
71    /// Create an L2TP layer starting at offset 0 (for standalone parsing).
72    pub fn at_start() -> Self {
73        Self {
74            index: LayerIndex::new(LayerKind::L2tp, 0, L2TP_MIN_HEADER_LEN),
75        }
76    }
77
78    /// Return a reference to a slice of the buffer corresponding to this layer.
79    fn slice<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
80        self.index.slice(buf)
81    }
82
83    // ========================================================================
84    // Raw flag word helpers
85    // ========================================================================
86
87    /// Read the 2-byte flags+version word.
88    pub fn flags_word(&self, buf: &[u8]) -> Result<u16, FieldError> {
89        let s = self.slice(buf);
90        if s.len() < 2 {
91            return Err(FieldError::BufferTooShort {
92                offset: self.index.start,
93                need: 2,
94                have: s.len(),
95            });
96        }
97        Ok(u16::from_be_bytes([s[0], s[1]]))
98    }
99
100    /// Get the raw flags byte (high byte of the flags word, bits 0-7).
101    pub fn flags(&self, buf: &[u8]) -> Result<u16, FieldError> {
102        self.flags_word(buf)
103    }
104
105    /// Get the version nibble (bits 12-15 of the flags word; should be 2).
106    pub fn version(&self, buf: &[u8]) -> Result<u8, FieldError> {
107        let word = self.flags_word(buf)?;
108        Ok((word & 0x000F) as u8)
109    }
110
111    /// Get message type: 0 = data, 1 = control (T bit, bit 15 of word).
112    pub fn msg_type(&self, buf: &[u8]) -> Result<u8, FieldError> {
113        let word = self.flags_word(buf)?;
114        Ok(((word >> 15) & 0x01) as u8)
115    }
116
117    /// Returns true if the T (type) bit is set (control message).
118    pub fn is_control(&self, buf: &[u8]) -> Result<bool, FieldError> {
119        Ok(self.msg_type(buf)? == 1)
120    }
121
122    /// Returns true if the L (length) bit is set, meaning Length field is present.
123    pub fn has_length(&self, buf: &[u8]) -> Result<bool, FieldError> {
124        let word = self.flags_word(buf)?;
125        Ok((word >> 14) & 0x01 == 1)
126    }
127
128    /// Returns true if the S (sequence) bit is set, meaning Ns and Nr are present.
129    pub fn has_sequence(&self, buf: &[u8]) -> Result<bool, FieldError> {
130        let word = self.flags_word(buf)?;
131        Ok((word >> 11) & 0x01 == 1)
132    }
133
134    /// Returns true if the O (offset) bit is set, meaning Offset fields are present.
135    pub fn has_offset(&self, buf: &[u8]) -> Result<bool, FieldError> {
136        let word = self.flags_word(buf)?;
137        Ok((word >> 9) & 0x01 == 1)
138    }
139
140    // ========================================================================
141    // Field accessors (conditional on flag bits)
142    // ========================================================================
143
144    /// Get the optional Length field (present when L bit is set).
145    pub fn length(&self, buf: &[u8]) -> Result<Option<u16>, FieldError> {
146        if !self.has_length(buf)? {
147            return Ok(None);
148        }
149        let s = self.slice(buf);
150        let offset = 2; // after flags word
151        if s.len() < offset + 2 {
152            return Err(FieldError::BufferTooShort {
153                offset: self.index.start + offset,
154                need: 2,
155                have: s.len().saturating_sub(offset),
156            });
157        }
158        Ok(Some(u16::from_be_bytes([s[offset], s[offset + 1]])))
159    }
160
161    /// Compute the byte offset of tunnel_id within the layer slice.
162    fn tunnel_id_offset(&self, buf: &[u8]) -> Result<usize, FieldError> {
163        let mut off = 2; // after flags word
164        if self.has_length(buf)? {
165            off += 2; // skip Length field
166        }
167        Ok(off)
168    }
169
170    /// Get the Tunnel ID field.
171    pub fn tunnel_id(&self, buf: &[u8]) -> Result<u16, FieldError> {
172        let s = self.slice(buf);
173        let off = self.tunnel_id_offset(buf)?;
174        if s.len() < off + 2 {
175            return Err(FieldError::BufferTooShort {
176                offset: self.index.start + off,
177                need: 2,
178                have: s.len().saturating_sub(off),
179            });
180        }
181        Ok(u16::from_be_bytes([s[off], s[off + 1]]))
182    }
183
184    /// Set the Tunnel ID field.
185    pub fn set_tunnel_id(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
186        let off = self.index.start + self.tunnel_id_offset(buf)?;
187        if buf.len() < off + 2 {
188            return Err(FieldError::BufferTooShort {
189                offset: off,
190                need: 2,
191                have: buf.len().saturating_sub(off),
192            });
193        }
194        buf[off..off + 2].copy_from_slice(&value.to_be_bytes());
195        Ok(())
196    }
197
198    /// Compute byte offset of session_id within the layer slice.
199    fn session_id_offset(&self, buf: &[u8]) -> Result<usize, FieldError> {
200        Ok(self.tunnel_id_offset(buf)? + 2)
201    }
202
203    /// Get the Session ID field.
204    pub fn session_id(&self, buf: &[u8]) -> Result<u16, FieldError> {
205        let s = self.slice(buf);
206        let off = self.session_id_offset(buf)?;
207        if s.len() < off + 2 {
208            return Err(FieldError::BufferTooShort {
209                offset: self.index.start + off,
210                need: 2,
211                have: s.len().saturating_sub(off),
212            });
213        }
214        Ok(u16::from_be_bytes([s[off], s[off + 1]]))
215    }
216
217    /// Set the Session ID field.
218    pub fn set_session_id(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
219        let off = self.index.start + self.session_id_offset(buf)?;
220        if buf.len() < off + 2 {
221            return Err(FieldError::BufferTooShort {
222                offset: off,
223                need: 2,
224                have: buf.len().saturating_sub(off),
225            });
226        }
227        buf[off..off + 2].copy_from_slice(&value.to_be_bytes());
228        Ok(())
229    }
230
231    /// Compute offset of Ns within layer slice (only valid when S bit set).
232    fn ns_offset(&self, buf: &[u8]) -> Result<usize, FieldError> {
233        Ok(self.session_id_offset(buf)? + 2)
234    }
235
236    /// Get the Ns (send sequence number) field if S bit is set.
237    pub fn ns(&self, buf: &[u8]) -> Result<Option<u16>, FieldError> {
238        if !self.has_sequence(buf)? {
239            return Ok(None);
240        }
241        let s = self.slice(buf);
242        let off = self.ns_offset(buf)?;
243        if s.len() < off + 2 {
244            return Err(FieldError::BufferTooShort {
245                offset: self.index.start + off,
246                need: 2,
247                have: s.len().saturating_sub(off),
248            });
249        }
250        Ok(Some(u16::from_be_bytes([s[off], s[off + 1]])))
251    }
252
253    /// Set the Ns field.
254    pub fn set_ns(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
255        if !self.has_sequence(buf)? {
256            return Err(FieldError::InvalidValue(
257                "S bit not set; Ns field not present".into(),
258            ));
259        }
260        let off = self.index.start + self.ns_offset(buf)?;
261        if buf.len() < off + 2 {
262            return Err(FieldError::BufferTooShort {
263                offset: off,
264                need: 2,
265                have: buf.len().saturating_sub(off),
266            });
267        }
268        buf[off..off + 2].copy_from_slice(&value.to_be_bytes());
269        Ok(())
270    }
271
272    /// Compute offset of Nr within layer slice (only valid when S bit set).
273    fn nr_offset(&self, buf: &[u8]) -> Result<usize, FieldError> {
274        Ok(self.ns_offset(buf)? + 2)
275    }
276
277    /// Get the Nr (receive sequence number) field if S bit is set.
278    pub fn nr(&self, buf: &[u8]) -> Result<Option<u16>, FieldError> {
279        if !self.has_sequence(buf)? {
280            return Ok(None);
281        }
282        let s = self.slice(buf);
283        let off = self.nr_offset(buf)?;
284        if s.len() < off + 2 {
285            return Err(FieldError::BufferTooShort {
286                offset: self.index.start + off,
287                need: 2,
288                have: s.len().saturating_sub(off),
289            });
290        }
291        Ok(Some(u16::from_be_bytes([s[off], s[off + 1]])))
292    }
293
294    /// Set the Nr field.
295    pub fn set_nr(&self, buf: &mut [u8], value: u16) -> Result<(), FieldError> {
296        if !self.has_sequence(buf)? {
297            return Err(FieldError::InvalidValue(
298                "S bit not set; Nr field not present".into(),
299            ));
300        }
301        let off = self.index.start + self.nr_offset(buf)?;
302        if buf.len() < off + 2 {
303            return Err(FieldError::BufferTooShort {
304                offset: off,
305                need: 2,
306                have: buf.len().saturating_sub(off),
307            });
308        }
309        buf[off..off + 2].copy_from_slice(&value.to_be_bytes());
310        Ok(())
311    }
312
313    /// Compute the dynamic header length based on flag bits.
314    fn compute_header_len(&self, buf: &[u8]) -> usize {
315        let s = self.slice(buf);
316        if s.len() < 2 {
317            return L2TP_MIN_HEADER_LEN;
318        }
319        let word = u16::from_be_bytes([s[0], s[1]]);
320        let has_l = (word >> 14) & 0x01 == 1;
321        let has_s = (word >> 11) & 0x01 == 1;
322        let has_o = (word >> 9) & 0x01 == 1;
323
324        let mut len = 2; // flags word
325        if has_l {
326            len += 2;
327        }
328        len += 4; // tunnel_id + session_id
329        if has_s {
330            len += 4; // Ns + Nr
331        }
332        if has_o {
333            // Offset Size (2 bytes) + Offset Pad (variable)
334            let off_size_pos = len;
335            if s.len() >= off_size_pos + 2 {
336                let offset_size =
337                    u16::from_be_bytes([s[off_size_pos], s[off_size_pos + 1]]) as usize;
338                len += 2 + offset_size;
339            } else {
340                len += 2;
341            }
342        }
343        len
344    }
345
346    // ========================================================================
347    // Summary / display
348    // ========================================================================
349
350    /// Generate a one-line summary of this L2TP layer.
351    pub fn summary(&self, buf: &[u8]) -> String {
352        let s = self.slice(buf);
353        if s.len() < 2 {
354            return "L2TP".to_string();
355        }
356        let is_ctrl = self
357            .is_control(buf)
358            .map(|c| if c { "ctrl" } else { "data" })
359            .unwrap_or("?");
360        let tid = self
361            .tunnel_id(buf)
362            .map(|v| v.to_string())
363            .unwrap_or_else(|_| "?".to_string());
364        let sid = self
365            .session_id(buf)
366            .map(|v| v.to_string())
367            .unwrap_or_else(|_| "?".to_string());
368        format!("L2TP {} tunnel_id={} session_id={}", is_ctrl, tid, sid)
369    }
370
371    // ========================================================================
372    // Field access API
373    // ========================================================================
374
375    /// Get the field names for this layer.
376    pub fn field_names() -> &'static [&'static str] {
377        L2TP_FIELD_NAMES
378    }
379
380    /// Get a field value by name.
381    pub fn get_field(&self, buf: &[u8], name: &str) -> Option<Result<FieldValue, FieldError>> {
382        match name {
383            "flags" => Some(self.flags_word(buf).map(FieldValue::U16)),
384            "version" => Some(self.version(buf).map(FieldValue::U8)),
385            "msg_type" => Some(self.msg_type(buf).map(FieldValue::U8)),
386            "length" => Some(self.length(buf).map(|v| FieldValue::U16(v.unwrap_or(0)))),
387            "tunnel_id" => Some(self.tunnel_id(buf).map(FieldValue::U16)),
388            "session_id" => Some(self.session_id(buf).map(FieldValue::U16)),
389            "ns" => Some(self.ns(buf).map(|v| FieldValue::U16(v.unwrap_or(0)))),
390            "nr" => Some(self.nr(buf).map(|v| FieldValue::U16(v.unwrap_or(0)))),
391            _ => None,
392        }
393    }
394
395    /// Set a field value by name.
396    pub fn set_field(
397        &self,
398        buf: &mut [u8],
399        name: &str,
400        value: FieldValue,
401    ) -> Option<Result<(), FieldError>> {
402        match name {
403            "tunnel_id" => {
404                if let FieldValue::U16(v) = value {
405                    Some(self.set_tunnel_id(buf, v))
406                } else {
407                    Some(Err(FieldError::InvalidValue(format!(
408                        "tunnel_id: expected U16, got {:?}",
409                        value
410                    ))))
411                }
412            }
413            "session_id" => {
414                if let FieldValue::U16(v) = value {
415                    Some(self.set_session_id(buf, v))
416                } else {
417                    Some(Err(FieldError::InvalidValue(format!(
418                        "session_id: expected U16, got {:?}",
419                        value
420                    ))))
421                }
422            }
423            "ns" => {
424                if let FieldValue::U16(v) = value {
425                    Some(self.set_ns(buf, v))
426                } else {
427                    Some(Err(FieldError::InvalidValue(format!(
428                        "ns: expected U16, got {:?}",
429                        value
430                    ))))
431                }
432            }
433            "nr" => {
434                if let FieldValue::U16(v) = value {
435                    Some(self.set_nr(buf, v))
436                } else {
437                    Some(Err(FieldError::InvalidValue(format!(
438                        "nr: expected U16, got {:?}",
439                        value
440                    ))))
441                }
442            }
443            _ => None,
444        }
445    }
446}
447
448impl Layer for L2tpLayer {
449    fn kind(&self) -> LayerKind {
450        LayerKind::L2tp
451    }
452
453    fn summary(&self, data: &[u8]) -> String {
454        self.summary(data)
455    }
456
457    fn header_len(&self, data: &[u8]) -> usize {
458        self.compute_header_len(data)
459    }
460
461    fn field_names(&self) -> &'static [&'static str] {
462        L2TP_FIELD_NAMES
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469
470    /// Build a minimal data-type L2TP header (no optional fields).
471    /// Flags word: 0x0002 (ver=2, all bits clear = data, no L, no S, no O)
472    fn data_header(tunnel_id: u16, session_id: u16) -> Vec<u8> {
473        let mut buf = vec![0u8; 6];
474        buf[0] = 0x00;
475        buf[1] = 0x02; // flags = 0x0002 (ver=2)
476        buf[2..4].copy_from_slice(&tunnel_id.to_be_bytes());
477        buf[4..6].copy_from_slice(&session_id.to_be_bytes());
478        buf
479    }
480
481    /// Build a control+length L2TP header (T=1, L=1, S=1, ver=2).
482    /// Matches the UTS test: `L2TP(hdr="control+length", tunnel_id=1, session_id=2)`.
483    fn control_length_header(tunnel_id: u16, session_id: u16) -> Vec<u8> {
484        // flags word: T=1 (bit15), L=1 (bit14), S=1 (bit11) → 0xC002 | 0x0800 = 0xC802
485        // Wait — "control+length" in UTS → T=1, L=1 → 0xC002
486        // Then Ns=0, Nr=0 also appear in the UTS output, meaning S=1 too
487        // Bytes: \xc0\x02\x00\x0c\x00\x01\x00\x02\x00\x00\x00\x00
488        // 0xC0 = 1100_0000: T=1 (bit7), L=1 (bit6) of high byte
489        // 0x02 = 0000_0010: ver=2
490        // So S and O are in the HIGH byte bits 4 and 2 respectively:
491        // High byte bit mapping: T=7, L=6, x=5, x=4, S=3, x=2, x=1, O=0 (but wait…)
492        // Actually RFC 2661 bit ordering in 16-bit word (network byte order):
493        // bit 0 = MSB of high byte = T
494        // bit 1 = L
495        // bit 4 = S
496        // bit 6 = O
497        // bit 8 = P (low byte bit 7)
498        // bits 12-15 = Ver (low nibble of low byte)
499        // So for control+length+sequence (T=1, L=1, S=1):
500        // high byte: bit7=T=1, bit6=L=1, bit3=S=1 → 0xC8 | ... no
501        // Let's just use the raw bytes from the UTS test:
502        // \xc0\x02 = 0xC002 → flags word
503        // 0xC0 high byte: 1100_0000 → T=1(bit7), L=1(bit6), others=0
504        // 0x02 low byte: 0000_0010 → ver=2
505        // Then: \x00\x0c = length=12
506        // \x00\x01 = tunnel_id=1, \x00\x02 = session_id=2
507        // \x00\x00 = Ns=0, \x00\x00 = Nr=0
508        // BUT that needs S bit set for Ns/Nr to appear!
509        // 0xC002: bit15=1(T), bit14=1(L), bit11=0(S)... so S is NOT set?
510        // But the output has 12 bytes: 2+2+2+2+2+2=12 which includes Ns+Nr...
511        // Looking at UTS bytes again: b'\xc0\x02\x00\x0c\x00\x01\x00\x02\x00\x00\x00\x00'
512        // = flags(2) + length(2) + tunnel(2) + session(2) + ns(2) + nr(2) = 12 bytes
513        // So S bit MUST be set. 0xC002 in binary:
514        // 1100 0000 0000 0010
515        // MSB to LSB: T=1, L=1, 0, 0, S=0?, 0, 0, 0, 0, 0, 0, 0, ver=0010
516        // S is at bit position 11 from MSB (0-indexed): bit 11 from left = bit 4 in low byte?
517        // Actually 16-bit word bit numbering: bit 0 = bit 15 of the 16-bit word (T)
518        // From RFC 2661 section 3.1:
519        // The Flags and Version Fields are in network byte order.
520        // T L x x S x O  P  x  x  x  x  Ver Ver Ver Ver
521        // 0 1 2 3 4 5 6  7  8  9 10 11 12  13  14  15
522        // So in network byte order (big-endian):
523        // First byte (high byte): bits 0-7 = T, L, x, x, S, x, O, P (using RFC numbering 0-7)
524        // → no wait. In network byte order, bit 0 is MSB.
525        // RFC 2661: "The 16-bit flags/version field has the following format:
526        //  0                   1
527        //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
528        // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
529        // |T|L|x|x|S|x|O|P|x|x|x|x| Ver |
530        // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"
531        // So bit 0 (MSB) = T, bit 1 = L, bit 4 = S, bit 6 = O, bit 7 = P, bits 12-15 = Ver
532        // In the 16-bit big-endian word, the high byte is bits 0-7 and low byte is bits 8-15.
533        // High byte: T(bit0/bit7 of byte)=1, L(bit1/bit6 of byte)=1 → 0xC0
534        // S is bit 4 of the 16-bit word = bit 3 of high byte → 0xC0 | (1<<3) = 0xC8 for S=1
535        // But UTS shows 0xC002 which means S=0 in the flags!
536        // The 12-byte output for control+length must include something else...
537        // Let me recount the UTS bytes for "control+length":
538        // b'\xc0\x02\x00\x0c\x00\x01\x00\x02\x00\x00\x00\x00'
539        // If S=0 (no sequence), the fields would be:
540        // flags(2) + length(2) + tunnel(2) + session(2) = 8 bytes total... but length=12
541        // Scapy's default for L2TP control message includes Ns and Nr.
542        // So the "control+length" hdr option sets T=1, L=1, AND Scapy also sets S=1 by default
543        // for control messages. The flags word would then be:
544        // T=1, L=1, S=1 → bits 0,1,4 set → 0xC0 | 0x08 = 0xC8 for high byte
545        // But the UTS shows 0xC0... Let me just trust the test bytes directly.
546        // Actually \xc0\x02 as a 16-bit: 0xC002
547        // T = bit 15 (MSB of 16-bit) = 1 ✓
548        // L = bit 14 = 1 ✓
549        // S = bit 11 = 0... but we have Ns and Nr in the output
550        // Something is off. Let me re-examine. 0xC002:
551        // binary: 1100 0000 0000 0010
552        //         TLXX SXOP XXXX VVVV
553        // T=1, L=1, S=0, O=0, P=0, Ver=2
554        // With T=1, L=1, S=0: header = flags(2)+len(2)+tunnel(2)+session(2) = 8 bytes, length=8
555        // But UTS shows length=12 and 4 more bytes (the zeros at end).
556        // UNLESS Scapy's "control+length" hdr actually means something different.
557        // Perhaps Scapy L2TP with control messages ALWAYS adds Ns/Nr.
558        // The actual bytes say length=0x0c=12 with 12 total bytes including Ns+Nr.
559        // So S bit must be set somehow. Maybe Scapy represents it differently.
560        // Let me look at 0xC002 again: maybe the bit ordering in Scapy is different.
561        // Scapy field "hdr" is a FlagsField with bit names in order:
562        // From Scapy source: BitField("hdr", 0xC802, 16) or similar
563        // Actually I think Scapy might encode S differently.
564        // For our implementation, let's just use what the test expects.
565        // The 12-byte control+length header:
566        // \xc0\x02\x00\x0c\x00\x01\x00\x02\x00\x00\x00\x00
567        // We'll treat bits 12-15 (low nibble) as version=2 ✓
568        // And interpret the bytes literally for our builder.
569        let mut buf = Vec::with_capacity(12);
570        buf.extend_from_slice(&0xC002u16.to_be_bytes()); // flags word: T=1, L=1, ver=2
571        buf.extend_from_slice(&12u16.to_be_bytes()); // length = 12
572        buf.extend_from_slice(&tunnel_id.to_be_bytes());
573        buf.extend_from_slice(&session_id.to_be_bytes());
574        buf.extend_from_slice(&0u16.to_be_bytes()); // Ns = 0
575        buf.extend_from_slice(&0u16.to_be_bytes()); // Nr = 0
576        buf
577    }
578
579    #[test]
580    fn test_data_header_parse() {
581        let data = data_header(0, 0);
582        let idx = LayerIndex::new(LayerKind::L2tp, 0, data.len());
583        let l2tp = L2tpLayer::new(idx);
584
585        assert_eq!(l2tp.version(&data).unwrap(), 2);
586        assert_eq!(l2tp.msg_type(&data).unwrap(), 0); // data
587        assert!(!l2tp.has_length(&data).unwrap());
588        assert!(!l2tp.has_sequence(&data).unwrap());
589        assert_eq!(l2tp.tunnel_id(&data).unwrap(), 0);
590        assert_eq!(l2tp.session_id(&data).unwrap(), 0);
591        assert!(l2tp.ns(&data).unwrap().is_none());
592        assert!(l2tp.nr(&data).unwrap().is_none());
593    }
594
595    #[test]
596    fn test_default_data_bytes() {
597        // UTS: L2TP() default → \x00\x02\x00\x00\x00\x00
598        let data = data_header(0, 0);
599        assert_eq!(data, b"\x00\x02\x00\x00\x00\x00");
600    }
601
602    #[test]
603    fn test_header_len_data() {
604        let data = data_header(1, 2);
605        let idx = LayerIndex::new(LayerKind::L2tp, 0, data.len());
606        let l2tp = L2tpLayer::new(idx);
607        assert_eq!(l2tp.compute_header_len(&data), 6);
608    }
609
610    #[test]
611    fn test_header_len_with_length_field() {
612        // L bit set → flags(2) + length(2) + tunnel(2) + session(2) = 8
613        let mut data = vec![0xC0u8, 0x02, 0x00, 0x08, 0x00, 0x01, 0x00, 0x02];
614        let idx = LayerIndex::new(LayerKind::L2tp, 0, data.len());
615        let l2tp = L2tpLayer::new(idx);
616        assert!(l2tp.has_length(&data).unwrap());
617        assert_eq!(l2tp.compute_header_len(&data), 8);
618        assert_eq!(l2tp.tunnel_id(&data).unwrap(), 1);
619        assert_eq!(l2tp.session_id(&data).unwrap(), 2);
620        let _ = data;
621    }
622
623    #[test]
624    fn test_set_tunnel_session_id() {
625        let mut data = data_header(0, 0);
626        let idx = LayerIndex::new(LayerKind::L2tp, 0, data.len());
627        let l2tp = L2tpLayer::new(idx);
628        l2tp.set_tunnel_id(&mut data, 42).unwrap();
629        l2tp.set_session_id(&mut data, 99).unwrap();
630        assert_eq!(l2tp.tunnel_id(&data).unwrap(), 42);
631        assert_eq!(l2tp.session_id(&data).unwrap(), 99);
632    }
633
634    #[test]
635    fn test_summary() {
636        let data = data_header(1, 2);
637        let idx = LayerIndex::new(LayerKind::L2tp, 0, data.len());
638        let l2tp = L2tpLayer::new(idx);
639        let s = l2tp.summary(&data);
640        assert!(s.contains("tunnel_id=1"));
641        assert!(s.contains("session_id=2"));
642        assert!(s.contains("data"));
643    }
644}