packet_formats/
ethernet.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Parsing and serialization of Ethernet frames.

use net_types::ethernet::Mac;
use net_types::ip::{Ip, IpVersion, Ipv4, Ipv6};
use packet::{
    BufferView, BufferViewMut, FragmentedBytesMut, PacketBuilder, PacketConstraints,
    ParsablePacket, ParseMetadata, SerializeTarget,
};
use zerocopy::byteorder::network_endian::{U16, U32};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};

use crate::error::{ParseError, ParseResult};

const ETHERNET_MIN_ILLEGAL_ETHERTYPE: u16 = 1501;
const ETHERNET_MAX_ILLEGAL_ETHERTYPE: u16 = 1535;

create_protocol_enum!(
    /// An EtherType number.
    #[allow(missing_docs)]
    #[derive(Copy, Clone, Hash, Eq, PartialEq)]
    pub enum EtherType: u16 {
        Ipv4, 0x0800, "IPv4";
        Arp, 0x0806, "ARP";
        Ipv6, 0x86DD, "IPv6";
        _, "EtherType {}";
    }
);

impl EtherType {
    /// Constructs the relevant [`EtherType`] from the given [`IpVersion`].
    pub fn from_ip_version(ip_version: IpVersion) -> Self {
        match ip_version {
            IpVersion::V4 => EtherType::Ipv4,
            IpVersion::V6 => EtherType::Ipv6,
        }
    }
}

/// An extension trait adding IP-related functionality to `Ipv4` and `Ipv6`.
pub trait EthernetIpExt: Ip {
    /// The `EtherType` value for an associated IP version.
    const ETHER_TYPE: EtherType;
}

impl EthernetIpExt for Ipv4 {
    const ETHER_TYPE: EtherType = EtherType::Ipv4;
}

impl EthernetIpExt for Ipv6 {
    const ETHER_TYPE: EtherType = EtherType::Ipv6;
}

#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
#[repr(C)]
struct HeaderPrefix {
    dst_mac: Mac,
    src_mac: Mac,
}

const TPID_8021Q: u16 = 0x8100;
const TPID_8021AD: u16 = 0x88a8;

/// An Ethernet frame.
///
/// An `EthernetFrame` shares its underlying memory with the byte slice it was
/// parsed from or serialized to, meaning that no copying or extra allocation is
/// necessary.
pub struct EthernetFrame<B> {
    hdr_prefix: Ref<B, HeaderPrefix>,
    tag: Option<Ref<B, U32>>,
    ethertype: Ref<B, U16>,
    body: B,
}

/// Whether or not an Ethernet frame's length should be checked during parsing.
///
/// When the `Check` variant is used, the Ethernet frame will be rejected if its
/// total length (including header, but excluding the Frame Check Sequence (FCS)
/// footer) is less than the required minimum of 60 bytes.
#[derive(PartialEq)]
pub enum EthernetFrameLengthCheck {
    /// Check that the Ethernet frame's total length (including header, but
    /// excluding the Frame Check Sequence (FCS) footer) satisfies the required
    /// minimum of 60 bytes.
    Check,
    /// Do not check the Ethernet frame's total length. The frame will still be
    /// rejected if a complete, valid header is not present, but the body may be
    /// 0 bytes long.
    NoCheck,
}

impl<B: SplitByteSlice> ParsablePacket<B, EthernetFrameLengthCheck> for EthernetFrame<B> {
    type Error = ParseError;

    fn parse_metadata(&self) -> ParseMetadata {
        let header_len = Ref::bytes(&self.hdr_prefix).len()
            + self.tag.as_ref().map(|tag| Ref::bytes(tag).len()).unwrap_or(0)
            + Ref::bytes(&self.ethertype).len();
        ParseMetadata::from_packet(header_len, self.body.len(), 0)
    }

