| // 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::ops; |
| use std::sync::Once; |
| use std::time::Duration; |
| |
| use byteorder::{ByteOrder, NativeEndian}; |
| use log::debug; |
| use packet::{ParsablePacket, ParseBuffer}; |
| use rand::SeedableRng; |
| use rand_xorshift::XorShiftRng; |
| |
| use crate::device::ethernet::{EtherType, Mac}; |
| use crate::device::{DeviceId, DeviceLayerEventDispatcher}; |
| use crate::error::{IpParseResult, ParseError, ParseResult}; |
| use crate::ip::{ |
| AddrSubnet, Ip, IpAddr, IpAddress, IpExt, IpPacket, IpProto, Ipv4Addr, Ipv6Addr, Subnet, |
| SubnetEither, IPV6_MIN_MTU, |
| }; |
| use crate::transport::udp::UdpEventDispatcher; |
| use crate::transport::TransportLayerEventDispatcher; |
| use crate::wire::ethernet::EthernetFrame; |
| use crate::wire::icmp::{IcmpMessage, IcmpPacket, IcmpParseArgs}; |
| use crate::{handle_timeout, Context, EventDispatcher, Instant, StackStateBuilder, TimerId}; |
| |
| use specialize_ip_macro::specialize_ip_address; |
| |
| /// 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) |
| } |
| |
| #[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.current_time + 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.current_time <= 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; |
| } |
| } |
| } |
| |
| /// 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::<EthernetFrame<_>>()?; |
| 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)> { |
| let packet = (&mut buf).parse::<<I as IpExt<_>>::Packet>()?; |
| let src_ip = packet.src_ip(); |
| let dst_ip = packet.dst_ip(); |
| let proto = packet.proto(); |
| // 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)) |
| } |
| |
| /// 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: Ip, |
| C, |
| M: for<'a> IcmpMessage<I, &'a [u8], Code = C>, |
| F: for<'a> Fn(&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)> { |
| 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) = parse_ip_packet::<I>(body)?; |
| Ok((body, src_mac, dst_mac, src_ip, dst_ip, proto)) |
| } |
| |
| /// 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: Ip, |
| C, |
| M: for<'a> IcmpMessage<I, &'a [u8], Code = C>, |
| F: for<'a> Fn(&IcmpPacket<I, &'a [u8], M>), |
| >( |
| mut buf: &[u8], |
| f: F, |
| ) -> IpParseResult<I, (Mac, Mac, I::Addr, I::Addr, M, C)> |
| where |
| for<'a> IcmpPacket<I, &'a [u8], M>: |
| ParsablePacket<&'a [u8], IcmpParseArgs<I::Addr>, Error = ParseError>, |
| { |
| use crate::wire::icmp::IcmpIpExt; |
| |
| let (mut body, src_mac, dst_mac, src_ip, dst_ip, proto) = |
| parse_ip_packet_in_ethernet_frame::<I>(buf)?; |
| if proto != <I as IcmpIpExt<&[u8]>>::IP_PROTO { |
| debug!("unexpected IP protocol: {} (wanted {})", proto, <I as IcmpIpExt<&[u8]>>::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, message, code)) |
| } |
| |
| /// Get a DummyEventDispatcherConfig depending on the `IpAddress` |
| /// `get_dummy_config` is specialied with. |
| #[specialize_ip_address] |
| pub(crate) fn get_dummy_config<A: IpAddress>() -> DummyEventDispatcherConfig<A> { |
| #[ipv4addr] |
| { |
| DUMMY_CONFIG_V4 |
| } |
| |
| #[ipv6addr] |
| { |
| DUMMY_CONFIG_V6 |
| } |
| } |
| |
| /// 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: 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: 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> = |
| DummyEventDispatcherConfig { |
| subnet: unsafe { Subnet::new_unchecked(Ipv4Addr::new([192, 168, 0, 0]), 16) }, |
| local_ip: Ipv4Addr::new([192, 168, 0, 1]), |
| local_mac: Mac::new([0, 1, 2, 3, 4, 5]), |
| remote_ip: 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> = |
| DummyEventDispatcherConfig { |
| subnet: unsafe { |
| Subnet::new_unchecked( |
| Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 0]), |
| 112, |
| ) |
| }, |
| local_ip: 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: 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, IpAddr)>, |
| } |
| |
| impl DummyEventDispatcherBuilder { |
| /// Construct a `DummyEventDispatcherBuilder` from a `DummyEventDispatcherConfig`. |
| #[specialize_ip_address] |
| 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.into(), cfg.subnet.into())))); |
| |
| #[ipv4addr] |
| builder.arp_table_entries.push((0, cfg.remote_ip, cfg.remote_mac)); |
| #[ipv6addr] |
| builder.ndp_table_entries.push((0, cfg.remote_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(None), |
| 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)); |
| } |
| |
| /// Add a route to the forwarding table. |
| pub(crate) fn add_route<A: IpAddress>(&mut self, subnet: Subnet<A>, next_hop: A) { |
| self.routes.push((subnet.into(), next_hop.into())); |
| } |
| |
| /// Add a device route to the forwarding table. |
| pub(crate) fn add_device_route<A: IpAddress>(&mut self, subnet: Subnet<A>, device: usize) { |
| self.device_routes.push((subnet.into(), device)); |
| } |
| |
| /// Build a `Context` from the present configuration with a default state |
| /// and dispatcher. |
| /// |
| /// `b.build()` is equivalent to `b.build_with(StackStateBuilder::default(), |
| /// D::default())`. |
| pub(crate) fn build<D: EventDispatcher + Default>(self) -> Context<D> { |
| self.build_with(StackStateBuilder::default(), 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_mut().add_ethernet_device(mac, IPV6_MIN_MTU); |
| idx_to_device_id.insert(idx, id); |
| match ip_subnet { |
| Some((IpAddr::V4(ip), SubnetEither::V4(subnet))) => { |
| let addr_sub = AddrSubnet::new(ip, subnet.prefix()).unwrap(); |
| crate::device::set_ip_addr_subnet(&mut ctx, id, addr_sub); |
| } |
| Some((IpAddr::V6(ip), SubnetEither::V6(subnet))) => { |
| let addr_sub = AddrSubnet::new(ip, subnet.prefix()).unwrap(); |
| crate::device::set_ip_addr_subnet(&mut ctx, id, addr_sub); |
| } |
| None => {} |
| _ => unreachable!(), |
| } |
| } |
| for (idx, ip, mac) in arp_table_entries { |
| let device = *idx_to_device_id.get(&idx).unwrap(); |
| crate::device::ethernet::insert_static_arp_table_entry(&mut ctx, device.id(), ip, mac); |
| } |
| for (idx, ip, mac) in ndp_table_entries { |
| let device = *idx_to_device_id.get(&idx).unwrap(); |
| crate::device::ethernet::insert_ndp_table_entry(&mut ctx, device.id(), 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), |
| SubnetEither::V6(subnet) => crate::ip::add_device_route(&mut ctx, subnet, device), |
| }; |
| } |
| for (subnet, next_hop) in routes { |
| match (subnet, next_hop) { |
| (SubnetEither::V4(subnet), IpAddr::V4(next_hop)) => { |
| crate::ip::add_route(&mut ctx, subnet, next_hop) |
| } |
| (SubnetEither::V6(subnet), IpAddr::V6(next_hop)) => { |
| crate::ip::add_route(&mut ctx, subnet, next_hop) |
| } |
| _ => unreachable!(), |
| }; |
| } |
| |
| ctx |
| } |
| } |
| |
| /// 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. |
| #[derive(Default)] |
| pub(crate) struct DummyEventDispatcher { |
| frames_sent: Vec<(DeviceId, Vec<u8>)>, |
| timer_events: BinaryHeap<PendingTimer>, |
| current_time: DummyInstant, |
| } |
| |
| 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)> { |
| self.timer_events.iter().map(|t| (&t.0, &t.1)) |
| } |
| |
| /// Forwards all the frames kept in the `self` to another context. |
| /// |
| /// This function drains all the events in `self` and moves the data to |
| /// another context. `mapper` is used to map a `DeviceId` from the current |
| /// context to a `DeviceId` in `other`. |
| pub(crate) fn forward_frames<D: EventDispatcher, F>( |
| &mut self, |
| other: &mut Context<D>, |
| mapper: F, |
| ) where |
| F: Fn(DeviceId) -> DeviceId, |
| { |
| for (device_id, mut data) in self.frames_sent.drain(..) { |
| crate::receive_frame(other, mapper(device_id), &mut data); |
| } |
| } |
| } |
| |
| impl UdpEventDispatcher for DummyEventDispatcher { |
| type UdpConn = (); |
| type UdpListener = (); |
| } |
| |
| impl TransportLayerEventDispatcher for DummyEventDispatcher {} |
| |
| impl DeviceLayerEventDispatcher for DummyEventDispatcher { |
| fn send_frame(&mut self, device: DeviceId, frame: &[u8]) { |
| self.frames_sent.push((device, frame.to_vec())); |
| } |
| } |
| |
| 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 |
| } |
| } |
| |
| #[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) -> (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 the time jump in the last step. |
| pub(crate) fn time_delta(&self) -> Duration { |
| self.time_delta |
| } |
| |
| /// 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) -> (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: |
| 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, |
| &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, mut frame) in frames.into_iter() { |
| let (dst_context, dst_device, latency) = (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, 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(&self) -> Option<DummyInstant> { |
| // 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 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) -> (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 { |
| (b.clone(), DeviceId::new_ethernet(1), latency) |
| } else { |
| (a.clone(), DeviceId::new_ethernet(1), 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) -> (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 super::*; |
| |
| use crate::ip::{self, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use crate::wire::icmp::{ |
| IcmpDestUnreachable, IcmpEchoReply, IcmpEchoRequest, IcmpPacketBuilder, IcmpUnusedCode, |
| Icmpv4DestUnreachableCode, |
| }; |
| use crate::TimerIdInner; |
| use packet::{Buf, BufferSerializer, Serializer}; |
| use std::time::Duration; |
| |
| #[test] |
| fn test_parse_ethernet_frame() { |
| use crate::wire::testdata::ARP_REQUEST; |
| let (body, src_mac, dst_mac, ethertype) = parse_ethernet_frame(ARP_REQUEST).unwrap(); |
| assert_eq!(body, &ARP_REQUEST[14..]); |
| assert_eq!(src_mac, Mac::new([20, 171, 197, 116, 32, 52])); |
| assert_eq!(dst_mac, Mac::new([255, 255, 255, 255, 255, 255])); |
| assert_eq!(ethertype, Some(EtherType::Arp)); |
| } |
| |
| #[test] |
| fn test_parse_ip_packet() { |
| use crate::wire::testdata::icmp_redirect::IP_PACKET_BYTES; |
| let (body, src_ip, dst_ip, proto) = 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); |
| |
| use crate::wire::testdata::icmp_echo_v6::REQUEST_IP_PACKET_BYTES; |
| let (body, src_ip, dst_ip, proto) = |
| 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); |
| } |
| |
| #[test] |
| fn test_parse_ip_packet_in_ethernet_frame() { |
| use crate::wire::testdata::tls_client_hello::*; |
| let (body, src_mac, dst_mac, src_ip, dst_ip, proto) = |
| parse_ip_packet_in_ethernet_frame::<Ipv4>(ETHERNET_FRAME_BYTES).unwrap(); |
| assert_eq!(body, &(ETHERNET_FRAME_BYTES[ETHERNET_BODY_RANGE])[IP_BODY_RANGE]); |
| assert_eq!(src_mac, ETHERNET_SRC_MAC); |
| assert_eq!(dst_mac, ETHERNET_DST_MAC); |
| assert_eq!(src_ip, IP_SRC_IP); |
| assert_eq!(dst_ip, IP_DST_IP); |
| assert_eq!(proto, IpProto::Tcp); |
| } |
| |
| #[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_ip_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]; |
| BufferSerializer::new_vec(Buf::new(req_body.to_vec(), ..)).encapsulate( |
| IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| DUMMY_CONFIG_V4.local_ip, |
| DUMMY_CONFIG_V4.remote_ip, |
| IcmpUnusedCode, |
| req, |
| ), |
| ) |
| }, |
| ); |
| |
| // 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_ip_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]; |
| BufferSerializer::new_vec(Buf::new(req_body.to_vec(), ..)).encapsulate( |
| IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| DUMMY_CONFIG_V4.local_ip, |
| DUMMY_CONFIG_V4.remote_ip, |
| IcmpUnusedCode, |
| req, |
| ), |
| ) |
| }, |
| ); |
| |
| 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) -> (&'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("receive_icmp_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("receive_icmp_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()); |
| } |
| } |