Skip to main content

stackforge_core/layer/l2tp/
builder.rs

1//! L2TP packet builder.
2//!
3//! Provides a fluent API for constructing L2TPv2 packets (RFC 2661).
4//!
5//! # Examples
6//!
7//! ```rust
8//! use stackforge_core::layer::l2tp::builder::L2tpBuilder;
9//!
10//! // Default data message
11//! let pkt = L2tpBuilder::new().build();
12//! assert_eq!(pkt, b"\x00\x02\x00\x00\x00\x00");
13//!
14//! // Control + length message
15//! let pkt = L2tpBuilder::new()
16//!     .control()
17//!     .with_length()
18//!     .tunnel_id(1)
19//!     .session_id(2)
20//!     .build();
21//! assert_eq!(pkt, b"\xc0\x02\x00\x0c\x00\x01\x00\x02\x00\x00\x00\x00");
22//! ```
23
24/// Builder for L2TPv2 packets.
25///
26/// The builder encodes the header flags and optional fields according to
27/// RFC 2661.  Fields:
28/// - T bit (message type): cleared for data, set for control
29/// - L bit: when set a 2-byte Length field appears after the flags word
30/// - S bit: when set Ns and Nr (2 bytes each) appear after Session ID
31/// - O bit: when set Offset Size + Offset Pad appear after Nr (or Session ID)
32/// - Ver nibble: always 2 for L2TPv2
33#[derive(Debug, Clone)]
34pub struct L2tpBuilder {
35    /// Message type: 0 = data, 1 = control (T bit)
36    msg_type: u8,
37    /// Whether the L (length) bit is set
38    has_length: bool,
39    /// Whether the S (sequence) bit is set
40    has_sequence: bool,
41    /// Whether the O (offset) bit is set
42    has_offset: bool,
43    /// Priority bit (P), only meaningful for data messages
44    priority: bool,
45    /// Tunnel ID
46    tunnel_id: u16,
47    /// Session ID
48    session_id: u16,
49    /// Send sequence number (Ns), present when S=1
50    ns: u16,
51    /// Receive sequence number (Nr), present when S=1
52    nr: u16,
53    /// Offset size, present when O=1
54    offset_size: u16,
55    /// Payload after the L2TP header
56    payload: Vec<u8>,
57}
58
59impl Default for L2tpBuilder {
60    fn default() -> Self {
61        Self {
62            msg_type: 0,
63            has_length: false,
64            has_sequence: false,
65            has_offset: false,
66            priority: false,
67            tunnel_id: 0,
68            session_id: 0,
69            ns: 0,
70            nr: 0,
71            offset_size: 0,
72            payload: Vec::new(),
73        }
74    }
75}
76
77impl L2tpBuilder {
78    /// Create a new L2TP data message builder with minimal defaults.
79    /// Produces a 6-byte header: `\x00\x02\x00\x00\x00\x00` (ver=2, tid=0, sid=0).
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    // ========== Type / flags setters ==========
85
86    /// Set the T bit — makes this a control message.
87    pub fn control(mut self) -> Self {
88        self.msg_type = 1;
89        self
90    }
91
92    /// Set the T bit to 0 — data message (default).
93    pub fn data(mut self) -> Self {
94        self.msg_type = 0;
95        self
96    }
97
98    /// Set the L (length) bit so a Length field will be included in the header.
99    pub fn with_length(mut self) -> Self {
100        self.has_length = true;
101        self
102    }
103
104    /// Set the S (sequence) bit so Ns and Nr will be included.
105    pub fn with_sequence(mut self) -> Self {
106        self.has_sequence = true;
107        self
108    }
109
110    /// Set the O (offset) bit so Offset Size + Offset Pad will be included.
111    pub fn with_offset(mut self) -> Self {
112        self.has_offset = true;
113        self
114    }
115
116    /// Set the P (priority) bit for data messages.
117    pub fn priority(mut self) -> Self {
118        self.priority = true;
119        self
120    }
121
122    // ========== Field setters ==========
123
124    /// Set the Tunnel ID.
125    pub fn tunnel_id(mut self, id: u16) -> Self {
126        self.tunnel_id = id;
127        self
128    }
129
130    /// Set the Session ID.
131    pub fn session_id(mut self, id: u16) -> Self {
132        self.session_id = id;
133        self
134    }
135
136    /// Set the Ns (send sequence number) field.
137    /// Automatically enables the S bit.
138    pub fn ns(mut self, ns: u16) -> Self {
139        self.has_sequence = true;
140        self.ns = ns;
141        self
142    }
143
144    /// Set the Nr (receive sequence number) field.
145    /// Automatically enables the S bit.
146    pub fn nr(mut self, nr: u16) -> Self {
147        self.has_sequence = true;
148        self.nr = nr;
149        self
150    }
151
152    /// Set the Offset Size field.
153    /// Automatically enables the O bit.
154    pub fn offset_size(mut self, size: u16) -> Self {
155        self.has_offset = true;
156        self.offset_size = size;
157        self
158    }
159
160    /// Set the payload (data after the L2TP header).
161    pub fn payload<T: Into<Vec<u8>>>(mut self, data: T) -> Self {
162        self.payload = data.into();
163        self
164    }
165
166    // ========== Convenience constructors ==========
167
168    /// Create a control message with the Length bit set (matching Scapy's
169    /// `L2TP(hdr="control+length")` default).
170    pub fn control_length() -> Self {
171        Self::new().control().with_length().with_sequence()
172    }
173
174    // ========== Size / header helpers ==========
175
176    /// Compute the header length in bytes.
177    pub fn header_size(&self) -> usize {
178        let mut len = 2; // flags word
179        if self.has_length {
180            len += 2;
181        }
182        len += 4; // tunnel_id + session_id
183        if self.has_sequence {
184            len += 4; // Ns + Nr
185        }
186        if self.has_offset {
187            len += 2 + self.offset_size as usize; // Offset Size + Offset Pad
188        }
189        len
190    }
191
192    /// Compute the total packet size (header + payload).
193    pub fn packet_size(&self) -> usize {
194        self.header_size() + self.payload.len()
195    }
196
197    // ========== Build ==========
198
199    /// Serialize the L2TP header (and payload if any) into bytes.
200    pub fn build(&self) -> Vec<u8> {
201        let total = self.packet_size();
202        let mut buf = vec![0u8; total];
203        self.build_into(&mut buf);
204        buf
205    }
206
207    /// Serialize into an existing buffer (must be at least `packet_size()` bytes).
208    pub fn build_into(&self, buf: &mut Vec<u8>) {
209        // Build flags word (16-bit, big-endian)
210        // Bit layout (MSB to LSB of 16-bit word):
211        //  bit 15: T
212        //  bit 14: L
213        //  bit 13: x (reserved)
214        //  bit 12: x (reserved)
215        //  bit 11: S
216        //  bit 10: x (reserved)
217        //  bit  9: O
218        //  bit  8: P
219        //  bits 7-4: x (reserved)
220        //  bits 3-0: Ver (= 2)
221        let mut flags: u16 = 0;
222        if self.msg_type != 0 {
223            flags |= 1 << 15; // T
224        }
225        if self.has_length {
226            flags |= 1 << 14; // L
227        }
228        if self.has_sequence {
229            flags |= 1 << 11; // S
230        }
231        if self.has_offset {
232            flags |= 1 << 9; // O
233        }
234        if self.priority && self.msg_type == 0 {
235            flags |= 1 << 8; // P (data only)
236        }
237        flags |= 2; // Ver = 2
238
239        let mut pos = 0;
240
241        // Flags + Version
242        buf[pos..pos + 2].copy_from_slice(&flags.to_be_bytes());
243        pos += 2;
244
245        // Optional Length field
246        if self.has_length {
247            // Length = total header + payload
248            let total_len = self.packet_size() as u16;
249            buf[pos..pos + 2].copy_from_slice(&total_len.to_be_bytes());
250            pos += 2;
251        }
252
253        // Tunnel ID
254        buf[pos..pos + 2].copy_from_slice(&self.tunnel_id.to_be_bytes());
255        pos += 2;
256
257        // Session ID
258        buf[pos..pos + 2].copy_from_slice(&self.session_id.to_be_bytes());
259        pos += 2;
260
261        // Optional Ns + Nr
262        if self.has_sequence {
263            buf[pos..pos + 2].copy_from_slice(&self.ns.to_be_bytes());
264            pos += 2;
265            buf[pos..pos + 2].copy_from_slice(&self.nr.to_be_bytes());
266            pos += 2;
267        }
268
269        // Optional Offset Size + Offset Pad
270        if self.has_offset {
271            buf[pos..pos + 2].copy_from_slice(&self.offset_size.to_be_bytes());
272            pos += 2;
273            // Offset Pad: `offset_size` zero bytes
274            pos += self.offset_size as usize;
275        }
276
277        // Payload
278        if !self.payload.is_empty() {
279            let plen = self.payload.len();
280            buf[pos..pos + plen].copy_from_slice(&self.payload);
281        }
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn test_default_data_packet() {
291        // UTS: raw(L2TP()) → \x00\x02\x00\x00\x00\x00
292        let pkt = L2tpBuilder::new().build();
293        assert_eq!(
294            pkt, b"\x00\x02\x00\x00\x00\x00",
295            "default data L2TP bytes mismatch"
296        );
297    }
298
299    #[test]
300    fn test_control_length_packet() {
301        // UTS: bytes(L2TP(hdr="control+length", tunnel_id=1, session_id=2))
302        //    → b'\xc0\x02\x00\x0c\x00\x01\x00\x02\x00\x00\x00\x00'
303        // T=1, L=1, S=1, ver=2 → flags word = 0xC802? Let's check:
304        // 0xC802 = 1100 1000 0000 0010 → T=1, L=1, S=1, ver=2
305        // But UTS shows \xc0\x02 = 0xC002 = 1100 0000 0000 0010 → T=1, L=1, S=0
306        // With S=0 and L=1 the header would be: 2+2+2+2=8 bytes with length=8
307        // But the UTS bytes are 12 bytes long with 4 trailing zeros (Ns+Nr).
308        // This suggests Scapy's L2TP "control+length" hdr field also implies S=1.
309        // Let's test with with_sequence() enabled:
310        let pkt = L2tpBuilder::new()
311            .control()
312            .with_length()
313            .with_sequence()
314            .tunnel_id(1)
315            .session_id(2)
316            .build();
317        // Expected: flags=0xC802, len=12, tid=1, sid=2, ns=0, nr=0
318        assert_eq!(pkt.len(), 12);
319        let flags = u16::from_be_bytes([pkt[0], pkt[1]]);
320        // T=1 (bit15), L=1 (bit14), S=1 (bit11) → 0xC000 | 0x0800 | 0x0002 = 0xC802
321        assert_eq!(flags & 0x8000, 0x8000, "T bit should be set");
322        assert_eq!(flags & 0x4000, 0x4000, "L bit should be set");
323        assert_eq!(flags & 0x0800, 0x0800, "S bit should be set");
324        assert_eq!(flags & 0x000F, 2, "Version should be 2");
325        let length = u16::from_be_bytes([pkt[2], pkt[3]]);
326        assert_eq!(length, 12, "length field should be 12");
327        assert_eq!(u16::from_be_bytes([pkt[4], pkt[5]]), 1, "tunnel_id");
328        assert_eq!(u16::from_be_bytes([pkt[6], pkt[7]]), 2, "session_id");
329        assert_eq!(&pkt[8..12], b"\x00\x00\x00\x00", "Ns+Nr should be 0");
330    }
331
332    #[test]
333    fn test_data_with_tunnel_session() {
334        let pkt = L2tpBuilder::new()
335            .tunnel_id(0x1234)
336            .session_id(0x5678)
337            .build();
338        assert_eq!(pkt.len(), 6);
339        assert_eq!(u16::from_be_bytes([pkt[0], pkt[1]]), 0x0002); // data, ver=2
340        assert_eq!(u16::from_be_bytes([pkt[2], pkt[3]]), 0x1234);
341        assert_eq!(u16::from_be_bytes([pkt[4], pkt[5]]), 0x5678);
342    }
343
344    #[test]
345    fn test_with_payload() {
346        let pkt = L2tpBuilder::new()
347            .tunnel_id(1)
348            .session_id(2)
349            .payload(b"hello".to_vec())
350            .build();
351        assert_eq!(pkt.len(), 11);
352        assert_eq!(&pkt[6..], b"hello");
353    }
354
355    #[test]
356    fn test_header_size_data_only() {
357        let b = L2tpBuilder::new();
358        assert_eq!(b.header_size(), 6);
359    }
360
361    #[test]
362    fn test_header_size_with_length() {
363        let b = L2tpBuilder::new().with_length();
364        assert_eq!(b.header_size(), 8);
365    }
366
367    #[test]
368    fn test_header_size_with_sequence() {
369        let b = L2tpBuilder::new().with_sequence();
370        assert_eq!(b.header_size(), 10);
371    }
372
373    #[test]
374    fn test_header_size_full() {
375        let b = L2tpBuilder::new().with_length().with_sequence();
376        assert_eq!(b.header_size(), 12);
377    }
378
379    #[test]
380    fn test_ns_nr_setters() {
381        let pkt = L2tpBuilder::new().with_sequence().ns(100).nr(200).build();
382        assert_eq!(pkt.len(), 10);
383        let flags = u16::from_be_bytes([pkt[0], pkt[1]]);
384        assert_eq!(flags & 0x0800, 0x0800, "S bit should be set");
385        assert_eq!(u16::from_be_bytes([pkt[6], pkt[7]]), 100);
386        assert_eq!(u16::from_be_bytes([pkt[8], pkt[9]]), 200);
387    }
388}