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, net};
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
343impl From<net::Ipv6Addr> for IpV6Address {
344 fn from(address: net::Ipv6Addr) -> Self {
345 (&address).into()
346 }
347}
348
349impl From<&net::Ipv6Addr> for IpV6Address {
350 fn from(address: &net::Ipv6Addr) -> Self {
351 address.octets().into()
352 }
353}
354
355impl From<IpV6Address> for net::Ipv6Addr {
356 fn from(address: IpV6Address) -> Self {
357 address.octets.into()
358 }
359}
360
361impl From<net::SocketAddrV6> for SocketAddressV6 {
362 fn from(address: net::SocketAddrV6) -> Self {
363 let ip = address.ip().into();
364 let port = address.port().into();
365 Self { ip, port }
366 }
367}
368
369impl From<(net::Ipv6Addr, u16)> for SocketAddressV6 {
370 fn from((ip, port): (net::Ipv6Addr, u16)) -> Self {
371 Self::new(ip, port)
372 }
373}
374
375impl From<SocketAddressV6> for net::SocketAddrV6 {
376 fn from(address: SocketAddressV6) -> Self {
377 let ip = address.ip.into();
378 let port = address.port.into();
379 Self::new(ip, port, 0, 0)
380 }
381}
382
383impl From<&SocketAddressV6> for net::SocketAddrV6 {
384 fn from(address: &SocketAddressV6) -> Self {
385 let ip = address.ip.into();
386 let port = address.port.into();
387 Self::new(ip, port, 0, 0)
388 }
389}
390
391impl From<SocketAddressV6> for net::SocketAddr {
392 fn from(address: SocketAddressV6) -> Self {
393 let addr: net::SocketAddrV6 = address.into();
394 addr.into()
395 }
396}
397
398impl From<&SocketAddressV6> for net::SocketAddr {
399 fn from(address: &SocketAddressV6) -> Self {
400 let addr: net::SocketAddrV6 = address.into();
401 addr.into()
402 }
403}
404
405test_inet_snapshot!(socket_v6, socket_v6_snapshot_test, SocketAddressV6);
406
407impl From<[u8; IPV6_LEN]> for IpV6Address {
408 #[inline]
409 fn from(octets: [u8; IPV6_LEN]) -> Self {
410 Self { octets }
411 }
412}
413
414impl From<[u16; IPV6_LEN / 2]> for IpV6Address {
415 #[inline]
416 fn from(octets: [u16; IPV6_LEN / 2]) -> Self {
417 macro_rules! convert {
418 ($($segment:ident),*) => {{
419 let [$($segment),*] = octets;
420 $(
421 let $segment = u16::to_be_bytes($segment);
422 )*
423 Self {
424 octets: [
425 $(
426 $segment[0],
427 $segment[1],
428 )*
429 ]
430 }
431 }}
432 }
433 convert!(a, b, c, d, e, f, g, h)
434 }
435}
436
437impl From<IpV6Address> for [u8; IPV6_LEN] {
438 #[inline]
439 fn from(v: IpV6Address) -> Self {
440 v.octets
441 }
442}
443
444impl From<IpV6Address> for [u16; IPV6_LEN / 2] {
445 #[inline]
446 fn from(v: IpV6Address) -> Self {
447 v.segments()
448 }
449}
450
451//= https://www.rfc-editor.org/rfc/rfc8200#section-3
452//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
453//# |Version| Traffic Class | Flow Label |
454//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
455//# | Payload Length | Next Header | Hop Limit |
456//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
457//# | |
458//# + +
459//# | |
460//# + Source Address +
461//# | |
462//# + +
463//# | |
464//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
465//# | |
466//# + +
467//# | |
468//# + Destination Address +
469//# | |
470//# + +
471//# | |
472//# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
473
474define_inet_type!(
475 pub struct Header {
476 vtcfl: Vtcfl,
477 payload_len: U16,
478 next_header: ip::Protocol,
479 hop_limit: u8,
480 source: IpV6Address,
481 destination: IpV6Address,
482 }
483);
484
485impl fmt::Debug for Header {
486 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
487 f.debug_struct("ipv6::Header")
488 .field("version", &self.vtcfl.version())
489 .field("dscp", &self.vtcfl.dscp())
490 .field("ecn", &self.vtcfl.ecn())
491 .field(
492 "flow_label",
493 &format_args!("0x{:05x}", self.vtcfl.flow_label()),
494 )
495 .field("payload_len", &self.payload_len)
496 .field("next_header", &self.next_header)
497 .field("hop_limit", &self.hop_limit)
498 .field("source", &self.source)
499 .field("destination", &self.destination)
500 .finish()
501 }
502}
503
504impl Header {
505 /// Swaps the direction of the header
506 #[inline]
507 pub fn swap(&mut self) {
508 core::mem::swap(&mut self.source, &mut self.destination)
509 }
510
511 #[inline]
512 pub const fn vtcfl(&self) -> &Vtcfl {
513 &self.vtcfl
514 }
515
516 #[inline]
517 pub fn vtcfl_mut(&mut self) -> &mut Vtcfl {
518 &mut self.vtcfl
519 }
520
521 #[inline]
522 pub const fn payload_len(&self) -> &U16 {
523 &self.payload_len
524 }
525
526 #[inline]
527 pub fn payload_len_mut(&mut self) -> &mut U16 {
528 &mut self.payload_len
529 }
530
531 #[inline]
532 pub const fn next_header(&self) -> &ip::Protocol {
533 &self.next_header
534 }
535
536 #[inline]
537 pub fn next_header_mut(&mut self) -> &mut ip::Protocol {
538 &mut self.next_header
539 }
540
541 #[inline]
542 pub const fn hop_limit(&self) -> &u8 {
543 &self.hop_limit
544 }
545
546 #[inline]
547 pub fn hop_limit_mut(&mut self) -> &mut u8 {
548 &mut self.hop_limit
549 }
550
551 #[inline]
552 pub const fn source(&self) -> &IpV6Address {
553 &self.source
554 }
555
556 #[inline]
557 pub fn source_mut(&mut self) -> &mut IpV6Address {
558 &mut self.source
559 }
560
561 #[inline]
562 pub const fn destination(&self) -> &IpV6Address {
563 &self.destination
564 }
565
566 #[inline]
567 pub fn destination_mut(&mut self) -> &mut IpV6Address {
568 &mut self.destination
569 }
570}
571
572// This struct covers the bits for Version, Traffic Class, and Flow Label.
573//
574// Rust doesn't have the ability to do arbitrary bit sized values so we have to round up to the
575// nearest byte.
576define_inet_type!(
577 pub struct Vtcfl {
578 octets: [u8; 4],
579 }
580);
581
582impl fmt::Debug for Vtcfl {
583 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
584 f.debug_struct("ipv6::Vtf")
585 .field("version", &self.version())
586 .field("dscp", &self.dscp())
587 .field("ecn", &self.ecn())
588 .field("flow_label", &format_args!("0x{:05x}", self.flow_label()))
589 .finish()
590 }
591}
592
593impl Vtcfl {
594 #[inline]
595 pub const fn version(&self) -> u8 {
596 self.octets[0] >> 4
597 }
598
599 #[inline]
600 pub fn set_version(&mut self, version: u8) -> &mut Self {
601 self.octets[0] = (version << 4) | self.octets[0] & 0x0F;
602 self
603 }
604
605 #[inline]
606 pub fn dscp(&self) -> u8 {
607 let value = (self.octets[0] << 4) | (self.octets[1] >> 4);
608 value >> 2
609 }
610
611 #[inline]
612 pub fn set_dscp(&mut self, value: u8) -> &mut Self {
613 let value = value << 2;
614 self.octets[0] = self.octets[0] & 0xF0 | (value >> 4);
615 self.octets[1] = (value << 4) | self.octets[1] & 0b11_1111;
616 self
617 }
618
619 #[inline]
620 pub fn ecn(&self) -> ExplicitCongestionNotification {
621 ExplicitCongestionNotification::new((self.octets[1] >> 4) & 0b11)
622 }
623
624 #[inline]
625 pub fn set_ecn(&mut self, ecn: ExplicitCongestionNotification) -> &mut Self {
626 self.octets[1] = (self.octets[1] & !(0b11 << 4)) | ((ecn as u8) << 4);
627 self
628 }
629
630 #[inline]
631 pub const fn flow_label(&self) -> u32 {
632 u32::from_be_bytes([0, self.octets[1] & 0x0F, self.octets[2], self.octets[3]])
633 }
634
635 #[inline]
636 pub fn set_flow_label(&mut self, flow_label: u32) -> &mut Self {
637 let bytes = flow_label.to_be_bytes();
638 self.octets[1] = self.octets[1] & 0xF0 | bytes[1] & 0x0F;
639 self.octets[2] = bytes[2];
640 self.octets[3] = bytes[3];
641 self
642 }
643}
644
645#[cfg(any(test, feature = "std"))]
646mod std_conversion {
647 use super::*;
648 use std::net;
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}