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
473/// Context for parsing network packets in netstack3.
474#[derive(Clone, Debug, Default, Eq, PartialEq)]
475pub struct NetworkParsingContext {
476    /// Hardware checksum offloading context.
477    checksum_offload: ChecksumRxOffloading,
478    verified_checksum_count: u16,
479}
480
481impl NetworkParsingContext {
482    /// Creates a new `NetworkParsingContext`.
483    pub fn new(checksum_offload: ChecksumRxOffloading) -> Self {
484        NetworkParsingContext { checksum_offload, verified_checksum_count: 0 }
485    }
486
487    /// Returns the checksum offload status.
488    pub fn checksum_offload(&self) -> ChecksumRxOffloading {
489        self.checksum_offload
490    }
491
492    /// Returns the count of transport-layer checksums that were actually
493    /// verified.
494    ///
495    /// Note that this only counts actual verifications and does not include
496    /// skipped checksums.
497    pub fn verified_checksum_count(&self) -> u16 {
498        self.verified_checksum_count
499    }
500
501    /// Verifies a checksum using `f` if needed.
502    ///
503    /// If checksum verification should be skipped according to the offload
504    /// configuration, `f` is not called. Otherwise, `f` is called and the
505    /// verified checksum count is incremented on success.
506    fn verify_checksum_if_needed_inner<E>(
507        &mut self,
508        f: impl FnOnce() -> Result<(), E>,
509    ) -> Result<(), E> {
510        match self.checksum_offload {
511            ChecksumRxOffloading::FullyOffloaded => Ok(()),
512            ChecksumRxOffloading::Offloaded(Some(n)) => {
513                self.checksum_offload =
514                    ChecksumRxOffloading::Offloaded(NonZeroU16::new(n.get() - 1));
515                Ok(())
516            }
517            ChecksumRxOffloading::Offloaded(None) => match f() {
518                Ok(()) => {
519                    self.verified_checksum_count = self.verified_checksum_count.saturating_add(1);
520                    Ok(())
521                }
522                Err(e) => Err(e),
523            },
524        }
525    }
526}
527
528impl UdpParseContext for &mut NetworkParsingContext {
529    fn verify_checksum_if_needed<E>(&mut self, f: impl FnOnce() -> Result<(), E>) -> Result<(), E> {
530        self.verify_checksum_if_needed_inner(f)
531    }
532}
533
534impl TcpParseContext for &mut NetworkParsingContext {
535    fn verify_checksum_if_needed<E>(&mut self, f: impl FnOnce() -> Result<(), E>) -> Result<(), E> {
536        self.verify_checksum_if_needed_inner(f)
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543    use alloc::vec::Vec;
544    use assert_matches::assert_matches;
545    use core::num::NonZeroU16;
546    use net_types::ethernet::Mac;
547    use net_types::ip::{IpAddress, IpVersionMarker, Ipv4, Ipv4Addr, Ipv6Addr};
548    use packet::{
549        Buf, FragmentedBytesMut, FromRaw, NestablePacketBuilder, NestableSerializer, PacketBuilder,
550        PacketConstraints, ParseBuffer, SerializeTarget, Serializer,
551    };
552    use packet_formats::error::ParseError;
553    use packet_formats::ethernet::{
554        EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck,
555    };
556    use packet_formats::ip::{IpPacket, IpProto, Ipv4Proto, Ipv6Proto};
557    use packet_formats::ipv4::options::Ipv4Option;
558    use packet_formats::ipv4::{Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
559    use packet_formats::ipv6::ext_hdrs::{
560        ExtensionHeaderOptionAction, HopByHopOption, HopByHopOptionData,
561    };
562    use packet_formats::ipv6::{Ipv6PacketBuilder, Ipv6PacketBuilderWithHbhOptions};
563    use packet_formats::tcp::TcpSegmentBuilder;
564    use packet_formats::udp::{
565        HEADER_BYTES as UDP_HEADER_BYTES, UdpPacket, UdpPacketBuilder, UdpPacketRaw, UdpParseArgs,
566    };
567    use test_case::test_case;
568
569    const SRC_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]);
570    const DST_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]);
571    const SRC_IP_V4: Ipv4Addr = Ipv4Addr::new([192, 168, 0, 1]);
572    const DST_IP_V4: Ipv4Addr = Ipv4Addr::new([192, 168, 0, 2]);
573    const SRC_IP_V6: Ipv6Addr = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 1]);
574    const DST_IP_V6: Ipv6Addr = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 2]);
575    const SRC_PORT: u16 = 1234;
576    const DST_PORT: u16 = 5678;
577
578    #[test_case(
579        UdpPacketBuilder::new(
580            SRC_IP_V4,
581            DST_IP_V4,
582            NonZeroU16::new(SRC_PORT),
583            NonZeroU16::new(DST_PORT).unwrap(),
584        ),
585        IpProto::Udp ; "udp"
586    )]
587    #[test_case(
588        TcpSegmentBuilder::new(
589            SRC_IP_V4,
590            DST_IP_V4,
591            NonZeroU16::new(SRC_PORT).unwrap(),
592            NonZeroU16::new(DST_PORT).unwrap(),
593            123,
594            None,
595            1000,
596        ),
597        IpProto::Tcp ; "tcp"
598    )]
599    fn ipv4_no_options_csum_offload(
600        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
601        ip_proto: IpProto,
602    ) {
603        let mut payload = [0u8; 100];
604        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
605        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
606
607        let serializer =
608            Buf::new(&mut payload[..], ..).wrap_in(transport_builder).wrap_in(ip).wrap_in(ethernet);
609
610        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
611            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
612        ));
613        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
614
615        let expected_protocol = match ip_proto {
616            IpProto::Udp => OffloadableProtocol::Udp,
617            IpProto::Tcp => OffloadableProtocol::Tcp,
618            _ => panic!("invalid proto"),
619        };
620        assert_eq!(
621            context.csum_offload_result(),
622            Some(ChecksumOffloadResult::ProtocolSpecific(
623                OffloadableProtocols::ETHERNET
624                    | OffloadableProtocols::IPV4
625                    | expected_protocol.into()
626            ))
627        );
628    }
629
630    #[test_case(
631        UdpPacketBuilder::new(
632            SRC_IP_V4,
633            DST_IP_V4,
634            NonZeroU16::new(SRC_PORT),
635            NonZeroU16::new(DST_PORT).unwrap(),
636        ),
637        IpProto::Udp ; "udp"
638    )]
639    #[test_case(
640        TcpSegmentBuilder::new(
641            SRC_IP_V4,
642            DST_IP_V4,
643            NonZeroU16::new(SRC_PORT).unwrap(),
644            NonZeroU16::new(DST_PORT).unwrap(),
645            123,
646            None,
647            1000,
648        ),
649        IpProto::Tcp ; "tcp"
650    )]
651    fn ipv4_with_options_no_csum_offload(
652        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
653        ip_proto: IpProto,
654    ) {
655        let mut payload = [0u8; 100];
656        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
657        let options = [Ipv4Option::RouterAlert { data: 0 }];
658        let ip_with_options = Ipv4PacketBuilderWithOptions::new(ip, &options).unwrap();
659        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
660
661        let serializer = Buf::new(&mut payload[..], ..)
662            .wrap_in(transport_builder)
663            .wrap_in(ip_with_options)
664            .wrap_in(ethernet);
665
666        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
667            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
668        ));
669        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
670
671        assert_eq!(context.csum_offload_result(), None);
672    }
673
674    #[test_case(
675        UdpPacketBuilder::new(
676            SRC_IP_V6,
677            DST_IP_V6,
678            NonZeroU16::new(SRC_PORT),
679            NonZeroU16::new(DST_PORT).unwrap(),
680        ),
681        IpProto::Udp ; "udp"
682    )]
683    #[test_case(
684        TcpSegmentBuilder::new(
685            SRC_IP_V6,
686            DST_IP_V6,
687            NonZeroU16::new(SRC_PORT).unwrap(),
688            NonZeroU16::new(DST_PORT).unwrap(),
689            123,
690            None,
691            1000,
692        ),
693        IpProto::Tcp ; "tcp"
694    )]
695    fn ipv6_no_extensions_csum_offload(
696        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
697        ip_proto: IpProto,
698    ) {
699        let mut payload = [0u8; 100];
700        let ip = Ipv6PacketBuilder::new(SRC_IP_V6, DST_IP_V6, 64, Ipv6Proto::Proto(ip_proto));
701        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
702
703        let serializer =
704            Buf::new(&mut payload[..], ..).wrap_in(transport_builder).wrap_in(ip).wrap_in(ethernet);
705
706        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
707            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv6(),
708        ));
709        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
710
711        let expected_protocol = match ip_proto {
712            IpProto::Udp => OffloadableProtocol::Udp,
713            IpProto::Tcp => OffloadableProtocol::Tcp,
714            _ => panic!("invalid proto"),
715        };
716        assert_eq!(
717            context.csum_offload_result(),
718            Some(ChecksumOffloadResult::ProtocolSpecific(
719                OffloadableProtocols::ETHERNET
720                    | OffloadableProtocols::IPV6
721                    | expected_protocol.into()
722            ))
723        );
724    }
725
726    #[test_case(
727        UdpPacketBuilder::new(
728            SRC_IP_V6,
729            DST_IP_V6,
730            NonZeroU16::new(SRC_PORT),
731            NonZeroU16::new(DST_PORT).unwrap(),
732        ),
733        IpProto::Udp ; "udp"
734    )]
735    #[test_case(
736        TcpSegmentBuilder::new(
737            SRC_IP_V6,
738            DST_IP_V6,
739            NonZeroU16::new(SRC_PORT).unwrap(),
740            NonZeroU16::new(DST_PORT).unwrap(),
741            123,
742            None,
743            1000,
744        ),
745        IpProto::Tcp ; "tcp"
746    )]
747    fn ipv6_with_extension_hdrs_no_csum_offload(
748        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
749        ip_proto: IpProto,
750    ) {
751        let mut payload = [0u8; 100];
752        let ip = Ipv6PacketBuilder::new(SRC_IP_V6, DST_IP_V6, 64, Ipv6Proto::Proto(ip_proto));
753        let options = [HopByHopOption {
754            action: ExtensionHeaderOptionAction::SkipAndContinue,
755            mutable: false,
756            data: HopByHopOptionData::RouterAlert { data: 0 },
757        }];
758        let ip_with_options = Ipv6PacketBuilderWithHbhOptions::new(ip, options).unwrap();
759        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
760
761        let serializer = Buf::new(&mut payload[..], ..)
762            .wrap_in(transport_builder)
763            .wrap_in(ip_with_options)
764            .wrap_in(ethernet);
765
766        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
767            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv6(),
768        ));
769        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
770
771        assert_eq!(context.csum_offload_result(), None);
772    }
773
774    #[test]
775    fn generic_csum_offload_preferred_over_protocol_specific() {
776        let mut payload = [0u8; 100];
777        let udp = UdpPacketBuilder::new(
778            SRC_IP_V4,
779            DST_IP_V4,
780            NonZeroU16::new(SRC_PORT),
781            NonZeroU16::new(DST_PORT).unwrap(),
782        );
783        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
784        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
785
786        let serializer = Buf::new(&mut payload[..], ..).wrap_in(udp).wrap_in(ip).wrap_in(ethernet);
787
788        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::any([
789            ChecksumOffloadSpec::generic(),
790            ChecksumOffloadSpec::protocol_specific(
791                ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
792            ),
793        ]));
794        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
795
796        // We expect generic offload to be preferred.
797        assert_matches!(context.csum_offload_result(), Some(ChecksumOffloadResult::Generic(_)));
798    }
799
800    #[derive(Debug)]
801    struct TestPacketBuilder {
802        header_len: usize,
803    }
804    impl NestablePacketBuilder for TestPacketBuilder {
805        fn constraints(&self) -> PacketConstraints {
806            PacketConstraints::new(self.header_len, 0, 0, usize::MAX)
807        }
808    }
809    impl PacketBuilder<NetworkSerializationContext> for TestPacketBuilder {
810        fn context_state(&self) -> OffloadableProtocol {
811            OffloadableProtocol::NotOffloadable
812        }
813        fn serialize(
814            &self,
815            _context: &mut NetworkSerializationContext,
816            _target: &mut SerializeTarget<'_>,
817            _body: FragmentedBytesMut<'_, '_>,
818        ) {
819            // Do nothing.
820        }
821    }
822
823    #[test_case(
824        UdpPacketBuilder::new(
825            SRC_IP_V4,
826            DST_IP_V4,
827            NonZeroU16::new(SRC_PORT),
828            NonZeroU16::new(DST_PORT).unwrap(),
829        ),
830        IpProto::Udp,
831        UDP_CHECKSUM_OFFSET ; "udp"
832    )]
833    #[test_case(
834        TcpSegmentBuilder::new(
835            SRC_IP_V4,
836            DST_IP_V4,
837            NonZeroU16::new(SRC_PORT).unwrap(),
838            NonZeroU16::new(DST_PORT).unwrap(),
839            123,
840            None,
841            1000,
842        ),
843        IpProto::Tcp,
844        TCP_CHECKSUM_OFFSET ; "tcp"
845    )]
846    fn generic_csum_offload(
847        transport_builder: impl PacketBuilder<NetworkSerializationContext> + core::fmt::Debug,
848        ip_proto: IpProto,
849        expected_csum_offset: u16,
850    ) {
851        let mut payload = [0u8; 100];
852        let test_packet = TestPacketBuilder { header_len: 10 };
853        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(ip_proto));
854        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
855
856        // Buf -> test_packet -> transport_builder -> ip -> ethernet.
857        let serializer = Buf::new(&mut payload[..], ..)
858            // We add an additional header inside the transport packet to ensure
859            // that the correct `header_offset` is restored as we walk back up
860            // the stack.
861            .wrap_in(test_packet)
862            .wrap_in(transport_builder)
863            .wrap_in(ip)
864            .wrap_in(ethernet);
865
866        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
867        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
868
869        // Ethernet header (14) + Ipv4 header (20) = 34.
870        assert_eq!(
871            context.csum_offload_result(),
872            Some(ChecksumOffloadResult::Generic(PartialChecksum {
873                start: 34,
874                offset: expected_csum_offset
875            }))
876        );
877    }
878
879    #[test]
880    fn generic_csum_offload_disabled_on_overflow() {
881        let mut payload = [0u8; 100];
882        // Use a header length that exceeds u16::MAX (65535).
883        let test_packet = TestPacketBuilder { header_len: 66000 };
884        let udp = UdpPacketBuilder::new(
885            SRC_IP_V4,
886            DST_IP_V4,
887            NonZeroU16::new(SRC_PORT),
888            NonZeroU16::new(DST_PORT).unwrap(),
889        );
890        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
891        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
892
893        // Wrap test_packet *outside* UDP to make the starting byte of the UDP
894        // header overflow a u16.
895        // Buf -> udp -> ip -> test_packet -> ethernet.
896        let serializer = Buf::new(&mut payload[..], ..)
897            .wrap_in(udp)
898            .wrap_in(ip)
899            .wrap_in(test_packet)
900            .wrap_in(ethernet);
901
902        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
903        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
904
905        // Generic offload should be disabled because of overflow.
906        assert_eq!(context.csum_offload_result(), None);
907    }
908
909    #[test]
910    fn generic_csum_offload_enabled_with_inner_overflow() {
911        let mut payload = [0u8; 100];
912        // Use a header length that exceeds u16::MAX (65535).
913        let test_packet = TestPacketBuilder { header_len: 66000 };
914        let udp = UdpPacketBuilder::new(
915            SRC_IP_V6,
916            DST_IP_V6,
917            NonZeroU16::new(SRC_PORT),
918            NonZeroU16::new(DST_PORT).unwrap(),
919        );
920        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv6, 0);
921
922        // Wrap test_packet *inside* UDP, bypassing IP to avoid IP size limits.
923        // Buf -> test_packet -> udp -> ethernet.
924        let serializer =
925            Buf::new(&mut payload[..], ..).wrap_in(test_packet).wrap_in(udp).wrap_in(ethernet);
926
927        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
928        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
929
930        // Generic offload should work despite the overflow inside the UDP
931        // packet.
932        assert_eq!(
933            context.csum_offload_result(),
934            Some(ChecksumOffloadResult::Generic(PartialChecksum {
935                start: 14, // Ethernet header length.
936                offset: UDP_CHECKSUM_OFFSET
937            }))
938        );
939    }
940
941    #[test]
942    fn protocol_specific_csum_offload_with_size_limit() {
943        let mut payload = [0u8; 100];
944        let udp = UdpPacketBuilder::new(
945            SRC_IP_V4,
946            DST_IP_V4,
947            NonZeroU16::new(SRC_PORT),
948            NonZeroU16::new(DST_PORT).unwrap(),
949        );
950        let ip = Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
951        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
952
953        // Buf -> udp -> with_size_limit -> ip -> ethernet.
954        let serializer = Buf::new(&mut payload[..], ..)
955            .wrap_in(udp)
956            // Tests that intermediate protocol-less packet builders like
957            // `LimitedSizePacketBuilder` don't break protocol-specific
958            // offloading.
959            .with_size_limit(1000)
960            .wrap_in(ip)
961            .wrap_in(ethernet);
962
963        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
964            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
965        ));
966        let _ = serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
967
968        assert_eq!(
969            context.csum_offload_result(),
970            Some(ChecksumOffloadResult::ProtocolSpecific(
971                OffloadableProtocols::ETHERNET
972                    | OffloadableProtocols::IPV4
973                    | OffloadableProtocols::UDP
974            ))
975        );
976    }
977
978    #[test]
979    fn protocol_specific_csum_offload_duplicate_protocol() {
980        let mut payload = [0u8; 100];
981        let udp_inner = UdpPacketBuilder::new(
982            SRC_IP_V4,
983            DST_IP_V4,
984            NonZeroU16::new(SRC_PORT),
985            NonZeroU16::new(DST_PORT).unwrap(),
986        );
987        let ip_inner =
988            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
989        let udp_outer = UdpPacketBuilder::new(
990            SRC_IP_V4,
991            DST_IP_V4,
992            NonZeroU16::new(SRC_PORT),
993            NonZeroU16::new(DST_PORT).unwrap(),
994        );
995        let ip_outer =
996            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
997        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
998
999        // Buf -> udp_inner -> ip_inner -> udp_outer -> ip_outer -> ethernet.
1000        let serializer = Buf::new(&mut payload[..], ..)
1001            .wrap_in(udp_inner)
1002            .wrap_in(ip_inner)
1003            .wrap_in(udp_outer)
1004            .wrap_in(ip_outer)
1005            .wrap_in(ethernet);
1006
1007        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::protocol_specific(
1008            ProtocolSpecificOffloadSpec::tcp_or_udp_over_ipv4(),
1009        ));
1010        let buf =
1011            serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
1012
1013        // Protocol-specific offload should work for the outer UDP packet even
1014        // with the duplicate UDP packet.
1015        assert_eq!(
1016            context.csum_offload_result(),
1017            Some(ChecksumOffloadResult::ProtocolSpecific(
1018                OffloadableProtocols::ETHERNET
1019                    | OffloadableProtocols::IPV4
1020                    | OffloadableProtocols::UDP
1021            ))
1022        );
1023
1024        let mut buf_ref = buf.as_ref();
1025        let eth = buf_ref
1026            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
1027            .expect("ethernet parse should succeed");
1028        let mut body = eth.body();
1029        let ip_out = body.parse::<Ipv4Packet<_>>().expect("outer ipv4 parse should succeed");
1030
1031        // Parse outer UDP as raw (succeeds since it doesn't validate checksum).
1032        let mut outer_udp_bytes = ip_out.body();
1033        let udp_out_raw = outer_udp_bytes
1034            .parse_with::<_, UdpPacketRaw<_>>(IpVersionMarker::<Ipv4>::default())
1035            .expect("outer udp parse should succeed");
1036
1037        // Try to validate outer UDP, which should fail checksum validation
1038        // because the checksum was offloaded.
1039        assert_eq!(
1040            UdpPacket::try_from_raw_with(
1041                udp_out_raw,
1042                UdpParseArgs::new(ip_out.src_ip(), ip_out.dst_ip())
1043            )
1044            .err(),
1045            Some(ParseError::Checksum),
1046        );
1047
1048        let mut inner_ip_bytes = &ip_out.body()[UDP_HEADER_BYTES..];
1049        let ip_in =
1050            inner_ip_bytes.parse::<Ipv4Packet<_>>().expect("inner ipv4 parse should succeed");
1051        let mut body = ip_in.body();
1052
1053        // This should succeed because inner UDP checksum was computed in
1054        // software.
1055        let _udp_in = body
1056            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(ip_in.src_ip(), ip_in.dst_ip()))
1057            .expect("inner udp parse should succeed");
1058    }
1059
1060    #[test]
1061    fn generic_csum_offload_duplicate_protocol() {
1062        let mut payload = [0u8; 100];
1063        let udp_inner = UdpPacketBuilder::new(
1064            SRC_IP_V4,
1065            DST_IP_V4,
1066            NonZeroU16::new(SRC_PORT),
1067            NonZeroU16::new(DST_PORT).unwrap(),
1068        );
1069        let ip_inner =
1070            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
1071        let udp_outer = UdpPacketBuilder::new(
1072            SRC_IP_V4,
1073            DST_IP_V4,
1074            NonZeroU16::new(SRC_PORT),
1075            NonZeroU16::new(DST_PORT).unwrap(),
1076        );
1077        let ip_outer =
1078            Ipv4PacketBuilder::new(SRC_IP_V4, DST_IP_V4, 64, Ipv4Proto::Proto(IpProto::Udp));
1079        let ethernet = EthernetFrameBuilder::new(SRC_MAC, DST_MAC, EtherType::Ipv4, 0);
1080
1081        // Buf -> udp_inner -> ip_inner -> udp_outer -> ip_outer -> ethernet.
1082        let serializer = Buf::new(&mut payload[..], ..)
1083            .wrap_in(udp_inner)
1084            .wrap_in(ip_inner)
1085            .wrap_in(udp_outer)
1086            .wrap_in(ip_outer)
1087            .wrap_in(ethernet);
1088
1089        let mut context = NetworkSerializationContext::new(ChecksumOffloadSpec::generic());
1090        let buf =
1091            serializer.serialize_vec_outer(&mut context).expect("serialization should succeed");
1092
1093        // Generic offload should apply to the inner UDP packet.
1094        // Eth (14) + outer IPv4 (20) + UDP (8) + inner IPv4 (20) = 62.
1095        assert_eq!(
1096            context.csum_offload_result(),
1097            Some(ChecksumOffloadResult::Generic(PartialChecksum {
1098                start: 62,
1099                offset: UDP_CHECKSUM_OFFSET
1100            }))
1101        );
1102
1103        let mut buf_ref = buf.as_ref();
1104        let eth = buf_ref
1105            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
1106            .expect("ethernet parse should succeed");
1107        let mut body = eth.body();
1108        let ip_out = body.parse::<Ipv4Packet<_>>().expect("outer ipv4 parse should succeed");
1109
1110        // Outer UDP checksum was computed in software, so parse should succeed.
1111        let mut outer_udp_bytes = ip_out.body();
1112        let _udp_out = outer_udp_bytes
1113            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(ip_out.src_ip(), ip_out.dst_ip()))
1114            .expect("outer udp parse should succeed");
1115
1116        let mut inner_ip_bytes = &ip_out.body()[UDP_HEADER_BYTES..];
1117        let ip_in =
1118            inner_ip_bytes.parse::<Ipv4Packet<_>>().expect("inner ipv4 parse should succeed");
1119        let mut body = ip_in.body();
1120
1121        // Parse inner UDP as raw (succeeds since it doesn't validate checksum).
1122        let udp_in_raw = body
1123            .parse_with::<_, UdpPacketRaw<_>>(IpVersionMarker::<Ipv4>::default())
1124            .expect("inner udp parse should succeed");
1125
1126        // Try to validate inner UDP, which should fail checksum validation
1127        // because the checksum was offloaded.
1128        assert_eq!(
1129            UdpPacket::try_from_raw_with(
1130                udp_in_raw,
1131                UdpParseArgs::new(ip_in.src_ip(), ip_in.dst_ip())
1132            )
1133            .err(),
1134            Some(ParseError::Checksum),
1135        );
1136    }
1137
1138    fn build_udp_packet_invalid_csum<I: IpAddress>(
1139        src_ip: I,
1140        dst_ip: I,
1141        body: &mut [u8],
1142    ) -> Vec<u8> {
1143        let mut buf = Buf::new(body, ..)
1144            .wrap_in(UdpPacketBuilder::new(
1145                src_ip,
1146                dst_ip,
1147                NonZeroU16::new(1),
1148                NonZeroU16::new(2).unwrap(),
1149            ))
1150            .serialize_vec_outer(&mut NetworkSerializationContext::default())
1151            .unwrap()
1152            .as_ref()
1153            .to_vec();
1154
1155        // Corrupt the checksum.
1156        buf[packet_formats::udp::CHECKSUM_OFFSET] ^= 0xFF;
1157        buf[packet_formats::udp::CHECKSUM_OFFSET + 1] ^= 0xFF;
1158        buf
1159    }
1160
1161    /// Builds a UDP packet containing `nesting-1` nested UDP packets, all with
1162    /// invalid checksums.
1163    fn build_nested_udp_packets_invalid_csums<I: IpAddress>(
1164        src_ip: I,
1165        dst_ip: I,
1166        nesting: usize,
1167    ) -> Vec<u8> {
1168        let mut payload = alloc::vec![0u8; 100];
1169        for _ in 0..nesting {
1170            payload = build_udp_packet_invalid_csum(src_ip, dst_ip, &mut payload);
1171        }
1172        payload
1173    }
1174
1175    #[test]
1176    fn checksum_rx_offloading_none() {
1177        let buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 1);
1178
1179        // `None` offloads no checksums so we expect to be unable to parse any
1180        // UDP packets with invalid checksums.
1181        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::Offloaded(None));
1182        let mut buf_ref: &[u8] = buf.as_ref();
1183        assert_eq!(
1184            buf_ref
1185                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1186                    SRC_IP_V4, DST_IP_V4, &mut ctx
1187                ))
1188                .err(),
1189            Some(ParseError::Checksum)
1190        );
1191        assert_eq!(ctx.verified_checksum_count(), 0);
1192    }
1193
1194    #[test]
1195    fn checksum_rx_offloading_fully_offloaded() {
1196        let mut buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 3);
1197
1198        // `FullyOffloaded` offloads all checksums so we expect to be able to
1199        // parse an arbitrary number of UDP packets with invalid checksums.
1200        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::FullyOffloaded);
1201        for _ in 0..3 {
1202            let mut buf_ref: &[u8] = buf.as_ref();
1203            buf = buf_ref
1204                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1205                    SRC_IP_V4, DST_IP_V4, &mut ctx,
1206                ))
1207                .expect("udp parse should succeed")
1208                .body()
1209                .to_vec();
1210        }
1211        assert_eq!(ctx.verified_checksum_count(), 0);
1212    }
1213
1214    #[test]
1215    fn checksum_rx_offloading_offloaded() {
1216        let mut buf = build_nested_udp_packets_invalid_csums(SRC_IP_V4, DST_IP_V4, 3);
1217
1218        // `Offloaded` indicates the number checksums not to verify so we expect
1219        // to be able to parse exactly two UDP packets with invalid checksums.
1220        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::Offloaded(Some(
1221            NonZeroU16::new(2).unwrap(),
1222        )));
1223        for _ in 0..2 {
1224            let mut buf_ref: &[u8] = buf.as_ref();
1225            buf = buf_ref
1226                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1227                    SRC_IP_V4, DST_IP_V4, &mut ctx,
1228                ))
1229                .expect("udp parse should succeed")
1230                .body()
1231                .to_vec();
1232        }
1233        let mut buf_ref: &[u8] = buf.as_ref();
1234        assert_eq!(
1235            buf_ref
1236                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1237                    SRC_IP_V4, DST_IP_V4, &mut ctx
1238                ))
1239                .err(),
1240            Some(ParseError::Checksum)
1241        );
1242        assert_eq!(ctx.verified_checksum_count(), 0);
1243    }
1244
1245    #[test]
1246    fn checksum_rx_offloading_verified_ok() {
1247        let mut payload = [0u8; 10];
1248        let buf = Buf::new(&mut payload[..], ..)
1249            .wrap_in(UdpPacketBuilder::new(
1250                SRC_IP_V4,
1251                DST_IP_V4,
1252                Some(NonZeroU16::new(1234).unwrap()),
1253                NonZeroU16::new(5678).unwrap(),
1254            ))
1255            .serialize_vec_outer(&mut NetworkSerializationContext::default())
1256            .unwrap()
1257            .as_ref()
1258            .to_vec();
1259
1260        let mut ctx = NetworkParsingContext::new(ChecksumRxOffloading::Offloaded(None));
1261        let mut buf_ref: &[u8] = buf.as_ref();
1262        assert!(
1263            buf_ref
1264                .parse_with::<_, UdpPacket<_>>(UdpParseArgs::with_context(
1265                    SRC_IP_V4, DST_IP_V4, &mut ctx
1266                ))
1267                .is_ok()
1268        );
1269        assert_eq!(ctx.verified_checksum_count(), 1);
1270    }
1271}