| // 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, Ipv4, Ipv6}; |
| use packet::{ |
| BufferView, BufferViewMut, FragmentedBytesMut, PacketBuilder, PacketConstraints, |
| ParsablePacket, ParseMetadata, SerializeTarget, |
| }; |
| use zerocopy::{ |
| byteorder::network_endian::{U16, U32}, |
| AsBytes, ByteSlice, FromBytes, FromZeroes, Ref, 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 {}"; |
| } |
| ); |
| |
| /// 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(FromZeroes, FromBytes, AsBytes, 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: ByteSlice> ParsablePacket<B, EthernetFrameLengthCheck> for EthernetFrame<B> { |
| type Error = ParseError; |
| |
| fn parse_metadata(&self) -> ParseMetadata { |
| let header_len = self.hdr_prefix.bytes().len() |
| + self.tag.as_ref().map(|tag| tag.bytes().len()).unwrap_or(0) |
| + self.ethertype.bytes().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: ByteSlice> 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 { |
| self.hdr_prefix.bytes().len() |
| + self.tag.as_ref().map(|t| t.bytes().len()).unwrap_or(0) |
| + self.ethertype.bytes().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 packet::{ |
| AsFragmentedByteSlice, Buf, GrowBufferMut, InnerPacketBuilder, ParseBuffer, |
| SerializeTarget, Serializer, |
| }; |
| use zerocopy::byteorder::{ByteOrder, NetworkEndian}; |
| |
| 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/129396): 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] |
| ); |
| } |
| } |