    fn parse<BV: BufferView<B>>(
        mut buffer: BV,
        length_check: EthernetFrameLengthCheck,
    ) -> ParseResult<Self> {
        // See for details: https://en.wikipedia.org/wiki/Ethernet_frame#Frame_%E2%80%93_data_link_layer

        let hdr_prefix = buffer
            .take_obj_front::<HeaderPrefix>()
            .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?;
        if length_check == EthernetFrameLengthCheck::Check && buffer.len() < 48 {
            // The minimum frame size (not including the Frame Check Sequence
            // (FCS) footer, which we do not handle in this code) is 60 bytes.
            // We've already consumed 12 bytes for the header prefix, so we must
            // have at least 48 bytes left.
            return debug_err!(Err(ParseError::Format), "too few bytes for frame");
        }

        // The tag (either IEEE 802.1Q or 802.1ad) is an optional four-byte
        // field. If present, it precedes the ethertype, and its first two bytes
        // (where the ethertype bytes are normally) are called the Tag Protocol
        // Identifier (TPID). A TPID of TPID_8021Q implies an 802.1Q tag, a TPID
        // of TPID_8021AD implies an 802.1ad tag, and anything else implies that
        // there is no tag - it's a normal ethertype field.
        let ethertype_or_tpid = buffer
            .peek_obj_front::<U16>()
            .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?
            .get();
        let (tag, ethertype, body) = match ethertype_or_tpid {
            self::TPID_8021Q | self::TPID_8021AD => (
                Some(
                    buffer.take_obj_front().ok_or_else(debug_err_fn!(
                        ParseError::Format,
                        "too few bytes for header"
                    ))?,
                ),
                buffer
                    .take_obj_front()
                    .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?,
                buffer.into_rest(),
            ),
            _ => (
                None,
                buffer
                    .take_obj_front()
                    .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?,
                buffer.into_rest(),
            ),
        };

        let frame = EthernetFrame { hdr_prefix, tag, ethertype, body };
        let et = frame.ethertype.get();
        if (ETHERNET_MIN_ILLEGAL_ETHERTYPE..=ETHERNET_MAX_ILLEGAL_ETHERTYPE).contains(&et)
            || (et < ETHERNET_MIN_ILLEGAL_ETHERTYPE && et as usize != frame.body.len())
        {
            // EtherType values between 1500 and 1536 are disallowed, and values
            // of 1500 and below are used to indicate the body length.
            return debug_err!(Err(ParseError::Format), "invalid ethertype number: {:x}", et);
        }
        Ok(frame)
    }
}

impl<B: SplitByteSlice> EthernetFrame<B> {
    /// The frame body.
    pub fn body(&self) -> &[u8] {
        &self.body
    }

    /// Consumes the frame and returns the body.
    pub fn into_body(self) -> B
    where
        B: Copy,
    {
        self.body
    }

    /// The source MAC address.
    pub fn src_mac(&self) -> Mac {
        self.hdr_prefix.src_mac
    }

    /// The destination MAC address.
    pub fn dst_mac(&self) -> Mac {
        self.hdr_prefix.dst_mac
    }

    /// The EtherType.
    ///
    /// `ethertype` returns the `EtherType` from the Ethernet header. However,
    /// some values of the EtherType header field are used to indicate the
    /// length of the frame's body. In this case, `ethertype` returns `None`.
    pub fn ethertype(&self) -> Option<EtherType> {
        let et = self.ethertype.get();
        if et < ETHERNET_MIN_ILLEGAL_ETHERTYPE {
            return None;
        }
        // values in (1500, 1536) are illegal, and shouldn't make it through
        // parse
        debug_assert!(et > ETHERNET_MAX_ILLEGAL_ETHERTYPE);
        Some(EtherType::from(et))
    }

    // The size of the frame header.
    fn header_len(&self) -> usize {
        Ref::bytes(&self.hdr_prefix).len()
            + self.tag.as_ref().map(|t| Ref::bytes(t).len()).unwrap_or(0)
            + Ref::bytes(&self.ethertype).len()
    }

