Skip to main content

fidl_fuchsia_net_routes_ext/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Extensions for the fuchsia.net.routes FIDL library.
6//!
7//! The fuchsia.net.routes API has separate V4 and V6 watcher variants to
8//! enforce maximum type safety and access control at the API layer. For the
9//! most part, these APIs are a mirror image of one another. This library
10//! provides an a single implementation that is generic over
11//! [`net_types::ip::Ip`] version, as well as conversion utilities.
12
13#![deny(missing_docs)]
14
15pub mod admin;
16pub mod rules;
17pub mod testutil;
18
19use std::collections::HashSet;
20use std::fmt::{Debug, Display};
21
22use async_utils::{fold, stream};
23use fidl_fuchsia_net_ext::{self as fnet_ext, IntoExt as _, TryIntoExt as _};
24use flex_fuchsia_net as fnet;
25use flex_fuchsia_net_routes as fnet_routes;
26use flex_fuchsia_net_routes_admin as fnet_routes_admin;
27use flex_fuchsia_net_stack as fnet_stack;
28use futures::{Future, Stream, TryStreamExt as _};
29use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6, Ipv6Addr, Subnet};
30use net_types::{SpecifiedAddr, UnicastAddress, Witness as _};
31use thiserror::Error;
32
33/// Conversion errors from `fnet_routes` FIDL types to the generic equivalents
34/// defined in this module.
35#[derive(Clone, Copy, Debug, Error, PartialEq)]
36pub enum FidlConversionError<UnsetFieldSpecifier: Debug + Display> {
37    /// A required field was unset. The provided string is the human-readable
38    /// name of the unset field.
39    #[error("required field is unset: {0}")]
40    RequiredFieldUnset(UnsetFieldSpecifier),
41    /// Destination Subnet conversion failed.
42    #[error("failed to convert `destination` to net_types subnet: {0:?}")]
43    DestinationSubnet(net_types::ip::SubnetError),
44    /// Next-Hop specified address conversion failed.
45    #[error("failed to convert `next_hop` to a specified addr")]
46    UnspecifiedNextHop,
47    /// Next-Hop unicast address conversion failed.
48    #[error("failed to convert `next_hop` to a unicast addr")]
49    NextHopNotUnicast,
50}
51
52impl<T: Debug + Display> FidlConversionError<T> {
53    fn map_unset_fields<U: Debug + Display>(
54        self,
55        f: impl FnOnce(T) -> U,
56    ) -> FidlConversionError<U> {
57        match self {
58            FidlConversionError::RequiredFieldUnset(field) => {
59                FidlConversionError::RequiredFieldUnset(f(field))
60            }
61            FidlConversionError::DestinationSubnet(err) => {
62                FidlConversionError::DestinationSubnet(err)
63            }
64            FidlConversionError::UnspecifiedNextHop => FidlConversionError::UnspecifiedNextHop,
65            FidlConversionError::NextHopNotUnicast => FidlConversionError::NextHopNotUnicast,
66        }
67    }
68}
69
70impl From<FidlConversionError<RoutePropertiesRequiredFields>> for fnet_routes_admin::RouteSetError {
71    fn from(error: FidlConversionError<RoutePropertiesRequiredFields>) -> Self {
72        match error {
73            FidlConversionError::RequiredFieldUnset(field_name) => match field_name {
74                RoutePropertiesRequiredFields::SpecifiedProperties => {
75                    fnet_routes_admin::RouteSetError::MissingRouteProperties
76                }
77                RoutePropertiesRequiredFields::WithinSpecifiedProperties(field_name) => {
78                    match field_name {
79                        SpecifiedRoutePropertiesRequiredFields::Metric => {
80                            fnet_routes_admin::RouteSetError::MissingMetric
81                        }
82                    }
83                }
84            },
85            FidlConversionError::DestinationSubnet(_subnet_error) => {
86                fnet_routes_admin::RouteSetError::InvalidDestinationSubnet
87            }
88            FidlConversionError::UnspecifiedNextHop | FidlConversionError::NextHopNotUnicast => {
89                fnet_routes_admin::RouteSetError::InvalidNextHop
90            }
91        }
92    }
93}
94
95/// Conversion errors from generic route types defined in this module to their
96/// FIDL equivalents.
97#[derive(Clone, Copy, Debug, Error, PartialEq)]
98pub enum NetTypeConversionError {
99    /// A union type was `Unknown`.
100    #[error("Union type is of the `Unknown` variant: {0}")]
101    UnknownUnionVariant(&'static str),
102}
103
104/// The specified properties of a route. This type enforces that all required
105/// fields from [`fnet_routes::SpecifiedRouteProperties`] are set.
106#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
107pub struct SpecifiedRouteProperties {
108    /// The specified metric of the route.
109    pub metric: fnet_routes::SpecifiedMetric,
110}
111
112/// Required fields in [`SpecifiedRouteProperties`].
113#[derive(Error, Debug, Clone, PartialEq, Eq)]
114#[allow(missing_docs)]
115pub enum SpecifiedRoutePropertiesRequiredFields {
116    #[error("fuchsia.net.routes/SpecifiedRouteProperties.metric")]
117    Metric,
118}
119
120impl TryFrom<fnet_routes::SpecifiedRouteProperties> for SpecifiedRouteProperties {
121    type Error = FidlConversionError<SpecifiedRoutePropertiesRequiredFields>;
122    fn try_from(
123        specified_properties: fnet_routes::SpecifiedRouteProperties,
124    ) -> Result<Self, Self::Error> {
125        Ok(SpecifiedRouteProperties {
126            metric: specified_properties.metric.ok_or(FidlConversionError::RequiredFieldUnset(
127                SpecifiedRoutePropertiesRequiredFields::Metric,
128            ))?,
129        })
130    }
131}
132
133impl From<SpecifiedRouteProperties> for fnet_routes::SpecifiedRouteProperties {
134    fn from(
135        specified_properties: SpecifiedRouteProperties,
136    ) -> fnet_routes::SpecifiedRouteProperties {
137        let SpecifiedRouteProperties { metric } = specified_properties;
138        fnet_routes::SpecifiedRouteProperties { metric: Some(metric), ..Default::default() }
139    }
140}
141
142/// The effective properties of a route. This type enforces that all required
143/// fields from [`fnet_routes::EffectiveRouteProperties`] are set.
144#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
145pub struct EffectiveRouteProperties {
146    /// The effective metric of the route.
147    pub metric: u32,
148}
149
150#[derive(Debug, Error, Clone, PartialEq, Eq)]
151#[allow(missing_docs)]
152pub enum EffectiveRoutePropertiesRequiredFields {
153    #[error("fuchsia.net.routes/EffectiveRouteProperties.metric")]
154    Metric,
155}
156
157impl TryFrom<fnet_routes::EffectiveRouteProperties> for EffectiveRouteProperties {
158    type Error = FidlConversionError<EffectiveRoutePropertiesRequiredFields>;
159    fn try_from(
160        effective_properties: fnet_routes::EffectiveRouteProperties,
161    ) -> Result<Self, Self::Error> {
162        Ok(EffectiveRouteProperties {
163            metric: effective_properties.metric.ok_or(FidlConversionError::RequiredFieldUnset(
164                EffectiveRoutePropertiesRequiredFields::Metric,
165            ))?,
166        })
167    }
168}
169
170impl From<EffectiveRouteProperties> for fnet_routes::EffectiveRouteProperties {
171    fn from(
172        effective_properties: EffectiveRouteProperties,
173    ) -> fnet_routes::EffectiveRouteProperties {
174        let EffectiveRouteProperties { metric } = effective_properties;
175        fnet_routes::EffectiveRouteProperties { metric: Some(metric), ..Default::default() }
176    }
177}
178
179/// The properties of a route, abstracting over
180/// [`fnet_routes::RoutePropertiesV4`] and [`fnet_routes::RoutePropertiesV6`].
181#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
182pub struct RouteProperties {
183    /// the specified properties of the route.
184    pub specified_properties: SpecifiedRouteProperties,
185}
186
187impl RouteProperties {
188    /// Constructs a [`RouteProperties`] from a specified metric.
189    pub fn from_explicit_metric(metric: u32) -> Self {
190        Self {
191            specified_properties: SpecifiedRouteProperties {
192                metric: fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
193            },
194        }
195    }
196}
197
198#[derive(Debug, Error, Clone, PartialEq, Eq)]
199#[allow(missing_docs)]
200pub enum RoutePropertiesRequiredFields {
201    #[error("fuchsia.net.routes/RoutePropertiesV#.specified_properties")]
202    SpecifiedProperties,
203    #[error(transparent)]
204    WithinSpecifiedProperties(#[from] SpecifiedRoutePropertiesRequiredFields),
205}
206
207impl TryFrom<fnet_routes::RoutePropertiesV4> for RouteProperties {
208    type Error = FidlConversionError<RoutePropertiesRequiredFields>;
209    fn try_from(properties: fnet_routes::RoutePropertiesV4) -> Result<Self, Self::Error> {
210        Ok(RouteProperties {
211            specified_properties: properties
212                .specified_properties
213                .ok_or(FidlConversionError::RequiredFieldUnset(
214                    RoutePropertiesRequiredFields::SpecifiedProperties,
215                ))?
216                .try_into()
217                .map_err(|e: FidlConversionError<_>| {
218                    e.map_unset_fields(RoutePropertiesRequiredFields::WithinSpecifiedProperties)
219                })?,
220        })
221    }
222}
223
224impl TryFrom<fnet_routes::RoutePropertiesV6> for RouteProperties {
225    type Error = FidlConversionError<RoutePropertiesRequiredFields>;
226    fn try_from(properties: fnet_routes::RoutePropertiesV6) -> Result<Self, Self::Error> {
227        Ok(RouteProperties {
228            specified_properties: properties
229                .specified_properties
230                .ok_or(FidlConversionError::RequiredFieldUnset(
231                    RoutePropertiesRequiredFields::SpecifiedProperties,
232                ))?
233                .try_into()
234                .map_err(|e: FidlConversionError<_>| {
235                    e.map_unset_fields(RoutePropertiesRequiredFields::WithinSpecifiedProperties)
236                })?,
237        })
238    }
239}
240
241impl From<RouteProperties> for fnet_routes::RoutePropertiesV4 {
242    fn from(properties: RouteProperties) -> fnet_routes::RoutePropertiesV4 {
243        let RouteProperties { specified_properties } = properties;
244        fnet_routes::RoutePropertiesV4 {
245            specified_properties: Some(specified_properties.into()),
246            ..Default::default()
247        }
248    }
249}
250
251impl From<RouteProperties> for fnet_routes::RoutePropertiesV6 {
252    fn from(properties: RouteProperties) -> fnet_routes::RoutePropertiesV6 {
253        let RouteProperties { specified_properties } = properties;
254        fnet_routes::RoutePropertiesV6 {
255            specified_properties: Some(specified_properties.into()),
256            ..Default::default()
257        }
258    }
259}
260
261/// A target of a route, abstracting over [`fnet_routes::RouteTargetV4`] and
262/// [`fnet_routes::RouteTargetV6`].
263///
264/// The `next_hop` address is required to be unicast. IPv4 addresses can only be
265/// determined to be unicast within the broader context of a subnet, hence they
266/// are only guaranteed to be specified in this context. IPv6 addresses,
267/// however, will be confirmed to be unicast.
268#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
269pub struct RouteTarget<I: Ip> {
270    /// The outbound_interface to use when forwarding packets.
271    pub outbound_interface: u64,
272    /// The next-hop IP address of the route.
273    pub next_hop: Option<SpecifiedAddr<I::Addr>>,
274}
275
276impl TryFrom<fnet_routes::RouteTargetV4> for RouteTarget<Ipv4> {
277    type Error = FidlConversionError<NeverMissingFields>;
278    fn try_from(target: fnet_routes::RouteTargetV4) -> Result<Self, Self::Error> {
279        let fnet_routes::RouteTargetV4 { outbound_interface, next_hop } = target;
280        let next_hop: Option<SpecifiedAddr<net_types::ip::Ipv4Addr>> = next_hop
281            .map(|addr| {
282                SpecifiedAddr::new((*addr).into_ext())
283                    .ok_or(FidlConversionError::UnspecifiedNextHop)
284            })
285            .transpose()?;
286        if let Some(next_hop) = next_hop {
287            if next_hop.is_limited_broadcast() {
288                return Err(FidlConversionError::NextHopNotUnicast);
289            }
290        }
291        Ok(RouteTarget { outbound_interface, next_hop })
292    }
293}
294
295impl TryFrom<fnet_routes::RouteTargetV6> for RouteTarget<Ipv6> {
296    type Error = FidlConversionError<NeverMissingFields>;
297    fn try_from(target: fnet_routes::RouteTargetV6) -> Result<Self, Self::Error> {
298        let fnet_routes::RouteTargetV6 { outbound_interface, next_hop } = target;
299        let addr: Option<SpecifiedAddr<Ipv6Addr>> = next_hop
300            .map(|addr| {
301                SpecifiedAddr::new((*addr).into_ext())
302                    .ok_or(FidlConversionError::UnspecifiedNextHop)
303            })
304            .transpose()?;
305        if let Some(specified_addr) = addr {
306            if !specified_addr.is_unicast() {
307                return Err(FidlConversionError::NextHopNotUnicast);
308            }
309        }
310        Ok(RouteTarget { outbound_interface, next_hop: addr })
311    }
312}
313
314impl From<RouteTarget<Ipv4>> for fnet_routes::RouteTargetV4 {
315    fn from(target: RouteTarget<Ipv4>) -> fnet_routes::RouteTargetV4 {
316        let RouteTarget { outbound_interface, next_hop } = target;
317        fnet_routes::RouteTargetV4 {
318            outbound_interface: outbound_interface,
319            next_hop: next_hop.map(|addr| Box::new((*addr).into_ext())),
320        }
321    }
322}
323
324impl From<RouteTarget<Ipv6>> for fnet_routes::RouteTargetV6 {
325    fn from(target: RouteTarget<Ipv6>) -> fnet_routes::RouteTargetV6 {
326        let RouteTarget { outbound_interface, next_hop } = target;
327        fnet_routes::RouteTargetV6 {
328            outbound_interface: outbound_interface,
329            next_hop: next_hop.map(|addr| Box::new((*addr).into_ext())),
330        }
331    }
332}
333
334/// The action of a route, abstracting over [`fnet_routes::RouteActionV4`] and
335/// [`fnet_routes::RouteActionV6`].
336///
337/// These fidl types are both defined as flexible unions, which allows the
338/// definition to grow over time. The `Unknown` enum variant accounts for any
339/// new types that are not yet known to the local version of the FIDL bindings.
340#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
341pub enum RouteAction<I: Ip> {
342    /// The RouteAction is unknown.
343    Unknown,
344    /// Forward packets to the specified target.
345    Forward(RouteTarget<I>),
346}
347
348#[derive(Debug, Error, PartialEq, Eq)]
349#[allow(missing_docs)]
350pub enum NeverMissingFields {}
351
352impl TryFrom<fnet_routes::RouteActionV4> for RouteAction<Ipv4> {
353    type Error = FidlConversionError<NeverMissingFields>;
354    fn try_from(action: fnet_routes::RouteActionV4) -> Result<Self, Self::Error> {
355        match action {
356            fnet_routes::RouteActionV4::Forward(target) => {
357                Ok(RouteAction::Forward(target.try_into()?))
358            }
359            fnet_routes::RouteActionV4Unknown!() => Ok(RouteAction::Unknown),
360        }
361    }
362}
363
364impl TryFrom<fnet_routes::RouteActionV6> for RouteAction<Ipv6> {
365    type Error = FidlConversionError<NeverMissingFields>;
366    fn try_from(action: fnet_routes::RouteActionV6) -> Result<Self, Self::Error> {
367        match action {
368            fnet_routes::RouteActionV6::Forward(target) => {
369                Ok(RouteAction::Forward(target.try_into()?))
370            }
371            fnet_routes::RouteActionV4Unknown!() => Ok(RouteAction::Unknown),
372        }
373    }
374}
375
376const ROUTE_ACTION_V4_UNKNOWN_VARIANT_TAG: &str = "fuchsia.net.routes/RouteActionV4";
377
378impl TryFrom<RouteAction<Ipv4>> for fnet_routes::RouteActionV4 {
379    type Error = NetTypeConversionError;
380    fn try_from(action: RouteAction<Ipv4>) -> Result<Self, Self::Error> {
381        match action {
382            RouteAction::Forward(target) => Ok(fnet_routes::RouteActionV4::Forward(target.into())),
383            RouteAction::Unknown => Err(NetTypeConversionError::UnknownUnionVariant(
384                ROUTE_ACTION_V4_UNKNOWN_VARIANT_TAG,
385            )),
386        }
387    }
388}
389
390const ROUTE_ACTION_V6_UNKNOWN_VARIANT_TAG: &str = "fuchsia.net.routes/RouteActionV6";
391
392impl TryFrom<RouteAction<Ipv6>> for fnet_routes::RouteActionV6 {
393    type Error = NetTypeConversionError;
394    fn try_from(action: RouteAction<Ipv6>) -> Result<Self, Self::Error> {
395        match action {
396            RouteAction::Forward(target) => Ok(fnet_routes::RouteActionV6::Forward(target.into())),
397            RouteAction::Unknown => Err(NetTypeConversionError::UnknownUnionVariant(
398                ROUTE_ACTION_V6_UNKNOWN_VARIANT_TAG,
399            )),
400        }
401    }
402}
403
404/// A route, abstracting over [`fnet_routes::RouteV4`] and
405/// [`fnet_routes::RouteV6`].
406///
407/// The `destination` subnet is verified to be a valid subnet; e.g. its
408/// prefix-len is a valid value, and its host bits are cleared.
409#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
410pub struct Route<I: Ip> {
411    /// The destination subnet of the route.
412    pub destination: Subnet<I::Addr>,
413    /// The action specifying how to handle packets matching this route.
414    pub action: RouteAction<I>,
415    /// The additional properties of the route.
416    pub properties: RouteProperties,
417}
418
419impl<I: Ip> Route<I> {
420    /// Constructs a new route with metric `metric` that forwards any packets to `destination` over
421    /// `outbound_interface`.
422    pub fn new_forward(
423        destination: Subnet<I::Addr>,
424        outbound_interface: u64,
425        next_hop: Option<SpecifiedAddr<I::Addr>>,
426        metric: fnet_routes::SpecifiedMetric,
427    ) -> Self {
428        Self {
429            destination,
430            action: RouteAction::Forward(RouteTarget { outbound_interface, next_hop }),
431            properties: RouteProperties {
432                specified_properties: SpecifiedRouteProperties { metric },
433            },
434        }
435    }
436
437    /// Constructs a new route that forwards any packets to `destination` over
438    /// `outbound_interface`, inheriting `outbound_interface`'s metric.
439    pub fn new_forward_with_inherited_metric(
440        destination: Subnet<I::Addr>,
441        outbound_interface: u64,
442        next_hop: Option<SpecifiedAddr<I::Addr>>,
443    ) -> Self {
444        Self::new_forward(
445            destination,
446            outbound_interface,
447            next_hop,
448            fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
449        )
450    }
451
452    //// Constructs a new route with metric `metric` that forwards any packets to `destination` over
453    /// `outbound_interface`.
454    pub fn new_forward_with_explicit_metric(
455        destination: Subnet<I::Addr>,
456        outbound_interface: u64,
457        next_hop: Option<SpecifiedAddr<I::Addr>>,
458        metric: u32,
459    ) -> Self {
460        Self::new_forward(
461            destination,
462            outbound_interface,
463            next_hop,
464            fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
465        )
466    }
467}
468
469impl TryFrom<fnet_routes::RouteV4> for Route<Ipv4> {
470    type Error = FidlConversionError<RoutePropertiesRequiredFields>;
471    fn try_from(route: fnet_routes::RouteV4) -> Result<Self, Self::Error> {
472        let fnet_routes::RouteV4 { destination, action, properties } = route;
473        Ok(Route {
474            destination: destination
475                .try_into_ext()
476                .map_err(FidlConversionError::DestinationSubnet)?,
477            action: action
478                .try_into()
479                .map_err(|e: FidlConversionError<_>| e.map_unset_fields(|never| match never {}))?,
480            properties: properties.try_into()?,
481        })
482    }
483}
484
485impl TryFrom<fnet_routes::RouteV6> for Route<Ipv6> {
486    type Error = FidlConversionError<RoutePropertiesRequiredFields>;
487    fn try_from(route: fnet_routes::RouteV6) -> Result<Self, Self::Error> {
488        let fnet_routes::RouteV6 { destination, action, properties } = route;
489        let destination =
490            destination.try_into_ext().map_err(FidlConversionError::DestinationSubnet)?;
491        Ok(Route {
492            destination,
493            action: action
494                .try_into()
495                .map_err(|e: FidlConversionError<_>| e.map_unset_fields(|never| match never {}))?,
496            properties: properties.try_into()?,
497        })
498    }
499}
500
501impl TryFrom<Route<Ipv4>> for fnet_routes::RouteV4 {
502    type Error = NetTypeConversionError;
503    fn try_from(route: Route<Ipv4>) -> Result<Self, Self::Error> {
504        let Route { destination, action, properties } = route;
505        Ok(fnet_routes::RouteV4 {
506            destination: fnet::Ipv4AddressWithPrefix {
507                addr: destination.network().into_ext(),
508                prefix_len: destination.prefix(),
509            },
510            action: action.try_into()?,
511            properties: properties.into(),
512        })
513    }
514}
515
516impl TryFrom<Route<Ipv6>> for fnet_routes::RouteV6 {
517    type Error = NetTypeConversionError;
518    fn try_from(route: Route<Ipv6>) -> Result<Self, Self::Error> {
519        let Route { destination, action, properties } = route;
520        Ok(fnet_routes::RouteV6 {
521            destination: fnet::Ipv6AddressWithPrefix {
522                addr: destination.network().into_ext(),
523                prefix_len: destination.prefix(),
524            },
525            action: action.try_into()?,
526            properties: properties.into(),
527        })
528    }
529}
530
531impl<I: Ip> TryFrom<Route<I>> for fnet_stack::ForwardingEntry {
532    type Error = NetTypeConversionError;
533    fn try_from(
534        Route {
535            destination,
536            action,
537            properties:
538                RouteProperties { specified_properties: SpecifiedRouteProperties { metric } },
539        }: Route<I>,
540    ) -> Result<Self, Self::Error> {
541        let RouteTarget { outbound_interface, next_hop } = match action {
542            RouteAction::Unknown => {
543                return Err(NetTypeConversionError::UnknownUnionVariant(match I::VERSION {
544                    net_types::ip::IpVersion::V4 => ROUTE_ACTION_V4_UNKNOWN_VARIANT_TAG,
545                    net_types::ip::IpVersion::V6 => ROUTE_ACTION_V6_UNKNOWN_VARIANT_TAG,
546                }));
547            }
548            RouteAction::Forward(target) => target,
549        };
550
551        let next_hop = I::map_ip_in(
552            next_hop,
553            |next_hop| next_hop.map(|addr| fnet::IpAddress::Ipv4(addr.get().into_ext())),
554            |next_hop| next_hop.map(|addr| fnet::IpAddress::Ipv6(addr.get().into_ext())),
555        );
556
557        Ok(fnet_stack::ForwardingEntry {
558            subnet: destination.into_ext(),
559            device_id: outbound_interface,
560            next_hop: next_hop.map(Box::new),
561            metric: match metric {
562                fnet_routes::SpecifiedMetric::ExplicitMetric(metric) => metric,
563                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty) => 0,
564            },
565        })
566    }
567}
568
569/// An installed route, abstracting over [`fnet_routes::InstalledRouteV4`] and
570/// [`fnet_routes::InstalledRouteV6`].
571#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
572pub struct InstalledRoute<I: Ip> {
573    /// The route.
574    pub route: Route<I>,
575    /// The route's effective properties.
576    pub effective_properties: EffectiveRouteProperties,
577    /// The table which this route belongs to.
578    pub table_id: TableId,
579}
580
581impl<I: Ip> InstalledRoute<I> {
582    /// Tests if the [`InstalledRoute`] matches the given route and table_id.
583    pub fn matches_route_and_table_id(&self, route: &Route<I>, table_id: TableId) -> bool {
584        &self.route == route && self.table_id == table_id
585    }
586}
587
588/// A newtype representing the ID of a route table.
589#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
590pub struct TableId(u32);
591
592impl TableId {
593    /// Constructs a new table ID.
594    pub const fn new(id: u32) -> Self {
595        Self(id)
596    }
597
598    /// Extracts the table ID.
599    pub const fn get(self) -> u32 {
600        let Self(id) = self;
601        id
602    }
603}
604
605impl Display for TableId {
606    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
607        write!(f, "{}", self.get())
608    }
609}
610
611#[derive(Error, Clone, Debug, PartialEq, Eq)]
612#[allow(missing_docs)]
613pub enum InstalledRouteRequiredFields {
614    #[error("fuchsia.net.routes/InstalledRouteV#.route")]
615    Route,
616    #[error("fuchsia.net.routes/InstalledRouteV#.effective_properties")]
617    EffectiveProperties,
618    #[error(transparent)]
619    WithinRoute(#[from] RoutePropertiesRequiredFields),
620    #[error(transparent)]
621    WithinEffectiveProperties(#[from] EffectiveRoutePropertiesRequiredFields),
622    #[error("fuchsia.net.routes/InstalledRouteV#.table_id")]
623    TableId,
624}
625
626impl TryFrom<fnet_routes::InstalledRouteV4> for InstalledRoute<Ipv4> {
627    type Error = FidlConversionError<InstalledRouteRequiredFields>;
628    fn try_from(installed_route: fnet_routes::InstalledRouteV4) -> Result<Self, Self::Error> {
629        Ok(InstalledRoute {
630            route: installed_route
631                .route
632                .ok_or(FidlConversionError::RequiredFieldUnset(
633                    InstalledRouteRequiredFields::Route,
634                ))?
635                .try_into()
636                .map_err(|e: FidlConversionError<_>| {
637                    e.map_unset_fields(InstalledRouteRequiredFields::WithinRoute)
638                })?,
639            effective_properties: installed_route
640                .effective_properties
641                .ok_or(FidlConversionError::RequiredFieldUnset(
642                    InstalledRouteRequiredFields::EffectiveProperties,
643                ))?
644                .try_into()
645                .map_err(|e: FidlConversionError<_>| {
646                    e.map_unset_fields(InstalledRouteRequiredFields::WithinEffectiveProperties)
647                })?,
648            table_id: TableId(installed_route.table_id.ok_or(
649                FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::TableId),
650            )?),
651        })
652    }
653}
654
655impl TryFrom<fnet_routes::InstalledRouteV6> for InstalledRoute<Ipv6> {
656    type Error = FidlConversionError<InstalledRouteRequiredFields>;
657    fn try_from(installed_route: fnet_routes::InstalledRouteV6) -> Result<Self, Self::Error> {
658        Ok(InstalledRoute {
659            route: installed_route
660                .route
661                .ok_or(FidlConversionError::RequiredFieldUnset(
662                    InstalledRouteRequiredFields::Route,
663                ))?
664                .try_into()
665                .map_err(|e: FidlConversionError<_>| {
666                    e.map_unset_fields(InstalledRouteRequiredFields::WithinRoute)
667                })?,
668            effective_properties: installed_route
669                .effective_properties
670                .ok_or(FidlConversionError::RequiredFieldUnset(
671                    InstalledRouteRequiredFields::EffectiveProperties,
672                ))?
673                .try_into()
674                .map_err(|e: FidlConversionError<_>| {
675                    e.map_unset_fields(InstalledRouteRequiredFields::WithinEffectiveProperties)
676                })?,
677            table_id: TableId(installed_route.table_id.ok_or(
678                FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::TableId),
679            )?),
680        })
681    }
682}
683
684impl TryFrom<InstalledRoute<Ipv4>> for fnet_routes::InstalledRouteV4 {
685    type Error = NetTypeConversionError;
686    fn try_from(installed_route: InstalledRoute<Ipv4>) -> Result<Self, Self::Error> {
687        let InstalledRoute { route, effective_properties, table_id } = installed_route;
688        Ok(fnet_routes::InstalledRouteV4 {
689            route: Some(route.try_into()?),
690            effective_properties: Some(effective_properties.into()),
691            table_id: Some(table_id.get()),
692            ..Default::default()
693        })
694    }
695}
696
697impl TryFrom<InstalledRoute<Ipv6>> for fnet_routes::InstalledRouteV6 {
698    type Error = NetTypeConversionError;
699    fn try_from(installed_route: InstalledRoute<Ipv6>) -> Result<Self, Self::Error> {
700        let InstalledRoute { route, effective_properties, table_id } = installed_route;
701        Ok(fnet_routes::InstalledRouteV6 {
702            route: Some(route.try_into()?),
703            effective_properties: Some(effective_properties.into()),
704            table_id: Some(table_id.get()),
705            ..Default::default()
706        })
707    }
708}
709
710/// An event reported to the watcher, abstracting over
711/// [`fnet_routes::EventV4`] and [fnet_routes::EventV6`].
712///
713/// These fidl types are both defined as flexible unions, which allows the
714/// definition to grow over time. The `Unknown` enum variant accounts for any
715/// new types that are not yet known to the local version of the FIDL bindings.
716#[derive(Clone, Copy, Debug, PartialEq)]
717pub enum Event<I: Ip> {
718    /// An unknown event.
719    Unknown,
720    /// A route that existed prior to watching.
721    Existing(InstalledRoute<I>),
722    /// Sentinel value indicating no more `existing` events will be received.
723    Idle,
724    /// A route that was added while watching.
725    Added(InstalledRoute<I>),
726    /// A route that was removed while watching.
727    Removed(InstalledRoute<I>),
728}
729
730impl TryFrom<fnet_routes::EventV4> for Event<Ipv4> {
731    type Error = FidlConversionError<InstalledRouteRequiredFields>;
732    fn try_from(event: fnet_routes::EventV4) -> Result<Self, Self::Error> {
733        match event {
734            fnet_routes::EventV4::Existing(route) => Ok(Event::Existing(route.try_into()?)),
735            fnet_routes::EventV4::Idle(fnet_routes::Empty) => Ok(Event::Idle),
736            fnet_routes::EventV4::Added(route) => Ok(Event::Added(route.try_into()?)),
737            fnet_routes::EventV4::Removed(route) => Ok(Event::Removed(route.try_into()?)),
738            fnet_routes::EventV4Unknown!() => Ok(Event::Unknown),
739        }
740    }
741}
742
743impl TryFrom<fnet_routes::EventV6> for Event<Ipv6> {
744    type Error = FidlConversionError<InstalledRouteRequiredFields>;
745    fn try_from(event: fnet_routes::EventV6) -> Result<Self, Self::Error> {
746        match event {
747            fnet_routes::EventV6::Existing(route) => Ok(Event::Existing(route.try_into()?)),
748            fnet_routes::EventV6::Idle(fnet_routes::Empty) => Ok(Event::Idle),
749            fnet_routes::EventV6::Added(route) => Ok(Event::Added(route.try_into()?)),
750            fnet_routes::EventV6::Removed(route) => Ok(Event::Removed(route.try_into()?)),
751            fnet_routes::EventV6Unknown!() => Ok(Event::Unknown),
752        }
753    }
754}
755
756impl TryFrom<Event<Ipv4>> for fnet_routes::EventV4 {
757    type Error = NetTypeConversionError;
758    fn try_from(event: Event<Ipv4>) -> Result<Self, Self::Error> {
759        match event {
760            Event::Existing(route) => Ok(fnet_routes::EventV4::Existing(route.try_into()?)),
761            Event::Idle => Ok(fnet_routes::EventV4::Idle(fnet_routes::Empty)),
762            Event::Added(route) => Ok(fnet_routes::EventV4::Added(route.try_into()?)),
763            Event::Removed(route) => Ok(fnet_routes::EventV4::Removed(route.try_into()?)),
764            Event::Unknown => {
765                Err(NetTypeConversionError::UnknownUnionVariant("fuchsia_net_routes.EventV4"))
766            }
767        }
768    }
769}
770
771impl TryFrom<Event<Ipv6>> for fnet_routes::EventV6 {
772    type Error = NetTypeConversionError;
773    fn try_from(event: Event<Ipv6>) -> Result<Self, Self::Error> {
774        match event {
775            Event::Existing(route) => Ok(fnet_routes::EventV6::Existing(route.try_into()?)),
776            Event::Idle => Ok(fnet_routes::EventV6::Idle(fnet_routes::Empty)),
777            Event::Added(route) => Ok(fnet_routes::EventV6::Added(route.try_into()?)),
778            Event::Removed(route) => Ok(fnet_routes::EventV6::Removed(route.try_into()?)),
779            Event::Unknown => {
780                Err(NetTypeConversionError::UnknownUnionVariant("fuchsia_net_routes.EventV6"))
781            }
782        }
783    }
784}
785
786/// Route watcher creation errors.
787#[derive(Clone, Debug, Error)]
788pub enum WatcherCreationError {
789    /// Proxy creation failed.
790    #[error("failed to create route watcher proxy: {0}")]
791    CreateProxy(fidl::Error),
792    /// Watcher acquisition failed.
793    #[error("failed to get route watcher: {0}")]
794    GetWatcher(fidl::Error),
795}
796
797/// Route watcher `Watch` errors.
798#[derive(Clone, Debug, Error)]
799pub enum WatchError {
800    /// The call to `Watch` returned a FIDL error.
801    #[error("the call to `Watch()` failed: {0}")]
802    Fidl(fidl::Error),
803    /// The event returned by `Watch` encountered a conversion error.
804    #[error("failed to convert event returned by `Watch()`: {0}")]
805    Conversion(FidlConversionError<InstalledRouteRequiredFields>),
806    /// The server returned an empty batch of events.
807    #[error("the call to `Watch()` returned an empty batch of events")]
808    EmptyEventBatch,
809}
810
811/// IP Extension for the `fuchsia.net.routes` FIDL API.
812pub trait FidlRouteIpExt: Ip {
813    /// The "state" protocol to use for this IP version.
814    type StateMarker: flex_client::fidl::DiscoverableProtocolMarker;
815    /// The "watcher" protocol to use for this IP version.
816    type WatcherMarker: flex_client::fidl::ProtocolMarker;
817    /// The type of "event" returned by this IP version's watcher protocol.
818    type WatchEvent: TryInto<Event<Self>, Error = FidlConversionError<InstalledRouteRequiredFields>>
819        + TryFrom<Event<Self>, Error = NetTypeConversionError>
820        + Clone
821        + std::fmt::Debug
822        + PartialEq
823        + Unpin
824        + Send;
825    /// The "route" FIDL type to use for this IP version.
826    type Route: TryFrom<Route<Self>, Error = NetTypeConversionError>
827        + TryInto<Route<Self>, Error = FidlConversionError<RoutePropertiesRequiredFields>>
828        + std::fmt::Debug;
829}
830
831impl FidlRouteIpExt for Ipv4 {
832    type StateMarker = fnet_routes::StateV4Marker;
833    type WatcherMarker = fnet_routes::WatcherV4Marker;
834    type WatchEvent = fnet_routes::EventV4;
835    type Route = fnet_routes::RouteV4;
836}
837
838impl FidlRouteIpExt for Ipv6 {
839    type StateMarker = fnet_routes::StateV6Marker;
840    type WatcherMarker = fnet_routes::WatcherV6Marker;
841    type WatchEvent = fnet_routes::EventV6;
842    type Route = fnet_routes::RouteV6;
843}
844
845/// Abstracts over AddRoute and RemoveRoute RouteSet method responders.
846pub trait Responder: flex_client::fidl::Responder + Debug + Send {
847    /// The payload of the response.
848    type Payload;
849
850    /// Sends a FIDL response.
851    fn send(self, result: Self::Payload) -> Result<(), fidl::Error>;
852}
853
854/// A trait for responding with a slice of objects.
855///
856/// This is similar to [`Responder`], but it allows the sender to send a slice
857/// of objects.
858// These two traits can be merged into one with GATs.
859pub trait SliceResponder<Payload>: flex_client::fidl::Responder + Debug + Send {
860    /// Sends a FIDL response.
861    fn send(self, payload: &[Payload]) -> Result<(), fidl::Error>;
862}
863
864macro_rules! impl_responder {
865    ($resp:ty, &[$payload:ty] $(,)?) => {
866        impl $crate::SliceResponder<$payload> for $resp {
867            fn send(self, result: &[$payload]) -> Result<(), fidl::Error> {
868                <$resp>::send(self, result)
869            }
870        }
871    };
872    ($resp:ty, $payload:ty $(,)?) => {
873        impl $crate::Responder for $resp {
874            type Payload = $payload;
875
876            fn send(self, result: Self::Payload) -> Result<(), fidl::Error> {
877                <$resp>::send(self, result)
878            }
879        }
880    };
881}
882pub(crate) use impl_responder;
883
884/// Options for getting a route watcher.
885#[derive(Default, Clone)]
886pub struct WatcherOptions {
887    /// The route table the watcher is interested in.
888    pub table_interest: Option<fnet_routes::TableInterest>,
889}
890
891impl From<WatcherOptions> for fnet_routes::WatcherOptionsV4 {
892    fn from(WatcherOptions { table_interest }: WatcherOptions) -> Self {
893        Self { table_interest, __source_breaking: fidl::marker::SourceBreaking }
894    }
895}
896
897impl From<WatcherOptions> for fnet_routes::WatcherOptionsV6 {
898    fn from(WatcherOptions { table_interest }: WatcherOptions) -> Self {
899        Self { table_interest, __source_breaking: fidl::marker::SourceBreaking }
900    }
901}
902
903impl From<fnet_routes::WatcherOptionsV4> for WatcherOptions {
904    fn from(
905        fnet_routes::WatcherOptionsV4 { table_interest, __source_breaking: _ }: fnet_routes::WatcherOptionsV4,
906    ) -> Self {
907        Self { table_interest }
908    }
909}
910
911impl From<fnet_routes::WatcherOptionsV6> for WatcherOptions {
912    fn from(
913        fnet_routes::WatcherOptionsV6 { table_interest, __source_breaking: _ }: fnet_routes::WatcherOptionsV6,
914    ) -> Self {
915        Self { table_interest }
916    }
917}
918
919/// Dispatches either `GetWatcherV4` or `GetWatcherV6` on the state proxy.
920pub fn get_watcher<I: FidlRouteIpExt>(
921    state_proxy: &<I::StateMarker as flex_client::fidl::ProtocolMarker>::Proxy,
922    options: WatcherOptions,
923) -> Result<<I::WatcherMarker as flex_client::fidl::ProtocolMarker>::Proxy, WatcherCreationError> {
924    use flex_client::ProxyHasDomain as _;
925    let (watcher_proxy, watcher_server_end) =
926        state_proxy.domain().create_proxy::<I::WatcherMarker>();
927
928    #[derive(GenericOverIp)]
929    #[generic_over_ip(I, Ip)]
930    struct GetWatcherInputs<'a, I: FidlRouteIpExt> {
931        watcher_server_end: flex_client::fidl::ServerEnd<I::WatcherMarker>,
932        state_proxy: &'a <I::StateMarker as flex_client::fidl::ProtocolMarker>::Proxy,
933        options: WatcherOptions,
934    }
935    let result = I::map_ip_in(
936        GetWatcherInputs::<'_, I> { watcher_server_end, state_proxy, options },
937        |GetWatcherInputs { watcher_server_end, state_proxy, options }| {
938            state_proxy.get_watcher_v4(watcher_server_end, &options.into())
939        },
940        |GetWatcherInputs { watcher_server_end, state_proxy, options }| {
941            state_proxy.get_watcher_v6(watcher_server_end, &options.into())
942        },
943    );
944
945    result.map_err(WatcherCreationError::GetWatcher)?;
946    Ok(watcher_proxy)
947}
948
949/// Calls `Watch()` on the provided `WatcherV4` or `WatcherV6` proxy.
950pub fn watch<'a, I: FidlRouteIpExt>(
951    watcher_proxy: &'a <I::WatcherMarker as flex_client::fidl::ProtocolMarker>::Proxy,
952) -> impl Future<Output = Result<Vec<I::WatchEvent>, fidl::Error>> {
953    #[derive(GenericOverIp)]
954    #[generic_over_ip(I, Ip)]
955    struct WatchInputs<'a, I: FidlRouteIpExt> {
956        watcher_proxy: &'a <I::WatcherMarker as flex_client::fidl::ProtocolMarker>::Proxy,
957    }
958    #[derive(GenericOverIp)]
959    #[generic_over_ip(I, Ip)]
960    struct WatchOutputs<I: FidlRouteIpExt> {
961        watch_fut: fidl::client::QueryResponseFut<Vec<I::WatchEvent>, flex_client::Dialect>,
962    }
963    let WatchOutputs { watch_fut } = I::map_ip::<WatchInputs<'_, I>, WatchOutputs<I>>(
964        WatchInputs { watcher_proxy },
965        |WatchInputs { watcher_proxy }| WatchOutputs { watch_fut: watcher_proxy.watch() },
966        |WatchInputs { watcher_proxy }| WatchOutputs { watch_fut: watcher_proxy.watch() },
967    );
968    watch_fut
969}
970
971/// [`event_stream_from_state_with_options`] with default [`WatcherOptions`].
972pub fn event_stream_from_state<I: FidlRouteIpExt>(
973    routes_state: &<I::StateMarker as flex_client::fidl::ProtocolMarker>::Proxy,
974) -> Result<impl Stream<Item = Result<Event<I>, WatchError>> + use<I>, WatcherCreationError> {
975    event_stream_from_state_with_options(routes_state, Default::default())
976}
977
978/// Connects to the watcher protocol with [`WatcherOptions`] and converts the
979/// Hanging-Get style API into an Event stream.
980///
981/// Each call to `Watch` returns a batch of events, which are flattened into a
982/// single stream. If an error is encountered while calling `Watch` or while
983/// converting the event, the stream is immediately terminated.
984pub fn event_stream_from_state_with_options<I: FidlRouteIpExt>(
985    routes_state: &<I::StateMarker as flex_client::fidl::ProtocolMarker>::Proxy,
986    options: WatcherOptions,
987) -> Result<impl Stream<Item = Result<Event<I>, WatchError>> + use<I>, WatcherCreationError> {
988    let watcher = get_watcher::<I>(routes_state, options)?;
989    event_stream_from_watcher(watcher)
990}
991
992/// Turns the provided watcher client into a [`Event`] stream by applying
993/// Hanging-Get watch.
994///
995/// Each call to `Watch` returns a batch of events, which are flattened into a
996/// single stream. If an error is encountered while calling `Watch` or while
997/// converting the event, the stream is immediately terminated.
998pub fn event_stream_from_watcher<I: FidlRouteIpExt>(
999    watcher: <I::WatcherMarker as flex_client::fidl::ProtocolMarker>::Proxy,
1000) -> Result<impl Stream<Item = Result<Event<I>, WatchError>> + use<I>, WatcherCreationError> {
1001    Ok(stream::ShortCircuit::new(
1002        futures::stream::try_unfold(watcher, |watcher| async {
1003            let events_batch = watch::<I>(&watcher).await.map_err(WatchError::Fidl)?;
1004            if events_batch.is_empty() {
1005                return Err(WatchError::EmptyEventBatch);
1006            }
1007            let events_batch = events_batch
1008                .into_iter()
1009                .map(|event| event.try_into().map_err(WatchError::Conversion));
1010            let event_stream = futures::stream::iter(events_batch);
1011            Ok(Some((event_stream, watcher)))
1012        })
1013        // Flatten the stream of event streams into a single event stream.
1014        .try_flatten(),
1015    ))
1016}
1017
1018/// Errors returned by [`collect_routes_until_idle`].
1019#[derive(Clone, Debug, Error)]
1020pub enum CollectRoutesUntilIdleError<I: FidlRouteIpExt> {
1021    /// There was an error in the event stream.
1022    #[error("there was an error in the event stream: {0}")]
1023    ErrorInStream(WatchError),
1024    /// There was an unexpected event in the event stream. Only `existing` or
1025    /// `idle` events are expected.
1026    #[error("there was an unexpected event in the event stream: {0:?}")]
1027    UnexpectedEvent(Event<I>),
1028    /// The event stream unexpectedly ended.
1029    #[error("the event stream unexpectedly ended")]
1030    StreamEnded,
1031}
1032
1033/// Collects all `existing` events from the stream, stopping once the `idle`
1034/// event is observed.
1035pub async fn collect_routes_until_idle<
1036    I: FidlRouteIpExt,
1037    C: Extend<InstalledRoute<I>> + Default,
1038>(
1039    event_stream: impl futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
1040) -> Result<C, CollectRoutesUntilIdleError<I>> {
1041    fold::fold_while(
1042        event_stream,
1043        Ok(C::default()),
1044        |existing_routes: Result<C, CollectRoutesUntilIdleError<I>>, event| {
1045            futures::future::ready(match existing_routes {
1046                Err(_) => {
1047                    unreachable!("`existing_routes` must be `Ok`, because we stop folding on err")
1048                }
1049                Ok(mut existing_routes) => match event {
1050                    Err(e) => {
1051                        fold::FoldWhile::Done(Err(CollectRoutesUntilIdleError::ErrorInStream(e)))
1052                    }
1053                    Ok(e) => match e {
1054                        Event::Existing(e) => {
1055                            existing_routes.extend([e]);
1056                            fold::FoldWhile::Continue(Ok(existing_routes))
1057                        }
1058                        Event::Idle => fold::FoldWhile::Done(Ok(existing_routes)),
1059                        e @ Event::Unknown | e @ Event::Added(_) | e @ Event::Removed(_) => {
1060                            fold::FoldWhile::Done(Err(
1061                                CollectRoutesUntilIdleError::UnexpectedEvent(e),
1062                            ))
1063                        }
1064                    },
1065                },
1066            })
1067        },
1068    )
1069    .await
1070    .short_circuited()
1071    .map_err(|_accumulated_thus_far: Result<C, CollectRoutesUntilIdleError<I>>| {
1072        CollectRoutesUntilIdleError::StreamEnded
1073    })?
1074}
1075
1076/// Errors returned by [`wait_for_routes`].
1077#[derive(Clone, Debug, Error)]
1078pub enum WaitForRoutesError<I: FidlRouteIpExt> {
1079    /// There was an error in the event stream.
1080    #[error("there was an error in the event stream: {0}")]
1081    ErrorInStream(WatchError),
1082    /// There was an `Added` event for an already existing route.
1083    #[error("observed an added event for an already existing route: {0:?}")]
1084    AddedAlreadyExisting(InstalledRoute<I>),
1085    /// There was a `Removed` event for a non-existent route.
1086    #[error("observed a removed event for a non-existent route: {0:?}")]
1087    RemovedNonExistent(InstalledRoute<I>),
1088    /// There was an `Unknown` event in the stream.
1089    #[error("observed an unknown event")]
1090    UnknownEvent,
1091    /// The event stream unexpectedly ended.
1092    #[error("the event stream unexpectedly ended")]
1093    StreamEnded,
1094}
1095
1096/// Wait for a condition on routing state to be satisfied, yielding a result
1097/// from the predicate.
1098///
1099/// With the given `initial_state`, take events from `event_stream` and update
1100/// the state, calling `predicate` whenever the state changes. When `predicate`
1101/// returns `Some(T)` yield `Ok(T)`. Note, this function will hang if no events
1102/// arrive on `event_stream`.
1103pub async fn wait_for_routes_map<
1104    I: FidlRouteIpExt,
1105    S: futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
1106    T,
1107    F: Fn(&HashSet<InstalledRoute<I>>) -> Option<T>,
1108>(
1109    event_stream: S,
1110    initial_state: &mut HashSet<InstalledRoute<I>>,
1111    predicate: F,
1112) -> Result<T, WaitForRoutesError<I>> {
1113    fold::try_fold_while(
1114        event_stream.map_err(WaitForRoutesError::ErrorInStream),
1115        initial_state,
1116        |accumulated_routes, event| {
1117            futures::future::ready({
1118                match event {
1119                    Event::Existing(route) | Event::Added(route) => accumulated_routes
1120                        .insert(route)
1121                        .then_some(())
1122                        .ok_or(WaitForRoutesError::AddedAlreadyExisting(route)),
1123                    Event::Removed(route) => accumulated_routes
1124                        .remove(&route)
1125                        .then_some(())
1126                        .ok_or(WaitForRoutesError::RemovedNonExistent(route)),
1127                    Event::Idle => Ok(()),
1128                    Event::Unknown => Err(WaitForRoutesError::UnknownEvent),
1129                }
1130                .map(|()| match predicate(&accumulated_routes) {
1131                    Some(t) => fold::FoldWhile::Done(t),
1132                    None => fold::FoldWhile::Continue(accumulated_routes),
1133                })
1134            })
1135        },
1136    )
1137    .await?
1138    .short_circuited()
1139    .map_err(|_accumulated_thus_far: &mut HashSet<InstalledRoute<I>>| {
1140        WaitForRoutesError::StreamEnded
1141    })
1142}
1143
1144/// Wait for a condition on routing state to be satisfied.
1145///
1146/// With the given `initial_state`, take events from `event_stream` and update
1147/// the state, calling `predicate` whenever the state changes. When predicates
1148/// returns `True` yield `Ok(())`.
1149pub async fn wait_for_routes<
1150    I: FidlRouteIpExt,
1151    S: futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
1152    F: Fn(&HashSet<InstalledRoute<I>>) -> bool,
1153>(
1154    event_stream: S,
1155    initial_state: &mut HashSet<InstalledRoute<I>>,
1156    predicate: F,
1157) -> Result<(), WaitForRoutesError<I>> {
1158    wait_for_routes_map::<I, S, (), _>(event_stream, initial_state, |routes| {
1159        predicate(routes).then_some(())
1160    })
1161    .await
1162}
1163
1164/// Resolve options for resolving route.
1165#[derive(Debug, Default, Clone)]
1166pub struct ResolveOptions {
1167    /// The marks used for the route resolution.
1168    pub marks: fnet_ext::Marks,
1169}
1170
1171impl From<fnet_routes::ResolveOptions> for ResolveOptions {
1172    fn from(value: fnet_routes::ResolveOptions) -> Self {
1173        let fnet_routes::ResolveOptions { marks, __source_breaking } = value;
1174        Self { marks: marks.map(fnet_ext::Marks::from).unwrap_or_default() }
1175    }
1176}
1177
1178impl From<ResolveOptions> for fnet_routes::ResolveOptions {
1179    fn from(value: ResolveOptions) -> Self {
1180        let ResolveOptions { marks } = value;
1181        Self { marks: Some(marks.into()), __source_breaking: fidl::marker::SourceBreaking }
1182    }
1183}
1184
1185#[cfg(test)]
1186mod tests {
1187    use super::*;
1188    use crate::testutil::internal as internal_testutil;
1189    use assert_matches::assert_matches;
1190    use flex_fuchsia_net as _;
1191    use futures::{FutureExt as _, StreamExt as _};
1192    use ip_test_macro::ip_test;
1193    use net_declare::{
1194        fidl_ip_v4, fidl_ip_v4_with_prefix, fidl_ip_v6, fidl_ip_v6_with_prefix, net_ip_v4,
1195        net_ip_v6, net_subnet_v4, net_subnet_v6,
1196    };
1197    use test_case::test_case;
1198
1199    const ARBITRARY_TABLE_ID: TableId = TableId::new(0);
1200
1201    /// Allows types to provided an arbitrary but valid value for tests.
1202    trait ArbitraryTestValue {
1203        fn arbitrary_test_value() -> Self;
1204    }
1205
1206    impl ArbitraryTestValue for fnet_routes::SpecifiedRouteProperties {
1207        fn arbitrary_test_value() -> Self {
1208            fnet_routes::SpecifiedRouteProperties {
1209                metric: Some(fnet_routes::SpecifiedMetric::ExplicitMetric(0)),
1210                ..Default::default()
1211            }
1212        }
1213    }
1214
1215    impl ArbitraryTestValue for fnet_routes::EffectiveRouteProperties {
1216        fn arbitrary_test_value() -> Self {
1217            fnet_routes::EffectiveRouteProperties { metric: Some(0), ..Default::default() }
1218        }
1219    }
1220
1221    impl ArbitraryTestValue for fnet_routes::RoutePropertiesV4 {
1222        fn arbitrary_test_value() -> Self {
1223            fnet_routes::RoutePropertiesV4 {
1224                specified_properties: Some(
1225                    fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
1226                ),
1227                ..Default::default()
1228            }
1229        }
1230    }
1231
1232    impl ArbitraryTestValue for fnet_routes::RoutePropertiesV6 {
1233        fn arbitrary_test_value() -> Self {
1234            fnet_routes::RoutePropertiesV6 {
1235                specified_properties: Some(
1236                    fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
1237                ),
1238                ..Default::default()
1239            }
1240        }
1241    }
1242
1243    impl ArbitraryTestValue for fnet_routes::RouteTargetV4 {
1244        fn arbitrary_test_value() -> Self {
1245            fnet_routes::RouteTargetV4 { outbound_interface: 1, next_hop: None }
1246        }
1247    }
1248
1249    impl ArbitraryTestValue for fnet_routes::RouteTargetV6 {
1250        fn arbitrary_test_value() -> Self {
1251            fnet_routes::RouteTargetV6 { outbound_interface: 1, next_hop: None }
1252        }
1253    }
1254
1255    impl ArbitraryTestValue for fnet_routes::RouteActionV4 {
1256        fn arbitrary_test_value() -> Self {
1257            fnet_routes::RouteActionV4::Forward(fnet_routes::RouteTargetV4::arbitrary_test_value())
1258        }
1259    }
1260
1261    impl ArbitraryTestValue for fnet_routes::RouteActionV6 {
1262        fn arbitrary_test_value() -> Self {
1263            fnet_routes::RouteActionV6::Forward(fnet_routes::RouteTargetV6::arbitrary_test_value())
1264        }
1265    }
1266
1267    impl ArbitraryTestValue for fnet_routes::RouteV4 {
1268        fn arbitrary_test_value() -> Self {
1269            fnet_routes::RouteV4 {
1270                destination: fidl_ip_v4_with_prefix!("192.168.0.0/24"),
1271                action: fnet_routes::RouteActionV4::arbitrary_test_value(),
1272                properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
1273            }
1274        }
1275    }
1276
1277    impl ArbitraryTestValue for fnet_routes::RouteV6 {
1278        fn arbitrary_test_value() -> Self {
1279            fnet_routes::RouteV6 {
1280                destination: fidl_ip_v6_with_prefix!("fe80::0/64"),
1281                action: fnet_routes::RouteActionV6::arbitrary_test_value(),
1282                properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
1283            }
1284        }
1285    }
1286
1287    impl ArbitraryTestValue for fnet_routes::InstalledRouteV4 {
1288        fn arbitrary_test_value() -> Self {
1289            fnet_routes::InstalledRouteV4 {
1290                route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
1291                effective_properties: Some(
1292                    fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1293                ),
1294                table_id: Some(ARBITRARY_TABLE_ID.get()),
1295                ..Default::default()
1296            }
1297        }
1298    }
1299
1300    impl ArbitraryTestValue for fnet_routes::InstalledRouteV6 {
1301        fn arbitrary_test_value() -> Self {
1302            fnet_routes::InstalledRouteV6 {
1303                route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
1304                effective_properties: Some(
1305                    fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1306                ),
1307                table_id: Some(ARBITRARY_TABLE_ID.get()),
1308                ..Default::default()
1309            }
1310        }
1311    }
1312
1313    #[test]
1314    fn specified_route_properties_try_from_unset_metric() {
1315        assert_eq!(
1316            SpecifiedRouteProperties::try_from(fnet_routes::SpecifiedRouteProperties::default()),
1317            Err(FidlConversionError::RequiredFieldUnset(
1318                SpecifiedRoutePropertiesRequiredFields::Metric
1319            ))
1320        )
1321    }
1322
1323    #[test]
1324    fn specified_route_properties_try_from() {
1325        let fidl_type = fnet_routes::SpecifiedRouteProperties {
1326            metric: Some(fnet_routes::SpecifiedMetric::ExplicitMetric(1)),
1327            ..Default::default()
1328        };
1329        let local_type =
1330            SpecifiedRouteProperties { metric: fnet_routes::SpecifiedMetric::ExplicitMetric(1) };
1331        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1332        assert_eq!(
1333            <SpecifiedRouteProperties as std::convert::Into<
1334                fnet_routes::SpecifiedRouteProperties,
1335            >>::into(local_type),
1336            fidl_type.clone()
1337        );
1338    }
1339
1340    #[test]
1341    fn effective_route_properties_try_from_unset_metric() {
1342        assert_eq!(
1343            EffectiveRouteProperties::try_from(fnet_routes::EffectiveRouteProperties::default()),
1344            Err(FidlConversionError::RequiredFieldUnset(
1345                EffectiveRoutePropertiesRequiredFields::Metric
1346            ))
1347        )
1348    }
1349
1350    #[test]
1351    fn effective_route_properties_try_from() {
1352        let fidl_type =
1353            fnet_routes::EffectiveRouteProperties { metric: Some(1), ..Default::default() };
1354        let local_type = EffectiveRouteProperties { metric: 1 };
1355        assert_eq!(fidl_type.clone().try_into(), Ok(EffectiveRouteProperties { metric: 1 }));
1356        assert_eq!(
1357            <EffectiveRouteProperties as std::convert::Into<
1358                fnet_routes::EffectiveRouteProperties,
1359            >>::into(local_type),
1360            fidl_type.clone()
1361        );
1362    }
1363
1364    #[test]
1365    fn route_properties_try_from_unset_specified_properties_v4() {
1366        assert_eq!(
1367            RouteProperties::try_from(fnet_routes::RoutePropertiesV4::default()),
1368            Err(FidlConversionError::RequiredFieldUnset(
1369                RoutePropertiesRequiredFields::SpecifiedProperties
1370            ))
1371        )
1372    }
1373
1374    #[test]
1375    fn route_properties_try_from_unset_specified_properties_v6() {
1376        assert_eq!(
1377            RouteProperties::try_from(fnet_routes::RoutePropertiesV6::default()),
1378            Err(FidlConversionError::RequiredFieldUnset(
1379                RoutePropertiesRequiredFields::SpecifiedProperties
1380            ))
1381        )
1382    }
1383
1384    #[test]
1385    fn route_properties_try_from_v4() {
1386        let fidl_type = fnet_routes::RoutePropertiesV4 {
1387            specified_properties: Some(
1388                fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
1389            ),
1390            ..Default::default()
1391        };
1392        let local_type = RouteProperties {
1393            specified_properties: fnet_routes::SpecifiedRouteProperties::arbitrary_test_value()
1394                .try_into()
1395                .unwrap(),
1396        };
1397        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1398        assert_eq!(
1399            <RouteProperties as std::convert::Into<fnet_routes::RoutePropertiesV4>>::into(
1400                local_type
1401            ),
1402            fidl_type.clone()
1403        );
1404    }
1405
1406    #[test]
1407    fn route_properties_try_from_v6() {
1408        let fidl_type = fnet_routes::RoutePropertiesV6 {
1409            specified_properties: Some(
1410                fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
1411            ),
1412            ..Default::default()
1413        };
1414        let local_type = RouteProperties {
1415            specified_properties: fnet_routes::SpecifiedRouteProperties::arbitrary_test_value()
1416                .try_into()
1417                .unwrap(),
1418        };
1419        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1420        assert_eq!(
1421            <RouteProperties as std::convert::Into<fnet_routes::RoutePropertiesV6>>::into(
1422                local_type
1423            ),
1424            fidl_type.clone()
1425        );
1426    }
1427
1428    #[test]
1429    fn route_target_try_from_unspecified_next_hop_v4() {
1430        assert_eq!(
1431            RouteTarget::try_from(fnet_routes::RouteTargetV4 {
1432                outbound_interface: 1,
1433                next_hop: Some(Box::new(fidl_ip_v4!("0.0.0.0"))),
1434            }),
1435            Err(FidlConversionError::UnspecifiedNextHop)
1436        )
1437    }
1438
1439    #[test]
1440    fn route_target_try_from_unspecified_next_hop_v6() {
1441        assert_eq!(
1442            RouteTarget::try_from(fnet_routes::RouteTargetV6 {
1443                outbound_interface: 1,
1444                next_hop: Some(Box::new(fidl_ip_v6!("::"))),
1445            }),
1446            Err(FidlConversionError::UnspecifiedNextHop)
1447        );
1448    }
1449
1450    #[test]
1451    fn route_target_try_from_limited_broadcast_next_hop_v4() {
1452        assert_eq!(
1453            RouteTarget::try_from(fnet_routes::RouteTargetV4 {
1454                outbound_interface: 1,
1455                next_hop: Some(Box::new(fidl_ip_v4!("255.255.255.255"))),
1456            }),
1457            Err(FidlConversionError::NextHopNotUnicast)
1458        )
1459    }
1460
1461    #[test]
1462    fn route_target_try_from_multicast_next_hop_v6() {
1463        assert_eq!(
1464            RouteTarget::try_from(fnet_routes::RouteTargetV6 {
1465                outbound_interface: 1,
1466                next_hop: Some(Box::new(fidl_ip_v6!("ff00::1"))),
1467            }),
1468            Err(FidlConversionError::NextHopNotUnicast)
1469        )
1470    }
1471
1472    #[test]
1473    fn route_target_try_from_v4() {
1474        let fidl_type = fnet_routes::RouteTargetV4 {
1475            outbound_interface: 1,
1476            next_hop: Some(Box::new(fidl_ip_v4!("192.168.0.1"))),
1477        };
1478        let local_type = RouteTarget {
1479            outbound_interface: 1,
1480            next_hop: Some(SpecifiedAddr::new(net_ip_v4!("192.168.0.1")).unwrap()),
1481        };
1482        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1483        assert_eq!(
1484            <RouteTarget<Ipv4> as std::convert::Into<fnet_routes::RouteTargetV4>>::into(local_type),
1485            fidl_type
1486        );
1487    }
1488
1489    #[test]
1490    fn route_target_try_from_v6() {
1491        let fidl_type = fnet_routes::RouteTargetV6 {
1492            outbound_interface: 1,
1493            next_hop: Some(Box::new(fidl_ip_v6!("fe80::1"))),
1494        };
1495        let local_type = RouteTarget {
1496            outbound_interface: 1,
1497            next_hop: Some(SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap()),
1498        };
1499        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1500        assert_eq!(
1501            <RouteTarget<Ipv6> as std::convert::Into<fnet_routes::RouteTargetV6>>::into(local_type),
1502            fidl_type
1503        );
1504    }
1505
1506    #[test]
1507    fn route_action_try_from_forward_v4() {
1508        let fidl_type =
1509            fnet_routes::RouteActionV4::Forward(fnet_routes::RouteTargetV4::arbitrary_test_value());
1510        let local_type = RouteAction::Forward(
1511            fnet_routes::RouteTargetV4::arbitrary_test_value().try_into().unwrap(),
1512        );
1513        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1514        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1515    }
1516
1517    #[test]
1518    fn route_action_try_from_forward_v6() {
1519        let fidl_type =
1520            fnet_routes::RouteActionV6::Forward(fnet_routes::RouteTargetV6::arbitrary_test_value());
1521        let local_type = RouteAction::Forward(
1522            fnet_routes::RouteTargetV6::arbitrary_test_value().try_into().unwrap(),
1523        );
1524        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1525        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1526    }
1527
1528    #[test]
1529    fn route_action_try_from_unknown_v4() {
1530        let fidl_type = fnet_routes::RouteActionV4::unknown_variant_for_testing();
1531        const LOCAL_TYPE: RouteAction<Ipv4> = RouteAction::Unknown;
1532        assert_eq!(fidl_type.try_into(), Ok(LOCAL_TYPE));
1533        assert_eq!(
1534            LOCAL_TYPE.try_into(),
1535            Err::<fnet_routes::RouteActionV4, _>(NetTypeConversionError::UnknownUnionVariant(
1536                "fuchsia.net.routes/RouteActionV4"
1537            ))
1538        );
1539    }
1540
1541    #[test]
1542    fn route_action_try_from_unknown_v6() {
1543        let fidl_type = fnet_routes::RouteActionV6::unknown_variant_for_testing();
1544        const LOCAL_TYPE: RouteAction<Ipv6> = RouteAction::Unknown;
1545        assert_eq!(fidl_type.try_into(), Ok(LOCAL_TYPE));
1546        assert_eq!(
1547            LOCAL_TYPE.try_into(),
1548            Err::<fnet_routes::RouteActionV6, _>(NetTypeConversionError::UnknownUnionVariant(
1549                "fuchsia.net.routes/RouteActionV6"
1550            ))
1551        );
1552    }
1553
1554    #[test]
1555    fn route_try_from_invalid_destination_v4() {
1556        assert_matches!(
1557            Route::try_from(fnet_routes::RouteV4 {
1558                // Invalid, because subnets should not have the "host bits" set.
1559                destination: fidl_ip_v4_with_prefix!("192.168.0.1/24"),
1560                action: fnet_routes::RouteActionV4::arbitrary_test_value(),
1561                properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
1562            }),
1563            Err(FidlConversionError::DestinationSubnet(_))
1564        );
1565    }
1566
1567    #[test]
1568    fn route_try_from_invalid_destination_v6() {
1569        assert_matches!(
1570            Route::try_from(fnet_routes::RouteV6 {
1571                // Invalid, because subnets should not have the "host bits" set.
1572                destination: fidl_ip_v6_with_prefix!("fe80::1/64"),
1573                action: fnet_routes::RouteActionV6::arbitrary_test_value(),
1574                properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
1575            }),
1576            Err(FidlConversionError::DestinationSubnet(_))
1577        );
1578    }
1579
1580    #[test]
1581    fn route_try_from_v4() {
1582        let fidl_type = fnet_routes::RouteV4 {
1583            destination: fidl_ip_v4_with_prefix!("192.168.0.0/24"),
1584            action: fnet_routes::RouteActionV4::arbitrary_test_value(),
1585            properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
1586        };
1587        let local_type = Route {
1588            destination: net_subnet_v4!("192.168.0.0/24"),
1589            action: fnet_routes::RouteActionV4::arbitrary_test_value().try_into().unwrap(),
1590            properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value().try_into().unwrap(),
1591        };
1592        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1593        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1594    }
1595
1596    #[test]
1597    fn route_try_from_v6() {
1598        let fidl_type = fnet_routes::RouteV6 {
1599            destination: fidl_ip_v6_with_prefix!("fe80::0/64"),
1600            action: fnet_routes::RouteActionV6::arbitrary_test_value(),
1601            properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
1602        };
1603        let local_type = Route {
1604            destination: net_subnet_v6!("fe80::0/64"),
1605            action: fnet_routes::RouteActionV6::arbitrary_test_value().try_into().unwrap(),
1606            properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value().try_into().unwrap(),
1607        };
1608        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1609        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1610    }
1611
1612    #[test]
1613    fn installed_route_try_from_unset_route_v4() {
1614        assert_eq!(
1615            InstalledRoute::try_from(fnet_routes::InstalledRouteV4 {
1616                route: None,
1617                effective_properties: Some(
1618                    fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1619                ),
1620                table_id: Some(ARBITRARY_TABLE_ID.get()),
1621                ..Default::default()
1622            }),
1623            Err(FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::Route))
1624        )
1625    }
1626
1627    #[test]
1628    fn installed_route_try_from_unset_route_v6() {
1629        assert_eq!(
1630            InstalledRoute::try_from(fnet_routes::InstalledRouteV6 {
1631                route: None,
1632                effective_properties: Some(
1633                    fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1634                ),
1635                table_id: Some(ARBITRARY_TABLE_ID.get()),
1636                ..Default::default()
1637            }),
1638            Err(FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::Route))
1639        )
1640    }
1641
1642    #[test]
1643    fn installed_route_try_from_unset_effective_properties_v4() {
1644        assert_eq!(
1645            InstalledRoute::try_from(fnet_routes::InstalledRouteV4 {
1646                route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
1647                effective_properties: None,
1648                table_id: Some(ARBITRARY_TABLE_ID.get()),
1649                ..Default::default()
1650            }),
1651            Err(FidlConversionError::RequiredFieldUnset(
1652                InstalledRouteRequiredFields::EffectiveProperties
1653            ))
1654        )
1655    }
1656
1657    #[test]
1658    fn installed_route_try_from_unset_effective_properties_v6() {
1659        assert_eq!(
1660            InstalledRoute::try_from(fnet_routes::InstalledRouteV6 {
1661                route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
1662                effective_properties: None,
1663                table_id: Some(ARBITRARY_TABLE_ID.get()),
1664                ..Default::default()
1665            }),
1666            Err(FidlConversionError::RequiredFieldUnset(
1667                InstalledRouteRequiredFields::EffectiveProperties
1668            ))
1669        )
1670    }
1671
1672    #[test]
1673    fn installed_route_try_from_v4() {
1674        let fidl_type = fnet_routes::InstalledRouteV4 {
1675            route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
1676            effective_properties: Some(
1677                fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1678            ),
1679            table_id: Some(ARBITRARY_TABLE_ID.get()),
1680            ..Default::default()
1681        };
1682        let local_type = InstalledRoute {
1683            route: fnet_routes::RouteV4::arbitrary_test_value().try_into().unwrap(),
1684            effective_properties: fnet_routes::EffectiveRouteProperties::arbitrary_test_value()
1685                .try_into()
1686                .unwrap(),
1687            table_id: ARBITRARY_TABLE_ID,
1688        };
1689        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1690        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1691    }
1692
1693    #[test]
1694    fn installed_route_try_from_v6() {
1695        let fidl_type = fnet_routes::InstalledRouteV6 {
1696            route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
1697            effective_properties: Some(
1698                fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1699            ),
1700            table_id: Some(ARBITRARY_TABLE_ID.get()),
1701            ..Default::default()
1702        };
1703        let local_type = InstalledRoute {
1704            route: fnet_routes::RouteV6::arbitrary_test_value().try_into().unwrap(),
1705            effective_properties: fnet_routes::EffectiveRouteProperties::arbitrary_test_value()
1706                .try_into()
1707                .unwrap(),
1708            table_id: ARBITRARY_TABLE_ID,
1709        };
1710        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1711        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1712    }
1713
1714    #[test]
1715    fn event_try_from_v4() {
1716        let fidl_route = fnet_routes::InstalledRouteV4::arbitrary_test_value();
1717        let local_route = fidl_route.clone().try_into().unwrap();
1718        assert_eq!(
1719            fnet_routes::EventV4::unknown_variant_for_testing().try_into(),
1720            Ok(Event::Unknown)
1721        );
1722        assert_eq!(
1723            Event::<Ipv4>::Unknown.try_into(),
1724            Err::<fnet_routes::EventV4, _>(NetTypeConversionError::UnknownUnionVariant(
1725                "fuchsia_net_routes.EventV4"
1726            ))
1727        );
1728        assert_eq!(
1729            fnet_routes::EventV4::Existing(fidl_route.clone()).try_into(),
1730            Ok(Event::Existing(local_route))
1731        );
1732        assert_eq!(
1733            Event::Existing(local_route).try_into(),
1734            Ok(fnet_routes::EventV4::Existing(fidl_route.clone()))
1735        );
1736
1737        assert_eq!(fnet_routes::EventV4::Idle(fnet_routes::Empty).try_into(), Ok(Event::Idle));
1738        assert_eq!(Event::Idle.try_into(), Ok(fnet_routes::EventV4::Idle(fnet_routes::Empty)));
1739        assert_eq!(
1740            fnet_routes::EventV4::Added(fidl_route.clone()).try_into(),
1741            Ok(Event::Added(local_route))
1742        );
1743        assert_eq!(
1744            Event::Added(local_route).try_into(),
1745            Ok(fnet_routes::EventV4::Added(fidl_route.clone()))
1746        );
1747        assert_eq!(
1748            fnet_routes::EventV4::Removed(fidl_route.clone()).try_into(),
1749            Ok(Event::Removed(local_route))
1750        );
1751        assert_eq!(
1752            Event::Removed(local_route).try_into(),
1753            Ok(fnet_routes::EventV4::Removed(fidl_route.clone()))
1754        );
1755    }
1756
1757    #[test]
1758    fn event_try_from_v6() {
1759        let fidl_route = fnet_routes::InstalledRouteV6::arbitrary_test_value();
1760        let local_route = fidl_route.clone().try_into().unwrap();
1761        assert_eq!(
1762            fnet_routes::EventV6::unknown_variant_for_testing().try_into(),
1763            Ok(Event::Unknown)
1764        );
1765        assert_eq!(
1766            Event::<Ipv6>::Unknown.try_into(),
1767            Err::<fnet_routes::EventV6, _>(NetTypeConversionError::UnknownUnionVariant(
1768                "fuchsia_net_routes.EventV6"
1769            ))
1770        );
1771        assert_eq!(
1772            fnet_routes::EventV6::Existing(fidl_route.clone()).try_into(),
1773            Ok(Event::Existing(local_route))
1774        );
1775        assert_eq!(
1776            Event::Existing(local_route).try_into(),
1777            Ok(fnet_routes::EventV6::Existing(fidl_route.clone()))
1778        );
1779
1780        assert_eq!(fnet_routes::EventV6::Idle(fnet_routes::Empty).try_into(), Ok(Event::Idle));
1781        assert_eq!(Event::Idle.try_into(), Ok(fnet_routes::EventV6::Idle(fnet_routes::Empty)));
1782        assert_eq!(
1783            fnet_routes::EventV6::Added(fidl_route.clone()).try_into(),
1784            Ok(Event::Added(local_route))
1785        );
1786        assert_eq!(
1787            Event::Added(local_route).try_into(),
1788            Ok(fnet_routes::EventV6::Added(fidl_route.clone()))
1789        );
1790        assert_eq!(
1791            fnet_routes::EventV6::Removed(fidl_route.clone()).try_into(),
1792            Ok(Event::Removed(local_route))
1793        );
1794        assert_eq!(
1795            Event::Removed(local_route).try_into(),
1796            Ok(fnet_routes::EventV6::Removed(fidl_route.clone()))
1797        );
1798    }
1799
1800    // Tests the `event_stream_from_state` with various "shapes". The test
1801    // parameter is a vec of ranges, where each range corresponds to the batch
1802    // of events that will be sent in response to a single call to `Watch().
1803    #[ip_test(I)]
1804    #[test_case(Vec::new(); "no events")]
1805    #[test_case(vec![0..1]; "single_batch_single_event")]
1806    #[test_case(vec![0..10]; "single_batch_many_events")]
1807    #[test_case(vec![0..10, 10..20, 20..30]; "many_batches_many_events")]
1808    #[fuchsia_async::run_singlethreaded(test)]
1809    async fn event_stream_from_state_against_shape<I: FidlRouteIpExt>(
1810        test_shape: Vec<std::ops::Range<u32>>,
1811    ) {
1812        // Build the event stream based on the `test_shape`. Use a channel
1813        // so that the stream stays open until `close_channel` is called later.
1814        let (batches_sender, batches_receiver) =
1815            futures::channel::mpsc::unbounded::<Vec<I::WatchEvent>>();
1816        for batch_shape in &test_shape {
1817            batches_sender
1818                .unbounded_send(internal_testutil::generate_events_in_range::<I>(
1819                    batch_shape.clone(),
1820                ))
1821                .expect("failed to send event batch");
1822        }
1823
1824        // Instantiate the fake Watcher implementation.
1825        #[cfg(feature = "fdomain")]
1826        let client = fdomain_local::local_client_empty();
1827        #[cfg(not(feature = "fdomain"))]
1828        let client = fidl::endpoints::ZirconClient;
1829        let (state, state_server_end) = client.create_proxy::<I::StateMarker>();
1830        let (mut state_request_stream, _control_handle) =
1831            state_server_end.into_stream_and_control_handle();
1832        let watcher_fut = state_request_stream
1833            .next()
1834            .then(|req| {
1835                testutil::serve_state_request::<I>(
1836                    req.expect("State request_stream unexpectedly ended"),
1837                    batches_receiver,
1838                )
1839            })
1840            .fuse();
1841
1842        let event_stream =
1843            event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
1844
1845        futures::pin_mut!(watcher_fut, event_stream);
1846
1847        for batch_shape in test_shape {
1848            for event_idx in batch_shape.into_iter() {
1849                futures::select! {
1850                    () = watcher_fut => panic!("fake watcher implementation unexpectedly finished"),
1851                    event = event_stream.next() => {
1852                        let actual_event = event
1853                            .expect("event stream unexpectedly empty")
1854                            .expect("error processing event");
1855                        let expected_event = internal_testutil::generate_event::<I>(event_idx)
1856                                .try_into()
1857                                .expect("test event is unexpectedly invalid");
1858                        assert_eq!(actual_event, expected_event);
1859                    }
1860                };
1861            }
1862        }
1863
1864        // Close `batches_sender` and observe that the `event_stream` ends.
1865        batches_sender.close_channel();
1866        let ((), mut events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
1867        assert_matches!(
1868            events.pop(),
1869            Some(Err(WatchError::Fidl(fidl::Error::ClientChannelClosed {
1870                epitaph: fidl::Epitaph::PeerClosed,
1871                ..
1872            })))
1873        );
1874        assert_matches!(events[..], []);
1875    }
1876
1877    // Verify that calling `event_stream_from_state` multiple times with the
1878    // same `State` proxy, results in independent `Watcher` clients.
1879    #[ip_test(I)]
1880    #[fuchsia_async::run_singlethreaded]
1881    async fn event_stream_from_state_multiple_watchers<I: FidlRouteIpExt>() {
1882        // Events for 3 watchers. Each receives one batch containing 10 events.
1883        let test_data = vec![
1884            vec![internal_testutil::generate_events_in_range::<I>(0..10)],
1885            vec![internal_testutil::generate_events_in_range::<I>(10..20)],
1886            vec![internal_testutil::generate_events_in_range::<I>(20..30)],
1887        ];
1888
1889        // Instantiate the fake Watcher implementations.
1890        #[cfg(feature = "fdomain")]
1891        let client = fdomain_local::local_client_empty();
1892        #[cfg(not(feature = "fdomain"))]
1893        let client = fidl::endpoints::ZirconClient;
1894        let (state, state_server_end) = client.create_proxy::<I::StateMarker>();
1895        let (state_request_stream, _control_handle) =
1896            state_server_end.into_stream_and_control_handle();
1897        let watchers_fut = state_request_stream
1898            .zip(futures::stream::iter(test_data.clone()))
1899            .for_each_concurrent(std::usize::MAX, |(request, watcher_data)| {
1900                testutil::serve_state_request::<I>(request, futures::stream::iter(watcher_data))
1901            });
1902
1903        let validate_event_streams_fut =
1904            futures::future::join_all(test_data.into_iter().map(|watcher_data| {
1905                let events_fut = event_stream_from_state::<I>(&state)
1906                    .expect("failed to connect to watcher")
1907                    .collect::<std::collections::VecDeque<_>>();
1908                events_fut.then(|mut events| {
1909                    for expected_event in watcher_data.into_iter().flatten() {
1910                        assert_eq!(
1911                            events
1912                                .pop_front()
1913                                .expect("event_stream unexpectedly empty")
1914                                .expect("error processing event"),
1915                            expected_event.try_into().expect("test event is unexpectedly invalid"),
1916                        );
1917                    }
1918                    assert_matches!(
1919                        events.pop_front(),
1920                        Some(Err(WatchError::Fidl(fidl::Error::ClientChannelClosed {
1921                            epitaph: fidl::Epitaph::PeerClosed,
1922                            ..
1923                        })))
1924                    );
1925                    assert_matches!(events.make_contiguous(), []);
1926                    futures::future::ready(())
1927                })
1928            }));
1929
1930        let ((), _): ((), Vec<()>) = futures::join!(watchers_fut, validate_event_streams_fut);
1931    }
1932
1933    // Verify that failing to convert an event results in an error and closes
1934    // the event stream. `trailing_event` and `trailing_batch` control whether
1935    // a good event is sent after the bad event, either as part of the same
1936    // batch or in a subsequent batch. The test expects this data to be
1937    // truncated from the resulting event_stream.
1938    #[ip_test(I)]
1939    #[test_case(false, false; "no_trailing")]
1940    #[test_case(true, false; "trailing_event")]
1941    #[test_case(false, true; "trailing_batch")]
1942    #[test_case(true, true; "trailing_event_and_batch")]
1943    #[fuchsia_async::run_singlethreaded(test)]
1944    async fn event_stream_from_state_conversion_error<I: FidlRouteIpExt>(
1945        trailing_event: bool,
1946        trailing_batch: bool,
1947    ) {
1948        // Define an event with an invalid destination subnet; receiving it
1949        // from a call to `Watch` will result in conversion errors.
1950        #[derive(GenericOverIp)]
1951        #[generic_over_ip(I, Ip)]
1952        struct EventHolder<I: FidlRouteIpExt>(I::WatchEvent);
1953        let EventHolder(bad_event) = I::map_ip(
1954            (),
1955            |()| {
1956                EventHolder(fnet_routes::EventV4::Added(fnet_routes::InstalledRouteV4 {
1957                    route: Some(fnet_routes::RouteV4 {
1958                        destination: fidl_ip_v4_with_prefix!("192.168.0.1/24"),
1959                        ..fnet_routes::RouteV4::arbitrary_test_value()
1960                    }),
1961                    ..fnet_routes::InstalledRouteV4::arbitrary_test_value()
1962                }))
1963            },
1964            |()| {
1965                EventHolder(fnet_routes::EventV6::Added(fnet_routes::InstalledRouteV6 {
1966                    route: Some(fnet_routes::RouteV6 {
1967                        destination: fidl_ip_v6_with_prefix!("fe80::1/64"),
1968                        ..fnet_routes::RouteV6::arbitrary_test_value()
1969                    }),
1970                    ..fnet_routes::InstalledRouteV6::arbitrary_test_value()
1971                }))
1972            },
1973        );
1974
1975        let batch = std::iter::once(bad_event)
1976            // Optionally append a known good event to the batch.
1977            .chain(trailing_event.then(|| internal_testutil::generate_event::<I>(0)).into_iter())
1978            .collect::<Vec<_>>();
1979        let batches = std::iter::once(batch)
1980            // Optionally append a known good batch to the sequence of batches.
1981            .chain(trailing_batch.then(|| vec![internal_testutil::generate_event::<I>(1)]))
1982            .collect::<Vec<_>>();
1983
1984        // Instantiate the fake Watcher implementation.
1985        #[cfg(feature = "fdomain")]
1986        let client = fdomain_local::local_client_empty();
1987        #[cfg(not(feature = "fdomain"))]
1988        let client = fidl::endpoints::ZirconClient;
1989        let (state, state_server_end) = client.create_proxy::<I::StateMarker>();
1990        let (mut state_request_stream, _control_handle) =
1991            state_server_end.into_stream_and_control_handle();
1992        let watcher_fut = state_request_stream
1993            .next()
1994            .then(|req| {
1995                testutil::serve_state_request::<I>(
1996                    req.expect("State request_stream unexpectedly ended"),
1997                    futures::stream::iter(batches),
1998                )
1999            })
2000            .fuse();
2001
2002        let event_stream =
2003            event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
2004
2005        futures::pin_mut!(watcher_fut, event_stream);
2006        let ((), events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
2007        assert_matches!(&events[..], &[Err(WatchError::Conversion(_))]);
2008    }
2009
2010    // Verify that watching an empty batch results in an error and closes
2011    // the event stream. When `trailing_batch` is true, an additional "good"
2012    // batch will be sent after the empty batch; the test expects this data to
2013    // be truncated from the resulting event_stream.
2014    #[ip_test(I)]
2015    #[test_case(false; "no_trailing_batch")]
2016    #[test_case(true; "trailing_batch")]
2017    #[fuchsia_async::run_singlethreaded(test)]
2018    async fn event_stream_from_state_empty_batch_error<I: FidlRouteIpExt>(trailing_batch: bool) {
2019        let batches = std::iter::once(Vec::new())
2020            // Optionally append a known good batch to the sequence of batches.
2021            .chain(trailing_batch.then(|| vec![internal_testutil::generate_event::<I>(0)]))
2022            .collect::<Vec<_>>();
2023
2024        // Instantiate the fake Watcher implementation.
2025        #[cfg(feature = "fdomain")]
2026        let client = fdomain_local::local_client_empty();
2027        #[cfg(not(feature = "fdomain"))]
2028        let client = fidl::endpoints::ZirconClient;
2029        let (state, state_server_end) = client.create_proxy::<I::StateMarker>();
2030        let (mut state_request_stream, _control_handle) =
2031            state_server_end.into_stream_and_control_handle();
2032        let watcher_fut = state_request_stream
2033            .next()
2034            .then(|req| {
2035                testutil::serve_state_request::<I>(
2036                    req.expect("State request_stream unexpectedly ended"),
2037                    futures::stream::iter(batches),
2038                )
2039            })
2040            .fuse();
2041
2042        let event_stream =
2043            event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
2044
2045        futures::pin_mut!(watcher_fut, event_stream);
2046        let ((), events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
2047        assert_matches!(&events[..], &[Err(WatchError::EmptyEventBatch)]);
2048    }
2049
2050    fn arbitrary_test_route<I: Ip + FidlRouteIpExt>() -> InstalledRoute<I> {
2051        #[derive(GenericOverIp)]
2052        #[generic_over_ip(I, Ip)]
2053        struct RouteHolder<I: FidlRouteIpExt>(InstalledRoute<I>);
2054        let RouteHolder(route) = I::map_ip(
2055            (),
2056            |()| {
2057                RouteHolder(
2058                    fnet_routes::InstalledRouteV4::arbitrary_test_value().try_into().unwrap(),
2059                )
2060            },
2061            |()| {
2062                RouteHolder(
2063                    fnet_routes::InstalledRouteV6::arbitrary_test_value().try_into().unwrap(),
2064                )
2065            },
2066        );
2067        route
2068    }
2069
2070    enum CollectRoutesUntilIdleErrorTestCase {
2071        ErrorInStream,
2072        UnexpectedEvent,
2073        StreamEnded,
2074    }
2075
2076    #[ip_test(I)]
2077    #[test_case(CollectRoutesUntilIdleErrorTestCase::ErrorInStream; "error_in_stream")]
2078    #[test_case(CollectRoutesUntilIdleErrorTestCase::UnexpectedEvent; "unexpected_event")]
2079    #[test_case(CollectRoutesUntilIdleErrorTestCase::StreamEnded; "stream_ended")]
2080    #[fuchsia_async::run_singlethreaded(test)]
2081    async fn collect_routes_until_idle_error<I: FidlRouteIpExt>(
2082        test_case: CollectRoutesUntilIdleErrorTestCase,
2083    ) {
2084        // Build up the test data and the expected outcome base on `test_case`.
2085        // Note, that `netstack_test` doesn't support test cases whose args are
2086        // generic functions (below, `test_assertion` is generic over `I`).
2087        let route = arbitrary_test_route();
2088        let (event, test_assertion): (_, Box<dyn FnOnce(_)>) = match test_case {
2089            CollectRoutesUntilIdleErrorTestCase::ErrorInStream => (
2090                Err(WatchError::EmptyEventBatch),
2091                Box::new(|result| {
2092                    assert_matches!(result, Err(CollectRoutesUntilIdleError::ErrorInStream(_)))
2093                }),
2094            ),
2095            CollectRoutesUntilIdleErrorTestCase::UnexpectedEvent => (
2096                Ok(Event::Added(route)),
2097                Box::new(|result| {
2098                    assert_matches!(result, Err(CollectRoutesUntilIdleError::UnexpectedEvent(_)))
2099                }),
2100            ),
2101            CollectRoutesUntilIdleErrorTestCase::StreamEnded => (
2102                Ok(Event::Existing(route)),
2103                Box::new(|result| {
2104                    assert_matches!(result, Err(CollectRoutesUntilIdleError::StreamEnded))
2105                }),
2106            ),
2107        };
2108
2109        let event_stream = futures::stream::once(futures::future::ready(event));
2110        futures::pin_mut!(event_stream);
2111        let result = collect_routes_until_idle::<I, Vec<_>>(event_stream).await;
2112        test_assertion(result);
2113    }
2114
2115    // Verifies that `collect_routes_until_idle` collects all existing events,
2116    // drops the idle event, and leaves all trailing events intact.
2117    #[ip_test(I)]
2118    #[fuchsia_async::run_singlethreaded]
2119    async fn collect_routes_until_idle_success<I: FidlRouteIpExt>() {
2120        let route = arbitrary_test_route();
2121        let event_stream = futures::stream::iter([
2122            Ok(Event::Existing(route)),
2123            Ok(Event::Idle),
2124            Ok(Event::Added(route)),
2125        ]);
2126
2127        futures::pin_mut!(event_stream);
2128        let existing = collect_routes_until_idle::<I, Vec<_>>(event_stream.by_ref())
2129            .await
2130            .expect("failed to collect existing routes");
2131        assert_eq!(&existing, &[route]);
2132
2133        let trailing_events = event_stream.collect::<Vec<_>>().await;
2134        assert_matches!(
2135            &trailing_events[..],
2136            &[Ok(Event::Added(found_route))] if found_route == route
2137        );
2138    }
2139
2140    #[ip_test(I)]
2141    #[fuchsia_async::run_singlethreaded]
2142    async fn wait_for_routes_errors<I: FidlRouteIpExt>() {
2143        let mut state = HashSet::new();
2144        let event_stream =
2145            futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2146        assert_matches!(
2147            wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
2148            Err(WaitForRoutesError::ErrorInStream(WatchError::EmptyEventBatch))
2149        );
2150        assert!(state.is_empty());
2151
2152        let event_stream = futures::stream::empty();
2153        assert_matches!(
2154            wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
2155            Err(WaitForRoutesError::StreamEnded)
2156        );
2157        assert!(state.is_empty());
2158
2159        let event_stream = futures::stream::once(futures::future::ready(Ok(Event::<I>::Unknown)));
2160        assert_matches!(
2161            wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
2162            Err(WaitForRoutesError::UnknownEvent)
2163        );
2164        assert!(state.is_empty());
2165    }
2166
2167    #[ip_test(I)]
2168    #[fuchsia_async::run_singlethreaded]
2169    async fn wait_for_routes_add_remove<I: FidlRouteIpExt>() {
2170        let into_stream = |t| futures::stream::once(futures::future::ready(t));
2171
2172        let route = arbitrary_test_route::<I>();
2173        let mut state = HashSet::new();
2174
2175        // Verify that checking for the presence of a route blocks until the
2176        // route is added.
2177        let has_route = |routes: &HashSet<InstalledRoute<I>>| routes.contains(&route);
2178        assert_matches!(
2179            wait_for_routes::<I, _, _>(futures::stream::pending(), &mut state, has_route)
2180                .now_or_never(),
2181            None
2182        );
2183        assert!(state.is_empty());
2184        assert_matches!(
2185            wait_for_routes::<I, _, _>(into_stream(Ok(Event::Added(route))), &mut state, has_route)
2186                .now_or_never(),
2187            Some(Ok(()))
2188        );
2189        assert_eq!(state, HashSet::from_iter([route]));
2190
2191        // Re-add the route and observe an error.
2192        assert_matches!(
2193            wait_for_routes::<I, _, _>(into_stream(Ok(Event::Added(route))), &mut state, has_route)
2194                .now_or_never(),
2195            Some(Err(WaitForRoutesError::AddedAlreadyExisting(r))) if r == route
2196        );
2197        assert_eq!(state, HashSet::from_iter([route]));
2198
2199        // Verify that checking for the absence of a route blocks until the
2200        // route is removed.
2201        let does_not_have_route = |routes: &HashSet<InstalledRoute<I>>| !routes.contains(&route);
2202        assert_matches!(
2203            wait_for_routes::<I, _, _>(futures::stream::pending(), &mut state, does_not_have_route)
2204                .now_or_never(),
2205            None
2206        );
2207        assert_eq!(state, HashSet::from_iter([route]));
2208        assert_matches!(
2209            wait_for_routes::<I, _, _>(
2210                into_stream(Ok(Event::Removed(route))),
2211                &mut state,
2212                does_not_have_route
2213            )
2214            .now_or_never(),
2215            Some(Ok(()))
2216        );
2217        assert!(state.is_empty());
2218
2219        // Remove a non-existent route and observe an error.
2220        assert_matches!(
2221            wait_for_routes::<I, _, _>(
2222                into_stream(Ok(Event::Removed(route))),
2223                &mut state,
2224                does_not_have_route
2225            ).now_or_never(),
2226            Some(Err(WaitForRoutesError::RemovedNonExistent(r))) if r == route
2227        );
2228        assert!(state.is_empty());
2229    }
2230}