#![deny(missing_docs)]
pub mod admin;
pub mod rules;
pub mod testutil;
use std::collections::HashSet;
use std::fmt::{Debug, Display};
use async_utils::{fold, stream};
use fidl_fuchsia_net_ext::{IntoExt as _, TryIntoExt as _};
use futures::{Future, Stream, TryStreamExt as _};
use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6, Ipv6Addr, Subnet};
use net_types::{SpecifiedAddr, UnicastAddress, Witness as _};
use thiserror::Error;
use {
fidl_fuchsia_net as fnet, fidl_fuchsia_net_routes as fnet_routes,
fidl_fuchsia_net_routes_admin as fnet_routes_admin, fidl_fuchsia_net_stack as fnet_stack,
};
#[derive(Clone, Copy, Debug, Error, PartialEq)]
pub enum FidlConversionError<UnsetFieldSpecifier: Debug + Display> {
#[error("required field is unset: {0}")]
RequiredFieldUnset(UnsetFieldSpecifier),
#[error("failed to convert `destination` to net_types subnet: {0:?}")]
DestinationSubnet(net_types::ip::SubnetError),
#[error("failed to convert `next_hop` to a specified addr")]
UnspecifiedNextHop,
#[error("failed to convert `next_hop` to a unicast addr")]
NextHopNotUnicast,
}
impl<T: Debug + Display> FidlConversionError<T> {
fn map_unset_fields<U: Debug + Display>(
self,
f: impl FnOnce(T) -> U,
) -> FidlConversionError<U> {
match self {
FidlConversionError::RequiredFieldUnset(field) => {
FidlConversionError::RequiredFieldUnset(f(field))
}
FidlConversionError::DestinationSubnet(err) => {
FidlConversionError::DestinationSubnet(err)
}
FidlConversionError::UnspecifiedNextHop => FidlConversionError::UnspecifiedNextHop,
FidlConversionError::NextHopNotUnicast => FidlConversionError::NextHopNotUnicast,
}
}
}
impl From<FidlConversionError<RoutePropertiesRequiredFields>> for fnet_routes_admin::RouteSetError {
fn from(error: FidlConversionError<RoutePropertiesRequiredFields>) -> Self {
match error {
FidlConversionError::RequiredFieldUnset(field_name) => match field_name {
RoutePropertiesRequiredFields::SpecifiedProperties => {
fnet_routes_admin::RouteSetError::MissingRouteProperties
}
RoutePropertiesRequiredFields::WithinSpecifiedProperties(field_name) => {
match field_name {
SpecifiedRoutePropertiesRequiredFields::Metric => {
fnet_routes_admin::RouteSetError::MissingMetric
}
}
}
},
FidlConversionError::DestinationSubnet(_subnet_error) => {
fnet_routes_admin::RouteSetError::InvalidDestinationSubnet
}
FidlConversionError::UnspecifiedNextHop | FidlConversionError::NextHopNotUnicast => {
fnet_routes_admin::RouteSetError::InvalidNextHop
}
}
}
}
#[derive(Clone, Copy, Debug, Error, PartialEq)]
pub enum NetTypeConversionError {
#[error("Union type is of the `Unknown` variant: {0}")]
UnknownUnionVariant(&'static str),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct SpecifiedRouteProperties {
pub metric: fnet_routes::SpecifiedMetric,
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum SpecifiedRoutePropertiesRequiredFields {
#[error("fuchsia.net.routes/SpecifiedRouteProperties.metric")]
Metric,
}
impl TryFrom<fnet_routes::SpecifiedRouteProperties> for SpecifiedRouteProperties {
type Error = FidlConversionError<SpecifiedRoutePropertiesRequiredFields>;
fn try_from(
specified_properties: fnet_routes::SpecifiedRouteProperties,
) -> Result<Self, Self::Error> {
Ok(SpecifiedRouteProperties {
metric: specified_properties.metric.ok_or(FidlConversionError::RequiredFieldUnset(
SpecifiedRoutePropertiesRequiredFields::Metric,
))?,
})
}
}
impl From<SpecifiedRouteProperties> for fnet_routes::SpecifiedRouteProperties {
fn from(
specified_properties: SpecifiedRouteProperties,
) -> fnet_routes::SpecifiedRouteProperties {
let SpecifiedRouteProperties { metric } = specified_properties;
fnet_routes::SpecifiedRouteProperties { metric: Some(metric), ..Default::default() }
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct EffectiveRouteProperties {
pub metric: u32,
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum EffectiveRoutePropertiesRequiredFields {
#[error("fuchsia.net.routes/EffectiveRouteProperties.metric")]
Metric,
}
impl TryFrom<fnet_routes::EffectiveRouteProperties> for EffectiveRouteProperties {
type Error = FidlConversionError<EffectiveRoutePropertiesRequiredFields>;
fn try_from(
effective_properties: fnet_routes::EffectiveRouteProperties,
) -> Result<Self, Self::Error> {
Ok(EffectiveRouteProperties {
metric: effective_properties.metric.ok_or(FidlConversionError::RequiredFieldUnset(
EffectiveRoutePropertiesRequiredFields::Metric,
))?,
})
}
}
impl From<EffectiveRouteProperties> for fnet_routes::EffectiveRouteProperties {
fn from(
effective_properties: EffectiveRouteProperties,
) -> fnet_routes::EffectiveRouteProperties {
let EffectiveRouteProperties { metric } = effective_properties;
fnet_routes::EffectiveRouteProperties { metric: Some(metric), ..Default::default() }
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct RouteProperties {
pub specified_properties: SpecifiedRouteProperties,
}
impl RouteProperties {
pub fn from_explicit_metric(metric: u32) -> Self {
Self {
specified_properties: SpecifiedRouteProperties {
metric: fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
},
}
}
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum RoutePropertiesRequiredFields {
#[error("fuchsia.net.routes/RoutePropertiesV#.specified_properties")]
SpecifiedProperties,
#[error(transparent)]
WithinSpecifiedProperties(#[from] SpecifiedRoutePropertiesRequiredFields),
}
impl TryFrom<fnet_routes::RoutePropertiesV4> for RouteProperties {
type Error = FidlConversionError<RoutePropertiesRequiredFields>;
fn try_from(properties: fnet_routes::RoutePropertiesV4) -> Result<Self, Self::Error> {
Ok(RouteProperties {
specified_properties: properties
.specified_properties
.ok_or(FidlConversionError::RequiredFieldUnset(
RoutePropertiesRequiredFields::SpecifiedProperties,
))?
.try_into()
.map_err(|e: FidlConversionError<_>| {
e.map_unset_fields(RoutePropertiesRequiredFields::WithinSpecifiedProperties)
})?,
})
}
}
impl TryFrom<fnet_routes::RoutePropertiesV6> for RouteProperties {
type Error = FidlConversionError<RoutePropertiesRequiredFields>;
fn try_from(properties: fnet_routes::RoutePropertiesV6) -> Result<Self, Self::Error> {
Ok(RouteProperties {
specified_properties: properties
.specified_properties
.ok_or(FidlConversionError::RequiredFieldUnset(
RoutePropertiesRequiredFields::SpecifiedProperties,
))?
.try_into()
.map_err(|e: FidlConversionError<_>| {
e.map_unset_fields(RoutePropertiesRequiredFields::WithinSpecifiedProperties)
})?,
})
}
}
impl From<RouteProperties> for fnet_routes::RoutePropertiesV4 {
fn from(properties: RouteProperties) -> fnet_routes::RoutePropertiesV4 {
let RouteProperties { specified_properties } = properties;
fnet_routes::RoutePropertiesV4 {
specified_properties: Some(specified_properties.into()),
..Default::default()
}
}
}
impl From<RouteProperties> for fnet_routes::RoutePropertiesV6 {
fn from(properties: RouteProperties) -> fnet_routes::RoutePropertiesV6 {
let RouteProperties { specified_properties } = properties;
fnet_routes::RoutePropertiesV6 {
specified_properties: Some(specified_properties.into()),
..Default::default()
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct RouteTarget<I: Ip> {
pub outbound_interface: u64,
pub next_hop: Option<SpecifiedAddr<I::Addr>>,
}
impl TryFrom<fnet_routes::RouteTargetV4> for RouteTarget<Ipv4> {
type Error = FidlConversionError<NeverMissingFields>;
fn try_from(target: fnet_routes::RouteTargetV4) -> Result<Self, Self::Error> {
let fnet_routes::RouteTargetV4 { outbound_interface, next_hop } = target;
let next_hop: Option<SpecifiedAddr<net_types::ip::Ipv4Addr>> = next_hop
.map(|addr| {
SpecifiedAddr::new((*addr).into_ext())
.ok_or(FidlConversionError::UnspecifiedNextHop)
})
.transpose()?;
if let Some(next_hop) = next_hop {
if next_hop.is_limited_broadcast() {
return Err(FidlConversionError::NextHopNotUnicast);
}
}
Ok(RouteTarget { outbound_interface, next_hop })
}
}
impl TryFrom<fnet_routes::RouteTargetV6> for RouteTarget<Ipv6> {
type Error = FidlConversionError<NeverMissingFields>;
fn try_from(target: fnet_routes::RouteTargetV6) -> Result<Self, Self::Error> {
let fnet_routes::RouteTargetV6 { outbound_interface, next_hop } = target;
let addr: Option<SpecifiedAddr<Ipv6Addr>> = next_hop
.map(|addr| {
SpecifiedAddr::new((*addr).into_ext())
.ok_or(FidlConversionError::UnspecifiedNextHop)
})
.transpose()?;
if let Some(specified_addr) = addr {
if !specified_addr.is_unicast() {
return Err(FidlConversionError::NextHopNotUnicast);
}
}
Ok(RouteTarget { outbound_interface, next_hop: addr })
}
}
impl From<RouteTarget<Ipv4>> for fnet_routes::RouteTargetV4 {
fn from(target: RouteTarget<Ipv4>) -> fnet_routes::RouteTargetV4 {
let RouteTarget { outbound_interface, next_hop } = target;
fnet_routes::RouteTargetV4 {
outbound_interface: outbound_interface,
next_hop: next_hop.map(|addr| Box::new((*addr).into_ext())),
}
}
}
impl From<RouteTarget<Ipv6>> for fnet_routes::RouteTargetV6 {
fn from(target: RouteTarget<Ipv6>) -> fnet_routes::RouteTargetV6 {
let RouteTarget { outbound_interface, next_hop } = target;
fnet_routes::RouteTargetV6 {
outbound_interface: outbound_interface,
next_hop: next_hop.map(|addr| Box::new((*addr).into_ext())),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum RouteAction<I: Ip> {
Unknown,
Forward(RouteTarget<I>),
}
#[derive(Debug, Error, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum NeverMissingFields {}
impl TryFrom<fnet_routes::RouteActionV4> for RouteAction<Ipv4> {
type Error = FidlConversionError<NeverMissingFields>;
fn try_from(action: fnet_routes::RouteActionV4) -> Result<Self, Self::Error> {
match action {
fnet_routes::RouteActionV4::Forward(target) => {
Ok(RouteAction::Forward(target.try_into()?))
}
fnet_routes::RouteActionV4Unknown!() => Ok(RouteAction::Unknown),
}
}
}
impl TryFrom<fnet_routes::RouteActionV6> for RouteAction<Ipv6> {
type Error = FidlConversionError<NeverMissingFields>;
fn try_from(action: fnet_routes::RouteActionV6) -> Result<Self, Self::Error> {
match action {
fnet_routes::RouteActionV6::Forward(target) => {
Ok(RouteAction::Forward(target.try_into()?))
}
fnet_routes::RouteActionV4Unknown!() => Ok(RouteAction::Unknown),
}
}
}
const ROUTE_ACTION_V4_UNKNOWN_VARIANT_TAG: &str = "fuchsia.net.routes/RouteActionV4";
impl TryFrom<RouteAction<Ipv4>> for fnet_routes::RouteActionV4 {
type Error = NetTypeConversionError;
fn try_from(action: RouteAction<Ipv4>) -> Result<Self, Self::Error> {
match action {
RouteAction::Forward(target) => Ok(fnet_routes::RouteActionV4::Forward(target.into())),
RouteAction::Unknown => Err(NetTypeConversionError::UnknownUnionVariant(
ROUTE_ACTION_V4_UNKNOWN_VARIANT_TAG,
)),
}
}
}
const ROUTE_ACTION_V6_UNKNOWN_VARIANT_TAG: &str = "fuchsia.net.routes/RouteActionV6";
impl TryFrom<RouteAction<Ipv6>> for fnet_routes::RouteActionV6 {
type Error = NetTypeConversionError;
fn try_from(action: RouteAction<Ipv6>) -> Result<Self, Self::Error> {
match action {
RouteAction::Forward(target) => Ok(fnet_routes::RouteActionV6::Forward(target.into())),
RouteAction::Unknown => Err(NetTypeConversionError::UnknownUnionVariant(
ROUTE_ACTION_V6_UNKNOWN_VARIANT_TAG,
)),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Route<I: Ip> {
pub destination: Subnet<I::Addr>,
pub action: RouteAction<I>,
pub properties: RouteProperties,
}
impl<I: Ip> Route<I> {
pub fn new_forward(
destination: Subnet<I::Addr>,
outbound_interface: u64,
next_hop: Option<SpecifiedAddr<I::Addr>>,
metric: fnet_routes::SpecifiedMetric,
) -> Self {
Self {
destination,
action: RouteAction::Forward(RouteTarget { outbound_interface, next_hop }),
properties: RouteProperties {
specified_properties: SpecifiedRouteProperties { metric },
},
}
}
pub fn new_forward_with_inherited_metric(
destination: Subnet<I::Addr>,
outbound_interface: u64,
next_hop: Option<SpecifiedAddr<I::Addr>>,
) -> Self {
Self::new_forward(
destination,
outbound_interface,
next_hop,
fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
)
}
pub fn new_forward_with_explicit_metric(
destination: Subnet<I::Addr>,
outbound_interface: u64,
next_hop: Option<SpecifiedAddr<I::Addr>>,
metric: u32,
) -> Self {
Self::new_forward(
destination,
outbound_interface,
next_hop,
fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
)
}
}
impl TryFrom<fnet_routes::RouteV4> for Route<Ipv4> {
type Error = FidlConversionError<RoutePropertiesRequiredFields>;
fn try_from(route: fnet_routes::RouteV4) -> Result<Self, Self::Error> {
let fnet_routes::RouteV4 { destination, action, properties } = route;
Ok(Route {
destination: destination
.try_into_ext()
.map_err(FidlConversionError::DestinationSubnet)?,
action: action
.try_into()
.map_err(|e: FidlConversionError<_>| e.map_unset_fields(|never| match never {}))?,
properties: properties.try_into()?,
})
}
}
impl TryFrom<fnet_routes::RouteV6> for Route<Ipv6> {
type Error = FidlConversionError<RoutePropertiesRequiredFields>;
fn try_from(route: fnet_routes::RouteV6) -> Result<Self, Self::Error> {
let fnet_routes::RouteV6 { destination, action, properties } = route;
let destination =
destination.try_into_ext().map_err(FidlConversionError::DestinationSubnet)?;
Ok(Route {
destination,
action: action
.try_into()
.map_err(|e: FidlConversionError<_>| e.map_unset_fields(|never| match never {}))?,
properties: properties.try_into()?,
})
}
}
impl TryFrom<Route<Ipv4>> for fnet_routes::RouteV4 {
type Error = NetTypeConversionError;
fn try_from(route: Route<Ipv4>) -> Result<Self, Self::Error> {
let Route { destination, action, properties } = route;
Ok(fnet_routes::RouteV4 {
destination: fnet::Ipv4AddressWithPrefix {
addr: destination.network().into_ext(),
prefix_len: destination.prefix(),
},
action: action.try_into()?,
properties: properties.into(),
})
}
}
impl TryFrom<Route<Ipv6>> for fnet_routes::RouteV6 {
type Error = NetTypeConversionError;
fn try_from(route: Route<Ipv6>) -> Result<Self, Self::Error> {
let Route { destination, action, properties } = route;
Ok(fnet_routes::RouteV6 {
destination: fnet::Ipv6AddressWithPrefix {
addr: destination.network().into_ext(),
prefix_len: destination.prefix(),
},
action: action.try_into()?,
properties: properties.into(),
})
}
}
impl<I: Ip> TryFrom<Route<I>> for fnet_stack::ForwardingEntry {
type Error = NetTypeConversionError;
fn try_from(
Route {
destination,
action,
properties:
RouteProperties { specified_properties: SpecifiedRouteProperties { metric } },
}: Route<I>,
) -> Result<Self, Self::Error> {
let RouteTarget { outbound_interface, next_hop } = match action {
RouteAction::Unknown => {
return Err(NetTypeConversionError::UnknownUnionVariant(match I::VERSION {
net_types::ip::IpVersion::V4 => ROUTE_ACTION_V4_UNKNOWN_VARIANT_TAG,
net_types::ip::IpVersion::V6 => ROUTE_ACTION_V6_UNKNOWN_VARIANT_TAG,
}))
}
RouteAction::Forward(target) => target,
};
let next_hop = I::map_ip_in(
next_hop,
|next_hop| next_hop.map(|addr| fnet::IpAddress::Ipv4(addr.get().into_ext())),
|next_hop| next_hop.map(|addr| fnet::IpAddress::Ipv6(addr.get().into_ext())),
);
Ok(fnet_stack::ForwardingEntry {
subnet: destination.into_ext(),
device_id: outbound_interface,
next_hop: next_hop.map(Box::new),
metric: match metric {
fnet_routes::SpecifiedMetric::ExplicitMetric(metric) => metric,
fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty) => 0,
},
})
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct InstalledRoute<I: Ip> {
pub route: Route<I>,
pub effective_properties: EffectiveRouteProperties,
pub table_id: TableId,
}
impl<I: Ip> InstalledRoute<I> {
pub fn matches_route_and_table_id(&self, route: &Route<I>, table_id: TableId) -> bool {
&self.route == route && self.table_id == table_id
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct TableId(u32);
impl TableId {
pub const fn new(id: u32) -> Self {
Self(id)
}
pub const fn get(self) -> u32 {
let Self(id) = self;
id
}
}
impl Display for TableId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.get())
}
}
#[derive(Error, Clone, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum InstalledRouteRequiredFields {
#[error("fuchsia.net.routes/InstalledRouteV#.route")]
Route,
#[error("fuchsia.net.routes/InstalledRouteV#.effective_properties")]
EffectiveProperties,
#[error(transparent)]
WithinRoute(#[from] RoutePropertiesRequiredFields),
#[error(transparent)]
WithinEffectiveProperties(#[from] EffectiveRoutePropertiesRequiredFields),
#[error("fuchsia.net.routes/InstalledRouteV#.table_id")]
TableId,
}
impl TryFrom<fnet_routes::InstalledRouteV4> for InstalledRoute<Ipv4> {
type Error = FidlConversionError<InstalledRouteRequiredFields>;
fn try_from(installed_route: fnet_routes::InstalledRouteV4) -> Result<Self, Self::Error> {
Ok(InstalledRoute {
route: installed_route
.route
.ok_or(FidlConversionError::RequiredFieldUnset(
InstalledRouteRequiredFields::Route,
))?
.try_into()
.map_err(|e: FidlConversionError<_>| {
e.map_unset_fields(InstalledRouteRequiredFields::WithinRoute)
})?,
effective_properties: installed_route
.effective_properties
.ok_or(FidlConversionError::RequiredFieldUnset(
InstalledRouteRequiredFields::EffectiveProperties,
))?
.try_into()
.map_err(|e: FidlConversionError<_>| {
e.map_unset_fields(InstalledRouteRequiredFields::WithinEffectiveProperties)
})?,
table_id: TableId(installed_route.table_id.ok_or(
FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::TableId),
)?),
})
}
}
impl TryFrom<fnet_routes::InstalledRouteV6> for InstalledRoute<Ipv6> {
type Error = FidlConversionError<InstalledRouteRequiredFields>;
fn try_from(installed_route: fnet_routes::InstalledRouteV6) -> Result<Self, Self::Error> {
Ok(InstalledRoute {
route: installed_route
.route
.ok_or(FidlConversionError::RequiredFieldUnset(
InstalledRouteRequiredFields::Route,
))?
.try_into()
.map_err(|e: FidlConversionError<_>| {
e.map_unset_fields(InstalledRouteRequiredFields::WithinRoute)
})?,
effective_properties: installed_route
.effective_properties
.ok_or(FidlConversionError::RequiredFieldUnset(
InstalledRouteRequiredFields::EffectiveProperties,
))?
.try_into()
.map_err(|e: FidlConversionError<_>| {
e.map_unset_fields(InstalledRouteRequiredFields::WithinEffectiveProperties)
})?,
table_id: TableId(installed_route.table_id.ok_or(
FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::TableId),
)?),
})
}
}
impl TryFrom<InstalledRoute<Ipv4>> for fnet_routes::InstalledRouteV4 {
type Error = NetTypeConversionError;
fn try_from(installed_route: InstalledRoute<Ipv4>) -> Result<Self, Self::Error> {
let InstalledRoute { route, effective_properties, table_id } = installed_route;
Ok(fnet_routes::InstalledRouteV4 {
route: Some(route.try_into()?),
effective_properties: Some(effective_properties.into()),
table_id: Some(table_id.get()),
..Default::default()
})
}
}
impl TryFrom<InstalledRoute<Ipv6>> for fnet_routes::InstalledRouteV6 {
type Error = NetTypeConversionError;
fn try_from(installed_route: InstalledRoute<Ipv6>) -> Result<Self, Self::Error> {
let InstalledRoute { route, effective_properties, table_id } = installed_route;
Ok(fnet_routes::InstalledRouteV6 {
route: Some(route.try_into()?),
effective_properties: Some(effective_properties.into()),
table_id: Some(table_id.get()),
..Default::default()
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Event<I: Ip> {
Unknown,
Existing(InstalledRoute<I>),
Idle,
Added(InstalledRoute<I>),
Removed(InstalledRoute<I>),
}
impl TryFrom<fnet_routes::EventV4> for Event<Ipv4> {
type Error = FidlConversionError<InstalledRouteRequiredFields>;
fn try_from(event: fnet_routes::EventV4) -> Result<Self, Self::Error> {
match event {
fnet_routes::EventV4::Existing(route) => Ok(Event::Existing(route.try_into()?)),
fnet_routes::EventV4::Idle(fnet_routes::Empty) => Ok(Event::Idle),
fnet_routes::EventV4::Added(route) => Ok(Event::Added(route.try_into()?)),
fnet_routes::EventV4::Removed(route) => Ok(Event::Removed(route.try_into()?)),
fnet_routes::EventV4Unknown!() => Ok(Event::Unknown),
}
}
}
impl TryFrom<fnet_routes::EventV6> for Event<Ipv6> {
type Error = FidlConversionError<InstalledRouteRequiredFields>;
fn try_from(event: fnet_routes::EventV6) -> Result<Self, Self::Error> {
match event {
fnet_routes::EventV6::Existing(route) => Ok(Event::Existing(route.try_into()?)),
fnet_routes::EventV6::Idle(fnet_routes::Empty) => Ok(Event::Idle),
fnet_routes::EventV6::Added(route) => Ok(Event::Added(route.try_into()?)),
fnet_routes::EventV6::Removed(route) => Ok(Event::Removed(route.try_into()?)),
fnet_routes::EventV6Unknown!() => Ok(Event::Unknown),
}
}
}
impl TryFrom<Event<Ipv4>> for fnet_routes::EventV4 {
type Error = NetTypeConversionError;
fn try_from(event: Event<Ipv4>) -> Result<Self, Self::Error> {
match event {
Event::Existing(route) => Ok(fnet_routes::EventV4::Existing(route.try_into()?)),
Event::Idle => Ok(fnet_routes::EventV4::Idle(fnet_routes::Empty)),
Event::Added(route) => Ok(fnet_routes::EventV4::Added(route.try_into()?)),
Event::Removed(route) => Ok(fnet_routes::EventV4::Removed(route.try_into()?)),
Event::Unknown => {
Err(NetTypeConversionError::UnknownUnionVariant("fuchsia_net_routes.EventV4"))
}
}
}
}
impl TryFrom<Event<Ipv6>> for fnet_routes::EventV6 {
type Error = NetTypeConversionError;
fn try_from(event: Event<Ipv6>) -> Result<Self, Self::Error> {
match event {
Event::Existing(route) => Ok(fnet_routes::EventV6::Existing(route.try_into()?)),
Event::Idle => Ok(fnet_routes::EventV6::Idle(fnet_routes::Empty)),
Event::Added(route) => Ok(fnet_routes::EventV6::Added(route.try_into()?)),
Event::Removed(route) => Ok(fnet_routes::EventV6::Removed(route.try_into()?)),
Event::Unknown => {
Err(NetTypeConversionError::UnknownUnionVariant("fuchsia_net_routes.EventV6"))
}
}
}
}
#[derive(Clone, Debug, Error)]
pub enum WatcherCreationError {
#[error("failed to create route watcher proxy: {0}")]
CreateProxy(fidl::Error),
#[error("failed to get route watcher: {0}")]
GetWatcher(fidl::Error),
}
#[derive(Clone, Debug, Error)]
pub enum WatchError {
#[error("the call to `Watch()` failed: {0}")]
Fidl(fidl::Error),
#[error("failed to convert event returned by `Watch()`: {0}")]
Conversion(FidlConversionError<InstalledRouteRequiredFields>),
#[error("the call to `Watch()` returned an empty batch of events")]
EmptyEventBatch,
}
pub trait FidlRouteIpExt: Ip {
type StateMarker: fidl::endpoints::DiscoverableProtocolMarker;
type WatcherMarker: fidl::endpoints::ProtocolMarker;
type WatchEvent: TryInto<Event<Self>, Error = FidlConversionError<InstalledRouteRequiredFields>>
+ TryFrom<Event<Self>, Error = NetTypeConversionError>
+ Clone
+ std::fmt::Debug
+ PartialEq
+ Unpin
+ Send;
type Route: TryFrom<Route<Self>, Error = NetTypeConversionError>
+ TryInto<Route<Self>, Error = FidlConversionError<RoutePropertiesRequiredFields>>
+ std::fmt::Debug;
}
impl FidlRouteIpExt for Ipv4 {
type StateMarker = fnet_routes::StateV4Marker;
type WatcherMarker = fnet_routes::WatcherV4Marker;
type WatchEvent = fnet_routes::EventV4;
type Route = fnet_routes::RouteV4;
}
impl FidlRouteIpExt for Ipv6 {
type StateMarker = fnet_routes::StateV6Marker;
type WatcherMarker = fnet_routes::WatcherV6Marker;
type WatchEvent = fnet_routes::EventV6;
type Route = fnet_routes::RouteV6;
}
pub trait Responder: fidl::endpoints::Responder + Debug + Send {
type Payload;
fn send(self, result: Self::Payload) -> Result<(), fidl::Error>;
}
pub trait SliceResponder<Payload>: fidl::endpoints::Responder + Debug + Send {
fn send(self, payload: &[Payload]) -> Result<(), fidl::Error>;
}
macro_rules! impl_responder {
($resp:ty, &[$payload:ty] $(,)?) => {
impl $crate::SliceResponder<$payload> for $resp {
fn send(self, result: &[$payload]) -> Result<(), fidl::Error> {
<$resp>::send(self, result)
}
}
};
($resp:ty, $payload:ty $(,)?) => {
impl $crate::Responder for $resp {
type Payload = $payload;
fn send(self, result: Self::Payload) -> Result<(), fidl::Error> {
<$resp>::send(self, result)
}
}
};
}
pub(crate) use impl_responder;
#[derive(Default, Clone)]
pub struct WatcherOptions {
pub table_interest: Option<fnet_routes::TableInterest>,
}
impl From<WatcherOptions> for fnet_routes::WatcherOptionsV4 {
fn from(WatcherOptions { table_interest }: WatcherOptions) -> Self {
Self { table_interest, __source_breaking: fidl::marker::SourceBreaking }
}
}
impl From<WatcherOptions> for fnet_routes::WatcherOptionsV6 {
fn from(WatcherOptions { table_interest }: WatcherOptions) -> Self {
Self { table_interest, __source_breaking: fidl::marker::SourceBreaking }
}
}
impl From<fnet_routes::WatcherOptionsV4> for WatcherOptions {
fn from(
fnet_routes::WatcherOptionsV4 { table_interest, __source_breaking: _ }: fnet_routes::WatcherOptionsV4,
) -> Self {
Self { table_interest }
}
}
impl From<fnet_routes::WatcherOptionsV6> for WatcherOptions {
fn from(
fnet_routes::WatcherOptionsV6 { table_interest, __source_breaking: _ }: fnet_routes::WatcherOptionsV6,
) -> Self {
Self { table_interest }
}
}
pub fn get_watcher<I: FidlRouteIpExt>(
state_proxy: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
options: WatcherOptions,
) -> Result<<I::WatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy, WatcherCreationError> {
let (watcher_proxy, watcher_server_end) = fidl::endpoints::create_proxy::<I::WatcherMarker>();
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct GetWatcherInputs<'a, I: FidlRouteIpExt> {
watcher_server_end: fidl::endpoints::ServerEnd<I::WatcherMarker>,
state_proxy: &'a <I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
options: WatcherOptions,
}
let result = I::map_ip_in(
GetWatcherInputs::<'_, I> { watcher_server_end, state_proxy, options },
|GetWatcherInputs { watcher_server_end, state_proxy, options }| {
state_proxy.get_watcher_v4(watcher_server_end, &options.into())
},
|GetWatcherInputs { watcher_server_end, state_proxy, options }| {
state_proxy.get_watcher_v6(watcher_server_end, &options.into())
},
);
result.map_err(WatcherCreationError::GetWatcher)?;
Ok(watcher_proxy)
}
pub fn watch<'a, I: FidlRouteIpExt>(
watcher_proxy: &'a <I::WatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
) -> impl Future<Output = Result<Vec<I::WatchEvent>, fidl::Error>> {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct WatchInputs<'a, I: FidlRouteIpExt> {
watcher_proxy: &'a <I::WatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
}
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct WatchOutputs<I: FidlRouteIpExt> {
watch_fut: fidl::client::QueryResponseFut<Vec<I::WatchEvent>>,
}
let WatchOutputs { watch_fut } = I::map_ip::<WatchInputs<'_, I>, WatchOutputs<I>>(
WatchInputs { watcher_proxy },
|WatchInputs { watcher_proxy }| WatchOutputs { watch_fut: watcher_proxy.watch() },
|WatchInputs { watcher_proxy }| WatchOutputs { watch_fut: watcher_proxy.watch() },
);
watch_fut
}
pub fn event_stream_from_state<I: FidlRouteIpExt>(
routes_state: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
) -> Result<impl Stream<Item = Result<Event<I>, WatchError>>, WatcherCreationError> {
event_stream_from_state_with_options(routes_state, Default::default())
}
pub fn event_stream_from_state_with_options<I: FidlRouteIpExt>(
routes_state: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
options: WatcherOptions,
) -> Result<impl Stream<Item = Result<Event<I>, WatchError>>, WatcherCreationError> {
let watcher = get_watcher::<I>(routes_state, options)?;
event_stream_from_watcher(watcher)
}
pub fn event_stream_from_watcher<I: FidlRouteIpExt>(
watcher: <I::WatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
) -> Result<impl Stream<Item = Result<Event<I>, WatchError>>, WatcherCreationError> {
Ok(stream::ShortCircuit::new(
futures::stream::try_unfold(watcher, |watcher| async {
let events_batch = watch::<I>(&watcher).await.map_err(WatchError::Fidl)?;
if events_batch.is_empty() {
return Err(WatchError::EmptyEventBatch);
}
let events_batch = events_batch
.into_iter()
.map(|event| event.try_into().map_err(WatchError::Conversion));
let event_stream = futures::stream::iter(events_batch);
Ok(Some((event_stream, watcher)))
})
.try_flatten(),
))
}
#[derive(Clone, Debug, Error)]
pub enum CollectRoutesUntilIdleError<I: FidlRouteIpExt> {
#[error("there was an error in the event stream: {0}")]
ErrorInStream(WatchError),
#[error("there was an unexpected event in the event stream: {0:?}")]
UnexpectedEvent(Event<I>),
#[error("the event stream unexpectedly ended")]
StreamEnded,
}
pub async fn collect_routes_until_idle<
I: FidlRouteIpExt,
C: Extend<InstalledRoute<I>> + Default,
>(
event_stream: impl futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
) -> Result<C, CollectRoutesUntilIdleError<I>> {
fold::fold_while(
event_stream,
Ok(C::default()),
|existing_routes: Result<C, CollectRoutesUntilIdleError<I>>, event| {
futures::future::ready(match existing_routes {
Err(_) => {
unreachable!("`existing_routes` must be `Ok`, because we stop folding on err")
}
Ok(mut existing_routes) => match event {
Err(e) => {
fold::FoldWhile::Done(Err(CollectRoutesUntilIdleError::ErrorInStream(e)))
}
Ok(e) => match e {
Event::Existing(e) => {
existing_routes.extend([e]);
fold::FoldWhile::Continue(Ok(existing_routes))
}
Event::Idle => fold::FoldWhile::Done(Ok(existing_routes)),
e @ Event::Unknown | e @ Event::Added(_) | e @ Event::Removed(_) => {
fold::FoldWhile::Done(Err(
CollectRoutesUntilIdleError::UnexpectedEvent(e),
))
}
},
},
})
},
)
.await
.short_circuited()
.map_err(|_accumulated_thus_far: Result<C, CollectRoutesUntilIdleError<I>>| {
CollectRoutesUntilIdleError::StreamEnded
})?
}
#[derive(Clone, Debug, Error)]
pub enum WaitForRoutesError<I: FidlRouteIpExt> {
#[error("there was an error in the event stream: {0}")]
ErrorInStream(WatchError),
#[error("observed an added event for an already existing route: {0:?}")]
AddedAlreadyExisting(InstalledRoute<I>),
#[error("observed a removed event for a non-existent route: {0:?}")]
RemovedNonExistent(InstalledRoute<I>),
#[error("observed an unknown event")]
UnknownEvent,
#[error("the event stream unexpectedly ended")]
StreamEnded,
}
pub async fn wait_for_routes_map<
I: FidlRouteIpExt,
S: futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
T,
F: Fn(&HashSet<InstalledRoute<I>>) -> Option<T>,
>(
event_stream: S,
initial_state: &mut HashSet<InstalledRoute<I>>,
predicate: F,
) -> Result<T, WaitForRoutesError<I>> {
fold::try_fold_while(
event_stream.map_err(WaitForRoutesError::ErrorInStream),
initial_state,
|accumulated_routes, event| {
futures::future::ready({
match event {
Event::Existing(route) | Event::Added(route) => accumulated_routes
.insert(route)
.then_some(())
.ok_or(WaitForRoutesError::AddedAlreadyExisting(route)),
Event::Removed(route) => accumulated_routes
.remove(&route)
.then_some(())
.ok_or(WaitForRoutesError::RemovedNonExistent(route)),
Event::Idle => Ok(()),
Event::Unknown => Err(WaitForRoutesError::UnknownEvent),
}
.map(|()| match predicate(&accumulated_routes) {
Some(t) => fold::FoldWhile::Done(t),
None => fold::FoldWhile::Continue(accumulated_routes),
})
})
},
)
.await?
.short_circuited()
.map_err(|_accumulated_thus_far: &mut HashSet<InstalledRoute<I>>| {
WaitForRoutesError::StreamEnded
})
}
pub async fn wait_for_routes<
I: FidlRouteIpExt,
S: futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
F: Fn(&HashSet<InstalledRoute<I>>) -> bool,
>(
event_stream: S,
initial_state: &mut HashSet<InstalledRoute<I>>,
predicate: F,
) -> Result<(), WaitForRoutesError<I>> {
wait_for_routes_map::<I, S, (), _>(event_stream, initial_state, |routes| {
predicate(routes).then_some(())
})
.await
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testutil::internal as internal_testutil;
use assert_matches::assert_matches;
use futures::{FutureExt as _, StreamExt as _};
use ip_test_macro::ip_test;
use net_declare::{
fidl_ip_v4, fidl_ip_v4_with_prefix, fidl_ip_v6, fidl_ip_v6_with_prefix, net_ip_v4,
net_ip_v6, net_subnet_v4, net_subnet_v6,
};
use test_case::test_case;
use {fidl_fuchsia_net as _, zx_status};
const ARBITRARY_TABLE_ID: TableId = TableId::new(0);
trait ArbitraryTestValue {
fn arbitrary_test_value() -> Self;
}
impl ArbitraryTestValue for fnet_routes::SpecifiedRouteProperties {
fn arbitrary_test_value() -> Self {
fnet_routes::SpecifiedRouteProperties {
metric: Some(fnet_routes::SpecifiedMetric::ExplicitMetric(0)),
..Default::default()
}
}
}
impl ArbitraryTestValue for fnet_routes::EffectiveRouteProperties {
fn arbitrary_test_value() -> Self {
fnet_routes::EffectiveRouteProperties { metric: Some(0), ..Default::default() }
}
}
impl ArbitraryTestValue for fnet_routes::RoutePropertiesV4 {
fn arbitrary_test_value() -> Self {
fnet_routes::RoutePropertiesV4 {
specified_properties: Some(
fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
),
..Default::default()
}
}
}
impl ArbitraryTestValue for fnet_routes::RoutePropertiesV6 {
fn arbitrary_test_value() -> Self {
fnet_routes::RoutePropertiesV6 {
specified_properties: Some(
fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
),
..Default::default()
}
}
}
impl ArbitraryTestValue for fnet_routes::RouteTargetV4 {
fn arbitrary_test_value() -> Self {
fnet_routes::RouteTargetV4 { outbound_interface: 1, next_hop: None }
}
}
impl ArbitraryTestValue for fnet_routes::RouteTargetV6 {
fn arbitrary_test_value() -> Self {
fnet_routes::RouteTargetV6 { outbound_interface: 1, next_hop: None }
}
}
impl ArbitraryTestValue for fnet_routes::RouteActionV4 {
fn arbitrary_test_value() -> Self {
fnet_routes::RouteActionV4::Forward(fnet_routes::RouteTargetV4::arbitrary_test_value())
}
}
impl ArbitraryTestValue for fnet_routes::RouteActionV6 {
fn arbitrary_test_value() -> Self {
fnet_routes::RouteActionV6::Forward(fnet_routes::RouteTargetV6::arbitrary_test_value())
}
}
impl ArbitraryTestValue for fnet_routes::RouteV4 {
fn arbitrary_test_value() -> Self {
fnet_routes::RouteV4 {
destination: fidl_ip_v4_with_prefix!("192.168.0.0/24"),
action: fnet_routes::RouteActionV4::arbitrary_test_value(),
properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
}
}
}
impl ArbitraryTestValue for fnet_routes::RouteV6 {
fn arbitrary_test_value() -> Self {
fnet_routes::RouteV6 {
destination: fidl_ip_v6_with_prefix!("fe80::0/64"),
action: fnet_routes::RouteActionV6::arbitrary_test_value(),
properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
}
}
}
impl ArbitraryTestValue for fnet_routes::InstalledRouteV4 {
fn arbitrary_test_value() -> Self {
fnet_routes::InstalledRouteV4 {
route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
effective_properties: Some(
fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
),
table_id: Some(ARBITRARY_TABLE_ID.get()),
..Default::default()
}
}
}
impl ArbitraryTestValue for fnet_routes::InstalledRouteV6 {
fn arbitrary_test_value() -> Self {
fnet_routes::InstalledRouteV6 {
route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
effective_properties: Some(
fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
),
table_id: Some(ARBITRARY_TABLE_ID.get()),
..Default::default()
}
}
}
#[test]
fn specified_route_properties_try_from_unset_metric() {
assert_eq!(
SpecifiedRouteProperties::try_from(fnet_routes::SpecifiedRouteProperties::default()),
Err(FidlConversionError::RequiredFieldUnset(
SpecifiedRoutePropertiesRequiredFields::Metric
))
)
}
#[test]
fn specified_route_properties_try_from() {
let fidl_type = fnet_routes::SpecifiedRouteProperties {
metric: Some(fnet_routes::SpecifiedMetric::ExplicitMetric(1)),
..Default::default()
};
let local_type =
SpecifiedRouteProperties { metric: fnet_routes::SpecifiedMetric::ExplicitMetric(1) };
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(
<SpecifiedRouteProperties as std::convert::Into<
fnet_routes::SpecifiedRouteProperties,
>>::into(local_type),
fidl_type.clone()
);
}
#[test]
fn effective_route_properties_try_from_unset_metric() {
assert_eq!(
EffectiveRouteProperties::try_from(fnet_routes::EffectiveRouteProperties::default()),
Err(FidlConversionError::RequiredFieldUnset(
EffectiveRoutePropertiesRequiredFields::Metric
))
)
}
#[test]
fn effective_route_properties_try_from() {
let fidl_type =
fnet_routes::EffectiveRouteProperties { metric: Some(1), ..Default::default() };
let local_type = EffectiveRouteProperties { metric: 1 };
assert_eq!(fidl_type.clone().try_into(), Ok(EffectiveRouteProperties { metric: 1 }));
assert_eq!(
<EffectiveRouteProperties as std::convert::Into<
fnet_routes::EffectiveRouteProperties,
>>::into(local_type),
fidl_type.clone()
);
}
#[test]
fn route_properties_try_from_unset_specified_properties_v4() {
assert_eq!(
RouteProperties::try_from(fnet_routes::RoutePropertiesV4::default()),
Err(FidlConversionError::RequiredFieldUnset(
RoutePropertiesRequiredFields::SpecifiedProperties
))
)
}
#[test]
fn route_properties_try_from_unset_specified_properties_v6() {
assert_eq!(
RouteProperties::try_from(fnet_routes::RoutePropertiesV6::default()),
Err(FidlConversionError::RequiredFieldUnset(
RoutePropertiesRequiredFields::SpecifiedProperties
))
)
}
#[test]
fn route_properties_try_from_v4() {
let fidl_type = fnet_routes::RoutePropertiesV4 {
specified_properties: Some(
fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
),
..Default::default()
};
let local_type = RouteProperties {
specified_properties: fnet_routes::SpecifiedRouteProperties::arbitrary_test_value()
.try_into()
.unwrap(),
};
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(
<RouteProperties as std::convert::Into<fnet_routes::RoutePropertiesV4>>::into(
local_type
),
fidl_type.clone()
);
}
#[test]
fn route_properties_try_from_v6() {
let fidl_type = fnet_routes::RoutePropertiesV6 {
specified_properties: Some(
fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
),
..Default::default()
};
let local_type = RouteProperties {
specified_properties: fnet_routes::SpecifiedRouteProperties::arbitrary_test_value()
.try_into()
.unwrap(),
};
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(
<RouteProperties as std::convert::Into<fnet_routes::RoutePropertiesV6>>::into(
local_type
),
fidl_type.clone()
);
}
#[test]
fn route_target_try_from_unspecified_next_hop_v4() {
assert_eq!(
RouteTarget::try_from(fnet_routes::RouteTargetV4 {
outbound_interface: 1,
next_hop: Some(Box::new(fidl_ip_v4!("0.0.0.0"))),
}),
Err(FidlConversionError::UnspecifiedNextHop)
)
}
#[test]
fn route_target_try_from_unspecified_next_hop_v6() {
assert_eq!(
RouteTarget::try_from(fnet_routes::RouteTargetV6 {
outbound_interface: 1,
next_hop: Some(Box::new(fidl_ip_v6!("::"))),
}),
Err(FidlConversionError::UnspecifiedNextHop)
);
}
#[test]
fn route_target_try_from_limited_broadcast_next_hop_v4() {
assert_eq!(
RouteTarget::try_from(fnet_routes::RouteTargetV4 {
outbound_interface: 1,
next_hop: Some(Box::new(fidl_ip_v4!("255.255.255.255"))),
}),
Err(FidlConversionError::NextHopNotUnicast)
)
}
#[test]
fn route_target_try_from_multicast_next_hop_v6() {
assert_eq!(
RouteTarget::try_from(fnet_routes::RouteTargetV6 {
outbound_interface: 1,
next_hop: Some(Box::new(fidl_ip_v6!("ff00::1"))),
}),
Err(FidlConversionError::NextHopNotUnicast)
)
}
#[test]
fn route_target_try_from_v4() {
let fidl_type = fnet_routes::RouteTargetV4 {
outbound_interface: 1,
next_hop: Some(Box::new(fidl_ip_v4!("192.168.0.1"))),
};
let local_type = RouteTarget {
outbound_interface: 1,
next_hop: Some(SpecifiedAddr::new(net_ip_v4!("192.168.0.1")).unwrap()),
};
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(
<RouteTarget<Ipv4> as std::convert::Into<fnet_routes::RouteTargetV4>>::into(local_type),
fidl_type
);
}
#[test]
fn route_target_try_from_v6() {
let fidl_type = fnet_routes::RouteTargetV6 {
outbound_interface: 1,
next_hop: Some(Box::new(fidl_ip_v6!("fe80::1"))),
};
let local_type = RouteTarget {
outbound_interface: 1,
next_hop: Some(SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap()),
};
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(
<RouteTarget<Ipv6> as std::convert::Into<fnet_routes::RouteTargetV6>>::into(local_type),
fidl_type
);
}
#[test]
fn route_action_try_from_forward_v4() {
let fidl_type =
fnet_routes::RouteActionV4::Forward(fnet_routes::RouteTargetV4::arbitrary_test_value());
let local_type = RouteAction::Forward(
fnet_routes::RouteTargetV4::arbitrary_test_value().try_into().unwrap(),
);
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
}
#[test]
fn route_action_try_from_forward_v6() {
let fidl_type =
fnet_routes::RouteActionV6::Forward(fnet_routes::RouteTargetV6::arbitrary_test_value());
let local_type = RouteAction::Forward(
fnet_routes::RouteTargetV6::arbitrary_test_value().try_into().unwrap(),
);
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
}
#[test]
fn route_action_try_from_unknown_v4() {
let fidl_type = fnet_routes::RouteActionV4::unknown_variant_for_testing();
const LOCAL_TYPE: RouteAction<Ipv4> = RouteAction::Unknown;
assert_eq!(fidl_type.try_into(), Ok(LOCAL_TYPE));
assert_eq!(
LOCAL_TYPE.try_into(),
Err::<fnet_routes::RouteActionV4, _>(NetTypeConversionError::UnknownUnionVariant(
"fuchsia.net.routes/RouteActionV4"
))
);
}
#[test]
fn route_action_try_from_unknown_v6() {
let fidl_type = fnet_routes::RouteActionV6::unknown_variant_for_testing();
const LOCAL_TYPE: RouteAction<Ipv6> = RouteAction::Unknown;
assert_eq!(fidl_type.try_into(), Ok(LOCAL_TYPE));
assert_eq!(
LOCAL_TYPE.try_into(),
Err::<fnet_routes::RouteActionV6, _>(NetTypeConversionError::UnknownUnionVariant(
"fuchsia.net.routes/RouteActionV6"
))
);
}
#[test]
fn route_try_from_invalid_destination_v4() {
assert_matches!(
Route::try_from(fnet_routes::RouteV4 {
destination: fidl_ip_v4_with_prefix!("192.168.0.1/24"),
action: fnet_routes::RouteActionV4::arbitrary_test_value(),
properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
}),
Err(FidlConversionError::DestinationSubnet(_))
);
}
#[test]
fn route_try_from_invalid_destination_v6() {
assert_matches!(
Route::try_from(fnet_routes::RouteV6 {
destination: fidl_ip_v6_with_prefix!("fe80::1/64"),
action: fnet_routes::RouteActionV6::arbitrary_test_value(),
properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
}),
Err(FidlConversionError::DestinationSubnet(_))
);
}
#[test]
fn route_try_from_v4() {
let fidl_type = fnet_routes::RouteV4 {
destination: fidl_ip_v4_with_prefix!("192.168.0.0/24"),
action: fnet_routes::RouteActionV4::arbitrary_test_value(),
properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
};
let local_type = Route {
destination: net_subnet_v4!("192.168.0.0/24"),
action: fnet_routes::RouteActionV4::arbitrary_test_value().try_into().unwrap(),
properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value().try_into().unwrap(),
};
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
}
#[test]
fn route_try_from_v6() {
let fidl_type = fnet_routes::RouteV6 {
destination: fidl_ip_v6_with_prefix!("fe80::0/64"),
action: fnet_routes::RouteActionV6::arbitrary_test_value(),
properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
};
let local_type = Route {
destination: net_subnet_v6!("fe80::0/64"),
action: fnet_routes::RouteActionV6::arbitrary_test_value().try_into().unwrap(),
properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value().try_into().unwrap(),
};
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
}
#[test]
fn installed_route_try_from_unset_route_v4() {
assert_eq!(
InstalledRoute::try_from(fnet_routes::InstalledRouteV4 {
route: None,
effective_properties: Some(
fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
),
table_id: Some(ARBITRARY_TABLE_ID.get()),
..Default::default()
}),
Err(FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::Route))
)
}
#[test]
fn installed_route_try_from_unset_route_v6() {
assert_eq!(
InstalledRoute::try_from(fnet_routes::InstalledRouteV6 {
route: None,
effective_properties: Some(
fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
),
table_id: Some(ARBITRARY_TABLE_ID.get()),
..Default::default()
}),
Err(FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::Route))
)
}
#[test]
fn installed_route_try_from_unset_effective_properties_v4() {
assert_eq!(
InstalledRoute::try_from(fnet_routes::InstalledRouteV4 {
route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
effective_properties: None,
table_id: Some(ARBITRARY_TABLE_ID.get()),
..Default::default()
}),
Err(FidlConversionError::RequiredFieldUnset(
InstalledRouteRequiredFields::EffectiveProperties
))
)
}
#[test]
fn installed_route_try_from_unset_effective_properties_v6() {
assert_eq!(
InstalledRoute::try_from(fnet_routes::InstalledRouteV6 {
route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
effective_properties: None,
table_id: Some(ARBITRARY_TABLE_ID.get()),
..Default::default()
}),
Err(FidlConversionError::RequiredFieldUnset(
InstalledRouteRequiredFields::EffectiveProperties
))
)
}
#[test]
fn installed_route_try_from_v4() {
let fidl_type = fnet_routes::InstalledRouteV4 {
route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
effective_properties: Some(
fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
),
table_id: Some(ARBITRARY_TABLE_ID.get()),
..Default::default()
};
let local_type = InstalledRoute {
route: fnet_routes::RouteV4::arbitrary_test_value().try_into().unwrap(),
effective_properties: fnet_routes::EffectiveRouteProperties::arbitrary_test_value()
.try_into()
.unwrap(),
table_id: ARBITRARY_TABLE_ID,
};
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
}
#[test]
fn installed_route_try_from_v6() {
let fidl_type = fnet_routes::InstalledRouteV6 {
route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
effective_properties: Some(
fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
),
table_id: Some(ARBITRARY_TABLE_ID.get()),
..Default::default()
};
let local_type = InstalledRoute {
route: fnet_routes::RouteV6::arbitrary_test_value().try_into().unwrap(),
effective_properties: fnet_routes::EffectiveRouteProperties::arbitrary_test_value()
.try_into()
.unwrap(),
table_id: ARBITRARY_TABLE_ID,
};
assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
}
#[test]
fn event_try_from_v4() {
let fidl_route = fnet_routes::InstalledRouteV4::arbitrary_test_value();
let local_route = fidl_route.clone().try_into().unwrap();
assert_eq!(
fnet_routes::EventV4::unknown_variant_for_testing().try_into(),
Ok(Event::Unknown)
);
assert_eq!(
Event::<Ipv4>::Unknown.try_into(),
Err::<fnet_routes::EventV4, _>(NetTypeConversionError::UnknownUnionVariant(
"fuchsia_net_routes.EventV4"
))
);
assert_eq!(
fnet_routes::EventV4::Existing(fidl_route.clone()).try_into(),
Ok(Event::Existing(local_route))
);
assert_eq!(
Event::Existing(local_route).try_into(),
Ok(fnet_routes::EventV4::Existing(fidl_route.clone()))
);
assert_eq!(fnet_routes::EventV4::Idle(fnet_routes::Empty).try_into(), Ok(Event::Idle));
assert_eq!(Event::Idle.try_into(), Ok(fnet_routes::EventV4::Idle(fnet_routes::Empty)));
assert_eq!(
fnet_routes::EventV4::Added(fidl_route.clone()).try_into(),
Ok(Event::Added(local_route))
);
assert_eq!(
Event::Added(local_route).try_into(),
Ok(fnet_routes::EventV4::Added(fidl_route.clone()))
);
assert_eq!(
fnet_routes::EventV4::Removed(fidl_route.clone()).try_into(),
Ok(Event::Removed(local_route))
);
assert_eq!(
Event::Removed(local_route).try_into(),
Ok(fnet_routes::EventV4::Removed(fidl_route.clone()))
);
}
#[test]
fn event_try_from_v6() {
let fidl_route = fnet_routes::InstalledRouteV6::arbitrary_test_value();
let local_route = fidl_route.clone().try_into().unwrap();
assert_eq!(
fnet_routes::EventV6::unknown_variant_for_testing().try_into(),
Ok(Event::Unknown)
);
assert_eq!(
Event::<Ipv6>::Unknown.try_into(),
Err::<fnet_routes::EventV6, _>(NetTypeConversionError::UnknownUnionVariant(
"fuchsia_net_routes.EventV6"
))
);
assert_eq!(
fnet_routes::EventV6::Existing(fidl_route.clone()).try_into(),
Ok(Event::Existing(local_route))
);
assert_eq!(
Event::Existing(local_route).try_into(),
Ok(fnet_routes::EventV6::Existing(fidl_route.clone()))
);
assert_eq!(fnet_routes::EventV6::Idle(fnet_routes::Empty).try_into(), Ok(Event::Idle));
assert_eq!(Event::Idle.try_into(), Ok(fnet_routes::EventV6::Idle(fnet_routes::Empty)));
assert_eq!(
fnet_routes::EventV6::Added(fidl_route.clone()).try_into(),
Ok(Event::Added(local_route))
);
assert_eq!(
Event::Added(local_route).try_into(),
Ok(fnet_routes::EventV6::Added(fidl_route.clone()))
);
assert_eq!(
fnet_routes::EventV6::Removed(fidl_route.clone()).try_into(),
Ok(Event::Removed(local_route))
);
assert_eq!(
Event::Removed(local_route).try_into(),
Ok(fnet_routes::EventV6::Removed(fidl_route.clone()))
);
}
#[ip_test(I)]
#[test_case(Vec::new(); "no events")]
#[test_case(vec![0..1]; "single_batch_single_event")]
#[test_case(vec![0..10]; "single_batch_many_events")]
#[test_case(vec![0..10, 10..20, 20..30]; "many_batches_many_events")]
#[fuchsia_async::run_singlethreaded(test)]
async fn event_stream_from_state_against_shape<I: FidlRouteIpExt>(
test_shape: Vec<std::ops::Range<u32>>,
) {
let (batches_sender, batches_receiver) =
futures::channel::mpsc::unbounded::<Vec<I::WatchEvent>>();
for batch_shape in &test_shape {
batches_sender
.unbounded_send(internal_testutil::generate_events_in_range::<I>(
batch_shape.clone(),
))
.expect("failed to send event batch");
}
let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
let (mut state_request_stream, _control_handle) =
state_server_end.into_stream_and_control_handle();
let watcher_fut = state_request_stream
.next()
.then(|req| {
testutil::serve_state_request::<I>(
req.expect("State request_stream unexpectedly ended"),
batches_receiver,
)
})
.fuse();
let event_stream =
event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
futures::pin_mut!(watcher_fut, event_stream);
for batch_shape in test_shape {
for event_idx in batch_shape.into_iter() {
futures::select! {
() = watcher_fut => panic!("fake watcher implementation unexpectedly finished"),
event = event_stream.next() => {
let actual_event = event
.expect("event stream unexpectedly empty")
.expect("error processing event");
let expected_event = internal_testutil::generate_event::<I>(event_idx)
.try_into()
.expect("test event is unexpectedly invalid");
assert_eq!(actual_event, expected_event);
}
};
}
}
batches_sender.close_channel();
let ((), mut events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
assert_matches!(
events.pop(),
Some(Err(WatchError::Fidl(fidl::Error::ClientChannelClosed {
status: zx_status::Status::PEER_CLOSED,
..
})))
);
assert_matches!(events[..], []);
}
#[ip_test(I)]
#[fuchsia_async::run_singlethreaded]
async fn event_stream_from_state_multiple_watchers<I: FidlRouteIpExt>() {
let test_data = vec![
vec![internal_testutil::generate_events_in_range::<I>(0..10)],
vec![internal_testutil::generate_events_in_range::<I>(10..20)],
vec![internal_testutil::generate_events_in_range::<I>(20..30)],
];
let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
let (state_request_stream, _control_handle) =
state_server_end.into_stream_and_control_handle();
let watchers_fut = state_request_stream
.zip(futures::stream::iter(test_data.clone()))
.for_each_concurrent(std::usize::MAX, |(request, watcher_data)| {
testutil::serve_state_request::<I>(request, futures::stream::iter(watcher_data))
});
let validate_event_streams_fut =
futures::future::join_all(test_data.into_iter().map(|watcher_data| {
let events_fut = event_stream_from_state::<I>(&state)
.expect("failed to connect to watcher")
.collect::<std::collections::VecDeque<_>>();
events_fut.then(|mut events| {
for expected_event in watcher_data.into_iter().flatten() {
assert_eq!(
events
.pop_front()
.expect("event_stream unexpectedly empty")
.expect("error processing event"),
expected_event.try_into().expect("test event is unexpectedly invalid"),
);
}
assert_matches!(
events.pop_front(),
Some(Err(WatchError::Fidl(fidl::Error::ClientChannelClosed {
status: zx_status::Status::PEER_CLOSED,
..
})))
);
assert_matches!(events.make_contiguous(), []);
futures::future::ready(())
})
}));
let ((), _): ((), Vec<()>) = futures::join!(watchers_fut, validate_event_streams_fut);
}
#[ip_test(I)]
#[test_case(false, false; "no_trailing")]
#[test_case(true, false; "trailing_event")]
#[test_case(false, true; "trailing_batch")]
#[test_case(true, true; "trailing_event_and_batch")]
#[fuchsia_async::run_singlethreaded(test)]
async fn event_stream_from_state_conversion_error<I: FidlRouteIpExt>(
trailing_event: bool,
trailing_batch: bool,
) {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct EventHolder<I: FidlRouteIpExt>(I::WatchEvent);
let EventHolder(bad_event) = I::map_ip(
(),
|()| {
EventHolder(fnet_routes::EventV4::Added(fnet_routes::InstalledRouteV4 {
route: Some(fnet_routes::RouteV4 {
destination: fidl_ip_v4_with_prefix!("192.168.0.1/24"),
..fnet_routes::RouteV4::arbitrary_test_value()
}),
..fnet_routes::InstalledRouteV4::arbitrary_test_value()
}))
},
|()| {
EventHolder(fnet_routes::EventV6::Added(fnet_routes::InstalledRouteV6 {
route: Some(fnet_routes::RouteV6 {
destination: fidl_ip_v6_with_prefix!("fe80::1/64"),
..fnet_routes::RouteV6::arbitrary_test_value()
}),
..fnet_routes::InstalledRouteV6::arbitrary_test_value()
}))
},
);
let batch = std::iter::once(bad_event)
.chain(trailing_event.then(|| internal_testutil::generate_event::<I>(0)).into_iter())
.collect::<Vec<_>>();
let batches = std::iter::once(batch)
.chain(trailing_batch.then(|| vec![internal_testutil::generate_event::<I>(1)]))
.collect::<Vec<_>>();
let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
let (mut state_request_stream, _control_handle) =
state_server_end.into_stream_and_control_handle();
let watcher_fut = state_request_stream
.next()
.then(|req| {
testutil::serve_state_request::<I>(
req.expect("State request_stream unexpectedly ended"),
futures::stream::iter(batches),
)
})
.fuse();
let event_stream =
event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
futures::pin_mut!(watcher_fut, event_stream);
let ((), events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
assert_matches!(&events[..], &[Err(WatchError::Conversion(_))]);
}
#[ip_test(I)]
#[test_case(false; "no_trailing_batch")]
#[test_case(true; "trailing_batch")]
#[fuchsia_async::run_singlethreaded(test)]
async fn event_stream_from_state_empty_batch_error<I: FidlRouteIpExt>(trailing_batch: bool) {
let batches = std::iter::once(Vec::new())
.chain(trailing_batch.then(|| vec![internal_testutil::generate_event::<I>(0)]))
.collect::<Vec<_>>();
let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
let (mut state_request_stream, _control_handle) =
state_server_end.into_stream_and_control_handle();
let watcher_fut = state_request_stream
.next()
.then(|req| {
testutil::serve_state_request::<I>(
req.expect("State request_stream unexpectedly ended"),
futures::stream::iter(batches),
)
})
.fuse();
let event_stream =
event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
futures::pin_mut!(watcher_fut, event_stream);
let ((), events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
assert_matches!(&events[..], &[Err(WatchError::EmptyEventBatch)]);
}
fn arbitrary_test_route<I: Ip + FidlRouteIpExt>() -> InstalledRoute<I> {
#[derive(GenericOverIp)]
#[generic_over_ip(I, Ip)]
struct RouteHolder<I: FidlRouteIpExt>(InstalledRoute<I>);
let RouteHolder(route) = I::map_ip(
(),
|()| {
RouteHolder(
fnet_routes::InstalledRouteV4::arbitrary_test_value().try_into().unwrap(),
)
},
|()| {
RouteHolder(
fnet_routes::InstalledRouteV6::arbitrary_test_value().try_into().unwrap(),
)
},
);
route
}
enum CollectRoutesUntilIdleErrorTestCase {
ErrorInStream,
UnexpectedEvent,
StreamEnded,
}
#[ip_test(I)]
#[test_case(CollectRoutesUntilIdleErrorTestCase::ErrorInStream; "error_in_stream")]
#[test_case(CollectRoutesUntilIdleErrorTestCase::UnexpectedEvent; "unexpected_event")]
#[test_case(CollectRoutesUntilIdleErrorTestCase::StreamEnded; "stream_ended")]
#[fuchsia_async::run_singlethreaded(test)]
async fn collect_routes_until_idle_error<I: FidlRouteIpExt>(
test_case: CollectRoutesUntilIdleErrorTestCase,
) {
let route = arbitrary_test_route();
let (event, test_assertion): (_, Box<dyn FnOnce(_)>) = match test_case {
CollectRoutesUntilIdleErrorTestCase::ErrorInStream => (
Err(WatchError::EmptyEventBatch),
Box::new(|result| {
assert_matches!(result, Err(CollectRoutesUntilIdleError::ErrorInStream(_)))
}),
),
CollectRoutesUntilIdleErrorTestCase::UnexpectedEvent => (
Ok(Event::Added(route)),
Box::new(|result| {
assert_matches!(result, Err(CollectRoutesUntilIdleError::UnexpectedEvent(_)))
}),
),
CollectRoutesUntilIdleErrorTestCase::StreamEnded => (
Ok(Event::Existing(route)),
Box::new(|result| {
assert_matches!(result, Err(CollectRoutesUntilIdleError::StreamEnded))
}),
),
};
let event_stream = futures::stream::once(futures::future::ready(event));
futures::pin_mut!(event_stream);
let result = collect_routes_until_idle::<I, Vec<_>>(event_stream).await;
test_assertion(result);
}
#[ip_test(I)]
#[fuchsia_async::run_singlethreaded]
async fn collect_routes_until_idle_success<I: FidlRouteIpExt>() {
let route = arbitrary_test_route();
let event_stream = futures::stream::iter([
Ok(Event::Existing(route)),
Ok(Event::Idle),
Ok(Event::Added(route)),
]);
futures::pin_mut!(event_stream);
let existing = collect_routes_until_idle::<I, Vec<_>>(event_stream.by_ref())
.await
.expect("failed to collect existing routes");
assert_eq!(&existing, &[route]);
let trailing_events = event_stream.collect::<Vec<_>>().await;
assert_matches!(
&trailing_events[..],
&[Ok(Event::Added(found_route))] if found_route == route
);
}
#[ip_test(I)]
#[fuchsia_async::run_singlethreaded]
async fn wait_for_routes_errors<I: FidlRouteIpExt>() {
let mut state = HashSet::new();
let event_stream =
futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
assert_matches!(
wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
Err(WaitForRoutesError::ErrorInStream(WatchError::EmptyEventBatch))
);
assert!(state.is_empty());
let event_stream = futures::stream::empty();
assert_matches!(
wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
Err(WaitForRoutesError::StreamEnded)
);
assert!(state.is_empty());
let event_stream = futures::stream::once(futures::future::ready(Ok(Event::<I>::Unknown)));
assert_matches!(
wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
Err(WaitForRoutesError::UnknownEvent)
);
assert!(state.is_empty());
}
#[ip_test(I)]
#[fuchsia_async::run_singlethreaded]
async fn wait_for_routes_add_remove<I: FidlRouteIpExt>() {
let into_stream = |t| futures::stream::once(futures::future::ready(t));
let route = arbitrary_test_route::<I>();
let mut state = HashSet::new();
let has_route = |routes: &HashSet<InstalledRoute<I>>| routes.contains(&route);
assert_matches!(
wait_for_routes::<I, _, _>(futures::stream::pending(), &mut state, has_route)
.now_or_never(),
None
);
assert!(state.is_empty());
assert_matches!(
wait_for_routes::<I, _, _>(into_stream(Ok(Event::Added(route))), &mut state, has_route)
.now_or_never(),
Some(Ok(()))
);
assert_eq!(state, HashSet::from_iter([route]));
assert_matches!(
wait_for_routes::<I, _, _>(into_stream(Ok(Event::Added(route))), &mut state, has_route)
.now_or_never(),
Some(Err(WaitForRoutesError::AddedAlreadyExisting(r))) if r == route
);
assert_eq!(state, HashSet::from_iter([route]));
let does_not_have_route = |routes: &HashSet<InstalledRoute<I>>| !routes.contains(&route);
assert_matches!(
wait_for_routes::<I, _, _>(futures::stream::pending(), &mut state, does_not_have_route)
.now_or_never(),
None
);
assert_eq!(state, HashSet::from_iter([route]));
assert_matches!(
wait_for_routes::<I, _, _>(
into_stream(Ok(Event::Removed(route))),
&mut state,
does_not_have_route
)
.now_or_never(),
Some(Ok(()))
);
assert!(state.is_empty());
assert_matches!(
wait_for_routes::<I, _, _>(
into_stream(Ok(Event::Removed(route))),
&mut state,
does_not_have_route
).now_or_never(),
Some(Err(WaitForRoutesError::RemovedNonExistent(r))) if r == route
);
assert!(state.is_empty());
}
}