    // Total frame length including header prefix, tag, EtherType, and body.
    // This is not the same as the length as optionally encoded in the
    // EtherType.
    // TODO(rheacock): remove `allow(dead_code)` when this is used.
    #[allow(dead_code)]
    fn total_frame_len(&self) -> usize {
        self.header_len() + self.body.len()
    }

    /// Construct a builder with the same contents as this frame.
    pub fn builder(&self) -> EthernetFrameBuilder {
        EthernetFrameBuilder {
            src_mac: self.src_mac(),
            dst_mac: self.dst_mac(),
            ethertype: self.ethertype.get(),
            min_body_len: ETHERNET_MIN_BODY_LEN_NO_TAG,
        }
    }
}

/// A builder for Ethernet frames.
///
/// A [`PacketBuilder`] that serializes into an Ethernet frame. The padding
/// parameter `P` can be used to choose how the body of the frame is padded.
#[derive(Debug)]
pub struct EthernetFrameBuilder {
    src_mac: Mac,
    dst_mac: Mac,
    ethertype: u16,
    min_body_len: usize,
}

impl EthernetFrameBuilder {
    /// Construct a new `EthernetFrameBuilder`.
    ///
    /// The provided source and destination [`Mac`] addresses and [`EtherType`]
    /// will be placed in the Ethernet frame header. The `min_body_len`
    /// parameter sets the minimum length of the frame's body in bytes. If,
    /// during serialization, the inner packet builder produces a smaller body
    /// than `min_body_len`, it will be padded with trailing zero bytes up to
    /// `min_body_len`.
    pub fn new(
        src_mac: Mac,
        dst_mac: Mac,
        ethertype: EtherType,
        min_body_len: usize,
    ) -> EthernetFrameBuilder {
        EthernetFrameBuilder { src_mac, dst_mac, ethertype: ethertype.into(), min_body_len }
    }
}

// NOTE(joshlf): header_len and min_body_len assume no 802.1Q or 802.1ad tag. We
// don't support creating packets with these tags at the moment, so this is a
// sound assumption. If we support them in the future, we will need to update
// these to compute dynamically.

impl PacketBuilder for EthernetFrameBuilder {
    fn constraints(&self) -> PacketConstraints {
        PacketConstraints::new(ETHERNET_HDR_LEN_NO_TAG, 0, self.min_body_len, core::usize::MAX)
    }

    fn serialize(&self, target: &mut SerializeTarget<'_>, body: FragmentedBytesMut<'_, '_>) {
        // NOTE: EtherType values of 1500 and below are used to indicate the
        // length of the body in bytes. We don't need to validate this because
        // the EtherType enum has no variants with values in that range.

        let total_len = target.header.len() + body.len();
        // implements BufferViewMut, giving us take_obj_xxx_zero methods
        let mut header = &mut target.header;

        header
            .write_obj_front(&HeaderPrefix { src_mac: self.src_mac, dst_mac: self.dst_mac })
            .expect("too few bytes for Ethernet header");
        header
            .write_obj_front(&U16::new(self.ethertype))
            .expect("too few bytes for Ethernet header");

        // NOTE(joshlf): This doesn't include the tag. If we ever add support
        // for serializing tags, we will need to update this.
        let min_frame_size = self.min_body_len + ETHERNET_HDR_LEN_NO_TAG;

        // Assert this here so that if there isn't enough space for even an
        // Ethernet header, we report that more specific error.
        assert!(
            total_len >= min_frame_size,
            "total frame size of {} bytes is below minimum frame size of {}",
            total_len,
            min_frame_size,
        );
    }
}

/// The length of an Ethernet header when it has no tags.
pub const ETHERNET_HDR_LEN_NO_TAG: usize = 14;

/// The minimum length of an Ethernet frame's body when the header contains no tags.
pub const ETHERNET_MIN_BODY_LEN_NO_TAG: usize = 46;

