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}