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