s2n_quic_core/inet/ipv6.rs
1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::inet::{
5 ip, ipv4::IpV4Address, unspecified::Unspecified, ExplicitCongestionNotification, IpAddress,
6 SocketAddress, SocketAddressV4,
7};
8use core::fmt;
9use s2n_codec::zerocopy::U16;
10
11//= https://www.rfc-editor.org/rfc/rfc2373#section-2.0
12//# IPv6 addresses are 128-bit identifiers for interfaces and sets of interfaces.
13const IPV6_LEN: usize = 128 / 8;
14
15define_inet_type!(
16 pub struct IpV6Address {
17 octets: [u8; IPV6_LEN],
18 }
19);
20
21impl IpV6Address {
22 /// An unspecified IpV6Address
23 pub const UNSPECIFIED: Self = Self {
24 octets: [0; IPV6_LEN],
25 };
26
27 #[inline]
28 pub const fn segments(&self) -> [u16; 8] {
29 let octets = &self.octets;
30 [
31 u16::from_be_bytes([octets[0], octets[1]]),
32 u16::from_be_bytes([octets[2], octets[3]]),
33 u16::from_be_bytes([octets[4], octets[5]]),
34 u16::from_be_bytes([octets[6], octets[7]]),
35 u16::from_be_bytes([octets[8], octets[9]]),
36 u16::from_be_bytes([octets[10], octets[11]]),
37 u16::from_be_bytes([octets[12], octets[13]]),
38 u16::from_be_bytes([octets[14], octets[15]]),
39 ]
40 }
41
42 /// Converts the IP address into IPv4 if it is mapped, otherwise the address is unchanged
43 #[inline]
44 pub const fn unmap(self) -> IpAddress {
45 match self.segments() {
46 // special-case unspecified and loopback
47 [0, 0, 0, 0, 0, 0, 0, 0] => IpAddress::Ipv6(self),
48 [0, 0, 0, 0, 0, 0, 0, 1] => IpAddress::Ipv6(self),
49
50 //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.1
51 //# The format of the "IPv4-Compatible IPv6 address" is as
52 //# follows:
53 //#
54 //# | 80 bits | 16 | 32 bits |
55 //# +--------------------------------------+--------------------------+
56 //# |0000..............................0000|0000| IPv4 address |
57 //# +--------------------------------------+----+---------------------+
58
59 //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2
60 //# The format of the "IPv4-mapped IPv6
61 //# address" is as follows:
62 //#
63 //# | 80 bits | 16 | 32 bits |
64 //# +--------------------------------------+--------------------------+
65 //# |0000..............................0000|FFFF| IPv4 address |
66 //# +--------------------------------------+----+---------------------+
67
68 //= https://www.rfc-editor.org/rfc/rfc6052#section-2.1
69 //# This document reserves a "Well-Known Prefix" for use in an
70 //# algorithmic mapping. The value of this IPv6 prefix is:
71 //#
72 //# 64:ff9b::/96
73 [0, 0, 0, 0, 0, 0, ab, cd]
74 | [0, 0, 0, 0, 0, 0xffff, ab, cd]
75 | [0x64, 0xff9b, 0, 0, 0, 0, ab, cd] => {
76 let [a, b] = u16::to_be_bytes(ab);
77 let [c, d] = u16::to_be_bytes(cd);
78 IpAddress::Ipv4(IpV4Address {
79 octets: [a, b, c, d],
80 })
81 }
82 _ => IpAddress::Ipv6(self),
83 }
84 }
85
86 /// Returns the [`ip::UnicastScope`] for the given address
87 ///
88 /// See the [IANA Registry](https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml)
89 /// for more details.
90 ///
91 /// ```
92 /// use s2n_quic_core::inet::{IpV4Address, IpV6Address, ip::UnicastScope::*};
93 ///
94 /// assert_eq!(IpV6Address::from([0, 0, 0, 0, 0, 0, 0, 0]).unicast_scope(), None);
95 /// assert_eq!(IpV6Address::from([0, 0, 0, 0, 0, 0, 0, 1]).unicast_scope(), Some(Loopback));
96 /// assert_eq!(IpV6Address::from([0xff0e, 0, 0, 0, 0, 0, 0, 0]).unicast_scope(), None);
97 /// assert_eq!(IpV6Address::from([0xfe80, 0, 0, 0, 0, 0, 0, 0]).unicast_scope(), Some(LinkLocal));
98 /// assert_eq!(IpV6Address::from([0xfc02, 0, 0, 0, 0, 0, 0, 0]).unicast_scope(), Some(Private));
99 /// // documentation
100 /// assert_eq!(IpV6Address::from([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]).unicast_scope(), None);
101 /// // benchmarking
102 /// assert_eq!(IpV6Address::from([0x2001, 0x0200, 0, 0, 0, 0, 0, 0]).unicast_scope(), None);
103 /// // IPv4-mapped address
104 /// assert_eq!(IpV4Address::from([92, 88, 99, 123]).to_ipv6_mapped().unicast_scope(), Some(Global));
105 /// ```
106 #[inline]
107 pub const fn unicast_scope(self) -> Option<ip::UnicastScope> {
108 use ip::UnicastScope::*;
109
110 // If this is an IpV4 ip, delegate to that implementation
111 if let IpAddress::Ipv4(ip) = self.unmap() {
112 return ip.unicast_scope();
113 }
114
115 // https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
116 match self.segments() {
117 //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.2
118 //# The address 0:0:0:0:0:0:0:0 is called the unspecified address.
119 [0, 0, 0, 0, 0, 0, 0, 0] => None,
120
121 //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.3
122 //# The unicast address 0:0:0:0:0:0:0:1 is called the loopback address.
123 [0, 0, 0, 0, 0, 0, 0, 1] => Some(Loopback),
124
125 //= https://www.rfc-editor.org/rfc/rfc6666#section-4
126 //# Per this document, IANA has recorded the allocation of the IPv6
127 //# address prefix 0100::/64 as a Discard-Only Prefix in the "Internet
128 //# Protocol Version 6 Address Space" and added the prefix to the "IANA
129 //# IPv6 Special Purpose Address Registry" [IANA-IPV6REG].
130 [0x0100, 0, 0, 0, ..] => None,
131
132 //= https://www.rfc-editor.org/rfc/rfc7723#section-4.2
133 //# +----------------------+-------------------------------------------+
134 //# | Attribute | Value |
135 //# +----------------------+-------------------------------------------+
136 //# | Address Block | 2001:1::1/128 |
137 //# | Name | Port Control Protocol Anycast |
138 //# | RFC | RFC 7723 (this document) |
139 //# | Allocation Date | October 2015 |
140 //# | Termination Date | N/A |
141 //# | Source | True |
142 //# | Destination | True |
143 //# | Forwardable | True |
144 //# | Global | True |
145 //# | Reserved-by-Protocol | False |
146 //# +----------------------+-------------------------------------------+
147 [0x2001, 0x1, 0, 0, 0, 0, 0, 0x1] => Some(Global),
148
149 //= https://www.rfc-editor.org/rfc/rfc8155#section-8.2
150 //# +----------------------+-------------------------------------------+
151 //# | Attribute | Value |
152 //# +----------------------+-------------------------------------------+
153 //# | Address Block | 2001:1::2/128 |
154 //# | Name | Traversal Using Relays around NAT Anycast |
155 //# | RFC | RFC 8155 |
156 //# | Allocation Date | 2017-02 |
157 //# | Termination Date | N/A |
158 //# | Source | True |
159 //# | Destination | True |
160 //# | Forwardable | True |
161 //# | Global | True |
162 //# | Reserved-by-Protocol | False |
163 //# +----------------------+-------------------------------------------+
164 [0x2001, 0x1, 0, 0, 0, 0, 0, 0x2] => Some(Global),
165
166 //= https://www.rfc-editor.org/rfc/rfc6890#section-2.2.3
167 //# +----------------------+---------------------------+
168 //# | Attribute | Value |
169 //# +----------------------+---------------------------+
170 //# | Address Block | 2001::/23 |
171 //# | Name | IETF Protocol Assignments |
172 //# | RFC | [RFC2928] |
173 //# | Allocation Date | September 2000 |
174 //# | Termination Date | N/A |
175 //# | Source | False[1] |
176 //# | Destination | False[1] |
177 //# | Forwardable | False[1] |
178 //# | Global | False[1] |
179 //# | Reserved-by-Protocol | False |
180 //# +----------------------+---------------------------+
181 [0x2001, 0x0..=0x01ff, ..] => None,
182
183 //= https://www.rfc-editor.org/rfc/rfc5180#section-8
184 //# The IANA has allocated 2001:0200::/48 for IPv6 benchmarking, which is
185 //# a 48-bit prefix from the RFC 4773 pool.
186 [0x2001, 0x0200, 0, ..] => None,
187
188 //= https://www.rfc-editor.org/rfc/rfc3849#section-4
189 //# IANA is to record the allocation of the IPv6 global unicast address
190 //# prefix 2001:DB8::/32 as a documentation-only prefix in the IPv6
191 //# address registry.
192 [0x2001, 0xdb8, ..] => None,
193
194 //= https://www.rfc-editor.org/rfc/rfc4193#section-8
195 //# The IANA has assigned the FC00::/7 prefix to "Unique Local Unicast".
196 [0xfc00..=0xfdff, ..] => {
197 //= https://www.rfc-editor.org/rfc/rfc4193#section-1
198 //# They are not
199 //# expected to be routable on the global Internet. They are routable
200 //# inside of a more limited area such as a site. They may also be
201 //# routed between a limited set of sites.
202 Some(Private)
203 }
204
205 //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.6
206 //# Link-Local addresses have the following format:
207 //# | 10 |
208 //# | bits | 54 bits | 64 bits |
209 //# +----------+-------------------------+----------------------------+
210 //# |1111111010| 0 | interface ID |
211 //# +----------+-------------------------+----------------------------+
212 [0xfe80..=0xfebf, ..] => Some(LinkLocal),
213
214 //= https://www.rfc-editor.org/rfc/rfc4291#section-2.7
215 //# binary 11111111 at the start of the address identifies the address
216 //# as being a multicast address.
217 [0xff00..=0xffff, ..] => None,
218
219 // Everything else is considered globally-reachable
220 _ => Some(Global),
221 }
222 }
223
224 #[inline]
225 pub fn with_port(self, port: u16) -> SocketAddressV6 {
226 SocketAddressV6 {
227 ip: self,
228 port: port.into(),
229 }
230 }
231}
232
233impl fmt::Debug for IpV6Address {
234 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
235 write!(fmt, "IPv6Address({self})")
236 }
237}
238
239impl fmt::Display for IpV6Address {
240 #[allow(clippy::many_single_char_names)]
241 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
242 match self.segments() {
243 [0, 0, 0, 0, 0, 0, 0, 0] => write!(fmt, "::"),
244 [0, 0, 0, 0, 0, 0, 0, 1] => write!(fmt, "::1"),
245 // Ipv4 Compatible address
246 [0, 0, 0, 0, 0, 0, g, h] => write!(
247 fmt,
248 "::{}.{}.{}.{}",
249 (g >> 8) as u8,
250 g as u8,
251 (h >> 8) as u8,
252 h as u8
253 ),
254 // Ipv4-Mapped address
255 [0, 0, 0, 0, 0, 0xffff, g, h] => write!(
256 fmt,
257 "::ffff:{}.{}.{}.{}",
258 (g >> 8) as u8,
259 g as u8,
260 (h >> 8) as u8,
261 h as u8
262 ),
263 // TODO better formatting
264 [a, b, c, d, e, f, g, h] => {
265 write!(fmt, "{a:x}:{b:x}:{c:x}:{d:x}:{e:x}:{f:x}:{g:x}:{h:x}")
266 }
267 }
268 }
269}
270
271impl Unspecified for IpV6Address {
272 #[inline]
273 fn is_unspecified(&self) -> bool {
274 Self::UNSPECIFIED.eq(self)
275 }
276}
277
278test_inet_snapshot!(ipv6, ipv6_snapshot_test, IpV6Address);
279
280define_inet_type!(
281 pub struct SocketAddressV6 {
282 ip: IpV6Address,
283 port: U16,
284 }
285);
286
287impl SocketAddressV6 {
288 /// An unspecified SocketAddressV6
289 pub const UNSPECIFIED: Self = Self {
290 ip: IpV6Address::UNSPECIFIED,
291 port: U16::ZERO,
292 };
293
294 #[inline]
295 pub const fn ip(&self) -> &IpV6Address {
296 &self.ip
297 }
298
299 #[inline]
300 pub fn port(&self) -> u16 {
301 self.port.into()
302 }
303
304 #[inline]
305 pub fn set_port(&mut self, port: u16) {
306 self.port.set(port)
307 }
308
309 /// Converts the IP address into IPv4 if it is mapped, otherwise the address is unchanged
310 #[inline]
311 pub fn unmap(self) -> SocketAddress {
312 match self.ip.unmap() {
313 IpAddress::Ipv4(addr) => SocketAddressV4::new(addr, self.port).into(),
314 IpAddress::Ipv6(_) => self.into(),
315 }
316 }
317
318 #[inline]
319 pub const fn unicast_scope(&self) -> Option<ip::UnicastScope> {
320 self.ip.unicast_scope()
321 }
322}
323
324impl fmt::Debug for SocketAddressV6 {
325 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
326 write!(fmt, "SocketAddressV6({self})")
327 }
328}
329
330impl fmt::Display for SocketAddressV6 {
331 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
332 write!(fmt, "[{}]:{:?}", self.ip, self.port)
333 }
334}
335
336impl Unspecified for SocketAddressV6 {
337 #[inline]
338 fn is_unspecified(&self) -> bool {
339 Self::UNSPECIFIED.eq(self)
340 }
341}
342
343test_inet_snapshot!(socket_v6, socket_v6_snapshot_test, SocketAddressV6);
344
345impl From<[u8; IPV6_LEN]> for IpV6Address {
346 #[inline]
347 fn from(octets: [u8; IPV6_LEN]) -> Self {
348 Self { octets }
349 }
350}
351
352impl From<[u16; IPV6_LEN / 2]> for IpV6Address {
353 #[inline]
354 fn from(octets: [u16; IPV6_LEN / 2]) -> Self {
355 macro_rules! convert {
356 ($($segment:ident),*) => {{
357 let [$($segment),*] = octets;
358 $(
359 let $segment = u16::to_be_bytes($segment);
360 )*
361 Self {
362 octets: [
363 $(
364 $segment[0],
365 $segment[1],
366 )*
367 ]
368 }
369 }}
370 }
371 convert!(a, b, c, d, e, f, g, h)
372 }
373}
374
375impl From<IpV6Address> for [u8; IPV6_LEN] {
376 #[inline]
377 fn from(v: IpV6Address) -> Self {
378 v.octets
379 }
380}
381
382impl From<IpV6Address> for [u16; IPV6_LEN / 2] {
383 #[inline]
384 fn from(v: IpV6Address) -> Self {
385 v.segments()
386 }
387}
388
389//= https://www.rfc-editor.org/rfc/rfc8200#section-3
390//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
391//# |Version| Traffic Class | Flow Label |
392//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
393//# | Payload Length | Next Header | Hop Limit |
394//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
395//# | |
396//# + +
397//# | |
398//# + Source Address +
399//# | |
400//# + +
401//# | |
402//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
403//# | |
404//# + +
405//# | |
406//# + Destination Address +
407//# | |
408//# + +
409//# | |
410//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
411
412define_inet_type!(
413 pub struct Header {
414 vtcfl: Vtcfl,
415 payload_len: U16,
416 next_header: ip::Protocol,
417 hop_limit: u8,
418 source: IpV6Address,
419 destination: IpV6Address,
420 }
421);
422
423impl fmt::Debug for Header {
424 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
425 f.debug_struct("ipv6::Header")
426 .field("version", &self.vtcfl.version())
427 .field("dscp", &self.vtcfl.dscp())
428 .field("ecn", &self.vtcfl.ecn())
429 .field(
430 "flow_label",
431 &format_args!("0x{:05x}", self.vtcfl.flow_label()),
432 )
433 .field("payload_len", &self.payload_len)
434 .field("next_header", &self.next_header)
435 .field("hop_limit", &self.hop_limit)
436 .field("source", &self.source)
437 .field("destination", &self.destination)
438 .finish()
439 }
440}
441
442impl Header {
443 /// Swaps the direction of the header
444 #[inline]
445 pub fn swap(&mut self) {
446 core::mem::swap(&mut self.source, &mut self.destination)
447 }
448
449 #[inline]
450 pub const fn vtcfl(&self) -> &Vtcfl {
451 &self.vtcfl
452 }
453
454 #[inline]
455 pub fn vtcfl_mut(&mut self) -> &mut Vtcfl {
456 &mut self.vtcfl
457 }
458
459 #[inline]
460 pub const fn payload_len(&self) -> &U16 {
461 &self.payload_len
462 }
463
464 #[inline]
465 pub fn payload_len_mut(&mut self) -> &mut U16 {
466 &mut self.payload_len
467 }
468
469 #[inline]
470 pub const fn next_header(&self) -> &ip::Protocol {
471 &self.next_header
472 }
473
474 #[inline]
475 pub fn next_header_mut(&mut self) -> &mut ip::Protocol {
476 &mut self.next_header
477 }
478
479 #[inline]
480 pub const fn hop_limit(&self) -> &u8 {
481 &self.hop_limit
482 }
483
484 #[inline]
485 pub fn hop_limit_mut(&mut self) -> &mut u8 {
486 &mut self.hop_limit
487 }
488
489 #[inline]
490 pub const fn source(&self) -> &IpV6Address {
491 &self.source
492 }
493
494 #[inline]
495 pub fn source_mut(&mut self) -> &mut IpV6Address {
496 &mut self.source
497 }
498
499 #[inline]
500 pub const fn destination(&self) -> &IpV6Address {
501 &self.destination
502 }
503
504 #[inline]
505 pub fn destination_mut(&mut self) -> &mut IpV6Address {
506 &mut self.destination
507 }
508}
509
510// This struct covers the bits for Version, Traffic Class, and Flow Label.
511//
512// Rust doesn't have the ability to do arbitrary bit sized values so we have to round up to the
513// nearest byte.
514define_inet_type!(
515 pub struct Vtcfl {
516 octets: [u8; 4],
517 }
518);
519
520impl fmt::Debug for Vtcfl {
521 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
522 f.debug_struct("ipv6::Vtf")
523 .field("version", &self.version())
524 .field("dscp", &self.dscp())
525 .field("ecn", &self.ecn())
526 .field("flow_label", &format_args!("0x{:05x}", self.flow_label()))
527 .finish()
528 }
529}
530
531impl Vtcfl {
532 #[inline]
533 pub const fn version(&self) -> u8 {
534 self.octets[0] >> 4
535 }
536
537 #[inline]
538 pub fn set_version(&mut self, version: u8) -> &mut Self {
539 self.octets[0] = (version << 4) | self.octets[0] & 0x0F;
540 self
541 }
542
543 #[inline]
544 pub fn dscp(&self) -> u8 {
545 let value = (self.octets[0] << 4) | (self.octets[1] >> 4);
546 value >> 2
547 }
548
549 #[inline]
550 pub fn set_dscp(&mut self, value: u8) -> &mut Self {
551 let value = value << 2;
552 self.octets[0] = self.octets[0] & 0xF0 | (value >> 4);
553 self.octets[1] = (value << 4) | self.octets[1] & 0b11_1111;
554 self
555 }
556
557 #[inline]
558 pub fn ecn(&self) -> ExplicitCongestionNotification {
559 ExplicitCongestionNotification::new((self.octets[1] >> 4) & 0b11)
560 }
561
562 #[inline]
563 pub fn set_ecn(&mut self, ecn: ExplicitCongestionNotification) -> &mut Self {
564 self.octets[1] = (self.octets[1] & !(0b11 << 4)) | ((ecn as u8) << 4);
565 self
566 }
567
568 #[inline]
569 pub const fn flow_label(&self) -> u32 {
570 u32::from_be_bytes([0, self.octets[1] & 0x0F, self.octets[2], self.octets[3]])
571 }
572
573 #[inline]
574 pub fn set_flow_label(&mut self, flow_label: u32) -> &mut Self {
575 let bytes = flow_label.to_be_bytes();
576 self.octets[1] = self.octets[1] & 0xF0 | bytes[1] & 0x0F;
577 self.octets[2] = bytes[2];
578 self.octets[3] = bytes[3];
579 self
580 }
581}
582
583#[cfg(any(test, feature = "std"))]
584mod std_conversion {
585 use super::*;
586 use std::net;
587
588 impl From<net::Ipv6Addr> for IpV6Address {
589 fn from(address: net::Ipv6Addr) -> Self {
590 (&address).into()
591 }
592 }
593
594 impl From<&net::Ipv6Addr> for IpV6Address {
595 fn from(address: &net::Ipv6Addr) -> Self {
596 address.octets().into()
597 }
598 }
599
600 impl From<IpV6Address> for net::Ipv6Addr {
601 fn from(address: IpV6Address) -> Self {
602 address.octets.into()
603 }
604 }
605
606 impl From<net::SocketAddrV6> for SocketAddressV6 {
607 fn from(address: net::SocketAddrV6) -> Self {
608 let ip = address.ip().into();
609 let port = address.port().into();
610 Self { ip, port }
611 }
612 }
613
614 impl From<(net::Ipv6Addr, u16)> for SocketAddressV6 {
615 fn from((ip, port): (net::Ipv6Addr, u16)) -> Self {
616 Self::new(ip, port)
617 }
618 }
619
620 impl From<SocketAddressV6> for net::SocketAddrV6 {
621 fn from(address: SocketAddressV6) -> Self {
622 let ip = address.ip.into();
623 let port = address.port.into();
624 Self::new(ip, port, 0, 0)
625 }
626 }
627
628 impl From<&SocketAddressV6> for net::SocketAddrV6 {
629 fn from(address: &SocketAddressV6) -> Self {
630 let ip = address.ip.into();
631 let port = address.port.into();
632 Self::new(ip, port, 0, 0)
633 }
634 }
635
636 impl From<SocketAddressV6> for net::SocketAddr {
637 fn from(address: SocketAddressV6) -> Self {
638 let addr: net::SocketAddrV6 = address.into();
639 addr.into()
640 }
641 }
642
643 impl From<&SocketAddressV6> for net::SocketAddr {
644 fn from(address: &SocketAddressV6) -> Self {
645 let addr: net::SocketAddrV6 = address.into();
646 addr.into()
647 }
648 }
649
650 impl net::ToSocketAddrs for SocketAddressV6 {
651 type Iter = std::iter::Once<net::SocketAddr>;
652
653 fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
654 let ip = self.ip.into();
655 let port = self.port.into();
656 let addr = net::SocketAddrV6::new(ip, port, 0, 0);
657 Ok(std::iter::once(addr.into()))
658 }
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665 use bolero::{check, generator::*};
666 use s2n_codec::{DecoderBuffer, DecoderBufferMut};
667
668 /// Asserts the UnicastScope returned matches a known implementation
669 #[test]
670 #[cfg_attr(kani, kani::proof, kani::unwind(17), kani::solver(kissat))]
671 fn scope_test() {
672 let g = produce::<[u8; 16]>().map_gen(IpV6Address::from);
673 check!().with_generator(g).cloned().for_each(|subject| {
674 use ip::UnicastScope::*;
675
676 // the ipv4 scopes are tested elsewhere there so we just make sure the scopes match
677 if let IpAddress::Ipv4(ipv4) = subject.unmap() {
678 assert_eq!(ipv4.unicast_scope(), subject.unicast_scope());
679 return;
680 }
681
682 let expected = std::net::Ipv6Addr::from(subject);
683 let network = ip_network::Ipv6Network::from(expected);
684
685 match subject.unicast_scope() {
686 Some(Global) => {
687 // Site-local addresses are deprecated but the `ip_network` still partitions
688 // them out
689 // See: https://datatracker.ietf.org/doc/html/rfc3879
690
691 assert!(network.is_global() || network.is_unicast_site_local());
692 }
693 Some(Private) => {
694 assert!(network.is_unique_local());
695 }
696 Some(Loopback) => {
697 assert!(expected.is_loopback());
698 }
699 Some(LinkLocal) => {
700 assert!(network.is_unicast_link_local());
701 }
702 None => {
703 assert!(
704 expected.is_multicast()
705 || expected.is_unspecified()
706 // Discard space
707 || subject.segments()[0] == 0x0100
708 // IETF Reserved
709 || subject.segments()[0] == 0x2001
710 );
711 }
712 }
713 })
714 }
715
716 #[test]
717 #[cfg_attr(miri, ignore)]
718 fn snapshot_test() {
719 let mut buffer = vec![0u8; core::mem::size_of::<Header>()];
720 for (idx, byte) in buffer.iter_mut().enumerate() {
721 *byte = idx as u8;
722 }
723 let decoder = DecoderBuffer::new(&buffer);
724 let (header, _) = decoder.decode::<&Header>().unwrap();
725 insta::assert_debug_snapshot!("snapshot_test", header);
726
727 buffer.fill(255);
728 let decoder = DecoderBuffer::new(&buffer);
729 let (header, _) = decoder.decode::<&Header>().unwrap();
730 insta::assert_debug_snapshot!("snapshot_filled_test", header);
731 }
732
733 #[test]
734 #[cfg_attr(kani, kani::proof, kani::unwind(17), kani::solver(kissat))]
735 fn header_getter_setter_test() {
736 check!().with_type::<Header>().for_each(|expected| {
737 let mut buffer = [255u8; core::mem::size_of::<Header>()];
738 let decoder = DecoderBufferMut::new(&mut buffer);
739 let (header, _) = decoder.decode::<&mut Header>().unwrap();
740 {
741 // use all of the getters and setters to copy over each field
742 header
743 .vtcfl_mut()
744 .set_version(expected.vtcfl().version())
745 .set_dscp(expected.vtcfl().dscp())
746 .set_ecn(expected.vtcfl().ecn())
747 .set_flow_label(expected.vtcfl().flow_label());
748 *header.hop_limit_mut() = *expected.hop_limit();
749 *header.next_header_mut() = *expected.next_header();
750 header.payload_len_mut().set(expected.payload_len().get());
751 *header.source_mut() = *expected.source();
752 *header.destination_mut() = *expected.destination();
753 }
754
755 let decoder = DecoderBuffer::new(&buffer);
756 let (actual, _) = decoder.decode::<&Header>().unwrap();
757 {
758 // make sure all of the values match
759 assert_eq!(expected.vtcfl().version(), expected.vtcfl().version());
760 assert_eq!(expected.vtcfl().dscp(), expected.vtcfl().dscp());
761 assert_eq!(expected.vtcfl().ecn(), expected.vtcfl().ecn());
762 assert_eq!(expected.vtcfl().flow_label(), expected.vtcfl().flow_label());
763 assert_eq!(
764 expected.vtcfl(),
765 actual.vtcfl(),
766 "\nexpected: {:?}\n actual: {:?}",
767 expected.as_bytes(),
768 actual.as_bytes()
769 );
770 assert_eq!(expected.hop_limit(), actual.hop_limit());
771 assert_eq!(expected.next_header(), actual.next_header());
772 assert_eq!(expected.payload_len(), actual.payload_len());
773 assert_eq!(expected.source(), actual.source());
774 assert_eq!(expected.destination(), actual.destination());
775 assert_eq!(
776 expected,
777 actual,
778 "\nexpected: {:?}\n actual: {:?}",
779 expected.as_bytes(),
780 actual.as_bytes()
781 );
782 }
783 })
784 }
785
786 #[test]
787 fn header_round_trip_test() {
788 check!().for_each(|buffer| {
789 s2n_codec::assert_codec_round_trip_bytes!(Header, buffer);
790 });
791 }
792}