| // 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. |
| |
| use std::collections::{BinaryHeap, HashMap}; |
| use std::fmt::{self, Debug, Formatter}; |
| use std::hash::Hash; |
| use std::num::NonZeroU16; |
| use std::ops; |
| use std::sync::Once; |
| use std::time::Duration; |
| |
| use byteorder::{ByteOrder, NativeEndian}; |
| use log::{debug, trace}; |
| use net_types::ethernet::Mac; |
| use net_types::ip::{ |
| AddrSubnet, Ip, IpAddr, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Subnet, SubnetEither, |
| }; |
| use net_types::{SpecifiedAddr, Witness}; |
| use packet::{Buf, BufferMut, ParsablePacket, ParseBuffer, Serializer}; |
| use rand::{self, CryptoRng, RngCore, SeedableRng}; |
| use rand_xorshift::XorShiftRng; |
| |
| use crate::device::ethernet::EtherType; |
| use crate::device::{DeviceId, DeviceLayerEventDispatcher}; |
| use crate::error::{IpParseResult, NoRouteError, ParseError, ParseResult}; |
| use crate::ip::icmp::{BufferIcmpEventDispatcher, IcmpConnId, IcmpEventDispatcher, IcmpIpExt}; |
| use crate::ip::{IpExtByteSlice, IpLayerEventDispatcher, IpProto}; |
| use crate::transport::tcp::TcpOption; |
| use crate::transport::udp::UdpEventDispatcher; |
| use crate::transport::TransportLayerEventDispatcher; |
| use crate::wire::ethernet::{EthernetFrame, EthernetFrameLengthCheck}; |
| use crate::wire::icmp::{self as wire_icmp, IcmpMessage, IcmpPacket, IcmpParseArgs}; |
| use crate::wire::ipv4::Ipv4Packet; |
| use crate::wire::ipv6::Ipv6Packet; |
| use crate::wire::tcp::TcpSegment; |
| use crate::wire::udp::UdpPacket; |
| use crate::{handle_timeout, Context, EventDispatcher, Instant, StackStateBuilder, TimerId}; |
| |
| /// 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 mocks 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. |
| pub(crate) mod benchmarks { |
| /// A trait to allow mocking of the `test::Bencher` type. |
| pub(crate) trait Bencher { |
| fn iter<T, F: FnMut() -> T>(&mut self, inner: F); |
| } |
| |
| #[cfg(feature = "benchmark")] |
| impl Bencher for test::Bencher { |
| fn iter<T, F: FnMut() -> T>(&mut self, inner: F) { |
| test::Bencher::iter(self, inner) |
| } |
| } |
| |
| /// A `Bencher` whose `iter` method runs the provided argument once. |
| #[cfg(not(feature = "benchmark"))] |
| pub(crate) struct TestBencher; |
| |
| #[cfg(not(feature = "benchmark"))] |
| impl Bencher for TestBencher { |
| fn iter<T, F: FnMut() -> T>(&mut self, mut inner: F) { |
| super::set_logger_for_test(); |
| inner(); |
| } |
| } |
| |
| #[inline(always)] |
| pub(crate) fn black_box<T>(dummy: T) -> T { |
| #[cfg(feature = "benchmark")] |
| return test::black_box(dummy); |
| #[cfg(not(feature = "benchmark"))] |
| return dummy; |
| } |
| } |
| |
| /// 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! |
| pub(crate) struct FakeCryptoRng<R>(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: u64) -> FakeCryptoRng<XorShiftRng> { |
| FakeCryptoRng(new_rng(seed)) |
| } |
| } |
| |
| impl<R: RngCore> RngCore for FakeCryptoRng<R> { |
| fn next_u32(&mut self) -> u32 { |
| self.0.next_u32() |
| } |
| fn next_u64(&mut self) -> u64 { |
| self.0.next_u64() |
| } |
| fn fill_bytes(&mut self, dest: &mut [u8]) { |
| self.0.fill_bytes(dest) |
| } |
| fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { |
| self.0.try_fill_bytes(dest) |
| } |
| } |
| |
| impl<R: RngCore> CryptoRng for FakeCryptoRng<R> {} |
| |
| impl<R: RngCore> crate::context::RngContext for FakeCryptoRng<R> { |
| type Rng = Self; |
| |
| fn rng(&mut self) -> &mut Self::Rng { |
| self |
| } |
| } |
| |
| /// Create a new deterministic RNG from a seed. |
| pub(crate) fn new_rng(mut seed: u64) -> XorShiftRng { |
| if seed == 0 { |
| // XorShiftRng can't take 0 seeds |
| seed = 1; |
| } |
| let mut bytes = [0; 16]; |
| NativeEndian::write_u32(&mut bytes[0..4], seed as u32); |
| NativeEndian::write_u32(&mut bytes[4..8], (seed >> 32) as u32); |
| NativeEndian::write_u32(&mut bytes[8..12], seed as u32); |
| NativeEndian::write_u32(&mut bytes[12..16], (seed >> 32) as u32); |
| XorShiftRng::from_seed(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 weirdnesses that can |
| /// happen with certain random number sequences. |
| pub(crate) fn with_fake_rngs<F: Fn(FakeCryptoRng<XorShiftRng>)>(iterations: u64, f: F) { |
| for seed in 0..iterations { |
| f(FakeCryptoRng::new_xorshift(seed)) |
| } |
| } |
| |
| #[derive(Default, Debug)] |
| pub(crate) struct TestCounters { |
| data: HashMap<String, usize>, |
| } |
| |
| impl TestCounters { |
| pub(crate) fn increment(&mut self, key: &str) { |
| *(self.data.entry(key.to_string()).or_insert(0)) += 1; |
| } |
| |
| pub(crate) fn get(&self, key: &str) -> &usize { |
| self.data.get(key).unwrap_or(&0) |
| } |
| } |
| |
| /// log::Log implementation that uses stdout. |
| /// |
| /// Useful when debugging tests. |
| struct Logger; |
| |
| impl log::Log for Logger { |
| fn enabled(&self, _metadata: &log::Metadata) -> bool { |
| true |
| } |
| |
| fn log(&self, record: &log::Record) { |
| println!("{}", record.args()) |
| } |
| |
| fn flush(&self) {} |
| } |
| |
| static LOGGER: Logger = Logger; |
| |
| static LOGGER_ONCE: Once = Once::new(); |
| |
| /// 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. |
| pub(crate) fn set_logger_for_test() { |
| // log::set_logger will panic if called multiple times; using a Once makes |
| // set_logger_for_test idempotent |
| LOGGER_ONCE.call_once(|| { |
| log::set_logger(&LOGGER).unwrap(); |
| log::set_max_level(log::LevelFilter::Trace); |
| }) |
| } |
| |
| /// Skip current time forward to trigger the next timer event. |
| /// |
| /// Returns true if a timer was triggered, false if there were no timers waiting |
| /// to be triggered. |
| pub(crate) fn trigger_next_timer(ctx: &mut Context<DummyEventDispatcher>) -> bool { |
| match ctx.dispatcher.timer_events.pop() { |
| Some(InstantAndData(t, id)) => { |
| ctx.dispatcher.current_time = t; |
| handle_timeout(ctx, id); |
| true |
| } |
| None => false, |
| } |
| } |
| |
| /// Skip current time forward by `duration`, triggering all timer events until then, |
| /// inclusive. |
| /// |
| /// Returns the number of timer events triggered. |
| pub(crate) fn run_for(ctx: &mut Context<DummyEventDispatcher>, duration: Duration) -> usize { |
| let end_time = ctx.dispatcher.now() + duration; |
| let mut timers_fired = 0; |
| |
| while let Some(tmr) = ctx.dispatcher.timer_events.peek() { |
| if tmr.0 > end_time { |
| break; |
| } |
| |
| assert!(trigger_next_timer(ctx)); |
| timers_fired += 1; |
| } |
| |
| assert!(ctx.dispatcher.now() <= end_time); |
| ctx.dispatcher.current_time = end_time; |
| |
| timers_fired |
| } |
| |
| /// Trigger timer events until`f` callback returns true or passes the max |
| /// number of iterations. |
| /// |
| /// `trigger_timers_until` always calls `f` on the first timer event, as the |
| /// timer_events is dynamically updated. As soon as `f` returns true or |
| /// 1,000,000 timer events have been triggered, `trigger_timers_until` will |
| /// exit. |
| /// |
| /// Please note, the caller is expected to pass in an `f` which could return |
| /// true to exit `trigger_timer_until`. 1,000,000 limit is set to avoid an |
| /// endless loop. |
| pub(crate) fn trigger_timers_until<F: Fn(&TimerId) -> bool>( |
| ctx: &mut Context<DummyEventDispatcher>, |
| f: F, |
| ) { |
| for _ in 0..1_000_000 { |
| let InstantAndData(t, id) = if let Some(t) = ctx.dispatcher.timer_events.pop() { |
| t |
| } else { |
| return; |
| }; |
| |
| ctx.dispatcher.current_time = t; |
| handle_timeout(ctx, id); |
| if f(&id) { |
| break; |
| } |
| } |
| } |
| |
| /// Metadata of an Ethernet frame. |
| pub(crate) struct EthernetFrameMetadata { |
| pub(crate) src_mac: Mac, |
| pub(crate) dst_mac: Mac, |
| pub(crate) ethertype: Option<EtherType>, |
| } |
| |
| /// Metadata of an IPv4 packet. |
| pub(crate) struct Ipv4PacketMetadata { |
| pub(crate) dscp: u8, |
| pub(crate) ecn: u8, |
| pub(crate) id: u16, |
| pub(crate) dont_fragment: bool, |
| pub(crate) more_fragments: bool, |
| pub(crate) fragment_offset: u16, |
| pub(crate) ttl: u8, |
| pub(crate) proto: IpProto, |
| pub(crate) src_ip: Ipv4Addr, |
| pub(crate) dst_ip: Ipv4Addr, |
| } |
| |
| /// Metadata of an IPv6 packet. |
| pub(crate) struct Ipv6PacketMetadata { |
| pub(crate) ds: u8, |
| pub(crate) ecn: u8, |
| pub(crate) flowlabel: u32, |
| pub(crate) hop_limit: u8, |
| pub(crate) src_ip: Ipv6Addr, |
| pub(crate) dst_ip: Ipv6Addr, |
| } |
| |
| /// Metadata of a TCP segment. |
| pub(crate) struct TcpSegmentMetadata { |
| pub(crate) src_port: u16, |
| pub(crate) dst_port: u16, |
| pub(crate) seq_num: u32, |
| pub(crate) ack_num: Option<u32>, |
| pub(crate) _flags: u16, |
| pub(crate) _psh: bool, |
| pub(crate) rst: bool, |
| pub(crate) syn: bool, |
| pub(crate) fin: bool, |
| pub(crate) window_size: u16, |
| pub(crate) options: &'static [TcpOption<'static>], |
| } |
| |
| /// Metadata of a UDP packet. |
| pub(crate) struct UdpPacketMetadata { |
| pub(crate) src_port: u16, |
| pub(crate) dst_port: u16, |
| } |
| |
| /// Represents a packet (usually from a live capture) used for testing. |
| /// |
| /// Includes the raw bytes, metadata of the packet (currently just fields from the packet header) |
| /// and the range which indicates where the body is. |
| pub(crate) struct TestPacket<M> { |
| pub(crate) bytes: &'static [u8], |
| pub(crate) metadata: M, |
| pub(crate) body_range: ops::Range<usize>, |
| } |
| |
| /// Verify that a parsed Ethernet frame is as expected. |
| /// |
| /// Ensures the parsed packet's header fields and body are equal to those in the test packet. |
| pub(crate) fn verify_ethernet_frame( |
| frame: &EthernetFrame<&[u8]>, |
| expected: TestPacket<EthernetFrameMetadata>, |
| ) { |
| assert_eq!(frame.src_mac(), expected.metadata.src_mac); |
| assert_eq!(frame.dst_mac(), expected.metadata.dst_mac); |
| assert_eq!(frame.ethertype(), expected.metadata.ethertype); |
| assert_eq!(frame.body(), &expected.bytes[expected.body_range]); |
| } |
| |
| /// Verify that a parsed IPv4 packet is as expected. |
| /// |
| /// Ensures the parsed packet's header fields and body are equal to those in the test packet. |
| pub(crate) fn verify_ipv4_packet( |
| packet: &Ipv4Packet<&[u8]>, |
| expected: TestPacket<Ipv4PacketMetadata>, |
| ) { |
| use crate::wire::ipv4::Ipv4Header; |
| |
| assert_eq!(packet.dscp(), expected.metadata.dscp); |
| assert_eq!(packet.ecn(), expected.metadata.ecn); |
| assert_eq!(packet.id(), expected.metadata.id); |
| assert_eq!(packet.df_flag(), expected.metadata.dont_fragment); |
| assert_eq!(packet.mf_flag(), expected.metadata.more_fragments); |
| assert_eq!(packet.fragment_offset(), expected.metadata.fragment_offset); |
| assert_eq!(packet.ttl(), expected.metadata.ttl); |
| assert_eq!(packet.proto(), expected.metadata.proto); |
| assert_eq!(packet.src_ip(), expected.metadata.src_ip); |
| assert_eq!(packet.dst_ip(), expected.metadata.dst_ip); |
| assert_eq!(packet.body(), &expected.bytes[expected.body_range]); |
| } |
| |
| /// Verify that a parsed IPv6 packet is as expected. |
| /// |
| /// Ensures the parsed packet's header fields and body are equal to those in the test packet. |
| pub(crate) fn verify_ipv6_packet( |
| packet: &Ipv6Packet<&[u8]>, |
| expected: TestPacket<Ipv6PacketMetadata>, |
| ) { |
| assert_eq!(packet.ds(), expected.metadata.ds); |
| assert_eq!(packet.ecn(), expected.metadata.ecn); |
| assert_eq!(packet.flowlabel(), expected.metadata.flowlabel); |
| assert_eq!(packet.hop_limit(), expected.metadata.hop_limit); |
| assert_eq!(packet.src_ip(), expected.metadata.src_ip); |
| assert_eq!(packet.dst_ip(), expected.metadata.dst_ip); |
| assert_eq!(packet.body(), &expected.bytes[expected.body_range]); |
| } |
| |
| /// Verify that a parsed UDP packet is as expected. |
| /// |
| /// Ensures the parsed packet's header fields and body are equal to those in the test packet. |
| pub(crate) fn verify_udp_packet( |
| packet: &UdpPacket<&[u8]>, |
| expected: TestPacket<UdpPacketMetadata>, |
| ) { |
| assert_eq!(packet.src_port().map(NonZeroU16::get).unwrap_or(0), expected.metadata.src_port); |
| assert_eq!(packet.dst_port().get(), expected.metadata.dst_port); |
| assert_eq!(packet.body(), &expected.bytes[expected.body_range]); |
| } |
| |
| /// Verify that a parsed TCP segment is as expected. |
| /// |
| /// Ensures the parsed packet's header fields and body are equal to those in the test packet. |
| pub(crate) fn verify_tcp_segment( |
| segment: &TcpSegment<&[u8]>, |
| expected: TestPacket<TcpSegmentMetadata>, |
| ) { |
| assert_eq!(segment.src_port().get(), expected.metadata.src_port); |
| assert_eq!(segment.dst_port().get(), expected.metadata.dst_port); |
| assert_eq!(segment.seq_num(), expected.metadata.seq_num); |
| assert_eq!(segment.ack_num(), expected.metadata.ack_num); |
| assert_eq!(segment.rst(), expected.metadata.rst); |
| assert_eq!(segment.syn(), expected.metadata.syn); |
| assert_eq!(segment.fin(), expected.metadata.fin); |
| assert_eq!(segment.window_size(), expected.metadata.window_size); |
| assert_eq!(segment.iter_options().collect::<Vec<_>>().as_slice(), expected.metadata.options); |
| assert_eq!(segment.body(), &expected.bytes[expected.body_range]); |
| } |
| |
| /// Parse an ethernet frame. |
| /// |
| /// `parse_ethernet_frame` parses an ethernet frame, returning the body along |
| /// with some important header fields. |
| pub(crate) fn parse_ethernet_frame( |
| mut buf: &[u8], |
| ) -> ParseResult<(&[u8], Mac, Mac, Option<EtherType>)> { |
| let frame = (&mut buf).parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check)?; |
| let src_mac = frame.src_mac(); |
| let dst_mac = frame.dst_mac(); |
| let ethertype = frame.ethertype(); |
| Ok((buf, src_mac, dst_mac, ethertype)) |
| } |
| |
| /// Parse an IP packet. |
| /// |
| /// `parse_ip_packet` parses an IP packet, returning the body along with some |
| /// important header fields. |
| #[allow(clippy::type_complexity)] |
| pub(crate) fn parse_ip_packet<I: Ip>( |
| mut buf: &[u8], |
| ) -> IpParseResult<I, (&[u8], I::Addr, I::Addr, IpProto, u8)> { |
| use crate::ip::IpPacket; |
| |
| let packet = (&mut buf).parse::<<I as IpExtByteSlice<_>>::Packet>()?; |
| let src_ip = packet.src_ip(); |
| let dst_ip = packet.dst_ip(); |
| let proto = packet.proto(); |
| let ttl = packet.ttl(); |
| // Because the packet type here is generic, Rust doesn't know that it |
| // doesn't implement Drop, and so it doesn't know that it's safe to drop as |
| // soon as it's no longer used and allow buf to no longer be borrowed on the |
| // next line. It works fine in parse_ethernet_frame because EthernetFrame is |
| // a concrete type which Rust knows doesn't implement Drop. |
| std::mem::drop(packet); |
| Ok((buf, src_ip, dst_ip, proto, ttl)) |
| } |
| |
| /// Parse an ICMP packet. |
| /// |
| /// `parse_icmp_packet` parses an ICMP packet, returning the body along with |
| /// some important fields. Before returning, it invokes the callback `f` on the |
| /// parsed packet. |
| pub(crate) fn parse_icmp_packet< |
| I: wire_icmp::IcmpIpExt, |
| C, |
| M: for<'a> IcmpMessage<I, &'a [u8], Code = C>, |
| F: for<'a> FnOnce(&IcmpPacket<I, &'a [u8], M>), |
| >( |
| mut buf: &[u8], |
| src_ip: I::Addr, |
| dst_ip: I::Addr, |
| f: F, |
| ) -> ParseResult<(M, C)> |
| where |
| for<'a> IcmpPacket<I, &'a [u8], M>: |
| ParsablePacket<&'a [u8], IcmpParseArgs<I::Addr>, Error = ParseError>, |
| { |
| let packet = |
| (&mut buf).parse_with::<_, IcmpPacket<I, _, M>>(IcmpParseArgs::new(src_ip, dst_ip))?; |
| let message = *packet.message(); |
| let code = packet.code(); |
| f(&packet); |
| Ok((message, code)) |
| } |
| |
| /// Parse an IP packet in an Ethernet frame. |
| /// |
| /// `parse_ip_packet_in_ethernet_frame` parses an IP packet in an Ethernet |
| /// frame, returning the body of the IP packet along with some important fields |
| /// from both the IP and Ethernet headers. |
| #[allow(clippy::type_complexity)] |
| pub(crate) fn parse_ip_packet_in_ethernet_frame<I: Ip>( |
| buf: &[u8], |
| ) -> IpParseResult<I, (&[u8], Mac, Mac, I::Addr, I::Addr, IpProto, u8)> { |
| use crate::device::ethernet::EthernetIpExt; |
| let (body, src_mac, dst_mac, ethertype) = parse_ethernet_frame(buf)?; |
| if ethertype != Some(I::ETHER_TYPE) { |
| debug!("unexpected ethertype: {:?}", ethertype); |
| return Err(ParseError::NotExpected.into()); |
| } |
| |
| let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<I>(body)?; |
| Ok((body, src_mac, dst_mac, src_ip, dst_ip, proto, ttl)) |
| } |
| |
| /// Parse an ICMP packet in an IP packet in an Ethernet frame. |
| /// |
| /// `parse_icmp_packet_in_ip_packet_in_ethernet_frame` parses an ICMP packet in |
| /// an IP packet in an Ethernet frame, returning the message and code from the |
| /// ICMP packet along with some important fields from both the IP and Ethernet |
| /// headers. Before returning, it invokes the callback `f` on the parsed packet. |
| #[allow(clippy::type_complexity)] |
| pub(crate) fn parse_icmp_packet_in_ip_packet_in_ethernet_frame< |
| I: wire_icmp::IcmpIpExt, |
| C, |
| M: for<'a> IcmpMessage<I, &'a [u8], Code = C>, |
| F: for<'a> FnOnce(&IcmpPacket<I, &'a [u8], M>), |
| >( |
| buf: &[u8], |
| f: F, |
| ) -> IpParseResult<I, (Mac, Mac, I::Addr, I::Addr, u8, M, C)> |
| where |
| for<'a> IcmpPacket<I, &'a [u8], M>: |
| ParsablePacket<&'a [u8], IcmpParseArgs<I::Addr>, Error = ParseError>, |
| { |
| let (body, src_mac, dst_mac, src_ip, dst_ip, proto, ttl) = |
| parse_ip_packet_in_ethernet_frame::<I>(buf)?; |
| if proto != I::ICMP_IP_PROTO { |
| debug!("unexpected IP protocol: {} (wanted {})", proto, I::ICMP_IP_PROTO); |
| return Err(ParseError::NotExpected.into()); |
| } |
| let (message, code) = parse_icmp_packet(body, src_ip, dst_ip, f)?; |
| Ok((src_mac, dst_mac, src_ip, dst_ip, ttl, message, code)) |
| } |
| |
| /// Get the counter value for a `key`. |
| pub(crate) fn get_counter_val(ctx: &mut Context<DummyEventDispatcher>, key: &str) -> usize { |
| *ctx.state.test_counters.get(key) |
| } |
| |
| /// An extension trait for `Ip` providing test-related functionality. |
| pub(crate) trait TestIpExt: Ip { |
| /// Either [`DUMMY_CONFIG_V4`] or [`DUMMY_CONFIG_V6`]. |
| const DUMMY_CONFIG: DummyEventDispatcherConfig<Self::Addr>; |
| |
| /// Get an IP address in the same subnet as `Self::DUMMY_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::DUMMY_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>; |
| } |
| |
| impl TestIpExt for Ipv4 { |
| const DUMMY_CONFIG: DummyEventDispatcherConfig<Ipv4Addr> = DUMMY_CONFIG_V4; |
| |
| fn get_other_ip_address(last: u8) -> SpecifiedAddr<Ipv4Addr> { |
| let mut bytes = Self::DUMMY_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::DUMMY_CONFIG.local_ip.get().ipv4_bytes(); |
| bytes[bytes.len() - 3] += 1; |
| bytes[bytes.len() - 1] = last; |
| SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap() |
| } |
| } |
| |
| impl TestIpExt for Ipv6 { |
| const DUMMY_CONFIG: DummyEventDispatcherConfig<Ipv6Addr> = DUMMY_CONFIG_V6; |
| |
| fn get_other_ip_address(last: u8) -> SpecifiedAddr<Ipv6Addr> { |
| let mut bytes = Self::DUMMY_CONFIG.local_ip.get().ipv6_bytes(); |
| bytes[bytes.len() - 1] = last; |
| SpecifiedAddr::new(Ipv6Addr::new(bytes)).unwrap() |
| } |
| |
| fn get_other_remote_ip_address(last: u8) -> SpecifiedAddr<Self::Addr> { |
| let mut bytes = Self::DUMMY_CONFIG.local_ip.get().ipv6_bytes(); |
| bytes[bytes.len() - 3] += 1; |
| bytes[bytes.len() - 1] = last; |
| SpecifiedAddr::new(Ipv6Addr::new(bytes)).unwrap() |
| } |
| } |
| |
| /// A configuration for a simple network. |
| /// |
| /// `DummyEventDispatcherConfig` describes a simple network with two IP hosts |
| /// - one remote and one local - both on the same Ethernet network. |
| #[derive(Clone)] |
| pub(crate) struct DummyEventDispatcherConfig<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: 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: Mac, |
| } |
| |
| /// A `DummyEventDispatcherConfig` with reasonable values for an IPv4 network. |
| pub(crate) const DUMMY_CONFIG_V4: DummyEventDispatcherConfig<Ipv4Addr> = unsafe { |
| DummyEventDispatcherConfig { |
| subnet: Subnet::new_unchecked(Ipv4Addr::new([192, 168, 0, 0]), 16), |
| local_ip: SpecifiedAddr::new_unchecked(Ipv4Addr::new([192, 168, 0, 1])), |
| local_mac: Mac::new([0, 1, 2, 3, 4, 5]), |
| remote_ip: SpecifiedAddr::new_unchecked(Ipv4Addr::new([192, 168, 0, 2])), |
| remote_mac: Mac::new([6, 7, 8, 9, 10, 11]), |
| } |
| }; |
| |
| /// A `DummyEventDispatcherConfig` with reasonable values for an IPv6 network. |
| pub(crate) const DUMMY_CONFIG_V6: DummyEventDispatcherConfig<Ipv6Addr> = unsafe { |
| DummyEventDispatcherConfig { |
| subnet: Subnet::new_unchecked( |
| Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 0]), |
| 112, |
| ), |
| local_ip: SpecifiedAddr::new_unchecked(Ipv6Addr::new([ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 1, |
| ])), |
| local_mac: Mac::new([0, 1, 2, 3, 4, 5]), |
| remote_ip: SpecifiedAddr::new_unchecked(Ipv6Addr::new([ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 2, |
| ])), |
| remote_mac: Mac::new([6, 7, 8, 9, 10, 11]), |
| } |
| }; |
| |
| impl<A: IpAddress> DummyEventDispatcherConfig<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, |
| } |
| } |
| } |
| |
| /// A builder for `DummyEventDispatcher`s. |
| /// |
| /// A `DummyEventDispatcherBuilder` is capable of storing the configuration of a |
| /// network stack including forwarding table entries, devices and their assigned |
| /// IP addresses, ARP table entries, etc. It can be built using `build`, |
| /// producing a `Context<DummyEventDispatcher>` with all of the appropriate |
| /// state configured. |
| #[derive(Clone, Default)] |
| pub(crate) struct DummyEventDispatcherBuilder { |
| devices: Vec<(Mac, Option<(IpAddr, SubnetEither)>)>, |
| arp_table_entries: Vec<(usize, Ipv4Addr, Mac)>, |
| ndp_table_entries: Vec<(usize, Ipv6Addr, Mac)>, |
| // usize refers to index into devices Vec |
| device_routes: Vec<(SubnetEither, usize)>, |
| routes: Vec<(SubnetEither, SpecifiedAddr<IpAddr>)>, |
| } |
| |
| impl DummyEventDispatcherBuilder { |
| /// Construct a `DummyEventDispatcherBuilder` from a `DummyEventDispatcherConfig`. |
| pub(crate) fn from_config<A: IpAddress>( |
| cfg: DummyEventDispatcherConfig<A>, |
| ) -> DummyEventDispatcherBuilder { |
| assert!(cfg.subnet.contains(&cfg.local_ip)); |
| assert!(cfg.subnet.contains(&cfg.remote_ip)); |
| |
| let mut builder = DummyEventDispatcherBuilder::default(); |
| builder.devices.push((cfg.local_mac, Some((cfg.local_ip.get().into(), cfg.subnet.into())))); |
| |
| // NOTE: We use two separate calls here rather than a single call to |
| // `.with` because both closures mutably borrow `builder`, and so they |
| // can't exist at the same time, which would be required in order to |
| // pass them both to `.with`. |
| cfg.remote_ip |
| .get() |
| .with_v4(|ip| builder.arp_table_entries.push((0, ip, cfg.remote_mac)), ()); |
| cfg.remote_ip |
| .get() |
| .with_v6(|ip| builder.ndp_table_entries.push((0, ip, 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(crate) fn add_device(&mut self, mac: Mac) -> usize { |
| let idx = self.devices.len(); |
| self.devices.push((mac, None)); |
| 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(crate) fn add_device_with_ip<A: IpAddress>( |
| &mut self, |
| mac: Mac, |
| ip: A, |
| subnet: Subnet<A>, |
| ) -> usize { |
| let idx = self.devices.len(); |
| self.devices.push((mac, Some((ip.into(), subnet.into())))); |
| idx |
| } |
| |
| /// Add an ARP table entry for a device's ARP table. |
| pub(crate) fn add_arp_table_entry(&mut self, device: usize, ip: Ipv4Addr, mac: Mac) { |
| self.arp_table_entries.push((device, ip, mac)); |
| } |
| |
| /// Add an NDP table entry for a device's NDP table. |
| pub(crate) fn add_ndp_table_entry(&mut self, device: usize, ip: Ipv6Addr, mac: Mac) { |
| self.ndp_table_entries.push((device, ip, mac)); |
| } |
| |
| /// Build a `Context` from the present configuration with a default |
| /// dispatcher, and stack state set to disable NDP's Duplicate Address |
| /// Detection by default. |
| pub(crate) fn build<D: EventDispatcher + Default>(self) -> Context<D> { |
| 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 `Context` is constructed. |
| pub(crate) fn build_with_modifications< |
| D: EventDispatcher + Default, |
| F: FnOnce(&mut StackStateBuilder), |
| >( |
| self, |
| f: F, |
| ) -> Context<D> { |
| let mut stack_builder = StackStateBuilder::default(); |
| |
| // Most tests do not need NDP's DAD or router solicitation so disable it here. |
| let mut ndp_configs = crate::device::ndp::NdpConfigurations::default(); |
| ndp_configs.set_dup_addr_detect_transmits(None); |
| ndp_configs.set_max_router_solicitations(None); |
| stack_builder.device_builder().set_default_ndp_configs(ndp_configs); |
| |
| f(&mut stack_builder); |
| self.build_with(stack_builder, D::default()) |
| } |
| |
| /// Build a `Context` from the present configuration with a caller-provided |
| /// dispatcher and `StackStateBuilder`. |
| pub(crate) fn build_with<D: EventDispatcher>( |
| self, |
| state_builder: StackStateBuilder, |
| dispatcher: D, |
| ) -> Context<D> { |
| let mut ctx = Context::new(state_builder.build(), dispatcher); |
| |
| let DummyEventDispatcherBuilder { |
| devices, |
| arp_table_entries, |
| ndp_table_entries, |
| device_routes, |
| routes, |
| } = self; |
| let mut idx_to_device_id = |
| HashMap::<_, _, std::collections::hash_map::RandomState>::default(); |
| for (idx, (mac, ip_subnet)) in devices.into_iter().enumerate() { |
| let id = ctx.state.add_ethernet_device(mac, Ipv6::MINIMUM_LINK_MTU.into()); |
| idx_to_device_id.insert(idx, id); |
| crate::device::initialize_device(&mut ctx, id); |
| match ip_subnet { |
| Some((IpAddr::V4(ip), SubnetEither::V4(subnet))) => { |
| let addr_sub = AddrSubnet::new(ip, subnet.prefix()).unwrap(); |
| crate::device::add_ip_addr_subnet(&mut ctx, id, addr_sub).unwrap(); |
| } |
| Some((IpAddr::V6(ip), SubnetEither::V6(subnet))) => { |
| let addr_sub = AddrSubnet::new(ip, subnet.prefix()).unwrap(); |
| crate::device::add_ip_addr_subnet(&mut ctx, id, addr_sub).unwrap(); |
| } |
| None => {} |
| _ => unreachable!(), |
| } |
| } |
| for (idx, ip, mac) in arp_table_entries { |
| let device = *idx_to_device_id.get(&idx).unwrap(); |
| crate::device::insert_static_arp_table_entry(&mut ctx, device, ip, mac); |
| } |
| for (idx, ip, mac) in ndp_table_entries { |
| let device = *idx_to_device_id.get(&idx).unwrap(); |
| crate::device::insert_ndp_table_entry(&mut ctx, device, ip, mac); |
| } |
| for (subnet, idx) in device_routes { |
| let device = *idx_to_device_id.get(&idx).unwrap(); |
| match subnet { |
| SubnetEither::V4(subnet) => { |
| crate::ip::add_device_route(&mut ctx, subnet, device).unwrap() |
| } |
| SubnetEither::V6(subnet) => { |
| crate::ip::add_device_route(&mut ctx, subnet, device).unwrap() |
| } |
| }; |
| } |
| for (subnet, next_hop) in routes { |
| match (subnet, next_hop.into()) { |
| (SubnetEither::V4(subnet), IpAddr::V4(next_hop)) => { |
| crate::ip::add_route(&mut ctx, subnet, next_hop).unwrap() |
| } |
| (SubnetEither::V6(subnet), IpAddr::V6(next_hop)) => { |
| crate::ip::add_route(&mut ctx, subnet, next_hop).unwrap() |
| } |
| _ => unreachable!(), |
| }; |
| } |
| |
| ctx |
| } |
| } |
| |
| /// Add either an NDP entry (if IPv6) or ARP entry (if IPv4) to a `DummyEventDispatcherBuilder`. |
| pub(crate) fn add_arp_or_ndp_table_entry<A: IpAddress>( |
| builder: &mut DummyEventDispatcherBuilder, |
| device: usize, |
| ip: A, |
| mac: Mac, |
| ) { |
| // NOTE: We use two separate calls here rather than a single call to `.with` |
| // because both closures mutably borrow `builder`, and so they can't exist |
| // at the same time, which would be required in order to pass them both to |
| // `.with`. |
| ip.with_v4(|ip| builder.add_arp_table_entry(device, ip, mac), ()); |
| ip.with_v6(|ip| builder.add_ndp_table_entry(device, ip, mac), ()); |
| } |
| |
| impl Instant for std::time::Instant { |
| fn duration_since(&self, earlier: std::time::Instant) -> Duration { |
| std::time::Instant::duration_since(self, earlier) |
| } |
| |
| fn checked_add(&self, duration: Duration) -> Option<Self> { |
| std::time::Instant::checked_add(self, duration) |
| } |
| |
| fn checked_sub(&self, duration: Duration) -> Option<Self> { |
| std::time::Instant::checked_sub(self, duration) |
| } |
| } |
| |
| /// A dummy implementation of `Instant` for use in testing. |
| #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] |
| pub(crate) struct DummyInstant { |
| // A DummyInstant is just an offset from some arbitrary epoch. |
| offset: Duration, |
| } |
| |
| impl Instant for DummyInstant { |
| fn duration_since(&self, earlier: DummyInstant) -> Duration { |
| self.offset.checked_sub(earlier.offset).unwrap() |
| } |
| |
| fn checked_add(&self, duration: Duration) -> Option<DummyInstant> { |
| self.offset.checked_add(duration).map(|offset| DummyInstant { offset }) |
| } |
| |
| fn checked_sub(&self, duration: Duration) -> Option<DummyInstant> { |
| self.offset.checked_sub(duration).map(|offset| DummyInstant { offset }) |
| } |
| } |
| |
| impl ops::Add<Duration> for DummyInstant { |
| type Output = DummyInstant; |
| |
| fn add(self, other: Duration) -> DummyInstant { |
| DummyInstant { offset: self.offset + other } |
| } |
| } |
| |
| impl ops::Sub<DummyInstant> for DummyInstant { |
| type Output = Duration; |
| |
| fn sub(self, other: DummyInstant) -> Duration { |
| self.offset - other.offset |
| } |
| } |
| |
| impl ops::Sub<Duration> for DummyInstant { |
| type Output = DummyInstant; |
| |
| fn sub(self, other: Duration) -> DummyInstant { |
| DummyInstant { offset: self.offset - other } |
| } |
| } |
| |
| impl Debug for DummyInstant { |
| fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| write!(f, "{:?}", self.offset) |
| } |
| } |
| |
| /// Represents arbitrary data of type `D` attached to a `DummyInstant`. |
| /// |
| /// `InstantAndData` implements `Ord` and `Eq` to be used in a `BinaryHeap` and |
| /// ordered by `DummyInstant`. |
| struct InstantAndData<D>(DummyInstant, D); |
| |
| impl<D> InstantAndData<D> { |
| fn new(time: DummyInstant, data: D) -> Self { |
| Self(time, data) |
| } |
| } |
| |
| impl<D> Eq for InstantAndData<D> {} |
| |
| impl<D> PartialEq for InstantAndData<D> { |
| fn eq(&self, other: &Self) -> bool { |
| self.0 == other.0 |
| } |
| } |
| |
| impl<D> Ord for InstantAndData<D> { |
| fn cmp(&self, other: &Self) -> std::cmp::Ordering { |
| other.0.cmp(&self.0) |
| } |
| } |
| |
| impl<D> PartialOrd for InstantAndData<D> { |
| fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
| Some(self.cmp(other)) |
| } |
| } |
| |
| type PendingTimer = InstantAndData<TimerId>; |
| |
| /// A dummy `EventDispatcher` used for testing. |
| /// |
| /// A `DummyEventDispatcher` implements the `EventDispatcher` interface for |
| /// testing purposes. It provides facilities to inspect the history of what |
| /// events have been emitted to the system. |
| pub(crate) struct DummyEventDispatcher { |
| frames_sent: Vec<(DeviceId, Vec<u8>)>, |
| timer_events: BinaryHeap<PendingTimer>, |
| current_time: DummyInstant, |
| rng: FakeCryptoRng<XorShiftRng>, |
| icmpv4_replies: HashMap<IcmpConnId<Ipv4>, Vec<(u16, Vec<u8>)>>, |
| icmpv6_replies: HashMap<IcmpConnId<Ipv6>, Vec<(u16, Vec<u8>)>>, |
| } |
| |
| impl Default for DummyEventDispatcher { |
| fn default() -> DummyEventDispatcher { |
| DummyEventDispatcher { |
| frames_sent: Default::default(), |
| timer_events: Default::default(), |
| current_time: Default::default(), |
| rng: FakeCryptoRng(new_rng(0)), |
| icmpv4_replies: Default::default(), |
| icmpv6_replies: Default::default(), |
| } |
| } |
| } |
| |
| pub(crate) trait TestutilIpExt: Ip { |
| fn icmp_replies( |
| evt: &mut DummyEventDispatcher, |
| ) -> &mut HashMap<IcmpConnId<Self>, Vec<(u16, Vec<u8>)>>; |
| } |
| |
| impl TestutilIpExt for Ipv4 { |
| fn icmp_replies( |
| evt: &mut DummyEventDispatcher, |
| ) -> &mut HashMap<IcmpConnId<Ipv4>, Vec<(u16, Vec<u8>)>> { |
| &mut evt.icmpv4_replies |
| } |
| } |
| |
| impl TestutilIpExt for Ipv6 { |
| fn icmp_replies( |
| evt: &mut DummyEventDispatcher, |
| ) -> &mut HashMap<IcmpConnId<Ipv6>, Vec<(u16, Vec<u8>)>> { |
| &mut evt.icmpv6_replies |
| } |
| } |
| |
| impl DummyEventDispatcher { |
| pub(crate) fn frames_sent(&self) -> &[(DeviceId, Vec<u8>)] { |
| &self.frames_sent |
| } |
| |
| /// Get an ordered list of all scheduled timer events |
| pub(crate) fn timer_events(&self) -> impl Iterator<Item = (&'_ DummyInstant, &'_ TimerId)> { |
| // TODO(joshlf): `iter` doesn't actually guarantee an ordering, so this |
| // is a bug. We plan on removing this soon once we migrate to using the |
| // utilities in the `context` module, so this is left as-is. |
| self.timer_events.iter().map(|t| (&t.0, &t.1)) |
| } |
| |
| /// Takes all the received ICMP replies for a given `conn`. |
| pub(crate) fn take_icmp_replies<I: TestutilIpExt>( |
| &mut self, |
| conn: IcmpConnId<I>, |
| ) -> Vec<(u16, Vec<u8>)> { |
| I::icmp_replies(self).remove(&conn).unwrap_or_else(Vec::default) |
| } |
| } |
| |
| impl<I: IcmpIpExt> UdpEventDispatcher<I> for DummyEventDispatcher {} |
| |
| impl<I: IcmpIpExt> TransportLayerEventDispatcher<I> for DummyEventDispatcher {} |
| |
| impl<I: IcmpIpExt> IcmpEventDispatcher<I> for DummyEventDispatcher { |
| fn receive_icmp_error(&mut self, _conn: IcmpConnId<I>, _seq_num: u16, _err: I::ErrorCode) { |
| unimplemented!() |
| } |
| |
| fn close_icmp_connection(&mut self, _conn: IcmpConnId<I>, _err: NoRouteError) { |
| unimplemented!() |
| } |
| } |
| |
| impl<B: BufferMut> BufferIcmpEventDispatcher<Ipv4, B> for DummyEventDispatcher { |
| fn receive_icmp_echo_reply(&mut self, conn: IcmpConnId<Ipv4>, seq_num: u16, data: B) { |
| let replies = self.icmpv4_replies.entry(conn).or_insert_with(Vec::default); |
| replies.push((seq_num, data.as_ref().to_owned())) |
| } |
| } |
| |
| impl<B: BufferMut> BufferIcmpEventDispatcher<Ipv6, B> for DummyEventDispatcher { |
| fn receive_icmp_echo_reply(&mut self, conn: IcmpConnId<Ipv6>, seq_num: u16, data: B) { |
| let replies = self.icmpv6_replies.entry(conn).or_insert_with(Vec::default); |
| replies.push((seq_num, data.as_ref().to_owned())) |
| } |
| } |
| |
| impl<B: BufferMut> IpLayerEventDispatcher<B> for DummyEventDispatcher {} |
| |
| impl<B: BufferMut> DeviceLayerEventDispatcher<B> for DummyEventDispatcher { |
| fn send_frame<S: Serializer<Buffer = B>>( |
| &mut self, |
| device: DeviceId, |
| frame: S, |
| ) -> Result<(), S> { |
| let frame = frame.serialize_vec_outer().map_err(|(_, ser)| ser)?; |
| self.frames_sent.push((device, frame.as_ref().to_vec())); |
| Ok(()) |
| } |
| } |
| |
| impl EventDispatcher for DummyEventDispatcher { |
| type Instant = DummyInstant; |
| |
| fn now(&self) -> DummyInstant { |
| self.current_time |
| } |
| |
| fn schedule_timeout(&mut self, duration: Duration, id: TimerId) -> Option<DummyInstant> { |
| self.schedule_timeout_instant(self.current_time + duration, id) |
| } |
| |
| fn schedule_timeout_instant( |
| &mut self, |
| time: DummyInstant, |
| id: TimerId, |
| ) -> Option<DummyInstant> { |
| let ret = self.cancel_timeout(id); |
| self.timer_events.push(PendingTimer::new(time, id)); |
| ret |
| } |
| |
| fn cancel_timeout(&mut self, id: TimerId) -> Option<DummyInstant> { |
| let mut r: Option<DummyInstant> = None; |
| // NOTE(brunodalbo): cancelling timeouts can be made a faster than this |
| // if we kept two data structures and TimerId was Hashable. |
| self.timer_events = self |
| .timer_events |
| .drain() |
| .filter(|t| { |
| if t.1 == id { |
| r = Some(t.0); |
| false |
| } else { |
| true |
| } |
| }) |
| .collect::<Vec<_>>() |
| .into(); |
| r |
| } |
| |
| fn cancel_timeouts_with<F: FnMut(&TimerId) -> bool>(&mut self, mut f: F) { |
| self.timer_events = |
| self.timer_events.drain().filter(|t| !f(&t.1)).collect::<Vec<_>>().into(); |
| } |
| |
| fn scheduled_instant(&self, id: TimerId) -> Option<DummyInstant> { |
| self.timer_events.iter().find_map(|x| if x.1 == id { Some(x.0) } else { None }) |
| } |
| |
| type Rng = FakeCryptoRng<XorShiftRng>; |
| |
| fn rng(&self) -> &FakeCryptoRng<XorShiftRng> { |
| &self.rng |
| } |
| |
| fn rng_mut(&mut self) -> &mut FakeCryptoRng<XorShiftRng> { |
| &mut self.rng |
| } |
| } |
| |
| #[derive(Debug)] |
| struct PendingFrameData<N> { |
| data: Vec<u8>, |
| dst_context: N, |
| dst_device: DeviceId, |
| } |
| |
| type PendingFrame<N> = InstantAndData<PendingFrameData<N>>; |
| |
| /// A dummy network, composed of many `Context`s backed by |
| /// `DummyEventDispatcher`s. |
| /// |
| /// Provides a quick utility to have many contexts keyed by `N` that can |
| /// exchange frames between their interfaces, which are mapped by `mapper`. |
| /// `mapper` also provides the option to return a `Duration` parameter that is |
| /// interpreted as a delivery latency for a given packet. |
| pub(crate) struct DummyNetwork< |
| N: Eq + Hash + Clone, |
| F: Fn(&N, DeviceId) -> Vec<(N, DeviceId, Option<Duration>)>, |
| > { |
| contexts: HashMap<N, Context<DummyEventDispatcher>>, |
| mapper: F, |
| current_time: DummyInstant, |
| pending_frames: BinaryHeap<PendingFrame<N>>, |
| } |
| |
| /// The result of a single step in a `DummyNetwork` |
| #[derive(Debug)] |
| pub(crate) struct StepResult { |
| time_delta: Duration, |
| timers_fired: usize, |
| frames_sent: usize, |
| } |
| |
| impl StepResult { |
| fn new(time_delta: Duration, timers_fired: usize, frames_sent: usize) -> Self { |
| Self { time_delta, timers_fired, frames_sent } |
| } |
| |
| fn new_idle() -> Self { |
| Self::new(Duration::from_millis(0), 0, 0) |
| } |
| |
| /// Returns `true` if the last step did not perform any operations. |
| pub(crate) fn is_idle(&self) -> bool { |
| return self.timers_fired == 0 && self.frames_sent == 0; |
| } |
| |
| /// Returns the number of frames dispatched to their destinations in the |
| /// last step. |
| pub(crate) fn frames_sent(&self) -> usize { |
| self.frames_sent |
| } |
| |
| /// Returns the number of timers fired in the last step. |
| pub(crate) fn timers_fired(&self) -> usize { |
| self.timers_fired |
| } |
| } |
| |
| /// Error type that marks that one of the `run_until` family of functions |
| /// reached a maximum number of iterations. |
| #[derive(Debug)] |
| pub(crate) struct LoopLimitReachedError; |
| |
| impl<N, F> DummyNetwork<N, F> |
| where |
| N: Eq + Hash + Clone + std::fmt::Debug, |
| F: Fn(&N, DeviceId) -> Vec<(N, DeviceId, Option<Duration>)>, |
| { |
| /// Creates a new `DummyNetwork`. |
| /// |
| /// Creates a new `DummyNetwork` with the collection of `Context`s in |
| /// `contexts`. `Context`s are named by type parameter `N`. `mapper` is used |
| /// to route frames from one pair of (named `Context`, `DeviceId`) to |
| /// another. |
| /// |
| /// # Panics |
| /// |
| /// `mapper` must map to a valid name, otherwise calls to `step` will panic. |
| /// |
| /// Calls to `new` will panic if given a `Context` with timer events. |
| /// `Context`s given to `DummyNetwork` **must not** have any timer events |
| /// already attached to them, because `DummyNetwork` maintains all the |
| /// internal timers in dispatchers in sync to enable synchronous simulation |
| /// steps. |
| pub(crate) fn new<I: Iterator<Item = (N, Context<DummyEventDispatcher>)>>( |
| contexts: I, |
| mapper: F, |
| ) -> Self { |
| let mut ret = Self { |
| contexts: contexts.collect(), |
| mapper, |
| current_time: DummyInstant::default(), |
| pending_frames: BinaryHeap::new(), |
| }; |
| |
| // We can't guarantee that all contexts are safely running their timers |
| // together if we receive a context with any timers already set. |
| assert!( |
| !ret.contexts.iter().any(|(_n, ctx)| { !ctx.dispatcher.timer_events.is_empty() }), |
| "can't start network with contexts that already have timers set" |
| ); |
| |
| // synchronize all dispatchers' current time to the same value: |
| for (_, ctx) in ret.contexts.iter_mut() { |
| ctx.dispatcher.current_time = ret.current_time; |
| } |
| |
| ret |
| } |
| |
| /// Retrieves a `Context` named `context`. |
| pub(crate) fn context<K: Into<N>>(&mut self, context: K) -> &mut Context<DummyEventDispatcher> { |
| self.contexts.get_mut(&context.into()).unwrap() |
| } |
| |
| /// Performs a single step in network simulation. |
| /// |
| /// `step` performs a single logical step in the collection of `Context`s |
| /// held by this `DummyNetwork`. A single step consists of the following |
| /// operations: |
| /// |
| /// - All pending frames, kept in `frames_sent` of `DummyEventDispatcher` |
| /// are mapped to their destination `Context`/`DeviceId` pairs and moved to |
| /// an internal collection of pending frames. |
| /// - The collection of pending timers and scheduled frames is inspected and |
| /// a simulation time step is retrieved, which will cause a next event |
| /// to trigger. The simulation time is updated to the new time. |
| /// - All scheduled frames whose deadline is less than or equal to the new |
| /// simulation time are sent to their destinations. |
| /// - All timer events whose deadline is less than or equal to the new |
| /// simulation time are fired. |
| /// |
| /// If any new events are created during the operation of frames or timers, |
| /// they **will not** be taken into account in the current `step`. That is, |
| /// `step` collects all the pending events before dispatching them, ensuring |
| /// that an infinite loop can't be created as a side effect of calling |
| /// `step`. |
| /// |
| /// The return value of `step` indicates which of the operations were |
| /// performed. |
| /// |
| /// # Panics |
| /// |
| /// If `DummyNetwork` was set up with a bad `mapper`, calls to `step` may |
| /// panic when trying to route frames to their `Context`/`DeviceId` |
| /// destinations. |
| pub(crate) fn step(&mut self) -> StepResult { |
| self.collect_frames(); |
| |
| let next_step = if let Some(t) = self.next_step() { |
| t |
| } else { |
| return StepResult::new_idle(); |
| }; |
| |
| // this assertion holds the contract that `next_step` does not return |
| // a time in the past. |
| assert!(next_step >= self.current_time); |
| let mut ret = StepResult::new(next_step.duration_since(self.current_time), 0, 0); |
| |
| // move time forward: |
| trace!("testutil::DummyNetwork::step: current time = {:?}", next_step); |
| self.current_time = next_step; |
| for (_, ctx) in self.contexts.iter_mut() { |
| ctx.dispatcher.current_time = next_step; |
| } |
| |
| // dispatch all pending frames: |
| while let Some(InstantAndData(t, _)) = self.pending_frames.peek() { |
| // TODO(brunodalbo): remove this break once let_chains is stable |
| if *t > self.current_time { |
| break; |
| } |
| |
| // we can unwrap because we just peeked. |
| let mut frame = self.pending_frames.pop().unwrap().1; |
| crate::receive_frame( |
| self.context(frame.dst_context), |
| frame.dst_device, |
| Buf::new(&mut frame.data, ..), |
| ); |
| ret.frames_sent += 1; |
| } |
| |
| // dispatch all pending timers. |
| for (_n, ctx) in self.contexts.iter_mut() { |
| // We have to collect the timers before dispatching them, to avoid |
| // an infinite loop in case handle_timeout schedules another timer |
| // for the same or older DummyInstant. |
| let mut timers = Vec::<TimerId>::new(); |
| while let Some(InstantAndData(t, id)) = ctx.dispatcher.timer_events.peek() { |
| // TODO(brunodalbo): remove this break once let_chains is stable |
| if *t > self.current_time { |
| break; |
| } |
| timers.push(*id); |
| ctx.dispatcher.timer_events.pop(); |
| } |
| |
| for t in timers { |
| crate::handle_timeout(ctx, t); |
| ret.timers_fired += 1; |
| } |
| } |
| |
| ret |
| } |
| |
| /// Collects all queued frames. |
| /// |
| /// Collects all pending frames and schedules them for delivery to the |
| /// destination `Context`/`DeviceId` based on the result of `mapper`. The |
| /// collected frames are queued for dispatching in the `DummyNetwork`, |
| /// ordered by their scheduled delivery time given by the latency result |
| /// provided by `mapper`. |
| fn collect_frames(&mut self) { |
| let all_frames: Vec<(N, Vec<(DeviceId, Vec<u8>)>)> = self |
| .contexts |
| .iter_mut() |
| .filter_map(|(n, ctx)| { |
| if ctx.dispatcher.frames_sent.is_empty() { |
| None |
| } else { |
| Some((n.clone(), ctx.dispatcher.frames_sent.drain(..).collect())) |
| } |
| }) |
| .collect(); |
| |
| for (n, frames) in all_frames.into_iter() { |
| for (device_id, frame) in frames.into_iter() { |
| for (dst_context, dst_device, latency) in (self.mapper)(&n, device_id) { |
| self.pending_frames.push(PendingFrame::new( |
| self.current_time + latency.unwrap_or(Duration::from_millis(0)), |
| PendingFrameData::<N> { data: frame.clone(), dst_context, dst_device }, |
| )); |
| } |
| } |
| } |
| } |
| |
| /// Calculates the next `DummyInstant` when events are available. |
| /// |
| /// Returns the smallest `DummyInstant` greater than or equal to |
| /// `current_time` for which an event is available. If no events are |
| /// available, returns `None`. |
| fn next_step(&mut self) -> Option<DummyInstant> { |
| self.collect_frames(); |
| |
| // get earliest timer in all contexts: |
| let next_timer = self |
| .contexts |
| .iter() |
| .filter_map(|(_n, ctx)| match ctx.dispatcher.timer_events.peek() { |
| Some(tmr) => Some(tmr.0), |
| None => None, |
| }) |
| .min(); |
| // get the instant for the next packet |
| let next_packet_due = self.pending_frames.peek().map(|t| t.0); |
| |
| // Return the earliest of them both, and protect against returning a |
| // time in the past. |
| match next_timer { |
| Some(t) if next_packet_due.is_some() => Some(t).min(next_packet_due), |
| Some(t) => Some(t), |
| None => next_packet_due, |
| } |
| .map(|t| t.max(self.current_time)) |
| } |
| |
| /// Runs the dummy network for `duration` time. |
| /// |
| /// Runs `step` until `duration` time has passed since the call of `run_for`. |
| pub(crate) fn run_for(&mut self, duration: Duration) { |
| let start_time = self.current_time; |
| let end_time = start_time + duration; |
| |
| while let Some(next_step) = self.next_step() { |
| if next_step <= end_time { |
| assert!(!self.step().is_idle()); |
| assert!(self.current_time <= end_time); |
| } else { |
| break; |
| } |
| } |
| |
| // Move time to the end time. |
| self.current_time = end_time; |
| for (_, ctx) in self.contexts.iter_mut() { |
| ctx.dispatcher.current_time = end_time; |
| } |
| } |
| |
| /// Runs the dummy network simulation until it is starved of events. |
| /// |
| /// Runs `step` until it returns a `StepResult` where `is_idle` is `true` or |
| /// a total of 1,000,000 steps is performed. The imposed limit in steps is |
| /// there to prevent the call from blocking; reaching that limit should be |
| /// considered a logic error. |
| /// |
| /// # Panics |
| /// |
| /// See [`step`] for possible panic conditions. |
| pub(crate) fn run_until_idle(&mut self) -> Result<(), LoopLimitReachedError> { |
| for _ in 0..1_000_000 { |
| if self.step().is_idle() { |
| return Ok(()); |
| } |
| } |
| debug!("DummyNetwork seems to have gotten stuck in a loop."); |
| Err(LoopLimitReachedError) |
| } |
| |
| /// Runs the dummy network simulation until it is starved of events or |
| /// `stop` returns `true`. |
| /// |
| /// Runs `step` until it returns a `StepResult` where `is_idle` is `true` or |
| /// the provided function `stop` returns `true`, or a total of 1,000,000 |
| /// steps is performed. The imposed limit in steps is there to prevent the |
| /// call from blocking; reaching that limit should be considered a logic |
| /// error. |
| /// |
| /// # Panics |
| /// |
| /// See [`step`] for possible panic conditions. |
| pub(crate) fn run_until_idle_or<S: Fn(&mut Self) -> bool>( |
| &mut self, |
| stop: S, |
| ) -> Result<(), LoopLimitReachedError> { |
| for _ in 0..1_000_000 { |
| if self.step().is_idle() { |
| return Ok(()); |
| } else if stop(self) { |
| return Ok(()); |
| } |
| } |
| debug!("DummyNetwork seems to have gotten stuck in a loop."); |
| Err(LoopLimitReachedError) |
| } |
| } |
| |
| /// Convenience function to create `DummyNetwork`s |
| /// |
| /// `new_dummy_network_from_config` creates a `DummyNetwork` with two `Context`s |
| /// named `a` and `b`. `Context` `a` is created from the configuration provided |
| /// in `cfg`, and `Context` `b` is created from the symmetric configuration |
| /// generated by `DummyEventDispatcherConfig::swap`. A default `mapper` function |
| /// is provided that maps all frames from (`a`, ethernet device `1`) to |
| /// (`b`, ethernet device `1`) and vice-versa. |
| pub(crate) fn new_dummy_network_from_config_with_latency<A: IpAddress, N>( |
| a: N, |
| b: N, |
| cfg: DummyEventDispatcherConfig<A>, |
| latency: Option<Duration>, |
| ) -> DummyNetwork<N, impl Fn(&N, DeviceId) -> Vec<(N, DeviceId, Option<Duration>)>> |
| where |
| N: Eq + Hash + Clone + std::fmt::Debug, |
| { |
| let bob = DummyEventDispatcherBuilder::from_config(cfg.swap()).build(); |
| let alice = DummyEventDispatcherBuilder::from_config(cfg).build(); |
| let contexts = vec![(a.clone(), alice), (b.clone(), bob)].into_iter(); |
| DummyNetwork::<N, _>::new(contexts, move |net, _device_id| { |
| if *net == a { |
| vec![(b.clone(), DeviceId::new_ethernet(0), latency)] |
| } else { |
| vec![(a.clone(), DeviceId::new_ethernet(0), latency)] |
| } |
| }) |
| } |
| |
| /// Convenience function to create `DummyNetwork`s with no latency |
| /// |
| /// Creates a `DummyNetwork` by calling |
| /// [`new_dummy_network_from_config_with_latency`] with `latency` set to `None`. |
| pub(crate) fn new_dummy_network_from_config<A: IpAddress, N>( |
| a: N, |
| b: N, |
| cfg: DummyEventDispatcherConfig<A>, |
| ) -> DummyNetwork<N, impl Fn(&N, DeviceId) -> Vec<(N, DeviceId, Option<Duration>)>> |
| where |
| N: Eq + Hash + Clone + std::fmt::Debug, |
| { |
| new_dummy_network_from_config_with_latency(a, b, cfg, None) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::time::Duration; |
| |
| use net_types::ip::{Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use packet::{Buf, Serializer}; |
| use specialize_ip_macro::ip_test; |
| |
| use super::*; |
| use crate::ip; |
| use crate::wire::icmp::{ |
| IcmpDestUnreachable, IcmpEchoReply, IcmpEchoRequest, IcmpPacketBuilder, IcmpUnusedCode, |
| Icmpv4DestUnreachableCode, |
| }; |
| use crate::TimerIdInner; |
| |
| #[test] |
| fn test_parse_ethernet_frame() { |
| use crate::wire::testdata::arp_request::*; |
| let (body, src_mac, dst_mac, ethertype) = |
| parse_ethernet_frame(ETHERNET_FRAME.bytes).unwrap(); |
| assert_eq!(body, ÐERNET_FRAME.bytes[14..]); |
| assert_eq!(src_mac, ETHERNET_FRAME.metadata.src_mac); |
| assert_eq!(dst_mac, ETHERNET_FRAME.metadata.dst_mac); |
| assert_eq!(ethertype, ETHERNET_FRAME.metadata.ethertype); |
| } |
| |
| #[test] |
| fn test_parse_ip_packet() { |
| use crate::wire::testdata::icmp_redirect::IP_PACKET_BYTES; |
| let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(IP_PACKET_BYTES).unwrap(); |
| assert_eq!(body, &IP_PACKET_BYTES[20..]); |
| assert_eq!(src_ip, Ipv4Addr::new([10, 123, 0, 2])); |
| assert_eq!(dst_ip, Ipv4Addr::new([10, 123, 0, 1])); |
| assert_eq!(proto, IpProto::Icmp); |
| assert_eq!(ttl, 255); |
| |
| use crate::wire::testdata::icmp_echo_v6::REQUEST_IP_PACKET_BYTES; |
| let (body, src_ip, dst_ip, proto, ttl) = |
| parse_ip_packet::<Ipv6>(REQUEST_IP_PACKET_BYTES).unwrap(); |
| assert_eq!(body, &REQUEST_IP_PACKET_BYTES[40..]); |
| assert_eq!(src_ip, Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])); |
| assert_eq!(dst_ip, Ipv6Addr::new([0xFE, 0xC0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); |
| assert_eq!(proto, IpProto::Icmpv6); |
| assert_eq!(ttl, 64); |
| } |
| |
| #[test] |
| fn test_parse_ip_packet_in_ethernet_frame() { |
| use crate::wire::testdata::tls_client_hello_v4::*; |
| let (body, src_mac, dst_mac, src_ip, dst_ip, proto, ttl) = |
| parse_ip_packet_in_ethernet_frame::<Ipv4>(ETHERNET_FRAME.bytes).unwrap(); |
| assert_eq!(body, &IPV4_PACKET.bytes[IPV4_PACKET.body_range]); |
| assert_eq!(src_mac, ETHERNET_FRAME.metadata.src_mac); |
| assert_eq!(dst_mac, ETHERNET_FRAME.metadata.dst_mac); |
| assert_eq!(src_ip, IPV4_PACKET.metadata.src_ip); |
| assert_eq!(dst_ip, IPV4_PACKET.metadata.dst_ip); |
| assert_eq!(proto, IPV4_PACKET.metadata.proto); |
| assert_eq!(ttl, IPV4_PACKET.metadata.ttl); |
| } |
| |
| #[test] |
| fn test_parse_icmp_packet() { |
| set_logger_for_test(); |
| use crate::wire::testdata::icmp_dest_unreachable::*; |
| let (body, ..) = parse_ip_packet::<Ipv4>(&IP_PACKET_BYTES).unwrap(); |
| let (_, code) = parse_icmp_packet::<Ipv4, _, IcmpDestUnreachable, _>( |
| body, |
| Ipv4Addr::new([172, 217, 6, 46]), |
| Ipv4Addr::new([192, 168, 0, 105]), |
| |_| {}, |
| ) |
| .unwrap(); |
| assert_eq!(code, Icmpv4DestUnreachableCode::DestHostUnreachable); |
| } |
| |
| #[test] |
| fn test_parse_icmp_packet_in_ip_packet_in_ethernet_frame() { |
| set_logger_for_test(); |
| use crate::wire::testdata::icmp_echo_ethernet::*; |
| let (src_mac, dst_mac, src_ip, dst_ip, _, _, _) = |
| parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv4, _, IcmpEchoReply, _>( |
| &REPLY_ETHERNET_FRAME_BYTES, |
| |_| {}, |
| ) |
| .unwrap(); |
| assert_eq!(src_mac, Mac::new([0x50, 0xc7, 0xbf, 0x1d, 0xf4, 0xd2])); |
| assert_eq!(dst_mac, Mac::new([0x8c, 0x85, 0x90, 0xc9, 0xc9, 0x00])); |
| assert_eq!(src_ip, Ipv4Addr::new([172, 217, 6, 46])); |
| assert_eq!(dst_ip, Ipv4Addr::new([192, 168, 0, 105])); |
| } |
| |
| #[test] |
| fn test_dummy_network_transmits_packets() { |
| set_logger_for_test(); |
| let mut net = new_dummy_network_from_config("alice", "bob", DUMMY_CONFIG_V4); |
| |
| // alice sends bob a ping: |
| ip::send_ipv4_packet( |
| net.context("alice"), |
| DUMMY_CONFIG_V4.remote_ip, |
| ip::IpProto::Icmp, |
| |_| { |
| let req = IcmpEchoRequest::new(0, 0); |
| let req_body = &[1, 2, 3, 4]; |
| Buf::new(req_body.to_vec(), ..).encapsulate( |
| IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| DUMMY_CONFIG_V4.local_ip, |
| DUMMY_CONFIG_V4.remote_ip, |
| IcmpUnusedCode, |
| req, |
| ), |
| ) |
| }, |
| ) |
| .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()); |
| } |
| |
| #[test] |
| fn test_dummy_network_timers() { |
| set_logger_for_test(); |
| let mut net = new_dummy_network_from_config(1, 2, DUMMY_CONFIG_V4); |
| |
| net.context(1) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(1), TimerId(TimerIdInner::Nop(1))); |
| net.context(2) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(2), TimerId(TimerIdInner::Nop(2))); |
| net.context(2) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(3), TimerId(TimerIdInner::Nop(3))); |
| net.context(1) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(4), TimerId(TimerIdInner::Nop(4))); |
| |
| net.context(1) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(5), TimerId(TimerIdInner::Nop(5))); |
| net.context(2) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(5), TimerId(TimerIdInner::Nop(6))); |
| |
| // no timers fired before: |
| assert_eq!(*net.context(1).state.test_counters.get("timer::nop"), 0); |
| assert_eq!(*net.context(2).state.test_counters.get("timer::nop"), 0); |
| assert_eq!(net.step().timers_fired(), 1); |
| // only timer in context 1 should have fired: |
| assert_eq!(*net.context(1).state.test_counters.get("timer::nop"), 1); |
| assert_eq!(*net.context(2).state.test_counters.get("timer::nop"), 0); |
| assert_eq!(net.step().timers_fired(), 1); |
| // only timer in context 2 should have fired: |
| assert_eq!(*net.context(1).state.test_counters.get("timer::nop"), 1); |
| assert_eq!(*net.context(2).state.test_counters.get("timer::nop"), 1); |
| assert_eq!(net.step().timers_fired(), 1); |
| // only timer in context 2 should have fired: |
| assert_eq!(*net.context(1).state.test_counters.get("timer::nop"), 1); |
| assert_eq!(*net.context(2).state.test_counters.get("timer::nop"), 2); |
| assert_eq!(net.step().timers_fired(), 1); |
| // only timer in context 1 should have fired: |
| assert_eq!(*net.context(1).state.test_counters.get("timer::nop"), 2); |
| assert_eq!(*net.context(2).state.test_counters.get("timer::nop"), 2); |
| assert_eq!(net.step().timers_fired(), 2); |
| // both timers have fired at the same time: |
| assert_eq!(*net.context(1).state.test_counters.get("timer::nop"), 3); |
| assert_eq!(*net.context(2).state.test_counters.get("timer::nop"), 3); |
| |
| assert!(net.step().is_idle()); |
| // check that current time on contexts tick together: |
| let t1 = net.context(1).dispatcher.current_time; |
| let t2 = net.context(2).dispatcher.current_time; |
| assert_eq!(t1, t2); |
| } |
| |
| #[test] |
| fn test_dummy_network_until_idle() { |
| set_logger_for_test(); |
| let mut net = new_dummy_network_from_config(1, 2, DUMMY_CONFIG_V4); |
| net.context(1) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(1), TimerId(TimerIdInner::Nop(1))); |
| net.context(2) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(2), TimerId(TimerIdInner::Nop(2))); |
| net.context(2) |
| .dispatcher |
| .schedule_timeout(Duration::from_secs(3), TimerId(TimerIdInner::Nop(3))); |
| |
| net.run_until_idle_or(|net| { |
| *net.context(1).state.test_counters.get("timer::nop") == 1 |
| && *net.context(2).state.test_counters.get("timer::nop") == 1 |
| }) |
| .unwrap(); |
| // assert that we stopped before all times were fired, meaning we can |
| // step again: |
| assert_eq!(net.step().timers_fired(), 1); |
| } |
| |
| #[test] |
| fn test_instant_and_data() { |
| // verify implementation of InstantAndData to be used as a complex type |
| // in a BinaryHeap: |
| let mut heap = BinaryHeap::<InstantAndData<usize>>::new(); |
| let now = DummyInstant::default(); |
| |
| fn new_data(time: DummyInstant, id: usize) -> InstantAndData<usize> { |
| InstantAndData::new(time, id) |
| } |
| |
| heap.push(new_data(now + Duration::from_secs(1), 1)); |
| heap.push(new_data(now + Duration::from_secs(2), 2)); |
| |
| // earlier timer is popped first |
| assert!(heap.pop().unwrap().1 == 1); |
| assert!(heap.pop().unwrap().1 == 2); |
| assert!(heap.pop().is_none()); |
| |
| heap.push(new_data(now + Duration::from_secs(1), 1)); |
| heap.push(new_data(now + Duration::from_secs(1), 1)); |
| |
| // can pop twice with identical data: |
| assert!(heap.pop().unwrap().1 == 1); |
| assert!(heap.pop().unwrap().1 == 1); |
| assert!(heap.pop().is_none()); |
| } |
| |
| #[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_dummy_network_from_config_with_latency( |
| "alice", |
| "bob", |
| DUMMY_CONFIG_V4, |
| Some(Duration::from_millis(5)), |
| ); |
| |
| // alice sends bob a ping: |
| ip::send_ipv4_packet( |
| net.context("alice"), |
| DUMMY_CONFIG_V4.remote_ip, |
| ip::IpProto::Icmp, |
| |_| { |
| let req = IcmpEchoRequest::new(0, 0); |
| let req_body = &[1, 2, 3, 4]; |
| Buf::new(req_body.to_vec(), ..).encapsulate( |
| IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| DUMMY_CONFIG_V4.local_ip, |
| DUMMY_CONFIG_V4.remote_ip, |
| IcmpUnusedCode, |
| req, |
| ), |
| ) |
| }, |
| ) |
| .unwrap(); |
| |
| net.context("alice") |
| .dispatcher |
| .schedule_timeout(Duration::from_millis(3), TimerId(TimerIdInner::Nop(1))); |
| net.context("bob") |
| .dispatcher |
| .schedule_timeout(Duration::from_millis(7), TimerId(TimerIdInner::Nop(2))); |
| net.context("bob") |
| .dispatcher |
| .schedule_timeout(Duration::from_millis(10), TimerId(TimerIdInner::Nop(1))); |
| |
| // order of expected events is as follows: |
| // - Alice's timer expires at t = 3 |
| // - Bob receives Alice's packet at t = 5 |
| // - Bob's timer expires at t = 7 |
| // - Alice receives Bob's response and Bob's last timer fires at t = 10 |
| |
| fn assert_full_state<F>( |
| net: &mut DummyNetwork<&'static str, F>, |
| alice_nop: usize, |
| bob_nop: usize, |
| bob_echo_request: usize, |
| alice_echo_response: usize, |
| ) where |
| F: Fn(&&'static str, DeviceId) -> Vec<(&'static str, DeviceId, Option<Duration>)>, |
| { |
| let alice = net.context("alice"); |
| assert_eq!(*alice.state.test_counters.get("timer::nop"), alice_nop); |
| assert_eq!( |
| *alice.state.test_counters.get("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::echo_reply"), |
| alice_echo_response |
| ); |
| |
| let bob = net.context("bob"); |
| assert_eq!(*bob.state.test_counters.get("timer::nop"), bob_nop); |
| assert_eq!( |
| *bob.state.test_counters.get("<IcmpIpTransportContext as BufferIpTransportContext<Ipv4>>::receive_ip_packet::echo_request"), |
| bob_echo_request |
| ); |
| } |
| |
| 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()); |
| } |
| |
| #[ip_test] |
| fn test_send_to_many<I: Ip + TestIpExt>() { |
| fn send_packet<A: IpAddress>( |
| ctx: &mut Context<DummyEventDispatcher>, |
| src_ip: SpecifiedAddr<A>, |
| dst_ip: SpecifiedAddr<A>, |
| device: DeviceId, |
| ) { |
| crate::ip::send_ip_packet_from_device( |
| ctx, |
| device, |
| src_ip.get(), |
| dst_ip.get(), |
| dst_ip, |
| crate::ip::IpProto::Udp, |
| Buf::new(vec![1, 2, 3, 4], ..), |
| None, |
| ) |
| .unwrap(); |
| } |
| |
| let device = DeviceId::new_ethernet(0); |
| let a = "alice"; |
| let b = "bob"; |
| let c = "calvin"; |
| let mac_a = Mac::new([1, 2, 3, 4, 5, 6]); |
| let mac_b = Mac::new([1, 2, 3, 4, 5, 7]); |
| let mac_c = Mac::new([1, 2, 3, 4, 5, 8]); |
| 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 = DummyEventDispatcherBuilder::default(); |
| alice.add_device_with_ip(mac_a, ip_a.get(), subnet); |
| let mut bob = DummyEventDispatcherBuilder::default(); |
| bob.add_device_with_ip(mac_b, ip_b.get(), subnet); |
| let mut calvin = DummyEventDispatcherBuilder::default(); |
| calvin.add_device_with_ip(mac_c, ip_c.get(), subnet); |
| add_arp_or_ndp_table_entry(&mut alice, device.id(), ip_b.get(), mac_b); |
| add_arp_or_ndp_table_entry(&mut alice, device.id(), ip_c.get(), mac_c); |
| add_arp_or_ndp_table_entry(&mut bob, device.id(), ip_a.get(), mac_a); |
| add_arp_or_ndp_table_entry(&mut bob, device.id(), ip_c.get(), mac_c); |
| add_arp_or_ndp_table_entry(&mut calvin, device.id(), ip_a.get(), mac_a); |
| add_arp_or_ndp_table_entry(&mut calvin, device.id(), ip_b.get(), mac_b); |
| let contexts = |
| vec![(a.clone(), alice.build()), (b.clone(), bob.build()), (c.clone(), calvin.build())] |
| .into_iter(); |
| let mut net = DummyNetwork::new(contexts, move |net, _| { |
| let ret = match *net { |
| "alice" => vec![(b.clone(), device, None), (c.clone(), device, None)], |
| "bob" => vec![(a.clone(), device, None)], |
| "calvin" => Vec::new(), |
| _ => unreachable!(), |
| }; |
| |
| println!("{:?}", ret); |
| ret |
| }); |
| |
| net.collect_frames(); |
| assert_eq!(net.context("alice").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("bob").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("calvin").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.pending_frames.len(), 0); |
| |
| // |
| // bob and calvin should get any packet sent by alice. |
| // |
| |
| send_packet(net.context("alice"), ip_a, ip_b, device); |
| assert_eq!(net.context("alice").dispatcher().frames_sent().len(), 1); |
| assert_eq!(net.context("bob").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("calvin").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.pending_frames.len(), 0); |
| net.collect_frames(); |
| assert_eq!(net.context("alice").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("bob").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("calvin").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.pending_frames.len(), 2); |
| assert!(net |
| .pending_frames |
| .iter() |
| .any(|InstantAndData(_, x)| (x.dst_context == b) && (x.dst_device == device))); |
| assert!(net |
| .pending_frames |
| .iter() |
| .any(|InstantAndData(_, x)| (x.dst_context == c) && (x.dst_device == device))); |
| |
| // |
| // Only alice should get packets sent by bob. |
| // |
| |
| net.pending_frames = BinaryHeap::new(); |
| send_packet(net.context("bob"), ip_b, ip_a, device); |
| assert_eq!(net.context("alice").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("bob").dispatcher().frames_sent().len(), 1); |
| assert_eq!(net.context("calvin").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.pending_frames.len(), 0); |
| net.collect_frames(); |
| assert_eq!(net.context("alice").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("bob").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("calvin").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.pending_frames.len(), 1); |
| assert!(net |
| .pending_frames |
| .iter() |
| .any(|InstantAndData(_, x)| (x.dst_context == a) && (x.dst_device == device))); |
| |
| // |
| // No one gets packets sent by calvin. |
| // |
| |
| net.pending_frames = BinaryHeap::new(); |
| send_packet(net.context("calvin"), ip_c, ip_a, device); |
| assert_eq!(net.context("alice").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("bob").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("calvin").dispatcher().frames_sent().len(), 1); |
| assert_eq!(net.pending_frames.len(), 0); |
| net.collect_frames(); |
| assert_eq!(net.context("alice").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("bob").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.context("calvin").dispatcher().frames_sent().len(), 0); |
| assert_eq!(net.pending_frames.len(), 0); |
| } |
| } |