stackforge_core/layer/icmp/
builder.rs1use std::net::Ipv4Addr;
22
23use super::checksum::icmp_checksum;
24use super::types::types;
25use super::{ICMP_MIN_HEADER_LEN, offsets};
26use crate::layer::field::FieldError;
27
28#[derive(Debug, Clone)]
33pub struct IcmpBuilder {
34 icmp_type: u8,
36 code: u8,
37 checksum: Option<u16>,
38
39 type_specific: [u8; 4],
47
48 timestamp_data: Option<[u8; 12]>,
51
52 payload: Vec<u8>,
54
55 auto_checksum: bool,
57}
58
59impl Default for IcmpBuilder {
60 fn default() -> Self {
61 Self {
62 icmp_type: types::ECHO_REQUEST,
63 code: 0,
64 checksum: None,
65 type_specific: [0; 4],
66 timestamp_data: None,
67 payload: Vec::new(),
68 auto_checksum: true,
69 }
70 }
71}
72
73impl IcmpBuilder {
74 #[must_use]
76 pub fn new() -> Self {
77 Self::default()
78 }
79
80 #[must_use]
88 pub fn echo_request(id: u16, seq: u16) -> Self {
89 let mut builder = Self::new();
90 builder.icmp_type = types::ECHO_REQUEST;
91 builder.code = 0;
92 builder.set_id_seq(id, seq);
93 builder
94 }
95
96 #[must_use]
102 pub fn echo_reply(id: u16, seq: u16) -> Self {
103 let mut builder = Self::new();
104 builder.icmp_type = types::ECHO_REPLY;
105 builder.code = 0;
106 builder.set_id_seq(id, seq);
107 builder
108 }
109
110 #[must_use]
120 pub fn dest_unreach(code: u8) -> Self {
121 let mut builder = Self::new();
122 builder.icmp_type = types::DEST_UNREACH;
123 builder.code = code;
124 builder.type_specific = [0; 4]; builder
126 }
127
128 #[must_use]
133 pub fn dest_unreach_need_frag(mtu: u16) -> Self {
134 let mut builder = Self::new();
135 builder.icmp_type = types::DEST_UNREACH;
136 builder.code = 4; builder.type_specific[0] = 0; builder.type_specific[1] = 0; builder.type_specific[2..4].copy_from_slice(&mtu.to_be_bytes());
140 builder
141 }
142
143 #[must_use]
153 pub fn redirect(code: u8, gateway: Ipv4Addr) -> Self {
154 let mut builder = Self::new();
155 builder.icmp_type = types::REDIRECT;
156 builder.code = code;
157 builder.type_specific.copy_from_slice(&gateway.octets());
158 builder
159 }
160
161 #[must_use]
168 pub fn time_exceeded(code: u8) -> Self {
169 let mut builder = Self::new();
170 builder.icmp_type = types::TIME_EXCEEDED;
171 builder.code = code;
172 builder.type_specific = [0; 4]; builder
174 }
175
176 #[must_use]
181 pub fn param_problem(ptr: u8) -> Self {
182 let mut builder = Self::new();
183 builder.icmp_type = types::PARAM_PROBLEM;
184 builder.code = 0;
185 builder.type_specific[0] = ptr;
186 builder.type_specific[1] = 0; builder.type_specific[2] = 0; builder.type_specific[3] = 0; builder
190 }
191
192 #[must_use]
194 pub fn source_quench() -> Self {
195 let mut builder = Self::new();
196 builder.icmp_type = types::SOURCE_QUENCH;
197 builder.code = 0;
198 builder.type_specific = [0; 4]; builder
200 }
201
202 #[must_use]
211 pub fn timestamp_request(id: u16, seq: u16, ts_ori: u32, ts_rx: u32, ts_tx: u32) -> Self {
212 let mut builder = Self::new();
213 builder.icmp_type = types::TIMESTAMP;
214 builder.code = 0;
215 builder.set_id_seq(id, seq);
216
217 let mut ts_data = [0u8; 12];
218 ts_data[0..4].copy_from_slice(&ts_ori.to_be_bytes());
219 ts_data[4..8].copy_from_slice(&ts_rx.to_be_bytes());
220 ts_data[8..12].copy_from_slice(&ts_tx.to_be_bytes());
221 builder.timestamp_data = Some(ts_data);
222
223 builder
224 }
225
226 #[must_use]
235 pub fn timestamp_reply(id: u16, seq: u16, ts_ori: u32, ts_rx: u32, ts_tx: u32) -> Self {
236 let mut builder = Self::new();
237 builder.icmp_type = types::TIMESTAMP_REPLY;
238 builder.code = 0;
239 builder.set_id_seq(id, seq);
240
241 let mut ts_data = [0u8; 12];
242 ts_data[0..4].copy_from_slice(&ts_ori.to_be_bytes());
243 ts_data[4..8].copy_from_slice(&ts_rx.to_be_bytes());
244 ts_data[8..12].copy_from_slice(&ts_tx.to_be_bytes());
245 builder.timestamp_data = Some(ts_data);
246
247 builder
248 }
249
250 #[must_use]
256 pub fn address_mask_request(id: u16, seq: u16) -> Self {
257 let mut builder = Self::new();
258 builder.icmp_type = types::ADDRESS_MASK_REQUEST;
259 builder.code = 0;
260 builder.set_id_seq(id, seq);
261 builder
262 }
263
264 #[must_use]
271 pub fn address_mask_reply(id: u16, seq: u16, mask: Ipv4Addr) -> Self {
272 let mut builder = Self::new();
273 builder.icmp_type = types::ADDRESS_MASK_REPLY;
274 builder.code = 0;
275 builder.set_id_seq(id, seq);
276 builder.payload = mask.octets().to_vec();
278 builder
279 }
280
281 fn set_id_seq(&mut self, id: u16, seq: u16) {
285 self.type_specific[0..2].copy_from_slice(&id.to_be_bytes());
286 self.type_specific[2..4].copy_from_slice(&seq.to_be_bytes());
287 }
288
289 #[must_use]
293 pub fn icmp_type(mut self, t: u8) -> Self {
294 self.icmp_type = t;
295 self
296 }
297
298 #[must_use]
300 pub fn code(mut self, c: u8) -> Self {
301 self.code = c;
302 self
303 }
304
305 #[must_use]
309 pub fn checksum(mut self, csum: u16) -> Self {
310 self.checksum = Some(csum);
311 self.auto_checksum = false;
312 self
313 }
314
315 #[must_use]
317 pub fn chksum(self, csum: u16) -> Self {
318 self.checksum(csum)
319 }
320
321 #[must_use]
323 pub fn enable_auto_checksum(mut self) -> Self {
324 self.auto_checksum = true;
325 self.checksum = None;
326 self
327 }
328
329 #[must_use]
331 pub fn disable_auto_checksum(mut self) -> Self {
332 self.auto_checksum = false;
333 self
334 }
335
336 pub fn payload<T: Into<Vec<u8>>>(mut self, data: T) -> Self {
338 self.payload = data.into();
339 self
340 }
341
342 pub fn append_payload<T: AsRef<[u8]>>(mut self, data: T) -> Self {
344 self.payload.extend_from_slice(data.as_ref());
345 self
346 }
347
348 #[must_use]
352 pub fn packet_size(&self) -> usize {
353 let mut size = ICMP_MIN_HEADER_LEN; if self.timestamp_data.is_some() {
357 size += 12;
358 }
359
360 size + self.payload.len()
361 }
362
363 #[must_use]
365 pub fn header_size(&self) -> usize {
366 if self.timestamp_data.is_some() {
367 20 } else {
369 ICMP_MIN_HEADER_LEN
370 }
371 }
372
373 #[must_use]
377 pub fn build(&self) -> Vec<u8> {
378 let total_size = self.packet_size();
379 let mut buf = vec![0u8; total_size];
380 self.build_into(&mut buf)
381 .expect("buffer is correctly sized");
382 buf
383 }
384
385 pub fn build_into(&self, buf: &mut [u8]) -> Result<usize, FieldError> {
387 let total_size = self.packet_size();
388
389 if buf.len() < total_size {
390 return Err(FieldError::BufferTooShort {
391 offset: 0,
392 need: total_size,
393 have: buf.len(),
394 });
395 }
396
397 buf[offsets::TYPE] = self.icmp_type;
399
400 buf[offsets::CODE] = self.code;
402
403 buf[offsets::CHECKSUM..offsets::CHECKSUM + 2].copy_from_slice(&[0, 0]);
405
406 buf[4..8].copy_from_slice(&self.type_specific);
408
409 let mut offset = 8;
410
411 if let Some(ts_data) = &self.timestamp_data {
413 buf[offset..offset + 12].copy_from_slice(ts_data);
414 offset += 12;
415 }
416
417 if !self.payload.is_empty() {
419 buf[offset..offset + self.payload.len()].copy_from_slice(&self.payload);
420 }
421
422 if self.auto_checksum {
424 let csum = icmp_checksum(&buf[..total_size]);
425 buf[offsets::CHECKSUM..offsets::CHECKSUM + 2].copy_from_slice(&csum.to_be_bytes());
426 } else if let Some(csum) = self.checksum {
427 buf[offsets::CHECKSUM..offsets::CHECKSUM + 2].copy_from_slice(&csum.to_be_bytes());
428 }
429
430 Ok(total_size)
431 }
432
433 #[must_use]
435 pub fn build_header(&self) -> Vec<u8> {
436 let header_size = self.header_size();
437 let mut buf = vec![0u8; header_size];
438
439 let builder = Self {
441 payload: Vec::new(),
442 ..self.clone()
443 };
444 builder
445 .build_into(&mut buf)
446 .expect("buffer is correctly sized");
447
448 buf
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[test]
457 fn test_echo_request() {
458 let packet = IcmpBuilder::echo_request(0x1234, 5)
459 .payload(b"Hello")
460 .build();
461
462 assert_eq!(packet[0], types::ECHO_REQUEST); assert_eq!(packet[1], 0); assert_eq!(u16::from_be_bytes([packet[4], packet[5]]), 0x1234); assert_eq!(u16::from_be_bytes([packet[6], packet[7]]), 5); assert_eq!(&packet[8..], b"Hello"); }
469
470 #[test]
471 fn test_echo_reply() {
472 let packet = IcmpBuilder::echo_reply(0x5678, 10).build();
473
474 assert_eq!(packet[0], types::ECHO_REPLY);
475 assert_eq!(u16::from_be_bytes([packet[4], packet[5]]), 0x5678);
476 assert_eq!(u16::from_be_bytes([packet[6], packet[7]]), 10);
477 }
478
479 #[test]
480 fn test_dest_unreach() {
481 let packet = IcmpBuilder::dest_unreach(3).build(); assert_eq!(packet[0], types::DEST_UNREACH);
484 assert_eq!(packet[1], 3); }
486
487 #[test]
488 fn test_dest_unreach_need_frag() {
489 let packet = IcmpBuilder::dest_unreach_need_frag(1500).build();
490
491 assert_eq!(packet[0], types::DEST_UNREACH);
492 assert_eq!(packet[1], 4); assert_eq!(u16::from_be_bytes([packet[6], packet[7]]), 1500); }
495
496 #[test]
497 fn test_redirect() {
498 let gateway = Ipv4Addr::new(192, 168, 1, 1);
499 let packet = IcmpBuilder::redirect(1, gateway).build();
500
501 assert_eq!(packet[0], types::REDIRECT);
502 assert_eq!(packet[1], 1); assert_eq!(&packet[4..8], &[192, 168, 1, 1]); }
505
506 #[test]
507 fn test_time_exceeded() {
508 let packet = IcmpBuilder::time_exceeded(0).build(); assert_eq!(packet[0], types::TIME_EXCEEDED);
511 assert_eq!(packet[1], 0);
512 }
513
514 #[test]
515 fn test_param_problem() {
516 let packet = IcmpBuilder::param_problem(20).build();
517
518 assert_eq!(packet[0], types::PARAM_PROBLEM);
519 assert_eq!(packet[4], 20); }
521
522 #[test]
523 fn test_timestamp_request() {
524 let packet = IcmpBuilder::timestamp_request(0x1234, 1, 1000, 0, 0).build();
525
526 assert_eq!(packet[0], types::TIMESTAMP);
527 assert_eq!(packet.len(), 20); assert_eq!(u16::from_be_bytes([packet[4], packet[5]]), 0x1234); assert_eq!(u16::from_be_bytes([packet[6], packet[7]]), 1); assert_eq!(
531 u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]),
532 1000
533 ); }
535
536 #[test]
537 fn test_checksum_calculation() {
538 let packet = IcmpBuilder::echo_request(1, 1).payload(b"test").build();
539
540 let checksum = u16::from_be_bytes([packet[2], packet[3]]);
542 assert_ne!(checksum, 0);
543 }
544
545 #[test]
546 fn test_manual_checksum() {
547 let packet = IcmpBuilder::echo_request(1, 1).checksum(0xABCD).build();
548
549 assert_eq!(u16::from_be_bytes([packet[2], packet[3]]), 0xABCD);
550 }
551
552 #[test]
553 fn test_build_header_only() {
554 let header = IcmpBuilder::echo_request(1, 1)
555 .payload(b"this should not be included")
556 .build_header();
557
558 assert_eq!(header.len(), 8); }
560
561 #[test]
562 fn test_timestamp_header_size() {
563 let header = IcmpBuilder::timestamp_request(1, 1, 1000, 2000, 3000).build_header();
564
565 assert_eq!(header.len(), 20); }
567}