blob: 524cad01d87aca3d9ea6b57245b4dc6da014de40 [file] [log] [blame]
// 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.
//! Testing-related utilities.
#![cfg(any(test, feature = "testutils"))]
#[cfg(test)]
use alloc::vec;
use alloc::{borrow::ToOwned, collections::HashMap, sync::Arc, vec::Vec};
use assert_matches::assert_matches;
use core::{
borrow::Borrow,
convert::Infallible as Never,
ffi::CStr,
fmt::{self, Debug, Display},
hash::Hash,
num::NonZeroU64,
ops::{Deref, DerefMut},
sync::atomic::AtomicUsize,
time::Duration,
};
use lock_order::wrap::prelude::*;
use net_types::{
ethernet::Mac,
ip::{
AddrSubnetEither, GenericOverIp, Ip, IpAddress, IpInvariant, IpVersion, Ipv4, Ipv4Addr,
Ipv6, Ipv6Addr, Subnet, SubnetEither,
},
SpecifiedAddr, UnicastAddr, Witness as _,
};
#[cfg(test)]
use net_types::{ip::IpAddr, MulticastAddr, NonMappedAddr};
use packet::{Buf, BufferMut};
#[cfg(test)]
use packet_formats::ip::IpProto;
#[cfg(test)]
use rand::Rng as _;
use rand::{CryptoRng, RngCore, SeedableRng};
use rand_xorshift::XorShiftRng;
#[cfg(test)]
use tracing::Subscriber;
#[cfg(test)]
use tracing_subscriber::{
fmt::{
format::{self, FormatEvent, FormatFields},
FmtContext,
},
registry::LookupSpan,
};
#[cfg(test)]
use crate::{
context::testutil::{FakeFrameCtx, FakeNetworkContext, InstantAndData, WithFakeFrameContext},
ip::{device::Ipv6DeviceAddr, SendIpPacketMeta},
};
use crate::{
context::{
testutil::{
FakeInstant, FakeTimerCtx, FakeTimerCtxExt, PureIpDeviceAndIpVersion,
WithFakeTimerContext,
},
EventContext, InstantBindingsTypes, InstantContext, RngContext, TimerBindingsTypes,
TimerContext, TimerContext2, TimerHandler, TracingContext, UnlockedCoreCtx,
},
device::{
ethernet::MaxEthernetFrameSize,
ethernet::{EthernetCreationProperties, EthernetLinkDevice},
link::LinkDevice,
loopback::LoopbackDeviceId,
DeviceClassMatcher, DeviceId, DeviceIdAndNameMatcher, DeviceLayerEventDispatcher,
DeviceLayerStateTypes, DeviceSendFrameError, EthernetDeviceId, EthernetWeakDeviceId,
PureIpDeviceId, WeakDeviceId,
},
filter::FilterBindingsTypes,
ip::{
device::{
config::{Ipv4DeviceConfigurationUpdate, Ipv6DeviceConfigurationUpdate},
nud::{self, LinkResolutionContext, LinkResolutionNotifier},
IpDeviceEvent,
},
icmp::socket::{IcmpEchoBindingsContext, IcmpEchoBindingsTypes, IcmpSocketId},
types::{AddableEntry, AddableMetric, RawMetric},
IpLayerEvent,
},
state::{StackState, StackStateBuilder},
sync::Mutex,
time::TimerId,
transport::{
tcp::{
buffer::{
testutil::{ClientBuffers, ProvidedBuffers, TestSendBuffer},
RingBuffer,
},
socket::TcpBindingsTypes,
BufferSizes,
},
udp::{UdpBindingsTypes, UdpReceiveBindingsContext, UdpSocketId},
},
BindingsTypes,
};
/// NDP test utilities.
pub mod ndp {
pub use crate::device::ndp::testutil::*;
}
/// Context test utilities.
pub mod context {
pub use crate::context::testutil::*;
}
/// The default interface routing metric for test interfaces.
pub(crate) const DEFAULT_INTERFACE_METRIC: RawMetric = RawMetric(100);
/// A structure holding a core and a bindings context.
#[derive(Default, Clone)]
pub struct ContextPair<CC, BT> {
/// The core context.
pub core_ctx: CC,
/// The bindings context.
// We put `bindings_ctx` after `core_ctx` to make sure that `core_ctx` is
// dropped before `bindings_ctx` so that the existence of
// strongly-referenced device IDs in `bindings_ctx` causes test failures,
// forcing proper cleanup of device IDs in our unit tests.
//
// Note that if strongly-referenced (device) IDs exist when dropping the
// primary reference, the primary reference's drop impl will panic. See
// `crate::sync::PrimaryRc::drop` for details.
// TODO(https://fxbug.dev/320021524): disallow destructuring to actually
// uphold the intent above.
pub bindings_ctx: BT,
}
impl<CC, BC> ContextPair<CC, BC> {
#[cfg(test)]
pub(crate) fn with_core_ctx(core_ctx: CC) -> Self
where
BC: Default,
{
Self { core_ctx, bindings_ctx: BC::default() }
}
#[cfg(test)]
pub(crate) fn with_default_bindings_ctx<F: FnOnce(&mut BC) -> CC>(builder: F) -> Self
where
BC: Default,
{
let mut bindings_ctx = BC::default();
let core_ctx = builder(&mut bindings_ctx);
Self { core_ctx, bindings_ctx }
}
#[cfg(test)]
pub(crate) fn as_mut(&mut self) -> ContextPair<&mut CC, &mut BC> {
let Self { core_ctx, bindings_ctx } = self;
ContextPair { core_ctx, bindings_ctx }
}
}
impl<CC, BC> crate::context::ContextPair for ContextPair<CC, BC>
where
CC: crate::context::ContextProvider,
BC: crate::context::ContextProvider,
{
type CoreContext = CC::Context;
type BindingsContext = BC::Context;
fn contexts(&mut self) -> (&mut Self::CoreContext, &mut Self::BindingsContext) {
let Self { core_ctx, bindings_ctx } = self;
(core_ctx.context(), bindings_ctx.context())
}
}
/// Context available during the execution of the netstack.
pub type Ctx<BT> = ContextPair<StackState<BT>, BT>;
impl<BC: crate::BindingsContext + Default> Default for Ctx<BC> {
fn default() -> Self {
Self::new_with_builder(StackStateBuilder::default())
}
}
impl<BC: crate::BindingsContext + Default> Ctx<BC> {
pub(crate) fn new_with_builder(builder: StackStateBuilder) -> Self {
let mut bindings_ctx = Default::default();
let state = builder.build_with_ctx(&mut bindings_ctx);
Self { core_ctx: state, bindings_ctx }
}
}
impl<CC, BC> ContextPair<CC, BC>
where
CC: Borrow<StackState<BC>>,
BC: BindingsTypes,
{
/// Retrieves a [`crate::api::CoreApi`] from this [`Ctx`].
pub fn core_api(&mut self) -> crate::api::CoreApi<'_, &mut BC> {
let Self { core_ctx, bindings_ctx } = self;
CC::borrow(core_ctx).api(bindings_ctx)
}
/// Retrieves the core and bindings context, respectively.
///
/// This function can be used to call into non-api core functions that want
/// a core context.
pub fn contexts(&mut self) -> (UnlockedCoreCtx<'_, BC>, &mut BC) {
let Self { core_ctx, bindings_ctx } = self;
(UnlockedCoreCtx::new(&CC::borrow(core_ctx)), bindings_ctx)
}
/// Like [`ContextPair::contexts`], but retrieves only the core context.
pub fn core_ctx(&self) -> UnlockedCoreCtx<'_, BC> {
UnlockedCoreCtx::new(&CC::borrow(&self.core_ctx))
}
/// Retrieves a [`TestApi`] from this [`Ctx`].
pub fn test_api(&mut self) -> TestApi<'_, BC> {
let Self { core_ctx, bindings_ctx } = self;
TestApi(UnlockedCoreCtx::new(&CC::borrow(core_ctx)), bindings_ctx)
}
}
/// An API struct for test utilities.
pub struct TestApi<'a, BT: BindingsTypes>(UnlockedCoreCtx<'a, BT>, &'a mut BT);
impl<'l, BC> TestApi<'l, BC>
where
BC: crate::BindingsContext,
{
fn contexts(&mut self) -> (&mut UnlockedCoreCtx<'l, BC>, &mut BC) {
let Self(core_ctx, bindings_ctx) = self;
(core_ctx, bindings_ctx)
}
fn core_api(&mut self) -> crate::api::CoreApi<'_, &mut BC> {
let (core_ctx, bindings_ctx) = self.contexts();
let core_ctx = core_ctx.as_owned();
crate::api::CoreApi::new(crate::context::CtxPair { core_ctx, bindings_ctx })
}
/// Joins the multicast group `multicast_addr` for `device`.
#[netstack3_macros::context_ip_bounds(A::Version, BC, crate)]
#[cfg(test)]
pub fn join_ip_multicast<A: IpAddress>(
&mut self,
device: &DeviceId<BC>,
multicast_addr: MulticastAddr<A>,
) where
A::Version: crate::IpExt,
{
let (core_ctx, bindings_ctx) = self.contexts();
crate::ip::device::join_ip_multicast::<A::Version, _, _>(
core_ctx,
bindings_ctx,
device,
multicast_addr,
);
}
/// Leaves the multicast group `multicast_addr` for `device`.
#[cfg(test)]
#[netstack3_macros::context_ip_bounds(A::Version, BC, crate)]
pub fn leave_ip_multicast<A: IpAddress>(
&mut self,
device: &DeviceId<BC>,
multicast_addr: MulticastAddr<A>,
) where
A::Version: crate::IpExt,
{
let (core_ctx, bindings_ctx) = self.contexts();
crate::ip::device::leave_ip_multicast::<A::Version, _, _>(
core_ctx,
bindings_ctx,
device,
multicast_addr,
);
}
/// Returns whether `device` is in the multicast group `addr`.
#[cfg(test)]
#[netstack3_macros::context_ip_bounds(A::Version, BC, crate)]
pub fn is_in_ip_multicast<A: IpAddress>(
&mut self,
device: &DeviceId<BC>,
addr: MulticastAddr<A>,
) -> bool
where
A::Version: crate::IpExt,
{
use crate::ip::{
AddressStatus, IpDeviceStateContext, IpLayerIpExt, Ipv4PresentAddressStatus,
Ipv6PresentAddressStatus,
};
let (core_ctx, _) = self.contexts();
let addr_status = IpDeviceStateContext::<A::Version, _>::address_status_for_device(
core_ctx,
addr.into_specified(),
device,
);
let status = match addr_status {
AddressStatus::Present(p) => p,
AddressStatus::Unassigned => return false,
};
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Wrap<I: IpLayerIpExt>(I::AddressStatus);
A::Version::map_ip(
Wrap(status),
|Wrap(v4)| match v4 {
Ipv4PresentAddressStatus::Multicast => true,
Ipv4PresentAddressStatus::LimitedBroadcast
| Ipv4PresentAddressStatus::SubnetBroadcast
| Ipv4PresentAddressStatus::Unicast => false,
},
|Wrap(v6)| match v6 {
Ipv6PresentAddressStatus::Multicast => true,
Ipv6PresentAddressStatus::UnicastAssigned
| Ipv6PresentAddressStatus::UnicastTentative => false,
},
)
}
/// Receive an IP packet from a device.
///
/// `receive_ip_packet` injects a packet directly at the IP layer for this
/// context.
#[cfg(test)]
pub fn receive_ip_packet<I: Ip, B: BufferMut>(
&mut self,
device: &DeviceId<BC>,
frame_dst: Option<crate::device::FrameDestination>,
buffer: B,
) {
let (core_ctx, bindings_ctx) = self.contexts();
match I::VERSION {
IpVersion::V4 => {
crate::ip::receive_ipv4_packet(core_ctx, bindings_ctx, device, frame_dst, buffer)
}
IpVersion::V6 => {
crate::ip::receive_ipv6_packet(core_ctx, bindings_ctx, device, frame_dst, buffer)
}
}
}
/// Add a route directly to the forwarding table.
pub fn add_route(
&mut self,
entry: crate::ip::types::AddableEntryEither<crate::device::DeviceId<BC>>,
) -> Result<(), crate::ip::forwarding::AddRouteError> {
let (core_ctx, bindings_ctx) = self.contexts();
match entry {
crate::ip::types::AddableEntryEither::V4(entry) => {
crate::ip::forwarding::testutil::add_route::<Ipv4, _, _>(
core_ctx,
bindings_ctx,
entry,
)
}
crate::ip::types::AddableEntryEither::V6(entry) => {
crate::ip::forwarding::testutil::add_route::<Ipv6, _, _>(
core_ctx,
bindings_ctx,
entry,
)
}
}
}
/// Delete a route from the forwarding table, returning `Err` if no route
/// was found to be deleted.
pub fn del_routes_to_subnet(
&mut self,
subnet: net_types::ip::SubnetEither,
) -> crate::error::Result<()> {
let (core_ctx, bindings_ctx) = self.contexts();
match subnet {
SubnetEither::V4(subnet) => crate::ip::forwarding::testutil::del_routes_to_subnet::<
Ipv4,
_,
_,
>(core_ctx, bindings_ctx, subnet),
SubnetEither::V6(subnet) => crate::ip::forwarding::testutil::del_routes_to_subnet::<
Ipv6,
_,
_,
>(core_ctx, bindings_ctx, subnet),
}
.map_err(From::from)
}
/// Deletes all routes targeting `device`.
pub(crate) fn del_device_routes(&mut self, device: &DeviceId<BC>) {
let (core_ctx, bindings_ctx) = self.contexts();
crate::ip::forwarding::testutil::del_device_routes::<Ipv4, _, _>(
core_ctx,
bindings_ctx,
device,
);
crate::ip::forwarding::testutil::del_device_routes::<Ipv6, _, _>(
core_ctx,
bindings_ctx,
device,
);
}
/// Removes all of the routes through the device, then removes the device.
pub fn clear_routes_and_remove_ethernet_device(
&mut self,
ethernet_device: crate::device::EthernetDeviceId<BC>,
) {
let device_id = ethernet_device.into();
self.del_device_routes(&device_id);
let ethernet_device =
assert_matches!(device_id, crate::device::DeviceId::Ethernet(id) => id);
match self.core_api().device().remove_device(ethernet_device) {
crate::sync::RemoveResourceResult::Removed(_external_state) => {}
crate::sync::RemoveResourceResult::Deferred(_reference_receiver) => {
panic!("failed to remove ethernet device")
}
}
}
}
/// Helper functions for dealing with fake timers.
impl<BC: BindingsTypes> Ctx<BC> {
/// Shortcut for [`FakeTimerCtxExt::trigger_next_timer`].
pub fn trigger_next_timer<Id>(&mut self) -> Option<Id>
where
BC: FakeTimerCtxExt<Id>,
for<'a> UnlockedCoreCtx<'a, BC>: TimerHandler<BC, Id>,
{
let Self { core_ctx, bindings_ctx } = self;
bindings_ctx.trigger_next_timer(&mut core_ctx.context())
}
/// Shortcut for [`FakeTimerCtxExt::trigger_timers_for`].
pub fn trigger_timers_for<Id>(&mut self, duration: Duration) -> Vec<Id>
where
BC: FakeTimerCtxExt<Id>,
for<'a> UnlockedCoreCtx<'a, BC>: TimerHandler<BC, Id>,
{
let Self { core_ctx, bindings_ctx } = self;
bindings_ctx.trigger_timers_for(duration, &mut core_ctx.context())
}
/// Shortcut for [`FaketimerCtx::trigger_timers_until_instant`].
pub fn trigger_timers_until_instant<Id>(&mut self, instant: FakeInstant) -> Vec<Id>
where
BC: FakeTimerCtxExt<Id>,
for<'a> UnlockedCoreCtx<'a, BC>: TimerHandler<BC, Id>,
{
let Self { core_ctx, bindings_ctx } = self;
bindings_ctx.trigger_timers_until_instant(instant, &mut core_ctx.context())
}
/// Shortcut for [`FakeTimerCtxExt::trigger_timers_until_and_expect_unordered`].
pub fn trigger_timers_until_and_expect_unordered<Id, I: IntoIterator<Item = Id>>(
&mut self,
instant: FakeInstant,
timers: I,
) where
Id: Debug + Hash + Eq,
BC: FakeTimerCtxExt<Id>,
for<'a> UnlockedCoreCtx<'a, BC>: TimerHandler<BC, Id>,
{
let Self { core_ctx, bindings_ctx } = self;
bindings_ctx.trigger_timers_until_and_expect_unordered(
instant,
timers,
&mut core_ctx.context(),
)
}
}
/// Asserts that an iterable object produces zero items.
///
/// `assert_empty` drains `into_iter.into_iter()` and asserts that zero
/// items are produced. It panics with a message which includes the produced
/// items if this assertion fails.
#[cfg(test)]
#[track_caller]
pub(crate) fn assert_empty<I: IntoIterator>(into_iter: I)
where
I::Item: Debug,
{
// NOTE: Collecting into a `Vec` is cheap in the happy path because
// zero-capacity vectors are guaranteed not to allocate.
let vec = into_iter.into_iter().collect::<Vec<_>>();
assert!(vec.is_empty(), "vec={vec:?}");
}
/// Utilities to allow running benchmarks as tests.
///
/// Our benchmarks rely on the unstable `test` feature, which is disallowed in
/// Fuchsia's build system. In order to ensure that our benchmarks are always
/// compiled and tested, this module provides fakes that allow us to run our
/// benchmarks as normal tests when the `benchmark` feature is disabled.
///
/// See the `bench!` macro for details on how this module is used.
#[cfg(test)]
pub(crate) mod benchmarks {
/// A trait to allow faking of the `test::Bencher` type.
pub(crate) trait Bencher {
fn iter<T, F: FnMut() -> T>(&mut self, inner: F);
}
#[cfg(benchmark)]
impl Bencher for criterion::Bencher {
fn iter<T, F: FnMut() -> T>(&mut self, inner: F) {
criterion::Bencher::iter(self, inner)
}
}
/// A `Bencher` whose `iter` method runs the provided argument a small,
/// fixed number of times.
#[cfg(not(benchmark))]
pub(crate) struct TestBencher;
#[cfg(not(benchmark))]
impl Bencher for TestBencher {
fn iter<T, F: FnMut() -> T>(&mut self, mut inner: F) {
const NUM_TEST_ITERS: u32 = 256;
super::set_logger_for_test();
for _ in 0..NUM_TEST_ITERS {
let _: T = inner();
}
}
}
#[inline(always)]
pub(crate) fn black_box<T>(placeholder: T) -> T {
#[cfg(benchmark)]
return criterion::black_box(placeholder);
#[cfg(not(benchmark))]
return placeholder;
}
}
#[derive(Default)]
/// Bindings context state held by [`FakeBindingsCtx`].
pub struct FakeBindingsCtxState {
icmpv4_replies:
HashMap<IcmpSocketId<Ipv4, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>,
icmpv6_replies:
HashMap<IcmpSocketId<Ipv6, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>,
udpv4_received:
HashMap<UdpSocketId<Ipv4, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>,
udpv6_received:
HashMap<UdpSocketId<Ipv6, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>,
pub(crate) rx_available: Vec<LoopbackDeviceId<FakeBindingsCtx>>,
pub(crate) tx_available: Vec<DeviceId<FakeBindingsCtx>>,
}
impl FakeBindingsCtxState {
pub(crate) fn udp_state_mut<I: crate::IpExt>(
&mut self,
) -> &mut HashMap<UdpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>, Vec<Vec<u8>>>
{
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct Wrapper<'a, I: crate::IpExt>(
&'a mut HashMap<
UdpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>,
Vec<Vec<u8>>,
>,
);
let Wrapper(map) = I::map_ip::<_, Wrapper<'_, I>>(
IpInvariant(self),
|IpInvariant(this)| Wrapper(&mut this.udpv4_received),
|IpInvariant(this)| Wrapper(&mut this.udpv6_received),
);
map
}
}
/// Shorthand for [`Ctx`] with a [`FakeBindingsCtx`].
pub type FakeCtx = Ctx<FakeBindingsCtx>;
/// Shorthand for [`StackState`] that uses a [`FakeBindingsCtx`].
pub type FakeCoreCtx = StackState<FakeBindingsCtx>;
/// Test-only implementation of [`crate::BindingsContext`].
#[derive(Default, Clone)]
pub struct FakeBindingsCtx(
Arc<
Mutex<
crate::context::testutil::FakeBindingsCtx<
TimerId<Self>,
DispatchedEvent,
FakeBindingsCtxState,
DispatchedFrame,
>,
>,
>,
);
/// A wrapper type that makes it easier to implement `Deref` (and optionally
/// `DerefMut`) for a value that is protected by a lock.
///
/// The first field is the type that provides access to the inner value,
/// probably a lock guard. The second and third fields are functions that, given
/// the first field, provide shared and mutable access (respectively) to the
/// inner value.
struct Wrapper<S, Callback, CallbackMut>(S, Callback, CallbackMut);
impl<T: ?Sized, S: Deref, Callback: for<'a> Fn(&'a <S as Deref>::Target) -> &'a T, CallbackMut>
Deref for Wrapper<S, Callback, CallbackMut>
{
type Target = T;
fn deref(&self) -> &T {
let Self(guard, f, _) = self;
let target = guard.deref();
f(target)
}
}
impl<
T: ?Sized,
S: DerefMut,
Callback: for<'a> Fn(&'a <S as Deref>::Target) -> &'a T,
CallbackMut: for<'a> Fn(&'a mut <S as Deref>::Target) -> &'a mut T,
> DerefMut for Wrapper<S, Callback, CallbackMut>
{
fn deref_mut(&mut self) -> &mut T {
let Self(guard, _, f) = self;
let target = guard.deref_mut();
f(target)
}
}
impl FakeBindingsCtx {
fn with_inner<
F: FnOnce(
&crate::context::testutil::FakeBindingsCtx<
TimerId<Self>,
DispatchedEvent,
FakeBindingsCtxState,
DispatchedFrame,
>,
) -> O,
O,
>(
&self,
f: F,
) -> O {
let Self(this) = self;
let locked = this.lock();
f(&*locked)
}
fn with_inner_mut<
F: FnOnce(
&mut crate::context::testutil::FakeBindingsCtx<
TimerId<Self>,
DispatchedEvent,
FakeBindingsCtxState,
DispatchedFrame,
>,
) -> O,
O,
>(
&self,
f: F,
) -> O {
let Self(this) = self;
let mut locked = this.lock();
f(&mut *locked)
}
#[cfg(test)]
pub(crate) fn timer_ctx(&self) -> impl Deref<Target = FakeTimerCtx<TimerId<Self>>> + '_ {
Wrapper(self.0.lock(), crate::context::testutil::FakeBindingsCtx::timer_ctx, ())
}
pub(crate) fn state_mut(&mut self) -> impl DerefMut<Target = FakeBindingsCtxState> + '_ {
Wrapper(
self.0.lock(),
crate::context::testutil::FakeBindingsCtx::state,
crate::context::testutil::FakeBindingsCtx::state_mut,
)
}
/// Copy all ethernet frames sent so far.
///
/// # Panics
///
/// Panics if the there are non-Ethernet frames stored.
#[cfg(test)]
pub fn copy_ethernet_frames(
&mut self,
) -> Vec<(EthernetWeakDeviceId<FakeBindingsCtx>, Vec<u8>)> {
self.with_inner_mut(|ctx| {
ctx.frame_ctx_mut()
.frames()
.into_iter()
.map(|(meta, frame)| match meta {
DispatchedFrame::Ethernet(eth) => (eth.clone(), frame.clone()),
DispatchedFrame::PureIp(ip) => panic!("unexpected IP packet {ip:?}: {frame:?}"),
})
.collect()
})
}
/// Take all ethernet frames sent so far.
///
/// # Panics
///
/// Panics if the there are non-Ethernet frames stored.
pub fn take_ethernet_frames(
&mut self,
) -> Vec<(EthernetWeakDeviceId<FakeBindingsCtx>, Vec<u8>)> {
self.with_inner_mut(|ctx| {
ctx.frame_ctx_mut()
.take_frames()
.into_iter()
.map(|(meta, frame)| match meta {
DispatchedFrame::Ethernet(eth) => (eth, frame),
DispatchedFrame::PureIp(ip) => panic!("unexpected IP packet {ip:?}: {frame:?}"),
})
.collect()
})
}
/// Take all IP frames sent so far.
///
/// # Panics
///
/// Panics if the there are non-IP frames stored.
pub fn take_ip_frames(&mut self) -> Vec<(PureIpDeviceAndIpVersion<FakeBindingsCtx>, Vec<u8>)> {
self.with_inner_mut(|ctx| {
ctx.frame_ctx_mut()
.take_frames()
.into_iter()
.map(|(meta, frame)| match meta {
DispatchedFrame::Ethernet(eth) => {
panic!("unexpected Ethernet frame {eth:?}: {frame:?}")
}
DispatchedFrame::PureIp(ip) => (ip, frame),
})
.collect()
})
}
#[cfg(test)]
pub(crate) fn take_events(&mut self) -> Vec<DispatchedEvent> {
self.with_inner_mut(|ctx| ctx.events.take())
}
/// Takes all the received ICMP replies for a given `conn`.
#[cfg(test)]
pub(crate) fn take_icmp_replies<I: crate::IpExt>(
&mut self,
conn: &IcmpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>,
) -> Vec<Vec<u8>> {
I::map_ip::<_, IpInvariant<Option<Vec<_>>>>(
(IpInvariant(self), conn),
|(IpInvariant(this), conn)| IpInvariant(this.state_mut().icmpv4_replies.remove(conn)),
|(IpInvariant(this), conn)| IpInvariant(this.state_mut().icmpv6_replies.remove(conn)),
)
.into_inner()
.unwrap_or_else(Vec::default)
}
#[cfg(test)]
pub(crate) fn take_udp_received<I: crate::IpExt>(
&mut self,
conn: &UdpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>,
) -> Vec<Vec<u8>> {
self.state_mut().udp_state_mut::<I>().remove(conn).unwrap_or_else(Vec::default)
}
}
impl FilterBindingsTypes for FakeBindingsCtx {
type DeviceClass = ();
}
impl DeviceClassMatcher<()> for () {
fn device_class_matches(&self, (): &()) -> bool {
unimplemented!()
}
}
impl WithFakeTimerContext<TimerId<FakeBindingsCtx>> for FakeCtx {
fn with_fake_timer_ctx<O, F: FnOnce(&FakeTimerCtx<TimerId<FakeBindingsCtx>>) -> O>(
&self,
f: F,
) -> O {
let Self { core_ctx: _, bindings_ctx } = self;
bindings_ctx.with_inner(|ctx| f(&ctx.timers))
}
fn with_fake_timer_ctx_mut<O, F: FnOnce(&mut FakeTimerCtx<TimerId<FakeBindingsCtx>>) -> O>(
&mut self,
f: F,
) -> O {
let Self { core_ctx: _, bindings_ctx } = self;
bindings_ctx.with_inner_mut(|ctx| f(&mut ctx.timers))
}
}
#[cfg(test)]
impl WithFakeFrameContext<DispatchedFrame> for FakeCtx {
fn with_fake_frame_ctx_mut<O, F: FnOnce(&mut FakeFrameCtx<DispatchedFrame>) -> O>(
&mut self,
f: F,
) -> O {
let Self { core_ctx: _, bindings_ctx } = self;
bindings_ctx.with_inner_mut(|ctx| f(&mut ctx.frames))
}
}
impl WithFakeTimerContext<TimerId<FakeBindingsCtx>> for FakeBindingsCtx {
fn with_fake_timer_ctx<O, F: FnOnce(&FakeTimerCtx<TimerId<FakeBindingsCtx>>) -> O>(
&self,
f: F,
) -> O {
self.with_inner(|ctx| f(&ctx.timers))
}
fn with_fake_timer_ctx_mut<O, F: FnOnce(&mut FakeTimerCtx<TimerId<FakeBindingsCtx>>) -> O>(
&mut self,
f: F,
) -> O {
self.with_inner_mut(|ctx| f(&mut ctx.timers))
}
}
impl InstantBindingsTypes for FakeBindingsCtx {
type Instant = FakeInstant;
}
impl InstantContext for FakeBindingsCtx {
fn now(&self) -> FakeInstant {
self.with_inner(|ctx| ctx.now())
}
}
// TODO(https://fxbug.dev/42083407): Improve the fake timer implementation
// to not rely on hashing the dispatch IDs. This implementation gives us a
// way to soft transition to the new world, but the new API is not asking
// for DispatchId uniqueness between timers, even though that's how current
// usage works.
impl TimerBindingsTypes for FakeBindingsCtx {
type Timer = TimerId<Self>;
type DispatchId = TimerId<Self>;
}
impl TimerContext2 for FakeBindingsCtx {
fn new_timer(&mut self, id: Self::DispatchId) -> Self::Timer {
self.with_inner_mut(|ctx| ctx.new_timer(id))
}
fn schedule_timer_instant2(
&mut self,
time: Self::Instant,
timer: &mut Self::Timer,
) -> Option<Self::Instant> {
self.with_inner_mut(|ctx| ctx.schedule_timer_instant2(time, timer))
}
fn cancel_timer2(&mut self, timer: &mut Self::Timer) -> Option<Self::Instant> {
self.with_inner_mut(|ctx| ctx.cancel_timer2(timer))
}
fn scheduled_instant2(&self, timer: &mut Self::Timer) -> Option<Self::Instant> {
self.with_inner_mut(|ctx| ctx.scheduled_instant2(timer))
}
}
impl TimerContext<TimerId<FakeBindingsCtx>> for FakeBindingsCtx {
fn schedule_timer_instant(
&mut self,
time: FakeInstant,
id: TimerId<FakeBindingsCtx>,
) -> Option<FakeInstant> {
self.with_inner_mut(|ctx| ctx.schedule_timer_instant(time, id))
}
fn cancel_timer(&mut self, id: TimerId<FakeBindingsCtx>) -> Option<FakeInstant> {
self.with_inner_mut(|ctx| ctx.cancel_timer(id))
}
fn cancel_timers_with<F: FnMut(&TimerId<FakeBindingsCtx>) -> bool>(&mut self, f: F) {
self.with_inner_mut(|ctx| ctx.cancel_timers_with(f))
}
fn scheduled_instant(&self, id: TimerId<FakeBindingsCtx>) -> Option<FakeInstant> {
self.with_inner_mut(|ctx| ctx.scheduled_instant(id))
}
}
impl RngContext for FakeBindingsCtx {
type Rng<'a> = FakeCryptoRng<XorShiftRng>;
fn rng(&mut self) -> Self::Rng<'_> {
let Self(this) = self;
this.lock().rng()
}
}
impl<T: Into<DispatchedEvent>> EventContext<T> for FakeBindingsCtx {
fn on_event(&mut self, event: T) {
self.with_inner_mut(|ctx| ctx.events.on_event(event.into()))
}
}
impl TracingContext for FakeBindingsCtx {
type DurationScope = ();
fn duration(&self, _: &'static CStr) {}
}
impl TcpBindingsTypes for FakeBindingsCtx {
type ReceiveBuffer = Arc<Mutex<RingBuffer>>;
type SendBuffer = TestSendBuffer;
type ReturnedBuffers = ClientBuffers;
type ListenerNotifierOrProvidedBuffers = ProvidedBuffers;
fn new_passive_open_buffers(
buffer_sizes: BufferSizes,
) -> (Self::ReceiveBuffer, Self::SendBuffer, Self::ReturnedBuffers) {
let client = ClientBuffers::new(buffer_sizes);
(
Arc::clone(&client.receive),
TestSendBuffer::new(Arc::clone(&client.send), RingBuffer::default()),
client,
)
}
fn default_buffer_sizes() -> BufferSizes {
// Use the test-only default impl.
BufferSizes::default()
}
}
impl crate::ReferenceNotifiers for FakeBindingsCtx {
type ReferenceReceiver<T: 'static> = Never;
type ReferenceNotifier<T: Send + 'static> = Never;
fn new_reference_notifier<T: Send + 'static, D: Debug>(
debug_references: D,
) -> (Self::ReferenceNotifier<T>, Self::ReferenceReceiver<T>) {
// NB: We don't want deferred destruction in core tests. These are
// always single-threaded and single-task, and we want to encourage
// explicit cleanup.
panic!(
"FakeBindingsCtx can't create deferred reference notifiers for type {}: \
debug_references={debug_references:?}",
core::any::type_name::<T>()
);
}
}
impl<D: LinkDevice> LinkResolutionContext<D> for FakeBindingsCtx {
type Notifier = ();
}
impl<D: LinkDevice> LinkResolutionNotifier<D> for () {
type Observer = ();
fn new() -> (Self, Self::Observer) {
((), ())
}
fn notify(self, _result: Result<D::Address, crate::error::AddressResolutionFailed>) {}
}
/// A wrapper which implements `RngCore` and `CryptoRng` for any `RngCore`.
///
/// This is used to satisfy [`EventDispatcher`]'s requirement that the
/// associated `Rng` type implements `CryptoRng`.
///
/// # Security
///
/// This is obviously insecure. Don't use it except in testing!
#[derive(Clone, Debug)]
pub struct FakeCryptoRng<R>(Arc<Mutex<R>>);
impl Default for FakeCryptoRng<XorShiftRng> {
fn default() -> FakeCryptoRng<XorShiftRng> {
FakeCryptoRng::new_xorshift(12957992561116578403)
}
}
impl FakeCryptoRng<XorShiftRng> {
/// Creates a new [`FakeCryptoRng<XorShiftRng>`] from a seed.
pub(crate) fn new_xorshift(seed: u128) -> FakeCryptoRng<XorShiftRng> {
Self(Arc::new(Mutex::new(new_rng(seed))))
}
#[cfg(test)]
pub(crate) fn deep_clone(&self) -> Self {
Self(Arc::new(Mutex::new(self.0.lock().clone())))
}
}
impl<R: RngCore> RngCore for FakeCryptoRng<R> {
fn next_u32(&mut self) -> u32 {
self.0.lock().next_u32()
}
fn next_u64(&mut self) -> u64 {
self.0.lock().next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.lock().fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.0.lock().try_fill_bytes(dest)
}
}
impl<R: RngCore> CryptoRng for FakeCryptoRng<R> {}
impl<R: SeedableRng> SeedableRng for FakeCryptoRng<R> {
type Seed = R::Seed;
fn from_seed(seed: Self::Seed) -> Self {
Self(Arc::new(Mutex::new(R::from_seed(seed))))
}
}
impl<R: RngCore> crate::context::RngContext for FakeCryptoRng<R> {
type Rng<'a> = &'a mut Self where Self: 'a;
fn rng(&mut self) -> Self::Rng<'_> {
self
}
}
/// Create a new deterministic RNG from a seed.
pub(crate) fn new_rng(mut seed: u128) -> XorShiftRng {
if seed == 0 {
// XorShiftRng can't take 0 seeds
seed = 1;
}
XorShiftRng::from_seed(seed.to_ne_bytes())
}
/// Creates `iterations` fake RNGs.
///
/// `with_fake_rngs` will create `iterations` different [`FakeCryptoRng`]s and
/// call the function `f` for each one of them.
///
/// This function can be used for tests that weed out weirdness that can
/// happen with certain random number sequences.
#[cfg(test)]
pub(crate) fn with_fake_rngs<F: Fn(FakeCryptoRng<XorShiftRng>)>(iterations: u128, f: F) {
for seed in 0..iterations {
f(FakeCryptoRng::new_xorshift(seed))
}
}
/// Invokes a function multiple times with different RNG seeds.
#[cfg(test)]
pub(crate) fn run_with_many_seeds<F: FnMut(u128)>(mut f: F) {
// Arbitrary seed.
let mut rng = new_rng(0x0fe50fae6c37593d71944697f1245847);
for _ in 0..64 {
f(rng.gen());
}
}
#[cfg(test)]
struct SimpleFormatter;
#[cfg(test)]
impl<S, N> FormatEvent<S, N> for SimpleFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: format::Writer<'_>,
event: &tracing::Event<'_>,
) -> std::fmt::Result {
ctx.format_fields(writer.by_ref(), event)?;
writeln!(writer)
}
}
/// Install a logger for tests.
///
/// Call this method at the beginning of the test for which logging is desired.
/// This function sets global program state, so all tests that run after this
/// function is called will use the logger.
#[cfg(test)]
pub(crate) fn set_logger_for_test() {
tracing::subscriber::set_global_default(
tracing_subscriber::fmt()
.event_format(SimpleFormatter)
.with_max_level(tracing::Level::TRACE)
.with_test_writer()
.finish(),
)
.unwrap_or({
// Ignore errors caused by some other test invocation having already set
// the global default subscriber.
})
}
/// An extension trait for `Ip` providing test-related functionality.
#[cfg(test)]
pub(crate) trait TestIpExt:
crate::ip::IpExt + crate::ip::IpLayerIpExt + crate::ip::device::IpDeviceIpExt
{
/// Either [`FAKE_CONFIG_V4`] or [`FAKE_CONFIG_V6`].
const FAKE_CONFIG: FakeEventDispatcherConfig<Self::Addr>;
/// Get an IP address in the same subnet as `Self::FAKE_CONFIG`.
///
/// `last` is the value to be put in the last octet of the IP address.
fn get_other_ip_address(last: u8) -> SpecifiedAddr<Self::Addr>;
/// Get an IP address in a different subnet from `Self::FAKE_CONFIG`.
///
/// `last` is the value to be put in the last octet of the IP address.
fn get_other_remote_ip_address(last: u8) -> SpecifiedAddr<Self::Addr>;
/// Get a multicast IP address.
///
/// `last` is the value to be put in the last octet of the IP address.
fn get_multicast_addr(last: u8) -> MulticastAddr<Self::Addr>;
}
#[cfg(test)]
impl TestIpExt for Ipv4 {
const FAKE_CONFIG: FakeEventDispatcherConfig<Ipv4Addr> = FAKE_CONFIG_V4;
fn get_other_ip_address(last: u8) -> SpecifiedAddr<Ipv4Addr> {
let mut bytes = Self::FAKE_CONFIG.local_ip.get().ipv4_bytes();
bytes[bytes.len() - 1] = last;
SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
}
fn get_other_remote_ip_address(last: u8) -> SpecifiedAddr<Self::Addr> {
let mut bytes = Self::FAKE_CONFIG.local_ip.get().ipv4_bytes();
bytes[bytes.len() - 3] += 1;
bytes[bytes.len() - 1] = last;
SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
}
fn get_multicast_addr(last: u8) -> MulticastAddr<Self::Addr> {
assert!(u32::from(Self::Addr::BYTES * 8 - Self::MULTICAST_SUBNET.prefix()) > u8::BITS);
let mut bytes = Self::MULTICAST_SUBNET.network().ipv4_bytes();
bytes[bytes.len() - 1] = last;
MulticastAddr::new(Ipv4Addr::new(bytes)).unwrap()
}
}
#[cfg(test)]
impl TestIpExt for Ipv6 {
const FAKE_CONFIG: FakeEventDispatcherConfig<Ipv6Addr> = FAKE_CONFIG_V6;
fn get_other_ip_address(last: u8) -> SpecifiedAddr<Ipv6Addr> {
let mut bytes = Self::FAKE_CONFIG.local_ip.get().ipv6_bytes();
bytes[bytes.len() - 1] = last;
SpecifiedAddr::new(Ipv6Addr::from(bytes)).unwrap()
}
fn get_other_remote_ip_address(last: u8) -> SpecifiedAddr<Self::Addr> {
let mut bytes = Self::FAKE_CONFIG.local_ip.get().ipv6_bytes();
bytes[bytes.len() - 3] += 1;
bytes[bytes.len() - 1] = last;
SpecifiedAddr::new(Ipv6Addr::from(bytes)).unwrap()
}
fn get_multicast_addr(last: u8) -> MulticastAddr<Self::Addr> {
assert!((Self::Addr::BYTES * 8 - Self::MULTICAST_SUBNET.prefix()) as u32 > u8::BITS);
let mut bytes = Self::MULTICAST_SUBNET.network().ipv6_bytes();
bytes[bytes.len() - 1] = last;
MulticastAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
}
}
/// A configuration for a simple network.
///
/// `FakeEventDispatcherConfig` describes a simple network with two IP hosts
/// - one remote and one local - both on the same Ethernet network.
#[cfg(test)]
#[derive(Clone, net_types::ip::GenericOverIp)]
#[generic_over_ip(A, IpAddress)]
pub(crate) struct FakeEventDispatcherConfig<A: IpAddress> {
/// The subnet of the local Ethernet network.
pub(crate) subnet: Subnet<A>,
/// The IP address of our interface to the local network (must be in
/// subnet).
pub(crate) local_ip: SpecifiedAddr<A>,
/// The MAC address of our interface to the local network.
pub(crate) local_mac: UnicastAddr<Mac>,
/// The remote host's IP address (must be in subnet if provided).
pub(crate) remote_ip: SpecifiedAddr<A>,
/// The remote host's MAC address.
pub(crate) remote_mac: UnicastAddr<Mac>,
}
/// A `FakeEventDispatcherConfig` with reasonable values for an IPv4 network.
#[cfg(test)]
pub(crate) const FAKE_CONFIG_V4: FakeEventDispatcherConfig<Ipv4Addr> = unsafe {
FakeEventDispatcherConfig {
subnet: Subnet::new_unchecked(Ipv4Addr::new([192, 168, 0, 0]), 16),
local_ip: SpecifiedAddr::new_unchecked(Ipv4Addr::new([192, 168, 0, 1])),
local_mac: UnicastAddr::new_unchecked(Mac::new([0, 1, 2, 3, 4, 5])),
remote_ip: SpecifiedAddr::new_unchecked(Ipv4Addr::new([192, 168, 0, 2])),
remote_mac: UnicastAddr::new_unchecked(Mac::new([6, 7, 8, 9, 10, 11])),
}
};
/// A `FakeEventDispatcherConfig` with reasonable values for an IPv6 network.
#[cfg(test)]
pub(crate) const FAKE_CONFIG_V6: FakeEventDispatcherConfig<Ipv6Addr> = unsafe {
FakeEventDispatcherConfig {
subnet: Subnet::new_unchecked(
Ipv6Addr::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 0]),
112,
),
local_ip: SpecifiedAddr::new_unchecked(Ipv6Addr::from_bytes([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 1,
])),
local_mac: UnicastAddr::new_unchecked(Mac::new([0, 1, 2, 3, 4, 5])),
remote_ip: SpecifiedAddr::new_unchecked(Ipv6Addr::from_bytes([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 2,
])),
remote_mac: UnicastAddr::new_unchecked(Mac::new([6, 7, 8, 9, 10, 11])),
}
};
#[cfg(test)]
impl<A: IpAddress> FakeEventDispatcherConfig<A> {
/// Creates a copy of `self` with all the remote and local fields reversed.
pub(crate) fn swap(&self) -> Self {
Self {
subnet: self.subnet,
local_ip: self.remote_ip,
local_mac: self.remote_mac,
remote_ip: self.local_ip,
remote_mac: self.local_mac,
}
}
/// Shorthand for `FakeEventDispatcherBuilder::from_config(self)`.
pub(crate) fn into_builder(self) -> FakeEventDispatcherBuilder {
FakeEventDispatcherBuilder::from_config(self)
}
}
#[cfg(test)]
impl FakeEventDispatcherConfig<Ipv6Addr> {
pub(crate) fn local_ipv6_device_addr(&self) -> Ipv6DeviceAddr {
NonMappedAddr::new(UnicastAddr::try_from(self.local_ip).unwrap()).unwrap()
}
pub(crate) fn remote_ipv6_device_addr(&self) -> Ipv6DeviceAddr {
NonMappedAddr::new(UnicastAddr::try_from(self.remote_ip).unwrap()).unwrap()
}
}
#[derive(Clone)]
struct DeviceConfig {
mac: UnicastAddr<Mac>,
addr_subnet: Option<AddrSubnetEither>,
ipv4_config: Option<Ipv4DeviceConfigurationUpdate>,
ipv6_config: Option<Ipv6DeviceConfigurationUpdate>,
}
/// A builder for `FakeEventDispatcher`s.
///
/// A `FakeEventDispatcherBuilder` is capable of storing the configuration of a
/// network stack including forwarding table entries, devices and their assigned
/// addresses and configurations, ARP table entries, etc. It can be built using
/// `build`, producing a `Context<FakeEventDispatcher>` with all of the
/// appropriate state configured.
#[derive(Clone, Default)]
pub struct FakeEventDispatcherBuilder {
devices: Vec<DeviceConfig>,
// TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
arp_table_entries: Vec<(usize, SpecifiedAddr<Ipv4Addr>, UnicastAddr<Mac>)>,
ndp_table_entries: Vec<(usize, UnicastAddr<Ipv6Addr>, UnicastAddr<Mac>)>,
// usize refers to index into devices Vec.
device_routes: Vec<(SubnetEither, usize)>,
}
impl FakeEventDispatcherBuilder {
/// Construct a `FakeEventDispatcherBuilder` from a
/// `FakeEventDispatcherConfig`.
#[cfg(test)]
pub(crate) fn from_config<A: IpAddress>(
cfg: FakeEventDispatcherConfig<A>,
) -> FakeEventDispatcherBuilder {
assert!(cfg.subnet.contains(&cfg.local_ip));
assert!(cfg.subnet.contains(&cfg.remote_ip));
let mut builder = FakeEventDispatcherBuilder::default();
builder.devices.push(DeviceConfig {
mac: cfg.local_mac,
addr_subnet: Some(
AddrSubnetEither::new(cfg.local_ip.get().into(), cfg.subnet.prefix()).unwrap(),
),
ipv4_config: None,
ipv6_config: None,
});
match cfg.remote_ip.into() {
IpAddr::V4(ip) => builder.arp_table_entries.push((0, ip, cfg.remote_mac)),
IpAddr::V6(ip) => builder.ndp_table_entries.push((
0,
UnicastAddr::new(ip.get()).unwrap(),
cfg.remote_mac,
)),
};
// Even with fixed ipv4 address we can have IPv6 link local addresses
// pre-cached.
builder.ndp_table_entries.push((
0,
cfg.remote_mac.to_ipv6_link_local().addr().get(),
cfg.remote_mac,
));
builder.device_routes.push((cfg.subnet.into(), 0));
builder
}
/// Add a device.
///
/// `add_device` returns a key which can be used to refer to the device in
/// future calls to `add_arp_table_entry` and `add_device_route`.
pub fn add_device(&mut self, mac: UnicastAddr<Mac>) -> usize {
let idx = self.devices.len();
self.devices.push(DeviceConfig {
mac,
addr_subnet: None,
ipv4_config: None,
ipv6_config: None,
});
idx
}
/// Add a device with an IPv4 and IPv6 configuration.
///
/// `add_device_with_config` is like `add_device`, except that it takes an
/// IPv4 and IPv6 configuration to apply to the device when it is enabled.
#[cfg(test)]
pub(crate) fn add_device_with_config(
&mut self,
mac: UnicastAddr<Mac>,
ipv4_config: Ipv4DeviceConfigurationUpdate,
ipv6_config: Ipv6DeviceConfigurationUpdate,
) -> usize {
let idx = self.devices.len();
self.devices.push(DeviceConfig {
mac,
addr_subnet: None,
ipv4_config: Some(ipv4_config),
ipv6_config: Some(ipv6_config),
});
idx
}
/// Add a device with an associated IP address.
///
/// `add_device_with_ip` is like `add_device`, except that it takes an
/// associated IP address and subnet to assign to the device.
pub fn add_device_with_ip<A: IpAddress>(
&mut self,
mac: UnicastAddr<Mac>,
ip: A,
subnet: Subnet<A>,
) -> usize {
assert!(subnet.contains(&ip));
let idx = self.devices.len();
self.devices.push(DeviceConfig {
mac,
addr_subnet: Some(AddrSubnetEither::new(ip.into(), subnet.prefix()).unwrap()),
ipv4_config: None,
ipv6_config: None,
});
self.device_routes.push((subnet.into(), idx));
idx
}
/// Add a device with an associated IP address and a particular IPv4 and
/// IPv6 configuration.
///
/// `add_device_with_ip_and_config` is like `add_device`, except that it
/// takes an associated IP address and subnet to assign to the device, as
/// well as IPv4 and IPv6 configurations to apply to the device when it is
/// enabled.
#[cfg(test)]
pub(crate) fn add_device_with_ip_and_config<A: IpAddress>(
&mut self,
mac: UnicastAddr<Mac>,
ip: A,
subnet: Subnet<A>,
ipv4_config: Ipv4DeviceConfigurationUpdate,
ipv6_config: Ipv6DeviceConfigurationUpdate,
) -> usize {
assert!(subnet.contains(&ip));
let idx = self.devices.len();
self.devices.push(DeviceConfig {
mac,
addr_subnet: Some(AddrSubnetEither::new(ip.into(), subnet.prefix()).unwrap()),
ipv4_config: Some(ipv4_config),
ipv6_config: Some(ipv6_config),
});
self.device_routes.push((subnet.into(), idx));
idx
}
/// Add an ARP table entry for a device's ARP table.
#[cfg(test)]
pub(crate) fn add_arp_table_entry(
&mut self,
device: usize,
// TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
ip: SpecifiedAddr<Ipv4Addr>,
mac: UnicastAddr<Mac>,
) {
self.arp_table_entries.push((device, ip, mac));
}
/// Add an NDP table entry for a device's NDP table.
#[cfg(test)]
pub(crate) fn add_ndp_table_entry(
&mut self,
device: usize,
// TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
ip: UnicastAddr<Ipv6Addr>,
mac: UnicastAddr<Mac>,
) {
self.ndp_table_entries.push((device, ip, mac));
}
/// Builds a `Ctx` from the present configuration with a default dispatcher.
#[cfg(any(test, feature = "testutils"))]
pub fn build(self) -> (FakeCtx, Vec<EthernetDeviceId<FakeBindingsCtx>>) {
self.build_with_modifications(|_| {})
}
/// `build_with_modifications` is equivalent to `build`, except that after
/// the `StackStateBuilder` is initialized, it is passed to `f` for further
/// modification before the `Ctx` is constructed.
#[cfg(any(test, feature = "testutils"))]
pub(crate) fn build_with_modifications<F: FnOnce(&mut StackStateBuilder)>(
self,
f: F,
) -> (FakeCtx, Vec<EthernetDeviceId<FakeBindingsCtx>>) {
let mut stack_builder = StackStateBuilder::default();
f(&mut stack_builder);
self.build_with(stack_builder)
}
/// Build a `Ctx` from the present configuration with a caller-provided
/// dispatcher and `StackStateBuilder`.
#[cfg(any(test, feature = "testutils"))]
pub(crate) fn build_with(
self,
state_builder: StackStateBuilder,
) -> (FakeCtx, Vec<EthernetDeviceId<FakeBindingsCtx>>) {
let mut ctx = Ctx::new_with_builder(state_builder);
let FakeEventDispatcherBuilder {
devices,
arp_table_entries,
ndp_table_entries,
device_routes,
} = self;
let idx_to_device_id: Vec<_> = devices
.into_iter()
.map(|DeviceConfig { mac, addr_subnet: ip_and_subnet, ipv4_config, ipv6_config }| {
let eth_id =
ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state(
EthernetCreationProperties {
mac: mac,
max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE,
},
DEFAULT_INTERFACE_METRIC,
);
let id = eth_id.clone().into();
if let Some(ipv4_config) = ipv4_config {
let _previous = ctx
.core_api()
.device_ip::<Ipv4>()
.update_configuration(&id, ipv4_config)
.unwrap();
}
if let Some(ipv6_config) = ipv6_config {
let _previous = ctx
.core_api()
.device_ip::<Ipv6>()
.update_configuration(&id, ipv6_config)
.unwrap();
}
crate::device::testutil::enable_device(&mut ctx, &id);
match ip_and_subnet {
Some(addr_sub) => {
ctx.core_api().device_ip_any().add_ip_addr_subnet(&id, addr_sub).unwrap();
}
None => {}
}
eth_id
})
.collect();
for (idx, ip, mac) in arp_table_entries {
let device = &idx_to_device_id[idx];
ctx.core_api()
.neighbor::<Ipv4, EthernetLinkDevice>()
.insert_static_entry(&device, ip.get(), mac.get())
.expect("error inserting static ARP entry");
}
for (idx, ip, mac) in ndp_table_entries {
let device = &idx_to_device_id[idx];
ctx.core_api()
.neighbor::<Ipv6, EthernetLinkDevice>()
.insert_static_entry(&device, ip.get(), mac.get())
.expect("error inserting static NDP entry");
}
for (subnet, idx) in device_routes {
let device = &idx_to_device_id[idx];
ctx.test_api()
.add_route(crate::ip::types::AddableEntryEither::without_gateway(
subnet,
device.clone().into(),
AddableMetric::ExplicitMetric(RawMetric(0)),
))
.expect("add device route");
}
(ctx, idx_to_device_id)
}
}
/// Add either an NDP entry (if IPv6) or ARP entry (if IPv4) to a
/// `FakeEventDispatcherBuilder`.
#[cfg(test)]
pub(crate) fn add_arp_or_ndp_table_entry<A: IpAddress>(
builder: &mut FakeEventDispatcherBuilder,
device: usize,
// TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
ip: SpecifiedAddr<A>,
mac: UnicastAddr<Mac>,
) {
match ip.into() {
IpAddr::V4(ip) => builder.add_arp_table_entry(device, ip, mac),
IpAddr::V6(ip) => {
builder.add_ndp_table_entry(device, UnicastAddr::new(ip.get()).unwrap(), mac)
}
}
}
#[cfg(test)]
impl FakeNetworkContext for FakeCtx {
type TimerId = TimerId<FakeBindingsCtx>;
type SendMeta = DispatchedFrame;
type RecvMeta = EthernetDeviceId<FakeBindingsCtx>;
fn handle_frame(&mut self, device_id: Self::RecvMeta, data: Buf<Vec<u8>>) {
self.core_api()
.device::<crate::device::ethernet::EthernetLinkDevice>()
.receive_frame(crate::device::ethernet::RecvEthernetFrameMeta { device_id }, data)
}
fn handle_timer(&mut self, timer: Self::TimerId) {
self.core_api().handle_timer(timer)
}
fn process_queues(&mut self) -> bool {
handle_queued_rx_packets(self)
}
}
impl<I: crate::IpExt> UdpReceiveBindingsContext<I, DeviceId<Self>> for FakeBindingsCtx {
fn receive_udp<B: BufferMut>(
&mut self,
id: &UdpSocketId<I, WeakDeviceId<Self>, FakeBindingsCtx>,
_device: &DeviceId<Self>,
_dst_addr: (<I>::Addr, core::num::NonZeroU16),
_src_addr: (<I>::Addr, Option<core::num::NonZeroU16>),
body: &B,
) {
let mut state = self.state_mut();
let received =
(&mut *state).udp_state_mut::<I>().entry(id.clone()).or_insert_with(Vec::default);
received.push(body.as_ref().to_owned());
}
}
impl UdpBindingsTypes for FakeBindingsCtx {
type ExternalData<I: Ip> = ();
}
impl<I: crate::IpExt> IcmpEchoBindingsContext<I, DeviceId<Self>> for FakeBindingsCtx {
fn receive_icmp_echo_reply<B: BufferMut>(
&mut self,
conn: &IcmpSocketId<I, WeakDeviceId<FakeBindingsCtx>, FakeBindingsCtx>,
_device: &DeviceId<Self>,
_src_ip: I::Addr,
_dst_ip: I::Addr,
_id: u16,
data: B,
) {
I::map_ip(
(IpInvariant(self.state_mut()), conn.clone()),
|(IpInvariant(mut state), conn)| {
let replies = state.icmpv4_replies.entry(conn).or_insert_with(Vec::default);
replies.push(data.as_ref().to_owned());
},
|(IpInvariant(mut state), conn)| {
let replies = state.icmpv6_replies.entry(conn).or_insert_with(Vec::default);
replies.push(data.as_ref().to_owned());
},
)
}
}
impl IcmpEchoBindingsTypes for FakeBindingsCtx {
type ExternalData<I: Ip> = ();
}
impl crate::device::socket::DeviceSocketTypes for FakeBindingsCtx {
type SocketState = Mutex<Vec<(WeakDeviceId<FakeBindingsCtx>, Vec<u8>)>>;
}
impl crate::device::socket::DeviceSocketBindingsContext<DeviceId<Self>> for FakeBindingsCtx {
fn receive_frame(
&self,
state: &Self::SocketState,
device: &DeviceId<Self>,
_frame: crate::device::socket::Frame<&[u8]>,
raw_frame: &[u8],
) {
state.lock().push((device.downgrade(), raw_frame.into()));
}
}
impl DeviceLayerStateTypes for FakeBindingsCtx {
type LoopbackDeviceState = ();
type EthernetDeviceState = ();
type PureIpDeviceState = ();
type DeviceIdentifier = MonotonicIdentifier;
}
impl DeviceLayerEventDispatcher for FakeBindingsCtx {
fn wake_rx_task(&mut self, device: &LoopbackDeviceId<FakeBindingsCtx>) {
self.state_mut().rx_available.push(device.clone());
}
fn wake_tx_task(&mut self, device: &DeviceId<FakeBindingsCtx>) {
self.state_mut().tx_available.push(device.clone());
}
fn send_ethernet_frame(
&mut self,
device: &EthernetDeviceId<FakeBindingsCtx>,
frame: Buf<Vec<u8>>,
) -> Result<(), DeviceSendFrameError<Buf<Vec<u8>>>> {
let frame_meta = DispatchedFrame::Ethernet(device.downgrade());
self.with_inner_mut(|ctx| ctx.frame_ctx_mut().push(frame_meta, frame.into_inner()));
Ok(())
}
fn send_ip_packet(
&mut self,
device: &PureIpDeviceId<FakeBindingsCtx>,
packet: Buf<Vec<u8>>,
ip_version: IpVersion,
) -> Result<(), DeviceSendFrameError<Buf<Vec<u8>>>> {
let frame_meta = DispatchedFrame::PureIp(PureIpDeviceAndIpVersion {
device: device.downgrade(),
version: ip_version,
});
self.with_inner_mut(|ctx| ctx.frame_ctx_mut().push(frame_meta, packet.into_inner()));
Ok(())
}
}
/// Handles any pending frames and returns true if any frames that were in the
/// RX queue were processed.
#[cfg(test)]
pub(crate) fn handle_queued_rx_packets(ctx: &mut FakeCtx) -> bool {
let mut handled = false;
loop {
let rx_available = core::mem::take(&mut ctx.bindings_ctx.state_mut().rx_available);
if rx_available.len() == 0 {
break handled;
}
handled = true;
for id in rx_available.into_iter() {
loop {
match ctx.core_api().receive_queue().handle_queued_frames(&id) {
crate::work_queue::WorkQueueReport::AllDone => break,
crate::work_queue::WorkQueueReport::Pending => (),
}
}
}
}
}
/// Wraps all events emitted by Core into a single enum type.
#[derive(Debug, Eq, PartialEq, Hash, GenericOverIp)]
#[generic_over_ip()]
#[allow(missing_docs)]
pub enum DispatchedEvent {
IpDeviceIpv4(IpDeviceEvent<WeakDeviceId<FakeBindingsCtx>, Ipv4, FakeInstant>),
IpDeviceIpv6(IpDeviceEvent<WeakDeviceId<FakeBindingsCtx>, Ipv6, FakeInstant>),
IpLayerIpv4(IpLayerEvent<WeakDeviceId<FakeBindingsCtx>, Ipv4>),
IpLayerIpv6(IpLayerEvent<WeakDeviceId<FakeBindingsCtx>, Ipv6>),
NeighborIpv4(nud::Event<Mac, EthernetWeakDeviceId<FakeBindingsCtx>, Ipv4, FakeInstant>),
NeighborIpv6(nud::Event<Mac, EthernetWeakDeviceId<FakeBindingsCtx>, Ipv6, FakeInstant>),
}
impl<I: Ip> From<IpDeviceEvent<DeviceId<FakeBindingsCtx>, I, FakeInstant>>
for IpDeviceEvent<WeakDeviceId<FakeBindingsCtx>, I, FakeInstant>
{
fn from(
e: IpDeviceEvent<DeviceId<FakeBindingsCtx>, I, FakeInstant>,
) -> IpDeviceEvent<WeakDeviceId<FakeBindingsCtx>, I, FakeInstant> {
match e {
IpDeviceEvent::AddressAdded { device, addr, state, valid_until } => {
IpDeviceEvent::AddressAdded { device: device.downgrade(), addr, state, valid_until }
}
IpDeviceEvent::AddressRemoved { device, addr, reason } => {
IpDeviceEvent::AddressRemoved { device: device.downgrade(), addr, reason }
}
IpDeviceEvent::AddressStateChanged { device, addr, state } => {
IpDeviceEvent::AddressStateChanged { device: device.downgrade(), addr, state }
}
IpDeviceEvent::EnabledChanged { device, ip_enabled } => {
IpDeviceEvent::EnabledChanged { device: device.downgrade(), ip_enabled }
}
IpDeviceEvent::AddressPropertiesChanged { device, addr, valid_until } => {
IpDeviceEvent::AddressPropertiesChanged {
device: device.downgrade(),
addr,
valid_until,
}
}
}
}
}
/// A frame that's been dispatched to Bindings to be sent out the device driver.
pub enum DispatchedFrame {
/// A frame that's been dispatched to an Ethernet device.
Ethernet(EthernetWeakDeviceId<FakeBindingsCtx>),
/// A frame that's been dispatched to a PureIp device.
PureIp(PureIpDeviceAndIpVersion<FakeBindingsCtx>),
}
impl<I: Ip> From<IpDeviceEvent<DeviceId<FakeBindingsCtx>, I, FakeInstant>> for DispatchedEvent {
fn from(e: IpDeviceEvent<DeviceId<FakeBindingsCtx>, I, FakeInstant>) -> DispatchedEvent {
I::map_ip(
e,
|e| DispatchedEvent::IpDeviceIpv4(e.into()),
|e| DispatchedEvent::IpDeviceIpv6(e.into()),
)
}
}
impl<I: Ip> From<IpLayerEvent<DeviceId<FakeBindingsCtx>, I>>
for IpLayerEvent<WeakDeviceId<FakeBindingsCtx>, I>
{
fn from(
e: IpLayerEvent<DeviceId<FakeBindingsCtx>, I>,
) -> IpLayerEvent<WeakDeviceId<FakeBindingsCtx>, I> {
match e {
IpLayerEvent::AddRoute(AddableEntry { subnet, device, gateway, metric }) => {
IpLayerEvent::AddRoute(AddableEntry {
subnet,
device: device.downgrade(),
gateway,
metric,
})
}
IpLayerEvent::RemoveRoutes { subnet, device, gateway } => {
IpLayerEvent::RemoveRoutes { subnet, device: device.downgrade(), gateway }
}
}
}
}
impl<I: Ip> From<IpLayerEvent<DeviceId<FakeBindingsCtx>, I>> for DispatchedEvent {
fn from(e: IpLayerEvent<DeviceId<FakeBindingsCtx>, I>) -> DispatchedEvent {
I::map_ip(
e,
|e| DispatchedEvent::IpLayerIpv4(e.into()),
|e| DispatchedEvent::IpLayerIpv6(e.into()),
)
}
}
impl<I: Ip> From<nud::Event<Mac, EthernetDeviceId<FakeBindingsCtx>, I, FakeInstant>>
for nud::Event<Mac, EthernetWeakDeviceId<FakeBindingsCtx>, I, FakeInstant>
{
fn from(
nud::Event { device, kind, addr, at }: nud::Event<
Mac,
EthernetDeviceId<FakeBindingsCtx>,
I,
FakeInstant,
>,
) -> Self {
Self { device: device.downgrade(), kind, addr, at }
}
}
impl<I: Ip> From<nud::Event<Mac, EthernetDeviceId<FakeBindingsCtx>, I, FakeInstant>>
for DispatchedEvent
{
fn from(
e: nud::Event<Mac, EthernetDeviceId<FakeBindingsCtx>, I, FakeInstant>,
) -> DispatchedEvent {
I::map_ip(
e,
|e| DispatchedEvent::NeighborIpv4(e.into()),
|e| DispatchedEvent::NeighborIpv6(e.into()),
)
}
}
pub(crate) const IPV6_MIN_IMPLIED_MAX_FRAME_SIZE: MaxEthernetFrameSize =
const_unwrap::const_unwrap_option(MaxEthernetFrameSize::from_mtu(Ipv6::MINIMUM_LINK_MTU));
/// A convenient monotonically increasing identifier to use as the bindings'
/// `DeviceIdentifier` in tests.
pub struct MonotonicIdentifier(usize);
impl Debug for MonotonicIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NB: This type is used as part of the debug implementation in device
// IDs which should provide enough context themselves on the type. For
// brevity we omit the type name.
let Self(id) = self;
Debug::fmt(id, f)
}
}
impl Display for MonotonicIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(self, f)
}
}
static MONOTONIC_COUNTER: AtomicUsize = AtomicUsize::new(1);
impl MonotonicIdentifier {
/// Creates a new identifier with the next value.
pub fn new() -> Self {
Self(MONOTONIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::SeqCst))
}
}
impl Default for MonotonicIdentifier {
fn default() -> Self {
Self::new()
}
}
impl DeviceIdAndNameMatcher for MonotonicIdentifier {
fn id_matches(&self, _id: &NonZeroU64) -> bool {
unimplemented!()
}
fn name_matches(&self, _name: &str) -> bool {
unimplemented!()
}
}
#[cfg(test)]
mod tests {
use ip_test_macro::ip_test;
use packet::Serializer;
use packet_formats::{
icmp::{IcmpEchoRequest, IcmpPacketBuilder, IcmpUnusedCode},
ip::Ipv4Proto,
};
use super::*;
use crate::{
context::testutil::{FakeNetwork, FakeNetworkLinks},
ip::{
socket::{DefaultSendOptions, IpSocketHandler},
IpLayerHandler,
},
socket::address::SocketIpAddr,
};
#[test]
fn test_fake_network_transmits_packets() {
set_logger_for_test();
let (alice_ctx, alice_device_ids) = FAKE_CONFIG_V4.into_builder().build();
let (bob_ctx, bob_device_ids) = FAKE_CONFIG_V4.swap().into_builder().build();
let mut net = crate::context::testutil::new_simple_fake_network(
"alice",
alice_ctx,
alice_device_ids[0].downgrade(),
"bob",
bob_ctx,
bob_device_ids[0].downgrade(),
);
core::mem::drop((alice_device_ids, bob_device_ids));
// Alice sends Bob a ping.
net.with_context("alice", |Ctx { core_ctx, bindings_ctx }| {
IpSocketHandler::<Ipv4, _>::send_oneshot_ip_packet(
&mut core_ctx.context(),
bindings_ctx,
None, // device
None, // local_ip
SocketIpAddr::try_from(FAKE_CONFIG_V4.remote_ip).unwrap(),
Ipv4Proto::Icmp,
&DefaultSendOptions,
|_| {
let req = IcmpEchoRequest::new(0, 0);
let req_body = &[1, 2, 3, 4];
Buf::new(req_body.to_vec(), ..).encapsulate(IcmpPacketBuilder::<Ipv4, _>::new(
FAKE_CONFIG_V4.local_ip,
FAKE_CONFIG_V4.remote_ip,
IcmpUnusedCode,
req,
))
},
None,
)
.unwrap();
});
// Send from Alice to Bob.
assert_eq!(net.step().frames_sent, 1);
// Respond from Bob to Alice.
assert_eq!(net.step().frames_sent, 1);
// Should've starved all events.
assert!(net.step().is_idle());
}
// Define some fake contexts and links specifically to test the fake
// network timers implementation.
#[derive(Default)]
struct FakeNetworkTestCtx {
timer_ctx: FakeTimerCtx<u32>,
frame_ctx: FakeFrameCtx<()>,
fired_timers: HashMap<u32, usize>,
frames_received: usize,
}
impl FakeNetworkTestCtx {
#[track_caller]
fn drain_and_assert_timers(&mut self, iter: impl IntoIterator<Item = (u32, usize)>) {
for (timer, fire_count) in iter {
assert_eq!(self.fired_timers.remove(&timer), Some(fire_count), "for timer {timer}");
}
assert!(self.fired_timers.is_empty(), "remaining timers: {:?}", self.fired_timers);
}
/// Generates an arbitrary request.
fn request() -> Vec<u8> {
vec![1, 2, 3, 4]
}
/// Generates an arbitrary response.
fn response() -> Vec<u8> {
vec![4, 3, 2, 1]
}
}
impl FakeNetworkContext for FakeNetworkTestCtx {
type TimerId = u32;
type SendMeta = ();
type RecvMeta = ();
fn handle_frame(&mut self, _recv: (), data: Buf<Vec<u8>>) {
self.frames_received += 1;
// If data is a request, generate a response. This mimics ICMP echo
// behavior.
if data.into_inner() == Self::request() {
self.frame_ctx.push((), Self::response())
}
}
fn handle_timer(&mut self, timer: u32) {
*self.fired_timers.entry(timer).or_insert(0) += 1;
}
fn process_queues(&mut self) -> bool {
false
}
}
impl WithFakeFrameContext<()> for FakeNetworkTestCtx {
fn with_fake_frame_ctx_mut<O, F: FnOnce(&mut FakeFrameCtx<()>) -> O>(&mut self, f: F) -> O {
f(&mut self.frame_ctx)
}
}
impl WithFakeTimerContext<u32> for FakeNetworkTestCtx {
fn with_fake_timer_ctx<O, F: FnOnce(&FakeTimerCtx<u32>) -> O>(&self, f: F) -> O {
f(&self.timer_ctx)
}
fn with_fake_timer_ctx_mut<O, F: FnOnce(&mut FakeTimerCtx<u32>) -> O>(
&mut self,
f: F,
) -> O {
f(&mut self.timer_ctx)
}
}
fn new_fake_network_with_latency(
latency: Option<Duration>,
) -> FakeNetwork<i32, FakeNetworkTestCtx, impl FakeNetworkLinks<(), (), i32>> {
FakeNetwork::new(
[(1, FakeNetworkTestCtx::default()), (2, FakeNetworkTestCtx::default())],
move |id, ()| {
vec![(
match id {
1 => 2,
2 => 1,
_ => unreachable!(),
},
(),
latency,
)]
},
)
}
#[test]
fn test_fake_network_timers() {
set_logger_for_test();
let mut net = new_fake_network_with_latency(None);
net.with_context(1, |FakeNetworkTestCtx { timer_ctx, .. }| {
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(1), 1), None);
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(4), 4), None);
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(5), 5), None);
});
net.with_context(2, |FakeNetworkTestCtx { timer_ctx, .. }| {
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(2), 2), None);
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(3), 3), None);
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(5), 6), None);
});
// No timers fired before.
net.context(1).drain_and_assert_timers([]);
net.context(2).drain_and_assert_timers([]);
assert_eq!(net.step().timers_fired, 1);
// Only timer in context 1 should have fired.
net.context(1).drain_and_assert_timers([(1, 1)]);
net.context(2).drain_and_assert_timers([]);
assert_eq!(net.step().timers_fired, 1);
// Only timer in context 2 should have fired.
net.context(1).drain_and_assert_timers([]);
net.context(2).drain_and_assert_timers([(2, 1)]);
assert_eq!(net.step().timers_fired, 1);
// Only timer in context 2 should have fired.
net.context(1).drain_and_assert_timers([]);
net.context(2).drain_and_assert_timers([(3, 1)]);
assert_eq!(net.step().timers_fired, 1);
// Only timer in context 1 should have fired.
net.context(1).drain_and_assert_timers([(4, 1)]);
net.context(2).drain_and_assert_timers([]);
assert_eq!(net.step().timers_fired, 2);
// Both timers have fired at the same time.
net.context(1).drain_and_assert_timers([(5, 1)]);
net.context(2).drain_and_assert_timers([(6, 1)]);
assert!(net.step().is_idle());
// Check that current time on contexts tick together.
let t1 = net.with_context(1, |FakeNetworkTestCtx { timer_ctx, .. }| timer_ctx.now());
let t2 = net.with_context(2, |FakeNetworkTestCtx { timer_ctx, .. }| timer_ctx.now());
assert_eq!(t1, t2);
}
#[test]
fn test_fake_network_until_idle() {
set_logger_for_test();
let mut net = new_fake_network_with_latency(None);
net.with_context(1, |FakeNetworkTestCtx { timer_ctx, .. }| {
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(1), 1), None);
});
net.with_context(2, |FakeNetworkTestCtx { timer_ctx, .. }| {
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(2), 2), None);
assert_eq!(timer_ctx.schedule_timer(Duration::from_secs(3), 3), None);
});
while !net.step().is_idle() && net.context(1).fired_timers.len() < 1
|| net.context(2).fired_timers.len() < 1
{}
// Assert that we stopped before all times were fired, meaning we can
// step again.
assert_eq!(net.step().timers_fired, 1);
}
#[test]
fn test_delayed_packets() {
set_logger_for_test();
// Create a network that takes 5ms to get any packet to go through.
let mut net = new_fake_network_with_latency(Some(Duration::from_millis(5)));
// 1 sends 2 a request and schedules a timer.
net.with_context(1, |FakeNetworkTestCtx { frame_ctx, timer_ctx, .. }| {
frame_ctx.push((), FakeNetworkTestCtx::request());
assert_eq!(timer_ctx.schedule_timer(Duration::from_millis(3), 1), None);
});
// 2 schedules some timers.
net.with_context(2, |FakeNetworkTestCtx { timer_ctx, .. }| {
assert_eq!(timer_ctx.schedule_timer(Duration::from_millis(7), 2), None);
assert_eq!(timer_ctx.schedule_timer(Duration::from_millis(10), 1), None);
});
// Order of expected events is as follows:
// - ctx1's timer expires at t = 3
// - ctx2 receives ctx1's packet at t = 5
// - ctx2's timer expires at t = 7
// - ctx1 receives ctx2's response and ctx2's last timer fires at t = 10
let assert_full_state = |net: &mut FakeNetwork<_, FakeNetworkTestCtx, _>,
ctx1_timers,
ctx2_timers,
ctx2_frames,
ctx1_frames| {
let ctx1 = net.context(1);
assert_eq!(ctx1.fired_timers.len(), ctx1_timers);
assert_eq!(ctx1.frames_received, ctx1_frames);
let ctx2 = net.context(2);
assert_eq!(ctx2.fired_timers.len(), ctx2_timers);
assert_eq!(ctx2.frames_received, ctx2_frames);
};
assert_eq!(net.step().timers_fired, 1);
assert_full_state(&mut net, 1, 0, 0, 0);
assert_eq!(net.step().frames_sent, 1);
assert_full_state(&mut net, 1, 0, 1, 0);
assert_eq!(net.step().timers_fired, 1);
assert_full_state(&mut net, 1, 1, 1, 0);
let step = net.step();
assert_eq!(step.frames_sent, 1);
assert_eq!(step.timers_fired, 1);
assert_full_state(&mut net, 1, 2, 1, 1);
// Should've starved all events.
assert!(net.step().is_idle());
}
fn send_packet<'a, A: IpAddress>(
core_ctx: &'a FakeCoreCtx,
bindings_ctx: &mut FakeBindingsCtx,
src_ip: SpecifiedAddr<A>,
dst_ip: SpecifiedAddr<A>,
device: &DeviceId<FakeBindingsCtx>,
) where
A::Version: TestIpExt,
UnlockedCoreCtx<'a, FakeBindingsCtx>:
IpLayerHandler<A::Version, FakeBindingsCtx, DeviceId = DeviceId<FakeBindingsCtx>>,
{
let meta = SendIpPacketMeta {
device,
src_ip: Some(src_ip),
dst_ip,
broadcast: None,
next_hop: dst_ip,
proto: IpProto::Udp.into(),
ttl: None,
mtu: None,
};
IpLayerHandler::<A::Version, _>::send_ip_packet_from_device(
&mut core_ctx.context(),
bindings_ctx,
meta,
Buf::new(vec![1, 2, 3, 4], ..),
)
.unwrap();
}
#[ip_test]
fn test_send_to_many<I: Ip + TestIpExt>()
where
for<'a> UnlockedCoreCtx<'a, FakeBindingsCtx>: IpLayerHandler<
I,
FakeBindingsCtx,
DeviceId = DeviceId<FakeBindingsCtx>,
WeakDeviceId = WeakDeviceId<FakeBindingsCtx>,
>,
{
let mac_a = UnicastAddr::new(Mac::new([2, 3, 4, 5, 6, 7])).unwrap();
let mac_b = UnicastAddr::new(Mac::new([2, 3, 4, 5, 6, 8])).unwrap();
let mac_c = UnicastAddr::new(Mac::new([2, 3, 4, 5, 6, 9])).unwrap();
let ip_a = I::get_other_ip_address(1);
let ip_b = I::get_other_ip_address(2);
let ip_c = I::get_other_ip_address(3);
let subnet = Subnet::new(I::get_other_ip_address(0).get(), I::Addr::BYTES * 8 - 8).unwrap();
let mut alice = FakeEventDispatcherBuilder::default();
let alice_device_idx = alice.add_device_with_ip(mac_a, ip_a.get(), subnet);
let mut bob = FakeEventDispatcherBuilder::default();
let bob_device_idx = bob.add_device_with_ip(mac_b, ip_b.get(), subnet);
let mut calvin = FakeEventDispatcherBuilder::default();
let calvin_device_idx = calvin.add_device_with_ip(mac_c, ip_c.get(), subnet);
add_arp_or_ndp_table_entry(&mut alice, alice_device_idx, ip_b, mac_b);
add_arp_or_ndp_table_entry(&mut alice, alice_device_idx, ip_c, mac_c);
add_arp_or_ndp_table_entry(&mut bob, bob_device_idx, ip_a, mac_a);
add_arp_or_ndp_table_entry(&mut bob, bob_device_idx, ip_c, mac_c);
add_arp_or_ndp_table_entry(&mut calvin, calvin_device_idx, ip_a, mac_a);
add_arp_or_ndp_table_entry(&mut calvin, calvin_device_idx, ip_b, mac_b);
let (alice_ctx, alice_device_ids) = alice.build();
let (bob_ctx, bob_device_ids) = bob.build();
let (calvin_ctx, calvin_device_ids) = calvin.build();
let alice_device_id = alice_device_ids[alice_device_idx].clone();
let bob_device_id = bob_device_ids[bob_device_idx].clone();
let calvin_device_id = calvin_device_ids[calvin_device_idx].clone();
let mut net = FakeNetwork::new(
[("alice", alice_ctx), ("bob", bob_ctx), ("calvin", calvin_ctx)],
move |net: &'static str, _frame: DispatchedFrame| match net {
"alice" => vec![
("bob", bob_device_id.clone(), None),
("calvin", calvin_device_id.clone(), None),
],
"bob" => vec![("alice", alice_device_id.clone(), None)],
"calvin" => Vec::new(),
_ => unreachable!(),
},
);
let alice_device_id = alice_device_ids[alice_device_idx].clone();
let bob_device_id = bob_device_ids[bob_device_idx].clone();
let calvin_device_id = calvin_device_ids[calvin_device_idx].clone();
core::mem::drop((alice_device_ids, bob_device_ids, calvin_device_ids));
net.collect_frames();
assert_matches!(net.bindings_ctx("alice").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("bob").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("calvin").copy_ethernet_frames()[..], []);
assert_empty(net.iter_pending_frames());
// Bob and Calvin should get any packet sent by Alice.
net.with_context("alice", |Ctx { core_ctx, bindings_ctx }| {
send_packet(core_ctx, bindings_ctx, ip_a, ip_b, &alice_device_id.clone().into());
});
assert_matches!(&net.bindings_ctx("alice").copy_ethernet_frames()[..], [_frame]);
assert_matches!(net.bindings_ctx("bob").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("calvin").copy_ethernet_frames()[..], []);
assert_empty(net.iter_pending_frames());
net.collect_frames();
assert_matches!(net.bindings_ctx("alice").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("bob").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("calvin").copy_ethernet_frames()[..], []);
assert_eq!(net.iter_pending_frames().count(), 2);
assert!(net
.iter_pending_frames()
.any(|InstantAndData(_, x)| (x.dst_context == "bob") && (&x.meta == &bob_device_id)));
assert!(net
.iter_pending_frames()
.any(|InstantAndData(_, x)| (x.dst_context == "calvin")
&& (&x.meta == &calvin_device_id)));
// Only Alice should get packets sent by Bob.
net.drop_pending_frames();
net.with_context("bob", |Ctx { core_ctx, bindings_ctx }| {
send_packet(core_ctx, bindings_ctx, ip_b, ip_a, &bob_device_id.clone().into());
});
assert_matches!(net.bindings_ctx("alice").copy_ethernet_frames()[..], []);
assert_matches!(&net.bindings_ctx("bob").copy_ethernet_frames()[..], [_frame]);
assert_matches!(net.bindings_ctx("calvin").copy_ethernet_frames()[..], []);
assert_empty(net.iter_pending_frames());
net.collect_frames();
assert_matches!(net.bindings_ctx("alice").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("bob").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("calvin").copy_ethernet_frames()[..], []);
assert_eq!(net.iter_pending_frames().count(), 1);
assert!(net.iter_pending_frames().any(
|InstantAndData(_, x)| (x.dst_context == "alice") && (&x.meta == &alice_device_id)
));
// No one gets packets sent by Calvin.
net.drop_pending_frames();
net.with_context("calvin", |Ctx { core_ctx, bindings_ctx }| {
send_packet(core_ctx, bindings_ctx, ip_c, ip_a, &calvin_device_id.clone().into());
});
assert_matches!(net.bindings_ctx("alice").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("bob").copy_ethernet_frames()[..], []);
assert_matches!(&net.bindings_ctx("calvin").copy_ethernet_frames()[..], [_frame]);
assert_empty(net.iter_pending_frames());
net.collect_frames();
assert_matches!(net.bindings_ctx("alice").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("bob").copy_ethernet_frames()[..], []);
assert_matches!(net.bindings_ctx("calvin").copy_ethernet_frames()[..], []);
assert_empty(net.iter_pending_frames());
}
}