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