1use crate::{err::ValueTooBigError, *};
2use arrayvec::ArrayVec;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
6pub struct Icmpv6Header {
7 pub icmp_type: Icmpv6Type,
9 pub checksum: u16,
11}
12
13impl Icmpv6Header {
14 pub const MIN_LEN: usize = 8;
19
20 #[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MIN_LEN instead")]
22 pub const MIN_SERIALIZED_SIZE: usize = Icmpv6Header::MIN_LEN;
23
24 pub const MAX_LEN: usize = 8 + 16 + 16;
31
32 #[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MAX_LEN instead")]
34 pub const MAX_SERIALIZED_SIZE: usize = Icmpv6Header::MAX_LEN;
35
36 #[inline]
38 pub fn new(icmp_type: Icmpv6Type) -> Icmpv6Header {
39 Icmpv6Header {
40 icmp_type,
41 checksum: 0, }
43 }
44
45 pub fn with_checksum(
48 icmp_type: Icmpv6Type,
49 source_ip: [u8; 16],
50 destination_ip: [u8; 16],
51 payload: &[u8],
52 ) -> Result<Icmpv6Header, ValueTooBigError<usize>> {
53 let checksum = icmp_type.calc_checksum(source_ip, destination_ip, payload)?;
54 Ok(Icmpv6Header {
55 icmp_type,
56 checksum,
57 })
58 }
59
60 #[inline]
63 pub fn from_slice(slice: &[u8]) -> Result<(Icmpv6Header, &[u8]), err::LenError> {
64 let header = Icmpv6Slice::from_slice(slice)?.header();
65 let len = header.header_len();
66 Ok((header, &slice[len..]))
67 }
68
69 #[cfg(feature = "std")]
71 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
72 pub fn read<T: std::io::Read + Sized>(reader: &mut T) -> Result<Icmpv6Header, std::io::Error> {
73 let mut start = [0u8; 8];
75 reader.read_exact(&mut start)?;
76 Ok(Icmpv6Slice { slice: &start }.header())
77 }
78
79 #[cfg(feature = "std")]
81 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
82 pub fn write<T: std::io::Write + Sized>(&self, writer: &mut T) -> Result<(), std::io::Error> {
83 writer.write_all(&self.to_bytes())
84 }
85
86 #[inline]
91 pub fn header_len(&self) -> usize {
92 self.icmp_type.header_len()
93 }
94
95 #[inline]
98 pub fn fixed_payload_size(&self) -> Option<usize> {
99 self.icmp_type.fixed_payload_size()
100 }
101
102 pub fn update_checksum(
104 &mut self,
105 source_ip: [u8; 16],
106 destination_ip: [u8; 16],
107 payload: &[u8],
108 ) -> Result<(), ValueTooBigError<usize>> {
109 self.checksum = self
110 .icmp_type
111 .calc_checksum(source_ip, destination_ip, payload)?;
112 Ok(())
113 }
114
115 #[inline]
117 pub fn to_bytes(&self) -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
118 let checksum_be = self.checksum.to_be_bytes();
119
120 let return_trivial =
121 |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
122 #[rustfmt::skip]
123 let mut re = ArrayVec::from([
124 type_u8, code_u8, checksum_be[0], checksum_be[1],
125 0, 0, 0, 0,
126
127 0, 0, 0, 0,
128 0, 0, 0, 0,
129 0, 0, 0, 0,
130 0, 0, 0, 0,
131
132 0, 0, 0, 0,
133 0, 0, 0, 0,
134 0, 0, 0, 0,
135 0, 0, 0, 0,
136 ]);
137 unsafe {
139 re.set_len(8);
140 }
141 re
142 };
143
144 let return_4u8 = |type_u8: u8,
145 code_u8: u8,
146 bytes5to8: [u8; 4]|
147 -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
148 #[rustfmt::skip]
149 let mut re = ArrayVec::from([
150 type_u8, code_u8, checksum_be[0], checksum_be[1],
151 bytes5to8[0], bytes5to8[1], bytes5to8[2], bytes5to8[3],
152
153 0, 0, 0, 0,
154 0, 0, 0, 0,
155 0, 0, 0, 0,
156 0, 0, 0, 0,
157
158 0, 0, 0, 0,
159 0, 0, 0, 0,
160 0, 0, 0, 0,
161 0, 0, 0, 0,
162 ]);
163 unsafe {
165 re.set_len(8);
166 }
167 re
168 };
169
170 use crate::{icmpv6::*, Icmpv6Type::*};
171 match self.icmp_type {
172 Unknown {
173 type_u8,
174 code_u8,
175 bytes5to8,
176 } => return_4u8(type_u8, code_u8, bytes5to8),
177 DestinationUnreachable(header) => return_trivial(TYPE_DST_UNREACH, header.code_u8()),
178 PacketTooBig { mtu } => return_4u8(TYPE_PACKET_TOO_BIG, 0, mtu.to_be_bytes()),
179 TimeExceeded(code) => return_trivial(TYPE_TIME_EXCEEDED, code.code_u8()),
180 ParameterProblem(header) => return_4u8(
181 TYPE_PARAMETER_PROBLEM,
182 header.code.code_u8(),
183 header.pointer.to_be_bytes(),
184 ),
185 EchoRequest(echo) => return_4u8(TYPE_ECHO_REQUEST, 0, echo.to_bytes()),
186 EchoReply(echo) => return_4u8(TYPE_ECHO_REPLY, 0, echo.to_bytes()),
187 RouterSolicitation => return_trivial(TYPE_ROUTER_SOLICITATION, 0),
188 RouterAdvertisement(header) => {
189 return_4u8(TYPE_ROUTER_ADVERTISEMENT, 0, header.to_bytes())
190 }
191 NeighborSolicitation => return_trivial(TYPE_NEIGHBOR_SOLICITATION, 0),
192 NeighborAdvertisement(header) => {
193 return_4u8(TYPE_NEIGHBOR_ADVERTISEMENT, 0, header.to_bytes())
194 }
195 Redirect => return_trivial(TYPE_REDIRECT_MESSAGE, 0),
196 }
197 }
198}
199
200#[cfg(test)]
201mod test {
202 use crate::{
203 err::{ValueTooBigError, ValueType},
204 icmpv6::*,
205 test_gens::*,
206 *,
207 };
208 use alloc::{format, vec::Vec};
209 use arrayvec::ArrayVec;
210 use proptest::prelude::*;
211
212 proptest! {
213 #[test]
214 fn new(icmp_type in icmpv6_type_any()) {
215 assert_eq!(
216 Icmpv6Header::new(icmp_type.clone()),
217 Icmpv6Header {
218 icmp_type,
219 checksum: 0,
220 }
221 );
222 }
223 }
224
225 proptest! {
226 #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
227 #[test]
228 fn with_checksum(
229 ip_header in ipv6_any(),
230 icmp_type in icmpv6_type_any(),
231 bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
233 payload in proptest::collection::vec(any::<u8>(), 0..1024)
234 ) {
235
236 {
238 let too_big_slice = unsafe {
241 use core::ptr::NonNull;
245 core::slice::from_raw_parts(
246 NonNull::<u8>::dangling().as_ptr(),
247 bad_len
248 )
249 };
250 assert_eq!(
251 Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, too_big_slice),
252 Err(ValueTooBigError{
253 actual: bad_len,
254 max_allowed: (core::u32::MAX - 8) as usize,
255 value_type: ValueType::Icmpv6PayloadLength,
256 })
257 );
258 }
259
260 assert_eq!(
262 Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, &payload).unwrap(),
263 Icmpv6Header {
264 icmp_type,
265 checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
266 }
267 );
268 }
269 }
270
271 proptest! {
272 #[test]
273 fn from_slice(
274 icmp_type in icmpv6_type_any(),
275 checksum in any::<u16>(),
276 ) {
277 let bytes = {
278 Icmpv6Header {
279 icmp_type: icmp_type.clone(),
280 checksum,
281 }.to_bytes()
282 };
283
284 {
286 let result = Icmpv6Header::from_slice(&bytes).unwrap();
287 assert_eq!(
288 Icmpv6Header{
289 icmp_type,
290 checksum,
291 },
292 result.0,
293 );
294 assert_eq!(&bytes[8..], result.1);
295 }
296
297
298 for length in 0..8 {
300 assert_eq!(
301 Icmpv6Header::from_slice(&bytes[..length]).unwrap_err(),
302 err::LenError{
303 required_len: bytes.len(),
304 len: length,
305 len_source: LenSource::Slice,
306 layer: err::Layer::Icmpv6,
307 layer_start_offset: 0
308 }
309 );
310 }
311 }
312 }
313
314 proptest! {
315 #[test]
316 fn read(
317 icmp_type in icmpv6_type_any(),
318 checksum in any::<u16>(),
319 ) {
320 let header = Icmpv6Header {
321 icmp_type: icmp_type.clone(),
322 checksum,
323 };
324 let bytes = header.to_bytes();
325
326 {
328 let mut cursor = std::io::Cursor::new(&bytes);
329 let result = Icmpv6Header::read(&mut cursor).unwrap();
330 assert_eq!(header, result,);
331 assert_eq!(header.header_len() as u64, cursor.position());
332 }
333
334 for length in 0..header.header_len() {
336 let mut cursor = std::io::Cursor::new(&bytes[..length]);
337 assert!(Icmpv6Header::read(&mut cursor).is_err());
338 }
339 }
340 }
341
342 proptest! {
343 #[test]
344 fn write(
345 icmp_type in icmpv6_type_any(),
346 checksum in any::<u16>(),
347 bad_len in 0..8usize
348 ) {
349 {
351 let mut buffer = Vec::with_capacity(icmp_type.header_len());
352 let header = Icmpv6Header {
353 icmp_type,
354 checksum,
355 };
356 header.write(&mut buffer).unwrap();
357 assert_eq!(
358 &header.to_bytes(),
359 &buffer[..]
360 );
361 }
362
363 {
365 let mut buffer = [0u8;Icmpv6Header::MAX_LEN];
366 let mut writer = std::io::Cursor::new(&mut buffer[..bad_len]);
367 Icmpv6Header {
368 icmp_type,
369 checksum,
370 }.write(&mut writer).unwrap_err();
371 }
372 }
373 }
374
375 proptest! {
376 #[test]
377 fn header_len(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
378 assert_eq!(
379 icmp_type.header_len(),
380 Icmpv6Header{
381 icmp_type,
382 checksum
383 }.header_len()
384 );
385 }
386 }
387
388 proptest! {
389 #[test]
390 fn fixed_payload_size(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
391 assert_eq!(
392 icmp_type.fixed_payload_size(),
393 Icmpv6Header{
394 icmp_type,
395 checksum
396 }.fixed_payload_size()
397 );
398 }
399 }
400
401 proptest! {
402 #[test]
403 #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
404 fn update_checksum(
405 ip_header in ipv6_any(),
406 icmp_type in icmpv6_type_any(),
407 start_checksum in any::<u16>(),
408 bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
410 payload in proptest::collection::vec(any::<u8>(), 0..1024)
411 ) {
412
413 {
415 let too_big_slice = unsafe {
418 use core::ptr::NonNull;
422 core::slice::from_raw_parts(
423 NonNull::<u8>::dangling().as_ptr(),
424 bad_len
425 )
426 };
427 assert_eq!(
428 Icmpv6Header{
429 icmp_type,
430 checksum: 0
431 }.update_checksum(ip_header.source, ip_header.destination, too_big_slice),
432 Err(ValueTooBigError{
433 actual: bad_len,
434 max_allowed: (u32::MAX - 8) as usize,
435 value_type: ValueType::Icmpv6PayloadLength
436 })
437 );
438 }
439
440 assert_eq!(
442 {
443 let mut header = Icmpv6Header{
444 icmp_type,
445 checksum: start_checksum,
446 };
447 header.update_checksum(ip_header.source, ip_header.destination, &payload).unwrap();
448 header
449 },
450 Icmpv6Header{
451 icmp_type,
452 checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
453 }
454 );
455 }
456 }
457
458 proptest! {
459 #[test]
460 fn to_bytes(
461 checksum in any::<u16>(),
462 rand_u32 in any::<u32>(),
463 rand_4bytes in any::<[u8;4]>(),
464 rand_bool0 in any::<bool>(),
465 rand_bool1 in any::<bool>(),
466 rand_bool2 in any::<bool>(),
467 ) {
468 use Icmpv6Type::*;
469
470 let with_5to8_bytes = |type_u8: u8, code_u8: u8, bytes5to8: [u8;4]| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
471 let mut bytes = ArrayVec::<u8, { Icmpv6Header::MAX_LEN }>::new();
472 bytes.push(type_u8);
473 bytes.push(code_u8);
474 bytes.try_extend_from_slice(&checksum.to_be_bytes()).unwrap();
475 bytes.try_extend_from_slice(&bytes5to8).unwrap();
476 bytes
477 };
478
479 let simple_bytes = |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
480 with_5to8_bytes(type_u8, code_u8, [0;4])
481 };
482
483 for (code, code_u8) in dest_unreachable_code_test_consts::VALID_VALUES {
485 assert_eq!(
486 Icmpv6Header{
487 icmp_type: DestinationUnreachable(code),
488 checksum
489 }.to_bytes(),
490 simple_bytes(TYPE_DST_UNREACH, code_u8)
491 );
492 }
493
494 assert_eq!(
496 Icmpv6Header{
497 icmp_type: PacketTooBig{ mtu: rand_u32 },
498 checksum
499 }.to_bytes(),
500 with_5to8_bytes(TYPE_PACKET_TOO_BIG, 0, rand_u32.to_be_bytes())
501 );
502
503 for (code, code_u8) in time_exceeded_code_test_consts::VALID_VALUES {
505 assert_eq!(
506 Icmpv6Header{
507 icmp_type: TimeExceeded(code),
508 checksum
509 }.to_bytes(),
510 simple_bytes(TYPE_TIME_EXCEEDED, code_u8)
511 );
512 }
513
514 for (code, code_u8) in parameter_problem_code_test_consts::VALID_VALUES {
516 assert_eq!(
517 Icmpv6Header{
518 icmp_type: ParameterProblem(
519 ParameterProblemHeader{
520 code,
521 pointer: rand_u32,
522 }
523 ),
524 checksum
525 }.to_bytes(),
526 with_5to8_bytes(TYPE_PARAMETER_PROBLEM, code_u8, rand_u32.to_be_bytes())
527 );
528 }
529
530 assert_eq!(
532 Icmpv6Header{
533 icmp_type: EchoRequest(IcmpEchoHeader {
534 id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
535 seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
536 }),
537 checksum
538 }.to_bytes(),
539 with_5to8_bytes(TYPE_ECHO_REQUEST, 0, rand_4bytes)
540 );
541
542 assert_eq!(
544 Icmpv6Header{
545 icmp_type: EchoReply(IcmpEchoHeader {
546 id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
547 seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
548 }),
549 checksum
550 }.to_bytes(),
551 with_5to8_bytes(TYPE_ECHO_REPLY, 0, rand_4bytes)
552 );
553
554 assert_eq!(
556 Icmpv6Header{
557 icmp_type: NeighborSolicitation,
558 checksum
559 }.to_bytes(),
560 with_5to8_bytes(TYPE_NEIGHBOR_SOLICITATION, 0, [0;4])
561 );
562
563 assert_eq!(
565 Icmpv6Header{
566 icmp_type: NeighborAdvertisement(
567 NeighborAdvertisementHeader {
568 router: rand_bool0,
569 solicited: rand_bool1,
570 r#override: rand_bool2,
571 }
572 ),
573 checksum
574 }.to_bytes(),
575 with_5to8_bytes(TYPE_NEIGHBOR_ADVERTISEMENT, 0, [
576 if rand_bool0 {
577 NeighborAdvertisementHeader::ROUTER_MASK
578 } else {
579 0
580 } | if rand_bool1 {
581 NeighborAdvertisementHeader::SOLICITED_MASK
582 } else {
583 0
584 } | if rand_bool2 {
585 NeighborAdvertisementHeader::OVERRIDE_MASK
586 } else {
587 0
588 }, 0, 0, 0
589 ])
590 );
591
592 for type_u8 in 0..=u8::MAX {
594 for code_u8 in 0..=u8::MAX {
595 assert_eq!(
596 Icmpv6Header{
597 icmp_type: Unknown {
598 type_u8,
599 code_u8,
600 bytes5to8: rand_4bytes,
601 },
602 checksum
603 }.to_bytes(),
604 with_5to8_bytes(type_u8, code_u8, rand_4bytes)
605 );
606 }
607 }
608 }
609 }
610
611 #[test]
612 fn debug() {
613 let t = Icmpv6Type::Unknown {
614 type_u8: 0,
615 code_u8: 1,
616 bytes5to8: [2, 3, 4, 5],
617 };
618 assert_eq!(
619 format!(
620 "{:?}",
621 Icmpv6Header {
622 icmp_type: t.clone(),
623 checksum: 7
624 }
625 ),
626 format!("Icmpv6Header {{ icmp_type: {:?}, checksum: {:?} }}", t, 7)
627 );
628 }
629
630 proptest! {
631 #[test]
632 fn clone_eq(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
633 let header = Icmpv6Header{ icmp_type, checksum };
634 assert_eq!(header, header.clone());
635 }
636 }
637}