Skip to main content

netstack3_base/
packet.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Contexts for packet parsing and serialization in netstack3.
6
7use crate::inspect::{Inspectable, Inspector};
8use bitflags::bitflags;
9use core::num::NonZeroU16;
10use net_types::ip::IpInvariant;
11use packet::{
12    DynamicPartialSerializer, DynamicSerializer, PacketBuilder, PacketConstraints,
13    PartialSerializer, SerializationContext, Serializer,
14};
15use packet_formats::TransportChecksumAction;
16use packet_formats::ethernet::{EthernetEnvelope, EthernetSerializationContext};
17use packet_formats::icmp::{IcmpEnvelope, IcmpSerializationContext};
18use packet_formats::ip::{IpEnvelope, IpExt, IpSerializationContext};
19use packet_formats::tcp::{TcpEnvelope, TcpParseContext, TcpSerializationContext};
20use packet_formats::udp::{UdpEnvelope, UdpParseContext, UdpSerializationContext};
21use static_assertions::const_assert;
22
23/// The specific packet `Serializer` type used within netstack3.
24pub trait NetworkSerializer: Serializer<NetworkSerializationContext> {}
25impl<S: Serializer<NetworkSerializationContext>> NetworkSerializer for S {}
26
27/// The specific packet `PartialSerializer` type used within netstack3.
28pub trait NetworkPartialSerializer: PartialSerializer<NetworkSerializationContext> {}
29impl<S: PartialSerializer<NetworkSerializationContext>> NetworkPartialSerializer for S {}
30
31/// The specific dynamic packet `Serializer` type used within netstack3.
32pub trait DynamicNetworkSerializer: DynamicSerializer<NetworkSerializationContext> {}
33impl<S: DynamicSerializer<NetworkSerializationContext>> DynamicNetworkSerializer for S {}
34
35/// The specific dynamic packet `PartialSerializer` type used within netstack3.
36pub trait DynamicNetworkPartialSerializer:
37    DynamicPartialSerializer<NetworkSerializationContext>
38{
39}
40impl<S: DynamicPartialSerializer<NetworkSerializationContext>> DynamicNetworkPartialSerializer
41    for S
42{
43}
44
45/// Networking protocols that support checksum offloading.
46#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
47pub enum OffloadableProtocol {
48    /// No protocol.
49    #[default]
50    None,
51    /// Protocol does not support checksum offloading.
52    NotOffloadable,
53    /// Transmission Control Protocol.
54    Tcp,
55    /// User Datagram Protocol.
56    Udp,
57    /// Internet Protocol version 4.
58    Ipv4,
59    /// Internet Protocol version 6.
60    Ipv6,
61    /// Ethernet Frame.
62    Ethernet,
63}
64
65bitflags! {
66    /// Bitmask for networking protocols that support checksum offloading.
67    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
68    pub struct OffloadableProtocols: u8 {
69        /// Not an offloadable protocol.
70        const NOT_OFFLOADABLE = 1 << 0;
71        /// Transmission Control Protocol.
72        const TCP = 1 << 1;
73        /// User Datagram Protocol.
74        const UDP = 1 << 2;
75        /// Internet Protocol version 4.
76        const IPV4 = 1 << 3;
77        /// Internet Protocol version 6.
78        const IPV6 = 1 << 4;
79        /// Ethernet Frame.
80        const ETHERNET = 1 << 5;
81    }
82}
83
84impl From<OffloadableProtocol> for OffloadableProtocols {
85    fn from(p: OffloadableProtocol) -> Self {
86        match p {
87            OffloadableProtocol::None => Self::empty(),
88            OffloadableProtocol::NotOffloadable => Self::NOT_OFFLOADABLE,
89            OffloadableProtocol::Tcp => Self::TCP,
90            OffloadableProtocol::Udp => Self::UDP,
91            OffloadableProtocol::Ipv4 => Self::IPV4,
92            OffloadableProtocol::Ipv6 => Self::IPV6,
93            OffloadableProtocol::Ethernet => Self::ETHERNET,
94        }
95    }
96}
97
98bitflags! {
99    /// Indicates that a device supports protocol-specific checksum offloading.
100    #[derive(Clone, Debug, Default, Eq, PartialEq)]
101    pub struct ProtocolSpecificOffloadSpec: u8 {
102        /// Ethernet frame with IPv4 header (without options) and UDP payload.
103        const ETH_IPV4_UDP = 1 << 0;
104        /// Ethernet frame with IPv4 header (without options) and TCP payload.
105        const ETH_IPV4_TCP = 1 << 1;
106        /// Ethernet frame with IPv6 header (without extension headers) and UDP
107        /// payload.
108        const ETH_IPV6_UDP = 1 << 2;
109        /// Ethernet frame with IPv6 header (without extension headers) and TCP
110        /// payload.
111        const ETH_IPV6_TCP = 1 << 3;
112    }
113}
114
115impl ProtocolSpecificOffloadSpec {
116    /// Creates a `ProtocolSpecificOffloadSpec` for devices that support
117    /// protocol-specific checksum offloading for UDP and TCP packets over IPv4.
118    ///
119    /// This spec does not match when the IPv4 header contains options.
120    pub fn tcp_or_udp_over_ipv4() -> Self {
121        Self::ETH_IPV4_UDP | Self::ETH_IPV4_TCP
122    }
123
124    /// Creates a `ProtocolSpecificOffloadSpec` for devices that support
125    /// protocol-specific checksum offloading for UDP and TCP packets over IPv6.
126    ///
127    /// This spec does not match when IPv6 contains extension headers.
128    pub fn tcp_or_udp_over_ipv6() -> Self {
129        Self::ETH_IPV6_UDP | Self::ETH_IPV6_TCP
130    }
131
132    /// Creates a `ProtocolSpecificOffloadSpec` that matches if either this spec
133    /// or the other spec matches.
134    fn or(self, other: Self) -> Self {
135        self | other
136    }
137
138    /// Returns true if the `current` protocols match this spec.
139    fn matches(&self, current: OffloadableProtocols) -> bool {
140        use OffloadableProtocols as P;
141        match current {
142            f if f == (P::ETHERNET | P::IPV4 | P::UDP) => self.contains(Self::ETH_IPV4_UDP),
143            f if f == (P::ETHERNET | P::IPV4 | P::TCP) => self.contains(Self::ETH_IPV4_TCP),
144            f if f == (P::ETHERNET | P::IPV6 | P::UDP) => self.contains(Self::ETH_IPV6_UDP),
145            f if f == (P::ETHERNET | P::IPV6 | P::TCP) => self.contains(Self::ETH_IPV6_TCP),
146            _ => false,
147        }
148    }
149}
150
151/// Indicates that a device supports generic checksum offloading.
152#[derive(Clone, Debug, Default, Eq, PartialEq)]
153struct GenericOffloadSpec;
154
155/// Describes the checksum offloading capabilities available during serialization.
156#[derive(Clone, Debug, Default, Eq, PartialEq)]
157pub struct ChecksumOffloadSpec {
158    /// If `Some`, the device supports protocol-specific checksum offloading.
159    protocol_specific: Option<ProtocolSpecificOffloadSpec>,
160    /// If `Some`, the device supports generic checksum offloading.
161    generic: Option<GenericOffloadSpec>,
162}
163
164impl ChecksumOffloadSpec {
165    /// Creates a `ChecksumOffloadSpec` for a device that does not support any
166    /// checksum offloading.
167    pub fn none() -> Self {
168        Self { protocol_specific: None, generic: None }
169    }
170
171    /// Creates a `ChecksumOffloadSpec` for a device that supports protocol-specific
172    /// checksum offloading for the given protocols.
173    pub fn protocol_specific(spec: ProtocolSpecificOffloadSpec) -> Self {
174        Self { protocol_specific: Some(spec), generic: None }
175    }
176
177    /// Creates a `ChecksumOffloadSpec` for a device that supports generic
178    /// checksum offloading.
179    pub fn generic() -> Self {
180        Self { protocol_specific: None, generic: Some(GenericOffloadSpec::default()) }
181    }
182
183    /// Creates a `ChecksumOffloadSpec` that matches any of the given specs.
184    pub fn any<I: IntoIterator<Item = Self>>(specs: I) -> Self {
185        specs.into_iter().fold(Self::none(), |acc, spec| Self {
186            protocol_specific: match (acc.protocol_specific, spec.protocol_specific) {
187                (Some(acc), Some(spec)) => Some(acc.or(spec)),
188                (Some(acc), None) => Some(acc),
189                (None, Some(spec)) => Some(spec),
190                (None, None) => None,
191            },
192            generic: acc.generic.or(spec.generic),
193        })
194    }
195}
196
197impl Inspectable for ChecksumOffloadSpec {
198    fn record<I: Inspector>(&self, inspector: &mut I) {
199        inspector.record_bool("TxChecksumOffloadGeneric", self.generic.is_some());
200        if let Some(protocol_specific) = &self.protocol_specific {
201            inspector.record_child("TxChecksumOffloadProtocolSpecific", |inspector| {
202                inspector.record_bool(
203                    "EthIpv4Udp",
204                    protocol_specific.contains(ProtocolSpecificOffloadSpec::ETH_IPV4_UDP),
205                );
206                inspector.record_bool(
207                    "EthIpv4Tcp",
208                    protocol_specific.contains(ProtocolSpecificOffloadSpec::ETH_IPV4_TCP),
209                );
210                inspector.record_bool(
211                    "EthIpv6Udp",
212                    protocol_specific.contains(ProtocolSpecificOffloadSpec::ETH_IPV6_UDP),
213                );
214                inspector.record_bool(
215                    "EthIpv6Tcp",
216                    protocol_specific.contains(ProtocolSpecificOffloadSpec::ETH_IPV6_TCP),
217                );
218            });
219        }
220    }
221}
222
223#[derive(Clone, Debug, Eq, PartialEq)]
224struct ProtocolStackInfo {
225    // The offset in bytes from the start of the buffer to the start of the
226    // current packet's header, if it fits in a u16.
227    header_offset: Option<u16>,
228    // The current protocol stack.
229    protocols: OffloadableProtocols,
230}
231
232impl Default for ProtocolStackInfo {
233    fn default() -> Self {
234        Self { header_offset: Some(0), protocols: OffloadableProtocols::empty() }
235    }
236}
237
238#[derive(Clone, Debug, Default, Eq, PartialEq)]
239struct ChecksumOffloadState {
240    spec: ChecksumOffloadSpec,
241    stack_info: ProtocolStackInfo,
242}
243
244impl ChecksumOffloadState {
245    fn new(spec: ChecksumOffloadSpec) -> Self {
246        Self { spec, stack_info: Default::default() }
247    }
248
249    /// Updates the checksum offload state for the given protocol and the
250    /// constraints of the encapsulating context.
251    ///
252    /// Returns a value that can be passed to `restore` to restore the previous
253    /// state.
254    fn update(
255        &mut self,
256        protocol: OffloadableProtocol,
257        constraints: &PacketConstraints,
258    ) -> ProtocolStackInfo {
259        let previous = self.stack_info.clone();
260
261        let ProtocolStackInfo { header_offset, protocols } = &mut self.stack_info;
262
263        // A `header_offset` value of `None` is sticky: it will be `None` for
264        // all subsequent inner protocols and will remain so until an earlier
265        // state is `restore`d.
266        *header_offset = header_offset.and_then(|_| constraints.header_len().try_into().ok());
267
268        if protocol == OffloadableProtocol::None {
269            return previous;
270        }
271        let protocol = protocol.into();
272        if protocols.contains(protocol) {
273            // If we encounter a duplicate protocol in the stack, then we flag
274            // that protocol-specific offloading is not available from that
275            // point on. Like a `header_offset` of `None`, this value will be
276            // retained until an earlier state is `restore`d.
277            protocols.insert(OffloadableProtocol::NotOffloadable.into());
278        } else {
279            protocols.insert(protocol);
280        }
281
282        previous
283    }
284
285    /// Restores the previous checksum offload state.
286    fn restore(&mut self, previous: ProtocolStackInfo) {
287        self.stack_info = previous;
288    }
289
290    fn try_offload(&self, csum_offset: u16) -> Option<ChecksumOffloadResult> {
291        let ProtocolStackInfo { header_offset, protocols } = self.stack_info;
292
293        // We prefer generic offloading over protocol-specific offloading where
294        // both are available to be consistent with Linux, which has been trying
295        // to move toward generic offloading.
296        if let Some(start) = self.spec.generic.as_ref().and(header_offset) {
297            Some(ChecksumOffloadResult::Generic(PartialChecksum { start, offset: csum_offset }))
298        } else if self
299            .spec
300            .protocol_specific
301            .as_ref()
302            .map(|spec| spec.matches(protocols))
303            .unwrap_or(false)
304        {
305            Some(ChecksumOffloadResult::ProtocolSpecific(protocols))
306        } else {
307            None
308        }
309    }
310}
311
312/// Describes a partial checksum whose full checksum will be offloaded. The full checksum
313/// must be computed by summing from `start` (the offset in bytes from the start of the
314/// outermost packet header) to the end of the outermost packet and then placing the result
315/// at `start + offset`.
316#[derive(Clone, Debug, Default, Eq, PartialEq)]
317pub struct PartialChecksum {
318    /// The offset in bytes from the start of the outermost packet header to the start of the
319    /// checksum.
320    pub start: u16,
321    /// The offset in bytes from the start of the checksum to the field that it replaces.
322    pub offset: u16,
323}
324
325/// Describes the checksum offloading capability used during serialization.
326#[derive(Clone, Debug, Eq, PartialEq)]
327pub enum ChecksumOffloadResult {
328    /// Protocol-specific checksum offloading was utilized.
329    ProtocolSpecific(OffloadableProtocols),
330    /// Generic checksum offloading was utilized, producing a partial checksum.
331    Generic(PartialChecksum),
332}
333
334/// A concrete serialization context for the entire network stack.
335#[derive(Clone, Debug, Default, Eq, PartialEq)]
336pub struct NetworkSerializationContext {
337    csum_offload_state: ChecksumOffloadState,
338    /// Indicates whether or not checksum offloading capabilities have been
339    /// utilized yet in the current serialization operation. Because checksum
340    /// offloading can only be performed once per packet, a value of `Some`
341    /// prevents checksum offloading from being performed multiple times.
342    csum_offload_result: Option<ChecksumOffloadResult>,
343}
344
345impl NetworkSerializationContext {
346    /// Creates a new `NetworkSerializationContext` with the given checksum offload capabilities.
347    pub fn new(csum_offload_spec: ChecksumOffloadSpec) -> Self {
348        Self {
349            csum_offload_state: ChecksumOffloadState::new(csum_offload_spec),
350            csum_offload_result: None,
351        }
352    }
353
354    fn transport_checksum_action(&mut self, csum_offset: u16) -> TransportChecksumAction {
355        if self.csum_offload_result.is_some() {
356            // TODO(https://fxbug.dev/527140547): implement Local Checksum
357            // Offload (LCO) for offloading outer checksums of encapsulated
358            // packets.
359            TransportChecksumAction::ComputeFull
360        } else {
361            self.csum_offload_result = self.csum_offload_state.try_offload(csum_offset);
362            self.csum_offload_result
363                .as_ref()
364                .map(|_| TransportChecksumAction::ComputePartial)
365                .unwrap_or(TransportChecksumAction::ComputeFull)
366        }
367    }
368
369    /// Returns the result of the checksum offloading operation for the current
370    /// packet, if any.
371    pub fn csum_offload_result(self) -> Option<ChecksumOffloadResult> {
372        self.csum_offload_result
373    }
374}
375
376impl SerializationContext for NetworkSerializationContext {
377    type ContextState = OffloadableProtocol;
378
379    fn serialize_nested<O: PacketBuilder<Self>, R>(
380        &mut self,
381        outer: &O,
382        constraints: PacketConstraints,
383        serialize_fn: impl FnOnce(&mut Self, PacketConstraints) -> R,
384    ) -> R {
385        let previous_state = self.csum_offload_state.update(outer.context_state(), &constraints);
386        let result = serialize_fn(self, constraints);
387        self.csum_offload_state.restore(previous_state);
388        result
389    }
390}
391
392impl EthernetSerializationContext for NetworkSerializationContext {
393    fn envelope_to_state(_envelope: EthernetEnvelope) -> Self::ContextState {
394        OffloadableProtocol::Ethernet
395    }
396}
397
398impl<I: IpExt> IpSerializationContext<I> for NetworkSerializationContext {
399    fn envelope_to_state(envelope: IpEnvelope<I>) -> Self::ContextState {
400        I::map_ip_in(
401            IpInvariant(envelope),
402            |IpInvariant(envelope)| {
403                if envelope.has_options {
404                    OffloadableProtocol::NotOffloadable
405                } else {
406                    OffloadableProtocol::Ipv4
407                }
408            },
409            |IpInvariant(envelope)| {
410                if envelope.has_options {
411                    OffloadableProtocol::NotOffloadable
412                } else {
413                    OffloadableProtocol::Ipv6
414                }
415            },
416        )
417    }
418}
419
420impl IcmpSerializationContext for NetworkSerializationContext {
421    fn envelope_to_state(_envelope: IcmpEnvelope) -> Self::ContextState {
422        OffloadableProtocol::NotOffloadable
423    }
424}
425
426const_assert!(packet_formats::udp::CHECKSUM_OFFSET <= u16::MAX as usize);
427const UDP_CHECKSUM_OFFSET: u16 = packet_formats::udp::CHECKSUM_OFFSET as u16;
428
429impl UdpSerializationContext for NetworkSerializationContext {
430    fn envelope_to_state(_envelope: UdpEnvelope) -> Self::ContextState {
431        OffloadableProtocol::Udp
432    }
433
434    fn checksum_action(&mut self) -> TransportChecksumAction {
435        self.transport_checksum_action(UDP_CHECKSUM_OFFSET)
436    }
437}
438
439const_assert!(packet_formats::tcp::CHECKSUM_OFFSET <= u16::MAX as usize);
440const TCP_CHECKSUM_OFFSET: u16 = packet_formats::tcp::CHECKSUM_OFFSET as u16;
441
442impl TcpSerializationContext for NetworkSerializationContext {
443    fn envelope_to_state(_envelope: TcpEnvelope) -> Self::ContextState {
444        OffloadableProtocol::Tcp
445    }
446
447    fn checksum_action(&mut self) -> TransportChecksumAction {
448        self.transport_checksum_action(TCP_CHECKSUM_OFFSET)
449    }
450}
451
452/// An indication of the checksums offloaded, if any, for a packet received from
453/// a device.
454#[derive(Clone, Copy, Debug, Eq, PartialEq)]
455pub enum ChecksumRxOffloading {
456    /// The device offloaded zero or more checksums.
457    ///
458    /// `Some(n)` can only be used to describe offloading of TCP and UDP
459    /// checksums.
460    Offloaded(Option<NonZeroU16>),
461    /// The device requires no checksum verification on packet ingress.
462    ///
463    /// NOTE: only intended to be used by the loopback interface.
464    FullyOffloaded,
465}
466
467impl Default for ChecksumRxOffloading {
468    fn default() -> Self {
469        ChecksumRxOffloading::Offloaded(None)
470    }
471}
472
473impl ChecksumRxOffloading {
474    fn skip_checksum_verification(&mut self) -> bool {
475        match self {
476            ChecksumRxOffloading::FullyOffloaded => true,
477            ChecksumRxOffloading::Offloaded(Some(n)) => {
478                *self = ChecksumRxOffloading::Offloaded(NonZeroU16::new(n.get() - 1));
479                true
480            }
481            ChecksumRxOffloading::Offloaded(None) => false,
482        }
483    }
484}
485
486/// Context for parsing network packets in netstack3.
487#[derive(Clone, Debug, Default, Eq, PartialEq)]
488pub struct NetworkParsingContext {
489    /// Hardware checksum offloading context.
490    checksum_offload: ChecksumRxOffloading,
491}
492
493impl NetworkParsingContext {
494    /// Creates a new `NetworkParsingContext`.
495    pub fn new(checksum_offload: ChecksumRxOffloading) -> Self {
496        NetworkParsingContext { checksum_offload }
497    }
498
499    /// Returns the checksum offload status.
500    pub fn checksum_offload(&self) -> ChecksumRxOffloading {
501        self.checksum_offload
502    }
503}
504
505impl UdpParseContext for &mut NetworkParsingContext {
506    fn skip_checksum_verification(&mut self) -> bool {
507        self.checksum_offload.skip_checksum_verification()
508    }
509}
510
511impl TcpParseContext for &mut NetworkParsingContext {
512    fn skip_checksum_verification(&mut self) -> bool {
513        self.checksum_offload.skip_checksum_verification()
514    }
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520    use alloc::vec::Vec;
521    use assert_matches::assert_matches;
522    use core::num::NonZeroU16;
523    use net_types::ethernet::Mac;
524    use net_types::ip::{IpAddress, IpVersionMarker, Ipv4, Ipv4Addr, Ipv6Addr};
525    use packet::{
526        Buf, FragmentedBytesMut, FromRaw, NestablePacketBuilder, NestableSerializer, PacketBuilder,
527        PacketConstraints, ParseBuffer, SerializeTarget, Serializer,
528    };
529    use packet_formats::error::ParseError;
530    use packet_formats::ethernet::{
531        EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck,
532    };
533    use packet_formats::ip::{IpPacket, IpProto, Ipv4Proto, Ipv6Proto};
534    use packet_formats::ipv4::options::Ipv4Option;
535    use packet_formats::ipv4::{Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
536    use packet_formats::ipv6::ext_hdrs::{
537        ExtensionHeaderOptionAction, HopByHopOption, HopByHopOptionData,
538    };
539    use packet_formats::ipv6::{Ipv6PacketBuilder, Ipv6PacketBuilderWithHbhOptions};
540    use packet_formats::tcp::TcpSegmentBuilder;
541    use packet_formats::udp::{
542        HEADER_BYTES as UDP_HEADER_BYTES, UdpPacket, UdpPacketBuilder, UdpPacketRaw, UdpParseArgs,
543    };
544    use test_case::test_case;
545
546    const SRC_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]);
547    const DST_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]);
548    const SRC_IP_V4: Ipv4Addr = Ipv4Addr::new([192, 168, 0, 1]);
549    const DST_IP_V4: Ipv4Addr = Ipv4Addr::new([192, 168, 0, 2]);
550    const SRC_IP_V6: Ipv6Addr = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 1]);
551    const DST_IP_V6: Ipv6Addr = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 2]);
552    const SRC_PORT: u16 = 1234;
553    const DST_PORT: u16 = 5678;
554
555    #[test_case(
556        UdpPacketBuilder::new(
557            SRC_IP_V4,
558            DST_IP_V4,
559            NonZeroU16::new(SRC_PORT),
560            NonZeroU16::new(DST_PORT).unwrap(),
561        ),
562        IpProto::Udp ; "udp"
563    )]
564    #[test_case(
565        TcpSegmentBuilder::new(
566            SRC_IP_V4,
567            DST_IP_V4,
568            NonZeroU16::new(SRC_PORT).unwrap(),
569            NonZeroU16::new(DST_PORT).unwrap(),
570            123,
571            None,
572            1000,
573        ),
574        IpProto::Tcp ; "tcp"
575    )]
576    fn ipv4_no_options_csum_offload(
577        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
578        ip_proto: IpProto,
579    ) {
580        let mut payload = [0u8; 100];
581        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
582        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
583
584        let serializer =
585            Buf::new(&mut payload[..], ..).wrap_in(transport_builder).wrap_in(ip).wrap_in(ethernet);
586
587        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
588            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
589        ));
590        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
591
592        let expected_protocol = match ip_proto {
593            IpProto::Udp => OffloadableProtocol::Udp,
594            IpProto::Tcp => OffloadableProtocol::Tcp,
595            _ => panic!("invalid proto"),
596        };
597        assert_eq!(
598            context.csum_offload_result(),
599            Some(ChecksumOffloadResult::ProtocolSpecific(
600                OffloadableProtocols::ETHERNET
601                    | OffloadableProtocols::IPV4
602                    | expected_protocol.into()
603            ))
604        );
605    }
606
607    #[test_case(
608        UdpPacketBuilder::new(
609            SRC_IP_V4,
610            DST_IP_V4,
611            NonZeroU16::new(SRC_PORT),
612            NonZeroU16::new(DST_PORT).unwrap(),
613        ),
614        IpProto::Udp ; "udp"
615    )]
616    #[test_case(
617        TcpSegmentBuilder::new(
618            SRC_IP_V4,
619            DST_IP_V4,
620            NonZeroU16::new(SRC_PORT).unwrap(),
621            NonZeroU16::new(DST_PORT).unwrap(),
622            123,
623            None,
624            1000,
625        ),
626        IpProto::Tcp ; "tcp"
627    )]
628    fn ipv4_with_options_no_csum_offload(
629        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
630        ip_proto: IpProto,
631    ) {
632        let mut payload = [0u8; 100];
633        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
634        let options = [Ipv4Option::RouterAlert { data: 0 }];
635        let ip_with_options = Ipv4PacketBuilderWithOptions::new(ip, &options).unwrap();
636        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
637
638        let serializer = Buf::new(&mut payload[..], ..)
639            .wrap_in(transport_builder)
640            .wrap_in(ip_with_options)
641            .wrap_in(ethernet);
642
643        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
644            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
645        ));
646        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
647
648        assert_eq!(context.csum_offload_result(), None);
649    }
650
651    #[test_case(
652        UdpPacketBuilder::new(
653            SRC_IP_V6,
654            DST_IP_V6,
655            NonZeroU16::new(SRC_PORT),
656            NonZeroU16::new(DST_PORT).unwrap(),
657        ),
658        IpProto::Udp ; "udp"
659    )]
660    #[test_case(
661        TcpSegmentBuilder::new(
662            SRC_IP_V6,
663            DST_IP_V6,
664            NonZeroU16::new(SRC_PORT).unwrap(),
665            NonZeroU16::new(DST_PORT).unwrap(),
666            123,
667            None,
668            1000,
669        ),
670        IpProto::Tcp ; "tcp"
671    )]
672    fn ipv6_no_extensions_csum_offload(
673        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
674        ip_proto: IpProto,
675    ) {
676        let mut payload = [0u8; 100];
677        let ip = Ipv6PacketBuilder::new(SRC_IP_V6, DST_IP_V6, 64, Ipv6Proto::Proto(ip_proto));
678        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
679
680        let serializer =
681            Buf::new(&mut payload[..], ..).wrap_in(transport_builder).wrap_in(ip).wrap_in(ethernet);
682
683        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
684            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv6(),
685        ));
686        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
687
688        let expected_protocol = match ip_proto {
689            IpProto::Udp => OffloadableProtocol::Udp,
690            IpProto::Tcp => OffloadableProtocol::Tcp,
691            _ => panic!("invalid proto"),
692        };
693        assert_eq!(
694            context.csum_offload_result(),
695            Some(ChecksumOffloadResult::ProtocolSpecific(
696                OffloadableProtocols::ETHERNET
697                    | OffloadableProtocols::IPV6
698                    | expected_protocol.into()
699            ))
700        );
701    }
702
703    #[test_case(
704        UdpPacketBuilder::new(
705            SRC_IP_V6,
706            DST_IP_V6,
707            NonZeroU16::new(SRC_PORT),
708            NonZeroU16::new(DST_PORT).unwrap(),
709        ),
710        IpProto::Udp ; "udp"
711    )]
712    #[test_case(
713        TcpSegmentBuilder::new(
714            SRC_IP_V6,
715            DST_IP_V6,
716            NonZeroU16::new(SRC_PORT).unwrap(),
717            NonZeroU16::new(DST_PORT).unwrap(),
718            123,
719            None,
720            1000,
721        ),
722        IpProto::Tcp ; "tcp"
723    )]
724    fn ipv6_with_extension_hdrs_no_csum_offload(
725        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
726        ip_proto: IpProto,
727    ) {
728        let mut payload = [0u8; 100];
729        let ip = Ipv6PacketBuilder::new(SRC_IP_V6, DST_IP_V6, 64, Ipv6Proto::Proto(ip_proto));
730        let options = [HopByHopOption {
731            action: ExtensionHeaderOptionAction::SkipAndContinue,
732            mutable: false,
733            data: HopByHopOptionData::RouterAlert { data: 0 },
734        }];
735        let ip_with_options = Ipv6PacketBuilderWithHbhOptions::new(ip, options).unwrap();
736        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
737
738        let serializer = Buf::new(&mut payload[..], ..)
739            .wrap_in(transport_builder)
740            .wrap_in(ip_with_options)
741            .wrap_in(ethernet);
742
743        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
744            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv6(),
745        ));
746        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
747
748        assert_eq!(context.csum_offload_result(), None);
749    }
750
751    #[test]
752    fn generic_csum_offload_preferred_over_protocol_specific() {
753        let mut payload = [0u8; 100];
754        let udp = UdpPacketBuilder::new(
755            SRC_IP_V4,
756            DST_IP_V4,
757            NonZeroU16::new(SRC_PORT),
758            NonZeroU16::new(DST_PORT).unwrap(),
759        );
760        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
761        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
762
763        let serializer = Buf::new(&mut payload[..], ..).wrap_in(udp).wrap_in(ip).wrap_in(ethernet);
764
765        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::any([
766            ChecksumOffloadSpec::generic(),
767            ChecksumOffloadSpec::protocol_specific(
768                ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
769            ),
770        ]));
771        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
772
773        // We expect generic offload to be preferred.
774        assert_matches!(context.csum_offload_result(), Some(ChecksumOffloadResult::Generic(_)));
775    }
776
777    #[derive(Debug)]
778    struct TestPacketBuilder {
779        header_len: usize,
780    }
781    impl NestablePacketBuilder for TestPacketBuilder {
782        fn constraints(&self) -> PacketConstraints {
783            PacketConstraints::new(self.header_len, 0, 0, usize::MAX)
784        }
785    }
786    impl PacketBuilder<NetworkSerializationContext> for TestPacketBuilder {
787        fn context_state(&self) -> OffloadableProtocol {
788            OffloadableProtocol::NotOffloadable
789        }
790        fn serialize(
791            &self,
792            _context: &mut NetworkSerializationContext,
793            _target: &mut SerializeTarget<'_>,
794            _body: FragmentedBytesMut<'_, '_>,
795        ) {
796            // Do nothing.
797        }
798    }
799
800    #[test_case(
801        UdpPacketBuilder::new(
802            SRC_IP_V4,
803            DST_IP_V4,
804            NonZeroU16::new(SRC_PORT),
805            NonZeroU16::new(DST_PORT).unwrap(),
806        ),
807        IpProto::Udp,
808        UDP_CHECKSUM_OFFSET ; "udp"
809    )]
810    #[test_case(
811        TcpSegmentBuilder::new(
812            SRC_IP_V4,
813            DST_IP_V4,
814            NonZeroU16::new(SRC_PORT).unwrap(),
815            NonZeroU16::new(DST_PORT).unwrap(),
816            123,
817            None,
818            1000,
819        ),
820        IpProto::Tcp,
821        TCP_CHECKSUM_OFFSET ; "tcp"
822    )]
823    fn generic_csum_offload(
824        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
825        ip_proto: IpProto,
826        expected_csum_offset: u16,
827    ) {
828        let mut payload = [0u8; 100];
829        let test_packet = TestPacketBuilder { header_len: 10 };
830        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
831        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
832
833        // Buf -> test_packet -> transport_builder -> ip -> ethernet.
834        let serializer = Buf::new(&mut payload[..], ..)
835            // We add an additional header inside the transport packet to ensure
836            // that the correct `header_offset` is restored as we walk back up
837            // the stack.
838            .wrap_in(test_packet)
839            .wrap_in(transport_builder)
840            .wrap_in(ip)
841            .wrap_in(ethernet);
842
843        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
844        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
845
846        // Ethernet header (14) + Ipv4 header (20) = 34.
847        assert_eq!(
848            context.csum_offload_result(),
849            Some(ChecksumOffloadResult::Generic(PartialChecksum {
850                start: 34,
851                offset: expected_csum_offset
852            }))
853        );
854    }
855
856    #[test]
857    fn generic_csum_offload_disabled_on_overflow() {
858        let mut payload = [0u8; 100];
859        // Use a header length that exceeds u16::MAX (65535).
860        let test_packet = TestPacketBuilder { header_len: 66000 };
861        let udp = UdpPacketBuilder::new(
862            SRC_IP_V4,
863            DST_IP_V4,
864            NonZeroU16::new(SRC_PORT),
865            NonZeroU16::new(DST_PORT).unwrap(),
866        );
867        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
868        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
869
870        // Wrap test_packet *outside* UDP to make the starting byte of the UDP
871        // header overflow a u16.
872        // Buf -> udp -> ip -> test_packet -> ethernet.
873        let serializer = Buf::new(&mut payload[..], ..)
874            .wrap_in(udp)
875            .wrap_in(ip)
876            .wrap_in(test_packet)
877            .wrap_in(ethernet);
878
879        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
880        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
881
882        // Generic offload should be disabled because of overflow.
883        assert_eq!(context.csum_offload_result(), None);
884    }
885
886    #[test]
887    fn generic_csum_offload_enabled_with_inner_overflow() {
888        let mut payload = [0u8; 100];
889        // Use a header length that exceeds u16::MAX (65535).
890        let test_packet = TestPacketBuilder { header_len: 66000 };
891        let udp = UdpPacketBuilder::new(
892            SRC_IP_V6,
893            DST_IP_V6,
894            NonZeroU16::new(SRC_PORT),
895            NonZeroU16::new(DST_PORT).unwrap(),
896        );
897        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
898
899        // Wrap test_packet *inside* UDP, bypassing IP to avoid IP size limits.
900        // Buf -> test_packet -> udp -> ethernet.
901        let serializer =
902            Buf::new(&mut payload[..], ..).wrap_in(test_packet).wrap_in(udp).wrap_in(ethernet);
903
904        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
905        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
906
907        // Generic offload should work despite the overflow inside the UDP
908        // packet.
909        assert_eq!(
910            context.csum_offload_result(),
911            Some(ChecksumOffloadResult::Generic(PartialChecksum {
912                start: 14, // Ethernet header length.
913                offset: UDP_CHECKSUM_OFFSET
914            }))
915        );
916    }
917
918    #[test]
919    fn protocol_specific_csum_offload_with_size_limit() {
920        let mut payload = [0u8; 100];
921        let udp = UdpPacketBuilder::new(
922            SRC_IP_V4,
923            DST_IP_V4,
924            NonZeroU16::new(SRC_PORT),
925            NonZeroU16::new(DST_PORT).unwrap(),
926        );
927        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
928        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
929
930        // Buf -> udp -> with_size_limit -> ip -> ethernet.
931        let serializer = Buf::new(&mut payload[..], ..)
932            .wrap_in(udp)
933            // Tests that intermediate protocol-less packet builders like
934            // `LimitedSizePacketBuilder` don't break protocol-specific
935            // offloading.
936            .with_size_limit(1000)
937            .wrap_in(ip)
938            .wrap_in(ethernet);
939
940        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
941            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
942        ));
943        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
944
945        assert_eq!(
946            context.csum_offload_result(),
947            Some(ChecksumOffloadResult::ProtocolSpecific(
948                OffloadableProtocols::ETHERNET
949                    | OffloadableProtocols::IPV4
950                    | OffloadableProtocols::UDP
951            ))
952        );
953    }
954
955    #[test]
956    fn protocol_specific_csum_offload_duplicate_protocol() {
957        let mut payload = [0u8; 100];
958        let udp_inner = UdpPacketBuilder::new(
959            SRC_IP_V4,
960            DST_IP_V4,
961            NonZeroU16::new(SRC_PORT),
962            NonZeroU16::new(DST_PORT).unwrap(),
963        );
964        let ip_inner =
965            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
966        let udp_outer = UdpPacketBuilder::new(
967            SRC_IP_V4,
968            DST_IP_V4,
969            NonZeroU16::new(SRC_PORT),
970            NonZeroU16::new(DST_PORT).unwrap(),
971        );
972        let ip_outer =
973            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
974        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
975
976        // Buf -> udp_inner -> ip_inner -> udp_outer -> ip_outer -> ethernet.
977        let serializer = Buf::new(&mut payload[..], ..)
978            .wrap_in(udp_inner)
979            .wrap_in(ip_inner)
980            .wrap_in(udp_outer)
981            .wrap_in(ip_outer)
982            .wrap_in(ethernet);
983
984        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
985            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
986        ));
987        let buf =
988            serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
989
990        // Protocol-specific offload should work for the outer UDP packet even
991        // with the duplicate UDP packet.
992        assert_eq!(
993            context.csum_offload_result(),
994            Some(ChecksumOffloadResult::ProtocolSpecific(
995                OffloadableProtocols::ETHERNET
996                    | OffloadableProtocols::IPV4
997                    | OffloadableProtocols::UDP
998            ))
999        );
1000
1001        let mut buf_ref = buf.as_ref();
1002        let eth = buf_ref
1003            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
1004            .expect("ethernet parse should succeed");
1005        let mut body = eth.body();
1006        let ip_out = body.parse::<Ipv4Packet<_>>().expect("outer ipv4 parse should succeed");
1007
1008        // Parse outer UDP as raw (succeeds since it doesn't validate checksum).
1009        let mut outer_udp_bytes = ip_out.body();
1010        let udp_out_raw = outer_udp_bytes
1011            .parse_with::<_, UdpPacketRaw<_>>(IpVersionMarker::<Ipv4>::default())
1012            .expect("outer udp parse should succeed");
1013
1014        // Try to validate outer UDP, which should fail checksum validation
1015        // because the checksum was offloaded.
1016        assert_eq!(
1017            UdpPacket::try_from_raw_with(
1018                udp_out_raw,
1019                UdpParseArgs::new(ip_out.src_ip(), ip_out.dst_ip())
1020            )
1021            .err(),
1022            Some(ParseError::Checksum),
1023        );
1024
1025        let mut inner_ip_bytes = &ip_out.body()[UDP_HEADER_BYTES..];
1026        let ip_in =
1027            inner_ip_bytes.parse::<Ipv4Packet<_>>().expect("inner ipv4 parse should succeed");
1028        let mut body = ip_in.body();
1029
1030        // This should succeed because inner UDP checksum was computed in
1031        // software.
1032        let _udp_in = body
1033            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(ip_in.src_ip(), ip_in.dst_ip()))
1034            .expect("inner udp parse should succeed");
1035    }
1036
1037    #[test]
1038    fn generic_csum_offload_duplicate_protocol() {
1039        let mut payload = [0u8; 100];
1040        let udp_inner = UdpPacketBuilder::new(
1041            SRC_IP_V4,
1042            DST_IP_V4,
1043            NonZeroU16::new(SRC_PORT),
1044            NonZeroU16::new(DST_PORT).unwrap(),
1045        );
1046        let ip_inner =
1047            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
1048        let udp_outer = UdpPacketBuilder::new(
1049            SRC_IP_V4,
1050            DST_IP_V4,
1051            NonZeroU16::new(SRC_PORT),
1052            NonZeroU16::new(DST_PORT).unwrap(),
1053        );
1054        let ip_outer =
1055            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
1056        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
1057
1058        // Buf -> udp_inner -> ip_inner -> udp_outer -> ip_outer -> ethernet.
1059        let serializer = Buf::new(&mut payload[..], ..)
1060            .wrap_in(udp_inner)
1061            .wrap_in(ip_inner)
1062            .wrap_in(udp_outer)
1063            .wrap_in(ip_outer)
1064            .wrap_in(ethernet);
1065
1066        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
1067        let buf =
1068            serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
1069
1070        // Generic offload should apply to the inner UDP packet.
1071        // Eth (14) + outer IPv4 (20) + UDP (8) + inner IPv4 (20) = 62.
1072        assert_eq!(
1073            context.csum_offload_result(),
1074            Some(ChecksumOffloadResult::Generic(PartialChecksum {
1075                start: 62,
1076                offset: UDP_CHECKSUM_OFFSET
1077            }))
1078        );
1079
1080        let mut buf_ref = buf.as_ref();
1081        let eth = buf_ref
1082            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
1083            .expect("ethernet parse should succeed");
1084        let mut body = eth.body();
1085        let ip_out = body.parse::<Ipv4Packet<_>>().expect("outer ipv4 parse should succeed");
1086
1087        // Outer UDP checksum was computed in software, so parse should succeed.
1088        let mut outer_udp_bytes = ip_out.body();
1089        let _udp_out = outer_udp_bytes
1090            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(ip_out.src_ip(), ip_out.dst_ip()))
1091            .expect("outer udp parse should succeed");
1092
1093        let mut inner_ip_bytes = &ip_out.body()[UDP_HEADER_BYTES..];
1094        let ip_in =
1095            inner_ip_bytes.parse::<Ipv4Packet<_>>().expect("inner ipv4 parse should succeed");
1096        let mut body = ip_in.body();
1097
1098        // Parse inner UDP as raw (succeeds since it doesn't validate checksum).
1099        let udp_in_raw = body
1100            .parse_with::<_, UdpPacketRaw<_>>(IpVersionMarker::<Ipv4>::default())
1101            .expect("inner udp parse should succeed");
1102
1103        // Try to validate inner UDP, which should fail checksum validation
1104        // because the checksum was offloaded.
1105        assert_eq!(
1106            UdpPacket::try_from_raw_with(
1107                udp_in_raw,
1108                UdpParseArgs::new(ip_in.src_ip(), ip_in.dst_ip())
1109            )
1110            .err(),
1111            Some(ParseError::Checksum),
1112        );
1113    }
1114
1115    fn build_udp_packet_invalid_csum<I: IpAddress>(
1116        src_ip: I,
1117        dst_ip: I,
1118        body: &mut [u8],
1119    ) -> Vec<u8> {
1120        let mut buf = Buf::new(body, ..)
1121            .wrap_in(UdpPacketBuilder::new(
1122                src_ip,
1123                dst_ip,
1124                NonZeroU16::new(1),
1125                NonZeroU16::new(2).unwrap(),
1126            ))
1127            .serialize_vec_outer(&mut NetworkSerializationContext::default())
1128            .unwrap()
1129            .as_ref()
1130            .to_vec();
1131
1132        // Corrupt the checksum.
1133        buf[packet_formats::udp::CHECKSUM_OFFSET] ^= 0xFF;
1134        buf[packet_formats::udp::CHECKSUM_OFFSET + 1] ^= 0xFF;
1135        buf
1136    }
1137
1138    /// Builds a UDP packet containing `nesting-1` nested UDP packets, all with
1139    /// invalid checksums.
1140    fn build_nested_udp_packets_invalid_csums<I: IpAddress>(
1141        src_ip: I,
1142        dst_ip: I,
1143        nesting: usize,
1144    ) -> Vec<u8> {
1145        let mut payload = alloc::vec![0u8; 100];
1146        for _ in 0..nesting {
1147            payload = build_udp_packet_invalid_csum(src_ip, dst_ip, &mut payload);
1148        }
1149        payload
1150    }
1151
1152    #[test]
1153    fn checksum_rx_offloading_none() {
1154        let buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 1);
1155
1156        // `None` offloads no checksums so we expect to be unable to parse any
1157        // UDP packets with invalid checksums.
1158        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::Offloaded(None));
1159        let mut buf_ref: &[u8] = buf.as_ref();
1160        assert_eq!(
1161            buf_ref
1162                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1163                    SRC_IP_V4, DST_IP_V4, &mut ctx
1164                ))
1165                .err(),
1166            Some(ParseError::Checksum)
1167        );
1168    }
1169
1170    #[test]
1171    fn checksum_rx_offloading_fully_offloaded() {
1172        let mut buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 3);
1173
1174        // `FullyOffloaded` offloads all checksums so we expect to be able to
1175        // parse an arbitrary number of UDP packets with invalid checksums.
1176        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
1177        for _ in 0..3 {
1178            let mut buf_ref: &[u8] = buf.as_ref();
1179            buf = buf_ref
1180                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1181                    SRC_IP_V4, DST_IP_V4, &mut ctx,
1182                ))
1183                .expect("udp parse should succeed")
1184                .body()
1185                .to_vec();
1186        }
1187    }
1188
1189    #[test]
1190    fn checksum_rx_offloading_offloaded() {
1191        let mut buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 3);
1192
1193        // `Offloaded` indicates the number checksums not to verify so we expect
1194        // to be able to parse exactly two UDP packets with invalid checksums.
1195        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::Offloaded(Some(
1196            NonZeroU16::new(2).unwrap(),
1197        )));
1198        for _ in 0..2 {
1199            let mut buf_ref: &[u8] = buf.as_ref();
1200            buf = buf_ref
1201                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1202                    SRC_IP_V4, DST_IP_V4, &mut ctx,
1203                ))
1204                .expect("udp parse should succeed")
1205                .body()
1206                .to_vec();
1207        }
1208        let mut buf_ref: &[u8] = buf.as_ref();
1209        assert_eq!(
1210            buf_ref
1211                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1212                    SRC_IP_V4, DST_IP_V4, &mut ctx
1213                ))
1214                .err(),
1215            Some(ParseError::Checksum)
1216        );
1217    }
1218}