/// Constants useful for testing.
pub mod testutil {
    pub use super::{ETHERNET_HDR_LEN_NO_TAG, ETHERNET_MIN_BODY_LEN_NO_TAG};

    /// Ethernet frame, in bytes.
    pub const ETHERNET_DST_MAC_BYTE_OFFSET: usize = 0;

    /// The offset to the start of the source MAC address from the start of the
    /// Ethernet frame, in bytes.
    pub const ETHERNET_SRC_MAC_BYTE_OFFSET: usize = 6;
}

#[cfg(test)]
mod tests {
    use byteorder::{ByteOrder, NetworkEndian};
    use packet::{
        AsFragmentedByteSlice, Buf, GrowBufferMut, InnerPacketBuilder, ParseBuffer, Serializer,
    };

    use super::*;

    const DEFAULT_DST_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]);
    const DEFAULT_SRC_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]);
    const ETHERNET_ETHERTYPE_BYTE_OFFSET: usize = 12;
    const ETHERNET_MIN_FRAME_LEN: usize = 60;

    // Return a buffer for testing parsing with values 0..60 except for the
    // EtherType field, which is EtherType::Arp. Also return the contents
    // of the body.
    fn new_parse_buf() -> ([u8; ETHERNET_MIN_FRAME_LEN], [u8; ETHERNET_MIN_BODY_LEN_NO_TAG]) {
        let mut buf = [0; ETHERNET_MIN_FRAME_LEN];
        for (i, elem) in buf.iter_mut().enumerate() {
            *elem = i as u8;
        }
        NetworkEndian::write_u16(&mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..], EtherType::Arp.into());
        let mut body = [0; ETHERNET_MIN_BODY_LEN_NO_TAG];
        (&mut body).copy_from_slice(&buf[ETHERNET_HDR_LEN_NO_TAG..]);
        (buf, body)
    }

    // Return a test buffer with values 0..46 to be used as a test payload for
    // serialization.
    fn new_serialize_buf() -> [u8; ETHERNET_MIN_BODY_LEN_NO_TAG] {
        let mut buf = [0; ETHERNET_MIN_BODY_LEN_NO_TAG];
        for (i, elem) in buf.iter_mut().enumerate() {
            *elem = i as u8;
        }
        buf
    }

    #[test]
    fn test_parse() {
        crate::testutil::set_logger_for_test();
        let (mut backing_buf, body) = new_parse_buf();
        let mut buf = &mut backing_buf[..];
        // Test parsing with a sufficiently long body.
        let frame = buf.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap();
        assert_eq!(frame.hdr_prefix.dst_mac, DEFAULT_DST_MAC);
        assert_eq!(frame.hdr_prefix.src_mac, DEFAULT_SRC_MAC);
        assert!(frame.tag.is_none());
        assert_eq!(frame.ethertype(), Some(EtherType::Arp));
        assert_eq!(frame.body(), &body[..]);
        // Test parsing with a too-short body but length checking disabled.
        let mut buf = &mut backing_buf[..ETHERNET_HDR_LEN_NO_TAG];
        let frame =
            buf.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck).unwrap();
        assert_eq!(frame.hdr_prefix.dst_mac, DEFAULT_DST_MAC);
        assert_eq!(frame.hdr_prefix.src_mac, DEFAULT_SRC_MAC);
        assert!(frame.tag.is_none());
        assert_eq!(frame.ethertype(), Some(EtherType::Arp));
        assert_eq!(frame.body(), &[]);

        // For both of the TPIDs that imply the existence of a tag, make sure
        // that the tag is present and correct (and that all of the normal
        // checks succeed).
        for tpid in [TPID_8021Q, TPID_8021AD].iter() {
            let (mut buf, body) = new_parse_buf();
            let mut buf = &mut buf[..];

            const TPID_OFFSET: usize = 12;
            NetworkEndian::write_u16(&mut buf[TPID_OFFSET..], *tpid);
            // write a valid EtherType
            NetworkEndian::write_u16(&mut buf[TPID_OFFSET + 4..], EtherType::Arp.into());

            let frame =
                buf.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap();
            assert_eq!(frame.hdr_prefix.dst_mac, DEFAULT_DST_MAC);
            assert_eq!(frame.hdr_prefix.src_mac, DEFAULT_SRC_MAC);
            assert_eq!(frame.ethertype(), Some(EtherType::Arp));

            // help out with type inference
            let tag: &U32 = frame.tag.as_ref().unwrap();
            let want_tag =
                u32::from(*tpid) << 16 | ((TPID_OFFSET as u32 + 2) << 8) | (TPID_OFFSET as u32 + 3);
            assert_eq!(tag.get(), want_tag);
            // Offset by 4 since new_parse_buf returns a body on the assumption
            // that there's no tag.
            assert_eq!(frame.body(), &body[4..]);
        }
    }

    #[test]
    fn test_ethertype() {
        // EtherTypes of 1500 and below must match the body length
        let mut buf = [0u8; 1014];
        // an incorrect length results in error
        NetworkEndian::write_u16(&mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..], 1001);
        assert!((&mut buf[..])
            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
            .is_err());

        // a correct length results in success
        NetworkEndian::write_u16(&mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..], 1000);
        assert_eq!(
            (&mut buf[..])
                .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
                .unwrap()
                .ethertype(),
            None
        );

        // an unrecognized EtherType is returned numerically
        let mut buf = [0u8; 1014];
        NetworkEndian::write_u16(
            &mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..],
            ETHERNET_MAX_ILLEGAL_ETHERTYPE + 1,
        );
        assert_eq!(
            (&mut buf[..])
                .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
                .unwrap()
                .ethertype(),
            Some(EtherType::Other(ETHERNET_MAX_ILLEGAL_ETHERTYPE + 1))
        );
    }

    #[test]
    fn test_serialize() {
        let buf = (&new_serialize_buf()[..])
            .into_serializer()
            .encapsulate(EthernetFrameBuilder::new(
                DEFAULT_DST_MAC,
                DEFAULT_SRC_MAC,
                EtherType::Arp,
                ETHERNET_MIN_BODY_LEN_NO_TAG,
            ))
            .serialize_vec_outer()
            .unwrap();
        assert_eq!(
            &buf.as_ref()[..ETHERNET_HDR_LEN_NO_TAG],
            [6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 0x08, 0x06]
        );
    }

    #[test]
    fn test_serialize_zeroes() {
        // Test that EthernetFrame::serialize properly zeroes memory before
        // serializing the header.
        let mut buf_0 = [0; ETHERNET_MIN_FRAME_LEN];
        let _: Buf<&mut [u8]> = Buf::new(&mut buf_0[..], ETHERNET_HDR_LEN_NO_TAG..)
            .encapsulate(EthernetFrameBuilder::new(
                DEFAULT_SRC_MAC,
                DEFAULT_DST_MAC,
                EtherType::Arp,
                ETHERNET_MIN_BODY_LEN_NO_TAG,
            ))
            .serialize_vec_outer()
            .unwrap()
            .unwrap_a();
        let mut buf_1 = [0; ETHERNET_MIN_FRAME_LEN];
        (&mut buf_1[..ETHERNET_HDR_LEN_NO_TAG]).copy_from_slice(&[0xFF; ETHERNET_HDR_LEN_NO_TAG]);
        let _: Buf<&mut [u8]> = Buf::new(&mut buf_1[..], ETHERNET_HDR_LEN_NO_TAG..)
            .encapsulate(EthernetFrameBuilder::new(
                DEFAULT_SRC_MAC,
                DEFAULT_DST_MAC,
                EtherType::Arp,
                ETHERNET_MIN_BODY_LEN_NO_TAG,
            ))
            .serialize_vec_outer()
            .unwrap()
            .unwrap_a();
        assert_eq!(&buf_0[..], &buf_1[..]);
    }

    #[test]
    fn test_parse_error() {
        // 1 byte shorter than the minimum
        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN - 1];
        assert!((&mut buf[..])
            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
            .is_err());

        // 1 byte shorter than the minimum header length still fails even if
        // length checking is disabled
        let mut buf = [0u8; ETHERNET_HDR_LEN_NO_TAG - 1];
        assert!((&mut buf[..])
            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck)
            .is_err());

        // an ethertype of 1500 should be validated as the length of the body
        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN];
        NetworkEndian::write_u16(
            &mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..],
            ETHERNET_MIN_ILLEGAL_ETHERTYPE - 1,
        );
        assert!((&mut buf[..])
            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
            .is_err());

        // an ethertype of 1501 is illegal because it's in the range [1501, 1535]
        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN];
        NetworkEndian::write_u16(
            &mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..],
            ETHERNET_MIN_ILLEGAL_ETHERTYPE,
        );
        assert!((&mut buf[..])
            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
            .is_err());

        // an ethertype of 1535 is illegal
        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN];
        NetworkEndian::write_u16(
            &mut buf[ETHERNET_ETHERTYPE_BYTE_OFFSET..],
            ETHERNET_MAX_ILLEGAL_ETHERTYPE,
        );
        assert!((&mut buf[..])
            .parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)
            .is_err());
    }

    #[test]
    #[should_panic(expected = "bytes is below minimum frame size of")]
    fn test_serialize_panic() {
        // create with a body which is below the minimum length
        let mut buf = [0u8; ETHERNET_MIN_FRAME_LEN - 1];
        let mut b = [&mut buf[..]];
        let buf = b.as_fragmented_byte_slice();
        let (header, body, footer) = buf.try_split_contiguous(ETHERNET_HDR_LEN_NO_TAG..).unwrap();
        EthernetFrameBuilder::new(
            Mac::new([0, 1, 2, 3, 4, 5]),
            Mac::new([6, 7, 8, 9, 10, 11]),
            EtherType::Arp,
            ETHERNET_MIN_BODY_LEN_NO_TAG,
        )
        .serialize(&mut SerializeTarget { header, footer }, body);
    }

    #[test]
    fn test_custom_min_body_len() {
        const MIN_BODY_LEN: usize = 4;
        const UNWRITTEN_BYTE: u8 = 0xAA;

        let builder = EthernetFrameBuilder::new(
            Mac::new([0, 1, 2, 3, 4, 5]),
            Mac::new([6, 7, 8, 9, 10, 11]),
            EtherType::Arp,
            MIN_BODY_LEN,
        );

        let mut buffer = [UNWRITTEN_BYTE; ETHERNET_MIN_FRAME_LEN];
        // TODO(https://fxbug.dev/42079821): Don't use this `#[doc(hidden)]`
        // method, and use the public API instead.
        GrowBufferMut::serialize(
            &mut Buf::new(&mut buffer[..], ETHERNET_HDR_LEN_NO_TAG..ETHERNET_HDR_LEN_NO_TAG),
            builder,
        );

        let (header, tail) = buffer.split_at(ETHERNET_HDR_LEN_NO_TAG);
        let (padding, unwritten) = tail.split_at(MIN_BODY_LEN);
        assert_eq!(
            header,
            &[
                6, 7, 8, 9, 10, 11, // dst_mac
                0, 1, 2, 3, 4, 5, // src_mac
                08, 06, // ethertype
            ]
        );
        assert_eq!(padding, &[0; MIN_BODY_LEN]);
        assert_eq!(
            unwritten,
            &[UNWRITTEN_BYTE; ETHERNET_MIN_FRAME_LEN - MIN_BODY_LEN - ETHERNET_HDR_LEN_NO_TAG]
        );
    }
}