packet_formats/
ethernet.rs

1// Copyright 2018 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//! Parsing and serialization of Ethernet frames.
6
7use net_types::ethernet::Mac;
8use net_types::ip::{Ip, IpVersion, Ipv4, Ipv6};
9use packet::{
10    BufferView, BufferViewMut, FragmentedBytesMut, PacketBuilder, PacketConstraints,
11    ParsablePacket, ParseMetadata, SerializeTarget,
12};
13use zerocopy::byteorder::network_endian::{U16, U32};
14use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};
15
16use crate::error::{ParseError, ParseResult};
17
18const ETHERNET_MIN_ILLEGAL_ETHERTYPE: u16 = 1501;
19const ETHERNET_MAX_ILLEGAL_ETHERTYPE: u16 = 1535;
20
21create_protocol_enum!(
22    /// An EtherType number.
23    #[allow(missing_docs)]
24    #[derive(Copy, Clone, Hash, Eq, PartialEq)]
25    pub enum EtherType: u16 {
26        Ipv4, 0x0800, "IPv4";
27        Arp, 0x0806, "ARP";
28        Ipv6, 0x86DD, "IPv6";
29        _, "EtherType {}";
30    }
31);
32
33impl EtherType {
34    /// Constructs the relevant [`EtherType`] from the given [`IpVersion`].
35    pub fn from_ip_version(ip_version: IpVersion) -> Self {
36        match ip_version {
37            IpVersion::V4 => EtherType::Ipv4,
38            IpVersion::V6 => EtherType::Ipv6,
39        }
40    }
41}
42
43/// An extension trait adding IP-related functionality to `Ipv4` and `Ipv6`.
44pub trait EthernetIpExt: Ip {
45    /// The `EtherType` value for an associated IP version.
46    const ETHER_TYPE: EtherType;
47}
48
49impl EthernetIpExt for Ipv4 {
50    const ETHER_TYPE: EtherType = EtherType::Ipv4;
51}
52
53impl EthernetIpExt for Ipv6 {
54    const ETHER_TYPE: EtherType = EtherType::Ipv6;
55}
56
57#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
58#[repr(C)]
59struct HeaderPrefix {
60    dst_mac: Mac,
61    src_mac: Mac,
62}
63
64const TPID_8021Q: u16 = 0x8100;
65const TPID_8021AD: u16 = 0x88a8;
66
67/// An Ethernet frame.
68///
69/// An `EthernetFrame` shares its underlying memory with the byte slice it was
70/// parsed from or serialized to, meaning that no copying or extra allocation is
71/// necessary.
72pub struct EthernetFrame<B> {
73    hdr_prefix: Ref<B, HeaderPrefix>,
74    tag: Option<Ref<B, U32>>,
75    ethertype: Ref<B, U16>,
76    body: B,
77}
78
79/// Whether or not an Ethernet frame's length should be checked during parsing.
80///
81/// When the `Check` variant is used, the Ethernet frame will be rejected if its
82/// total length (including header, but excluding the Frame Check Sequence (FCS)
83/// footer) is less than the required minimum of 60 bytes.
84#[derive(PartialEq)]
85pub enum EthernetFrameLengthCheck {
86    /// Check that the Ethernet frame's total length (including header, but
87    /// excluding the Frame Check Sequence (FCS) footer) satisfies the required
88    /// minimum of 60 bytes.
89    Check,
90    /// Do not check the Ethernet frame's total length. The frame will still be
91    /// rejected if a complete, valid header is not present, but the body may be
92    /// 0 bytes long.
93    NoCheck,
94}
95
96impl<B: SplitByteSlice> ParsablePacket<B, EthernetFrameLengthCheck> for EthernetFrame<B> {
97    type Error = ParseError;
98
99    fn parse_metadata(&self) -> ParseMetadata {
100        let header_len = Ref::bytes(&self.hdr_prefix).len()
101            + self.tag.as_ref().map(|tag| Ref::bytes(tag).len()).unwrap_or(0)
102            + Ref::bytes(&self.ethertype).len();
103        ParseMetadata::from_packet(header_len, self.body.len(), 0)
104    }
105
106    fn parse<BV: BufferView<B>>(
107        mut buffer: BV,
108        length_check: EthernetFrameLengthCheck,
109    ) -> ParseResult<Self> {
110        // See for details: https://en.wikipedia.org/wiki/Ethernet_frame#Frame_%E2%80%93_data_link_layer
111
112        let hdr_prefix = buffer
113            .take_obj_front::<HeaderPrefix>()
114            .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?;
115        if length_check == EthernetFrameLengthCheck::Check && buffer.len() < 48 {
116            // The minimum frame size (not including the Frame Check Sequence
117            // (FCS) footer, which we do not handle in this code) is 60 bytes.
118            // We've already consumed 12 bytes for the header prefix, so we must
119            // have at least 48 bytes left.
120            return debug_err!(Err(ParseError::Format), "too few bytes for frame");
121        }
122
123        // The tag (either IEEE 802.1Q or 802.1ad) is an optional four-byte
124        // field. If present, it precedes the ethertype, and its first two bytes
125        // (where the ethertype bytes are normally) are called the Tag Protocol
126        // Identifier (TPID). A TPID of TPID_8021Q implies an 802.1Q tag, a TPID
127        // of TPID_8021AD implies an 802.1ad tag, and anything else implies that
128        // there is no tag - it's a normal ethertype field.
129        let ethertype_or_tpid = buffer
130            .peek_obj_front::<U16>()
131            .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?
132            .get();
133        let (tag, ethertype, body) = match ethertype_or_tpid {
134            self::TPID_8021Q | self::TPID_8021AD => (
135                Some(
136                    buffer.take_obj_front().ok_or_else(debug_err_fn!(
137                        ParseError::Format,
138                        "too few bytes for header"
139                    ))?,
140                ),
141                buffer
142                    .take_obj_front()
143                    .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?,
144                buffer.into_rest(),
145            ),
146            _ => (
147                None,
148                buffer
149                    .take_obj_front()
150                    .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?,
151                buffer.into_rest(),
152            ),
153        };
154
155        let frame = EthernetFrame { hdr_prefix, tag, ethertype, body };
156        let et = frame.ethertype.get();
157        if (ETHERNET_MIN_ILLEGAL_ETHERTYPE..=ETHERNET_MAX_ILLEGAL_ETHERTYPE).contains(&et)
158            || (et < ETHERNET_MIN_ILLEGAL_ETHERTYPE && et as usize != frame.body.len())
159        {
160            // EtherType values between 1500 and 1536 are disallowed, and values
161            // of 1500 and below are used to indicate the body length.
162            return debug_err!(Err(ParseError::Format), "invalid ethertype number: {:x}", et);
163        }
164        Ok(frame)
165    }
166}
167
168impl<B: SplitByteSlice> EthernetFrame<B> {
169    /// The frame body.
170    pub fn body(&self) -> &[u8] {
171        &self.body
172    }
173
174    /// Consumes the frame and returns the body.
175    pub fn into_body(self) -> B
176    where
177        B: Copy,
178    {
179        self.body
180    }
181
182    /// The source MAC address.
183    pub fn src_mac(&self) -> Mac {
184        self.hdr_prefix.src_mac
185    }
186
187    /// The destination MAC address.
188    pub fn dst_mac(&self) -> Mac {
189        self.hdr_prefix.dst_mac
190    }
191
192    /// The EtherType.
193    ///
194    /// `ethertype` returns the `EtherType` from the Ethernet header. However,
195    /// some values of the EtherType header field are used to indicate the
196    /// length of the frame's body. In this case, `ethertype` returns `None`.
197    pub fn ethertype(&self) -> Option<EtherType> {
198        let et = self.ethertype.get();
199        if et < ETHERNET_MIN_ILLEGAL_ETHERTYPE {
200            return None;
201        }
202        // values in (1500, 1536) are illegal, and shouldn't make it through
203        // parse
204        debug_assert!(et > ETHERNET_MAX_ILLEGAL_ETHERTYPE);
205        Some(EtherType::from(et))
206    }
207
208    // The size of the frame header.
209    fn header_len(&self) -> usize {
210        Ref::bytes(&self.hdr_prefix).len()
211            + self.tag.as_ref().map(|t| Ref::bytes(t).len()).unwrap_or(0)
212            + Ref::bytes(&self.ethertype).len()
213    }
214
215    // Total frame length including header prefix, tag, EtherType, and body.
216    // This is not the same as the length as optionally encoded in the
217    // EtherType.
218    // TODO(rheacock): remove `allow(dead_code)` when this is used.
219    #[allow(dead_code)]
220    fn total_frame_len(&self) -> usize {
221        self.header_len() + self.body.len()
222    }
223
224    /// Construct a builder with the same contents as this frame.
225    pub fn builder(&self) -> EthernetFrameBuilder {
226        EthernetFrameBuilder {
227            src_mac: self.src_mac(),
228            dst_mac: self.dst_mac(),
229            ethertype: self.ethertype.get(),
230            min_body_len: ETHERNET_MIN_BODY_LEN_NO_TAG,
231        }
232    }
233}
234
235/// A builder for Ethernet frames.
236///
237/// A [`PacketBuilder`] that serializes into an Ethernet frame. The padding
238/// parameter `P` can be used to choose how the body of the frame is padded.
239#[derive(Debug, Clone)]
240pub struct EthernetFrameBuilder {
241    src_mac: Mac,
242    dst_mac: Mac,
243    ethertype: u16,
244    min_body_len: usize,
245}
246
247impl EthernetFrameBuilder {
248    /// Construct a new `EthernetFrameBuilder`.
249    ///
250    /// The provided source and destination [`Mac`] addresses and [`EtherType`]
251    /// will be placed in the Ethernet frame header. The `min_body_len`
252    /// parameter sets the minimum length of the frame's body in bytes. If,
253    /// during serialization, the inner packet builder produces a smaller body
254    /// than `min_body_len`, it will be padded with trailing zero bytes up to
255    /// `min_body_len`.
256    pub fn new(
257        src_mac: Mac,
258        dst_mac: Mac,
259        ethertype: EtherType,
260        min_body_len: usize,
261    ) -> EthernetFrameBuilder {
262        EthernetFrameBuilder { src_mac, dst_mac, ethertype: ethertype.into(), min_body_len }
263    }
264
265    /// Returns the source MAC address for the builder.
266    pub fn src_mac(&self) -> Mac {
267        self.src_mac
268    }
269
270    /// Returns the destination MAC address for the builder.
271    pub fn dst_mac(&self) -> Mac {
272        self.dst_mac
273    }
274}
275
276// NOTE(joshlf): header_len and min_body_len assume no 802.1Q or 802.1ad tag. We
277// don't support creating packets with these tags at the moment, so this is a
278// sound assumption. If we support them in the future, we will need to update
279// these to compute dynamically.
280
281impl PacketBuilder for EthernetFrameBuilder {
282    fn constraints(&self) -> PacketConstraints {
283        PacketConstraints::new(ETHERNET_HDR_LEN_NO_TAG, 0, self.min_body_len, core::usize::MAX)
284    }
285
286    fn serialize(&self, target: &mut SerializeTarget<'_>, body: FragmentedBytesMut<'_, '_>) {
287        // NOTE: EtherType values of 1500 and below are used to indicate the
288        // length of the body in bytes. We don't need to validate this because
289        // the EtherType enum has no variants with values in that range.
290
291        let total_len = target.header.len() + body.len();
292        // implements BufferViewMut, giving us take_obj_xxx_zero methods
293        let mut header = &mut target.header;
294
295        header
296            .write_obj_front(&HeaderPrefix { src_mac: self.src_mac, dst_mac: self.dst_mac })
297            .expect("too few bytes for Ethernet header");
298        header
299            .write_obj_front(&U16::new(self.ethertype))
300            .expect("too few bytes for Ethernet header");
301
302        // NOTE(joshlf): This doesn't include the tag. If we ever add support
303        // for serializing tags, we will need to update this.
304        let min_frame_size = self.min_body_len + ETHERNET_HDR_LEN_NO_TAG;
305
306        // Assert this here so that if there isn't enough space for even an
307        // Ethernet header, we report that more specific error.
308        assert!(
309            total_len >= min_frame_size,
310            "total frame size of {} bytes is below minimum frame size of {}",
311            total_len,
312            min_frame_size,
313        );
314    }
315}
316
317/// The length of an Ethernet header when it has no tags.
318pub const ETHERNET_HDR_LEN_NO_TAG: usize = 14;
319
320/// The minimum length of an Ethernet frame's body when the header contains no tags.
321pub const ETHERNET_MIN_BODY_LEN_NO_TAG: usize = 46;
322
323/// Constants useful for testing.
324pub mod testutil {
325    pub use super::{ETHERNET_HDR_LEN_NO_TAG, ETHERNET_MIN_BODY_LEN_NO_TAG};
326
327    /// Ethernet frame, in bytes.
328    pub const ETHERNET_DST_MAC_BYTE_OFFSET: usize = 0;
329
330    /// The offset to the start of the source MAC address from the start of the
331    /// Ethernet frame, in bytes.
332    pub const ETHERNET_SRC_MAC_BYTE_OFFSET: usize = 6;
333}
334
335#[cfg(test)]
336mod tests {
337    use byteorder::{ByteOrder, NetworkEndian};
338    use packet::{
339        AsFragmentedByteSlice, Buf, GrowBufferMut, InnerPacketBuilder, ParseBuffer, Serializer,
340    };
341
342    use super::*;
343
344    const DEFAULT_DST_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]);
345    const DEFAULT_SRC_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]);
346    const ETHERNET_ETHERTYPE_BYTE_OFFSET: usize = 12;
347    const ETHERNET_MIN_FRAME_LEN: usize = 60;
348
349    // Return a buffer for testing parsing with values 0..60 except for the
350    // EtherType field, which is EtherType::Arp. Also return the contents
351    // of the body.
352    fn new_parse_buf() -> ([u8; ETHERNET_MIN_FRAME_LEN], [u8; ETHERNET_MIN_BODY_LEN_NO_TAG]) {
353        let mut buf = [0; ETHERNET_MIN_FRAME_LEN];
354        for (i, elem) in buf.iter_mut().enumerate() {
355            *elem = i as u8;
356        }
357        NetworkEndian::write_u16(&mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..], EtherType::Arp.into());
358        let mut body = [0; ETHERNET_MIN_BODY_LEN_NO_TAG];
359        (&mut body).copy_from_slice(&buf[ETHERNET_HDR_LEN_NO_TAG..]);
360        (buf, body)
361    }
362
363    // Return a test buffer with values 0..46 to be used as a test payload for
364    // serialization.
365    fn new_serialize_buf() -> [u8; ETHERNET_MIN_BODY_LEN_NO_TAG] {
366        let mut buf = [0; ETHERNET_MIN_BODY_LEN_NO_TAG];
367        for (i, elem) in buf.iter_mut().enumerate() {
368            *elem = i as u8;
369        }
370        buf
371    }
372
373    #[test]
374    fn test_parse() {
375        crate::testutil::set_logger_for_test();
376        let (mut backing_buf, body) = new_parse_buf();
377        let mut buf = &mut backing_buf[..];
378        // Test parsing with a sufficiently long body.
379        let frame = buf.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap();
380        assert_eq!(frame.hdr_prefix.dst_mac, DEFAULT_DST_MAC);
381        assert_eq!(frame.hdr_prefix.src_mac, DEFAULT_SRC_MAC);
382        assert!(frame.tag.is_none());
383        assert_eq!(frame.ethertype(), Some(EtherType::Arp));
384        assert_eq!(frame.body(), &body[..]);
385        // Test parsing with a too-short body but length checking disabled.
386        let mut buf = &mut backing_buf[..ETHERNET_HDR_LEN_NO_TAG];
387        let frame =
388            buf.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck).unwrap();
389        assert_eq!(frame.hdr_prefix.dst_mac, DEFAULT_DST_MAC);
390        assert_eq!(frame.hdr_prefix.src_mac, DEFAULT_SRC_MAC);
391        assert!(frame.tag.is_none());
392        assert_eq!(frame.ethertype(), Some(EtherType::Arp));
393        assert_eq!(frame.body(), &[]);
394
395        // For both of the TPIDs that imply the existence of a tag, make sure
396        // that the tag is present and correct (and that all of the normal
397        // checks succeed).
398        for tpid in [TPID_8021Q, TPID_8021AD].iter() {
399            let (mut buf, body) = new_parse_buf();
400            let mut buf = &mut buf[..];
401
402            const TPID_OFFSET: usize = 12;
403            NetworkEndian::write_u16(&mut buf[TPID_OFFSET..], *tpid);
404            // write a valid EtherType
405            NetworkEndian::write_u16(&mut buf[TPID_OFFSET + 4..], EtherType::Arp.into());
406
407            let frame =
408                buf.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap();
409            assert_eq!(frame.hdr_prefix.dst_mac, DEFAULT_DST_MAC);
410            assert_eq!(frame.hdr_prefix.src_mac, DEFAULT_SRC_MAC);
411            assert_eq!(frame.ethertype(), Some(EtherType::Arp));
412
413            // help out with type inference
414            let tag: &U32 = frame.tag.as_ref().unwrap();
415            let want_tag =
416                u32::from(*tpid) << 16 | ((TPID_OFFSET as u32 + 2) << 8) | (TPID_OFFSET as u32 + 3);
417            assert_eq!(tag.get(), want_tag);
418            // Offset by 4 since new_parse_buf returns a body on the assumption
419            // that there's no tag.
420            assert_eq!(frame.body(), &body[4..]);
421        }
422    }
423
424    #[test]
425    fn test_ethertype() {
426        // EtherTypes of 1500 and below must match the body length
427        let mut buf = [0u8; 1014];
428        // an incorrect length results in error
429        NetworkEndian::write_u16(&mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..], 1001);
430        assert!((&mut buf[..])
431            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
432            .is_err());
433
434        // a correct length results in success
435        NetworkEndian::write_u16(&mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..], 1000);
436        assert_eq!(
437            (&mut buf[..])
438                .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
439                .unwrap()
440                .ethertype(),
441            None
442        );
443
444        // an unrecognized EtherType is returned numerically
445        let mut buf = [0u8; 1014];
446        NetworkEndian::write_u16(
447            &mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..],
448            ETHERNET_MAX_ILLEGAL_ETHERTYPE + 1,
449        );
450        assert_eq!(
451            (&mut buf[..])
452                .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
453                .unwrap()
454                .ethertype(),
455            Some(EtherType::Other(ETHERNET_MAX_ILLEGAL_ETHERTYPE + 1))
456        );
457    }
458
459    #[test]
460    fn test_serialize() {
461        let buf = (&new_serialize_buf()[..])
462            .into_serializer()
463            .encapsulate(EthernetFrameBuilder::new(
464                DEFAULT_DST_MAC,
465                DEFAULT_SRC_MAC,
466                EtherType::Arp,
467                ETHERNET_MIN_BODY_LEN_NO_TAG,
468            ))
469            .serialize_vec_outer()
470            .unwrap();
471        assert_eq!(
472            &buf.as_ref()[..ETHERNET_HDR_LEN_NO_TAG],
473            [6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 0x08, 0x06]
474        );
475    }
476
477    #[test]
478    fn test_serialize_zeroes() {
479        // Test that EthernetFrame::serialize properly zeroes memory before
480        // serializing the header.
481        let mut buf_0 = [0; ETHERNET_MIN_FRAME_LEN];
482        let _: Buf<&mut [u8]> = Buf::new(&mut buf_0[..], ETHERNET_HDR_LEN_NO_TAG..)
483            .encapsulate(EthernetFrameBuilder::new(
484                DEFAULT_SRC_MAC,
485                DEFAULT_DST_MAC,
486                EtherType::Arp,
487                ETHERNET_MIN_BODY_LEN_NO_TAG,
488            ))
489            .serialize_vec_outer()
490            .unwrap()
491            .unwrap_a();
492        let mut buf_1 = [0; ETHERNET_MIN_FRAME_LEN];
493        (&mut buf_1[..ETHERNET_HDR_LEN_NO_TAG]).copy_from_slice(&[0xFF; ETHERNET_HDR_LEN_NO_TAG]);
494        let _: Buf<&mut [u8]> = Buf::new(&mut buf_1[..], ETHERNET_HDR_LEN_NO_TAG..)
495            .encapsulate(EthernetFrameBuilder::new(
496                DEFAULT_SRC_MAC,
497                DEFAULT_DST_MAC,
498                EtherType::Arp,
499                ETHERNET_MIN_BODY_LEN_NO_TAG,
500            ))
501            .serialize_vec_outer()
502            .unwrap()
503            .unwrap_a();
504        assert_eq!(&buf_0[..], &buf_1[..]);
505    }
506
507    #[test]
508    fn test_parse_error() {
509        // 1 byte shorter than the minimum
510        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN - 1];
511        assert!((&mut buf[..])
512            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
513            .is_err());
514
515        // 1 byte shorter than the minimum header length still fails even if
516        // length checking is disabled
517        let mut buf = [0u8; ETHERNET_HDR_LEN_NO_TAG - 1];
518        assert!((&mut buf[..])
519            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck)
520            .is_err());
521
522        // an ethertype of 1500 should be validated as the length of the body
523        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN];
524        NetworkEndian::write_u16(
525            &mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..],
526            ETHERNET_MIN_ILLEGAL_ETHERTYPE - 1,
527        );
528        assert!((&mut buf[..])
529            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
530            .is_err());
531
532        // an ethertype of 1501 is illegal because it's in the range [1501, 1535]
533        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN];
534        NetworkEndian::write_u16(
535            &mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..],
536            ETHERNET_MIN_ILLEGAL_ETHERTYPE,
537        );
538        assert!((&mut buf[..])
539            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
540            .is_err());
541
542        // an ethertype of 1535 is illegal
543        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN];
544        NetworkEndian::write_u16(
545            &mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..],
546            ETHERNET_MAX_ILLEGAL_ETHERTYPE,
547        );
548        assert!((&mut buf[..])
549            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
550            .is_err());
551    }
552
553    #[test]
554    #[should_panic(expected = "bytes is below minimum frame size of")]
555    fn test_serialize_panic() {
556        // create with a body which is below the minimum length
557        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN - 1];
558        let mut b = [&mut buf[..]];
559        let buf = b.as_fragmented_byte_slice();
560        let (header, body, footer) = buf.try_split_contiguous(ETHERNET_HDR_LEN_NO_TAG..).unwrap();
561        EthernetFrameBuilder::new(
562            Mac::new([0, 1, 2, 3, 4, 5]),
563            Mac::new([6, 7, 8, 9, 10, 11]),
564            EtherType::Arp,
565            ETHERNET_MIN_BODY_LEN_NO_TAG,
566        )
567        .serialize(&mut SerializeTarget { header, footer }, body);
568    }
569
570    #[test]
571    fn test_custom_min_body_len() {
572        const MIN_BODY_LEN: usize = 4;
573        const UNWRITTEN_BYTE: u8 = 0xAA;
574
575        let builder = EthernetFrameBuilder::new(
576            Mac::new([0, 1, 2, 3, 4, 5]),
577            Mac::new([6, 7, 8, 9, 10, 11]),
578            EtherType::Arp,
579            MIN_BODY_LEN,
580        );
581
582        let mut buffer = [UNWRITTEN_BYTE; ETHERNET_MIN_FRAME_LEN];
583        // TODO(https://fxbug.dev/42079821): Don't use this `#[doc(hidden)]`
584        // method, and use the public API instead.
585        GrowBufferMut::serialize(
586            &mut Buf::new(&mut buffer[..], ETHERNET_HDR_LEN_NO_TAG..ETHERNET_HDR_LEN_NO_TAG),
587            builder,
588        );
589
590        let (header, tail) = buffer.split_at(ETHERNET_HDR_LEN_NO_TAG);
591        let (padding, unwritten) = tail.split_at(MIN_BODY_LEN);
592        assert_eq!(
593            header,
594            &[
595                6, 7, 8, 9, 10, 11, // dst_mac
596                0, 1, 2, 3, 4, 5, // src_mac
597                08, 06, // ethertype
598            ]
599        );
600        assert_eq!(padding, &[0; MIN_BODY_LEN]);
601        assert_eq!(
602            unwritten,
603            &[UNWRITTEN_BYTE; ETHERNET_MIN_FRAME_LEN - MIN_BODY_LEN - ETHERNET_HDR_LEN_NO_TAG]
604        );
605    }
606}