blob: 2390aa8533c107d74d60d2ef6514fedffa33ec93 [file] [log] [blame]
// Copyright 2022 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.
//! The loopback device.
use alloc::vec::Vec;
use core::convert::Infallible as Never;
use lock_order::{
lock::{LockFor, RwLockFor, UnlockedAccess},
relation::LockBefore,
wrap::prelude::*,
};
use net_types::{
ethernet::Mac,
ip::{Ip, IpAddress, Mtu},
SpecifiedAddr,
};
use packet::{Buf, Buffer as _, BufferMut, Serializer};
use packet_formats::ethernet::{
EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, EthernetIpExt,
};
use tracing::trace;
use crate::{
context::{
CoreTimerContext, CounterContext, ResourceCounterContext, SendFrameContext, TimerContext2,
},
device::{
self,
id::{BaseDeviceId, BasePrimaryDeviceId, BaseWeakDeviceId},
queue::{
rx::{
ReceiveDequeContext, ReceiveDequeFrameContext, ReceiveQueue,
ReceiveQueueBindingsContext, ReceiveQueueContext, ReceiveQueueHandler,
ReceiveQueueState, ReceiveQueueTypes,
},
tx::{
BufVecU8Allocator, TransmitDequeueContext, TransmitQueue,
TransmitQueueBindingsContext, TransmitQueueCommon, TransmitQueueContext,
TransmitQueueHandler, TransmitQueueState,
},
DequeueState, ReceiveQueueFullError, TransmitQueueFrameError,
},
socket::{
DatagramHeader, DeviceSocketHandler, DeviceSocketMetadata, HeldDeviceSockets,
ParseSentFrameError, ReceivedFrame, SentFrame,
},
state::{DeviceStateSpec, IpLinkDeviceState},
Device, DeviceCounters, DeviceIdContext, DeviceLayerEventDispatcher, DeviceLayerTypes,
DeviceReceiveFrameSpec, DeviceSendFrameError, EthernetDeviceCounters, FrameDestination,
},
BindingsContext, BindingsTypes, CoreCtx,
};
/// The MAC address corresponding to the loopback interface.
const LOOPBACK_MAC: Mac = Mac::UNSPECIFIED;
/// A weak device ID identifying a loopback device.
///
/// This device ID is like [`WeakDeviceId`] but specifically for loopback
/// devices.
///
/// [`WeakDeviceId`]: crate::device::WeakDeviceId
pub type LoopbackWeakDeviceId<BT> = BaseWeakDeviceId<LoopbackDevice, BT>;
/// A strong device ID identifying a loopback device.
///
/// This device ID is like [`DeviceId`] but specifically for loopback devices.
///
/// [`DeviceId`]: crate::device::DeviceId
pub type LoopbackDeviceId<BT> = BaseDeviceId<LoopbackDevice, BT>;
/// The primary reference for a loopback device.
pub(crate) type LoopbackPrimaryDeviceId<BT> = BasePrimaryDeviceId<LoopbackDevice, BT>;
/// Loopback device domain.
#[derive(Copy, Clone)]
pub enum LoopbackDevice {}
impl Device for LoopbackDevice {}
impl DeviceStateSpec for LoopbackDevice {
type Link<BT: DeviceLayerTypes> = LoopbackDeviceState;
type External<BT: DeviceLayerTypes> = BT::LoopbackDeviceState;
type CreationProperties = LoopbackCreationProperties;
type Counters = EthernetDeviceCounters;
type TimerId<D: device::WeakId> = Never;
fn new_link_state<
CC: CoreTimerContext<Self::TimerId<CC::WeakDeviceId>, BC> + DeviceIdContext<Self>,
BC: DeviceLayerTypes + TimerContext2,
>(
_bindings_ctx: &mut BC,
_self_id: CC::WeakDeviceId,
LoopbackCreationProperties { mtu }: Self::CreationProperties,
) -> Self::Link<BC> {
LoopbackDeviceState {
counters: Default::default(),
mtu,
rx_queue: Default::default(),
tx_queue: Default::default(),
}
}
const IS_LOOPBACK: bool = true;
const DEBUG_TYPE: &'static str = "Loopback";
}
impl<BT: BindingsTypes, L> DeviceIdContext<LoopbackDevice> for CoreCtx<'_, BT, L> {
type DeviceId = LoopbackDeviceId<BT>;
type WeakDeviceId = LoopbackWeakDeviceId<BT>;
fn downgrade_device_id(&self, device_id: &Self::DeviceId) -> Self::WeakDeviceId {
device_id.downgrade()
}
fn upgrade_weak_device_id(
&self,
weak_device_id: &Self::WeakDeviceId,
) -> Option<Self::DeviceId> {
weak_device_id.upgrade()
}
}
/// Properties used to create a loopback device.
#[derive(Debug)]
pub struct LoopbackCreationProperties {
/// The device's MTU.
pub mtu: Mtu,
}
/// State for a loopback device.
pub struct LoopbackDeviceState {
counters: EthernetDeviceCounters,
mtu: Mtu,
rx_queue: ReceiveQueue<(), Buf<Vec<u8>>>,
tx_queue: TransmitQueue<(), Buf<Vec<u8>>, BufVecU8Allocator>,
}
impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::EthernetDeviceCounters>
for IpLinkDeviceState<LoopbackDevice, BC>
{
type Data = EthernetDeviceCounters;
type Guard<'l> = &'l EthernetDeviceCounters
where
Self: 'l ;
fn access(&self) -> Self::Guard<'_> {
&self.link.counters
}
}
impl<BC: BindingsContext> LockFor<crate::lock_ordering::LoopbackRxQueue>
for IpLinkDeviceState<LoopbackDevice, BC>
{
type Data = ReceiveQueueState<(), Buf<Vec<u8>>>;
type Guard<'l> = crate::sync::LockGuard<'l, ReceiveQueueState<(), Buf<Vec<u8>>>>
where
Self: 'l;
fn lock(&self) -> Self::Guard<'_> {
self.link.rx_queue.queue.lock()
}
}
impl<BC: BindingsContext> LockFor<crate::lock_ordering::LoopbackRxDequeue>
for IpLinkDeviceState<LoopbackDevice, BC>
{
type Data = DequeueState<(), Buf<Vec<u8>>>;
type Guard<'l> = crate::sync::LockGuard<'l, DequeueState<(), Buf<Vec<u8>>>>
where
Self: 'l;
fn lock(&self) -> Self::Guard<'_> {
self.link.rx_queue.deque.lock()
}
}
impl<BC: BindingsContext> LockFor<crate::lock_ordering::LoopbackTxQueue>
for IpLinkDeviceState<LoopbackDevice, BC>
{
type Data = TransmitQueueState<(), Buf<Vec<u8>>, BufVecU8Allocator>;
type Guard<'l> = crate::sync::LockGuard<'l, TransmitQueueState<(), Buf<Vec<u8>>, BufVecU8Allocator>>
where
Self: 'l;
fn lock(&self) -> Self::Guard<'_> {
self.link.tx_queue.queue.lock()
}
}
impl<BC: BindingsContext> LockFor<crate::lock_ordering::LoopbackTxDequeue>
for IpLinkDeviceState<LoopbackDevice, BC>
{
type Data = DequeueState<(), Buf<Vec<u8>>>;
type Guard<'l> = crate::sync::LockGuard<'l, DequeueState<(), Buf<Vec<u8>>>>
where
Self: 'l;
fn lock(&self) -> Self::Guard<'_> {
self.link.tx_queue.deque.lock()
}
}
impl<BC: BindingsContext> RwLockFor<crate::lock_ordering::DeviceSockets>
for IpLinkDeviceState<LoopbackDevice, BC>
{
type Data = HeldDeviceSockets<BC>;
type ReadGuard<'l> = crate::sync::RwLockReadGuard<'l, HeldDeviceSockets<BC>>
where
Self: 'l ;
type WriteGuard<'l> = crate::sync::RwLockWriteGuard<'l, HeldDeviceSockets<BC>>
where
Self: 'l ;
fn read_lock(&self) -> Self::ReadGuard<'_> {
self.sockets.read()
}
fn write_lock(&self) -> Self::WriteGuard<'_> {
self.sockets.write()
}
}
impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::LoopbackTxQueue>>
SendFrameContext<BC, DeviceSocketMetadata<LoopbackDeviceId<BC>>> for CoreCtx<'_, BC, L>
{
fn send_frame<S>(
&mut self,
bindings_ctx: &mut BC,
metadata: DeviceSocketMetadata<LoopbackDeviceId<BC>>,
body: S,
) -> Result<(), S>
where
S: Serializer,
S::Buffer: BufferMut,
{
let DeviceSocketMetadata { device_id, header } = metadata;
match header {
Some(DatagramHeader { dest_addr, protocol }) => send_as_ethernet_frame_to_dst(
self,
bindings_ctx,
&device_id,
body,
protocol,
dest_addr,
),
None => send_ethernet_frame(self, bindings_ctx, &device_id, body),
}
}
}
pub(super) fn send_ip_frame<BC, A, S, L>(
core_ctx: &mut CoreCtx<'_, BC, L>,
bindings_ctx: &mut BC,
device_id: &LoopbackDeviceId<BC>,
_local_addr: SpecifiedAddr<A>,
packet: S,
) -> Result<(), S>
where
BC: BindingsContext,
A: IpAddress,
S: Serializer,
S::Buffer: BufferMut,
L: LockBefore<crate::lock_ordering::LoopbackTxQueue>,
A::Version: EthernetIpExt,
{
core_ctx.with_counters(|counters: &DeviceCounters| {
let () = A::Version::map_ip(
(),
|()| counters.send_ipv4_frame.increment(),
|()| counters.send_ipv6_frame.increment(),
);
});
send_as_ethernet_frame_to_dst(
core_ctx,
bindings_ctx,
device_id,
packet,
<A::Version as EthernetIpExt>::ETHER_TYPE,
LOOPBACK_MAC,
)
}
fn send_as_ethernet_frame_to_dst<BC, S, L>(
core_ctx: &mut CoreCtx<'_, BC, L>,
bindings_ctx: &mut BC,
device_id: &LoopbackDeviceId<BC>,
packet: S,
protocol: EtherType,
dst_mac: Mac,
) -> Result<(), S>
where
BC: BindingsContext,
S: Serializer,
S::Buffer: BufferMut,
L: LockBefore<crate::lock_ordering::LoopbackTxQueue>,
{
/// The minimum length of bodies of Ethernet frames sent over the loopback
/// device.
///
/// Use zero since the frames are never sent out a physical device, so it
/// doesn't matter if they are shorter than would be required.
const MIN_BODY_LEN: usize = 0;
let frame = packet.encapsulate(EthernetFrameBuilder::new(
LOOPBACK_MAC,
dst_mac,
protocol,
MIN_BODY_LEN,
));
send_ethernet_frame(core_ctx, bindings_ctx, device_id, frame).map_err(|s| s.into_inner())
}
fn send_ethernet_frame<L, S, BC>(
core_ctx: &mut CoreCtx<'_, BC, L>,
bindings_ctx: &mut BC,
device_id: &LoopbackDeviceId<BC>,
frame: S,
) -> Result<(), S>
where
L: LockBefore<crate::lock_ordering::LoopbackTxQueue>,
S: Serializer,
S::Buffer: BufferMut,
BC: BindingsContext,
{
core_ctx.increment(device_id, |counters: &DeviceCounters| &counters.send_total_frames);
match TransmitQueueHandler::<LoopbackDevice, _>::queue_tx_frame(
core_ctx,
bindings_ctx,
device_id,
(),
frame,
) {
Ok(()) => {
core_ctx.increment(device_id, |counters: &DeviceCounters| &counters.send_frame);
Ok(())
}
Err(TransmitQueueFrameError::NoQueue(_)) => {
unreachable!("loopback never fails to send a frame")
}
Err(TransmitQueueFrameError::QueueFull(s)) => {
core_ctx.increment(device_id, |counters: &DeviceCounters| &counters.send_queue_full);
Err(s)
}
Err(TransmitQueueFrameError::SerializeError(s)) => {
core_ctx
.increment(device_id, |counters: &DeviceCounters| &counters.send_serialize_error);
Err(s)
}
}
}
/// Gets the MTU associated with this device.
pub(super) fn get_mtu<BC: BindingsContext, L>(
core_ctx: &mut CoreCtx<'_, BC, L>,
device_id: &LoopbackDeviceId<BC>,
) -> Mtu {
device::integration::with_device_state(core_ctx, device_id, |mut state| {
state.cast_with(|s| &s.link.mtu).copied()
})
}
impl DeviceReceiveFrameSpec for LoopbackDevice {
// Loopback never receives frames from bindings, so make it impossible to
// instantiate it.
type FrameMetadata<D> = Never;
}
impl<BC: BindingsContext> ReceiveQueueBindingsContext<LoopbackDevice, LoopbackDeviceId<BC>> for BC {
fn wake_rx_task(&mut self, device_id: &LoopbackDeviceId<BC>) {
DeviceLayerEventDispatcher::wake_rx_task(self, device_id)
}
}
impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::LoopbackRxQueue>>
ReceiveQueueTypes<LoopbackDevice, BC> for CoreCtx<'_, BC, L>
{
type Meta = ();
type Buffer = Buf<Vec<u8>>;
}
impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::LoopbackRxQueue>>
ReceiveQueueContext<LoopbackDevice, BC> for CoreCtx<'_, BC, L>
{
fn with_receive_queue_mut<
O,
F: FnOnce(&mut ReceiveQueueState<Self::Meta, Self::Buffer>) -> O,
>(
&mut self,
device_id: &LoopbackDeviceId<BC>,
cb: F,
) -> O {
device::integration::with_device_state(self, device_id, |mut state| {
let mut x = state.lock::<crate::lock_ordering::LoopbackRxQueue>();
cb(&mut x)
})
}
}
impl<BC: BindingsContext> ReceiveDequeFrameContext<LoopbackDevice, BC>
for CoreCtx<'_, BC, crate::lock_ordering::LoopbackRxDequeue>
{
fn handle_frame(
&mut self,
bindings_ctx: &mut BC,
device_id: &LoopbackDeviceId<BC>,
(): Self::Meta,
mut buf: Buf<Vec<u8>>,
) {
self.increment(device_id, |counters: &DeviceCounters| &counters.recv_frame);
let (frame, whole_body) = match buf
.parse_with_view::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck)
{
Err(e) => {
self.increment(device_id, |counters: &DeviceCounters| &counters.recv_parse_error);
trace!("dropping invalid ethernet frame over loopback: {:?}", e);
return;
}
Ok(e) => e,
};
let frame_dest = FrameDestination::from_dest(frame.dst_mac(), Mac::UNSPECIFIED);
let ethertype = frame.ethertype();
DeviceSocketHandler::<LoopbackDevice, _>::handle_frame(
self,
bindings_ctx,
device_id,
ReceivedFrame::from_ethernet(frame, frame_dest).into(),
whole_body,
);
let ethertype = match ethertype {
Some(e) => e,
None => {
self.increment(device_id, |counters: &EthernetDeviceCounters| {
&counters.recv_no_ethertype
});
trace!("dropping ethernet frame without ethertype");
return;
}
};
match ethertype {
EtherType::Ipv4 => {
self.increment(device_id, |counters: &DeviceCounters| {
&counters.recv_ipv4_delivered
});
crate::ip::receive_ipv4_packet(
self,
bindings_ctx,
&device_id.clone().into(),
Some(frame_dest),
buf,
)
}
EtherType::Ipv6 => {
self.increment(device_id, |counters: &DeviceCounters| {
&counters.recv_ipv6_delivered
});
crate::ip::receive_ipv6_packet(
self,
bindings_ctx,
&device_id.clone().into(),
Some(frame_dest),
buf,
)
}
ethertype @ EtherType::Arp | ethertype @ EtherType::Other(_) => {
self.increment(device_id, |counters: &EthernetDeviceCounters| {
&counters.recv_unsupported_ethertype
});
trace!("not handling loopback frame of type {:?}", ethertype)
}
}
}
}
impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::LoopbackRxDequeue>>
ReceiveDequeContext<LoopbackDevice, BC> for CoreCtx<'_, BC, L>
{
type ReceiveQueueCtx<'a> = CoreCtx<'a, BC, crate::lock_ordering::LoopbackRxDequeue>;
fn with_dequed_frames_and_rx_queue_ctx<
O,
F: FnOnce(&mut DequeueState<(), Buf<Vec<u8>>>, &mut Self::ReceiveQueueCtx<'_>) -> O,
>(
&mut self,
device_id: &LoopbackDeviceId<BC>,
cb: F,
) -> O {
device::integration::with_device_state_and_core_ctx(
self,
device_id,
|mut core_ctx_and_resource| {
let (mut x, mut locked) =
core_ctx_and_resource
.lock_with_and::<crate::lock_ordering::LoopbackRxDequeue, _>(|c| c.right());
cb(&mut x, &mut locked.cast_core_ctx())
},
)
}
}
impl<BC: BindingsContext> TransmitQueueBindingsContext<LoopbackDevice, LoopbackDeviceId<BC>>
for BC
{
fn wake_tx_task(&mut self, device_id: &LoopbackDeviceId<BC>) {
DeviceLayerEventDispatcher::wake_tx_task(self, &device_id.clone().into())
}
}
impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::LoopbackTxQueue>>
TransmitQueueCommon<LoopbackDevice, BC> for CoreCtx<'_, BC, L>
{
type Meta = ();
type Allocator = BufVecU8Allocator;
type Buffer = Buf<Vec<u8>>;
fn parse_outgoing_frame(buf: &[u8]) -> Result<SentFrame<&[u8]>, ParseSentFrameError> {
SentFrame::try_parse_as_ethernet(buf)
}
}
impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::LoopbackTxQueue>>
TransmitQueueContext<LoopbackDevice, BC> for CoreCtx<'_, BC, L>
{
fn with_transmit_queue_mut<
O,
F: FnOnce(&mut TransmitQueueState<Self::Meta, Self::Buffer, Self::Allocator>) -> O,
>(
&mut self,
device_id: &LoopbackDeviceId<BC>,
cb: F,
) -> O {
device::integration::with_device_state(self, device_id, |mut state| {
let mut x = state.lock::<crate::lock_ordering::LoopbackTxQueue>();
cb(&mut x)
})
}
fn send_frame(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
meta: Self::Meta,
buf: Self::Buffer,
) -> Result<(), DeviceSendFrameError<(Self::Meta, Self::Buffer)>> {
// Never handle frames synchronously with the send path - always queue
// the frame to be received by the loopback device into a queue which
// a dedicated RX task will kick to handle the queued packet.
//
// This is done so that a socket lock may be held while sending a packet
// which may need to be delivered to the sending socket itself. Without
// this decoupling of RX/TX paths, sending a packet while holding onto
// the socket lock will result in a deadlock.
match ReceiveQueueHandler::queue_rx_frame(self, bindings_ctx, device_id, meta, buf) {
Ok(()) => {}
Err(ReceiveQueueFullError(((), _frame))) => {
// RX queue is full - there is nothing further we can do here.
tracing::error!("dropped RX frame on loopback device due to full RX queue")
}
}
Ok(())
}
}
impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::LoopbackTxDequeue>>
TransmitDequeueContext<LoopbackDevice, BC> for CoreCtx<'_, BC, L>
{
type TransmitQueueCtx<'a> = CoreCtx<'a, BC, crate::lock_ordering::LoopbackTxDequeue>;
fn with_dequed_packets_and_tx_queue_ctx<
O,
F: FnOnce(&mut DequeueState<Self::Meta, Self::Buffer>, &mut Self::TransmitQueueCtx<'_>) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O {
device::integration::with_device_state_and_core_ctx(
self,
device_id,
|mut core_ctx_and_resource| {
let (mut x, mut locked) =
core_ctx_and_resource
.lock_with_and::<crate::lock_ordering::LoopbackTxDequeue, _>(|c| c.right());
cb(&mut x, &mut locked.cast_core_ctx())
},
)
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use ip_test_macro::ip_test;
use net_types::ip::{AddrSubnet, Ipv4, Ipv6};
use packet::ParseBuffer;
use crate::{
error::NotFoundError,
ip::device::IpAddressId as _,
testutil::{
FakeBindingsCtx, FakeEventDispatcherConfig, TestIpExt, DEFAULT_INTERFACE_METRIC,
},
};
use super::*;
const MTU: Mtu = Mtu::new(66);
#[test]
fn loopback_mtu() {
let mut ctx = crate::testutil::FakeCtx::default();
let device = ctx
.core_api()
.device::<LoopbackDevice>()
.add_device_with_default_state(
LoopbackCreationProperties { mtu: MTU },
DEFAULT_INTERFACE_METRIC,
)
.into();
crate::device::testutil::enable_device(&mut ctx, &device);
assert_eq!(
crate::ip::IpDeviceContext::<Ipv4, _>::get_mtu(&mut ctx.core_ctx(), &device),
MTU
);
assert_eq!(
crate::ip::IpDeviceContext::<Ipv6, _>::get_mtu(&mut ctx.core_ctx(), &device),
MTU
);
}
#[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)]
#[ip_test]
fn test_loopback_add_remove_addrs<I: Ip + TestIpExt + crate::IpExt>() {
let mut ctx = crate::testutil::FakeCtx::default();
let device = ctx
.core_api()
.device::<LoopbackDevice>()
.add_device_with_default_state(
LoopbackCreationProperties { mtu: MTU },
DEFAULT_INTERFACE_METRIC,
)
.into();
crate::device::testutil::enable_device(&mut ctx, &device);
let get_addrs = |ctx: &mut crate::testutil::FakeCtx| {
crate::ip::device::IpDeviceStateContext::<I, _>::with_address_ids(
&mut ctx.core_ctx(),
&device,
|addrs, _core_ctx| addrs.map(|a| SpecifiedAddr::from(a.addr())).collect::<Vec<_>>(),
)
};
let FakeEventDispatcherConfig {
subnet,
local_ip,
local_mac: _,
remote_ip: _,
remote_mac: _,
} = I::FAKE_CONFIG;
let addr =
AddrSubnet::from_witness(local_ip, subnet.prefix()).expect("error creating AddrSubnet");
assert_eq!(get_addrs(&mut ctx), []);
assert_eq!(ctx.core_api().device_ip::<I>().add_ip_addr_subnet(&device, addr,), Ok(()));
let addr = addr.addr();
assert_eq!(&get_addrs(&mut ctx)[..], [addr]);
assert_eq!(ctx.core_api().device_ip::<I>().del_ip_addr(&device, addr), Ok(()));
assert_eq!(get_addrs(&mut ctx), []);
assert_eq!(ctx.core_api().device_ip::<I>().del_ip_addr(&device, addr), Err(NotFoundError));
}
#[ip_test]
fn loopback_sends_ethernet<I: Ip + TestIpExt>() {
let mut ctx = crate::testutil::FakeCtx::default();
let device = ctx.core_api().device::<LoopbackDevice>().add_device_with_default_state(
LoopbackCreationProperties { mtu: MTU },
DEFAULT_INTERFACE_METRIC,
);
crate::device::testutil::enable_device(&mut ctx, &device.clone().into());
let crate::testutil::FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
let local_addr = I::FAKE_CONFIG.local_ip;
const BODY: &[u8] = b"IP body".as_slice();
let body = Buf::new(Vec::from(BODY), ..);
send_ip_frame(&mut core_ctx.context(), bindings_ctx, &device, local_addr, body)
.expect("can send");
// There is no transmit queue so the frames will immediately go into the
// receive queue.
let mut frames = ReceiveQueueContext::<LoopbackDevice, _>::with_receive_queue_mut(
&mut core_ctx.context(),
&device,
|queue_state| queue_state.take_frames().map(|((), frame)| frame).collect::<Vec<_>>(),
);
let frame = assert_matches!(frames.as_mut_slice(), [frame] => frame);
let eth = frame
.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::NoCheck)
.expect("is ethernet");
assert_eq!(eth.src_mac(), Mac::UNSPECIFIED);
assert_eq!(eth.dst_mac(), Mac::UNSPECIFIED);
assert_eq!(eth.ethertype(), Some(I::ETHER_TYPE));
// Trim the body to account for ethernet padding.
assert_eq!(&frame.as_ref()[..BODY.len()], BODY);
// Clear all device references.
ctx.bindings_ctx.state_mut().rx_available.clear();
ctx.core_api().device().remove_device(device).into_removed();
}
}