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 futures::{Future, Stream, TryStreamExt as _};
25use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6, Ipv6Addr, Subnet};
26use net_types::{SpecifiedAddr, UnicastAddress, Witness as _};
27use thiserror::Error;
28use {
29    fidl_fuchsia_net as fnet, fidl_fuchsia_net_routes as fnet_routes,
30    fidl_fuchsia_net_routes_admin as fnet_routes_admin, fidl_fuchsia_net_stack as fnet_stack,
31};
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: fidl::endpoints::DiscoverableProtocolMarker;
815    /// The "watcher" protocol to use for this IP version.
816    type WatcherMarker: fidl::endpoints::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: fidl::endpoints::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>: fidl::endpoints::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 fidl::endpoints::ProtocolMarker>::Proxy,
922    options: WatcherOptions,
923) -> Result<<I::WatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy, WatcherCreationError> {
924    let (watcher_proxy, watcher_server_end) = fidl::endpoints::create_proxy::<I::WatcherMarker>();
925
926    #[derive(GenericOverIp)]
927    #[generic_over_ip(I, Ip)]
928    struct GetWatcherInputs<'a, I: FidlRouteIpExt> {
929        watcher_server_end: fidl::endpoints::ServerEnd<I::WatcherMarker>,
930        state_proxy: &'a <I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
931        options: WatcherOptions,
932    }
933    let result = I::map_ip_in(
934        GetWatcherInputs::<'_, I> { watcher_server_end, state_proxy, options },
935        |GetWatcherInputs { watcher_server_end, state_proxy, options }| {
936            state_proxy.get_watcher_v4(watcher_server_end, &options.into())
937        },
938        |GetWatcherInputs { watcher_server_end, state_proxy, options }| {
939            state_proxy.get_watcher_v6(watcher_server_end, &options.into())
940        },
941    );
942
943    result.map_err(WatcherCreationError::GetWatcher)?;
944    Ok(watcher_proxy)
945}
946
947/// Calls `Watch()` on the provided `WatcherV4` or `WatcherV6` proxy.
948pub fn watch<'a, I: FidlRouteIpExt>(
949    watcher_proxy: &'a <I::WatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
950) -> impl Future<Output = Result<Vec<I::WatchEvent>, fidl::Error>> {
951    #[derive(GenericOverIp)]
952    #[generic_over_ip(I, Ip)]
953    struct WatchInputs<'a, I: FidlRouteIpExt> {
954        watcher_proxy: &'a <I::WatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
955    }
956    #[derive(GenericOverIp)]
957    #[generic_over_ip(I, Ip)]
958    struct WatchOutputs<I: FidlRouteIpExt> {
959        watch_fut: fidl::client::QueryResponseFut<Vec<I::WatchEvent>>,
960    }
961    let WatchOutputs { watch_fut } = I::map_ip::<WatchInputs<'_, I>, WatchOutputs<I>>(
962        WatchInputs { watcher_proxy },
963        |WatchInputs { watcher_proxy }| WatchOutputs { watch_fut: watcher_proxy.watch() },
964        |WatchInputs { watcher_proxy }| WatchOutputs { watch_fut: watcher_proxy.watch() },
965    );
966    watch_fut
967}
968
969/// [`event_stream_from_state_with_options`] with default [`WatcherOptions`].
970pub fn event_stream_from_state<I: FidlRouteIpExt>(
971    routes_state: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
972) -> Result<impl Stream<Item = Result<Event<I>, WatchError>>, WatcherCreationError> {
973    event_stream_from_state_with_options(routes_state, Default::default())
974}
975
976/// Connects to the watcher protocol with [`WatcherOptions`] and converts the
977/// Hanging-Get style API into an Event stream.
978///
979/// Each call to `Watch` returns a batch of events, which are flattened into a
980/// single stream. If an error is encountered while calling `Watch` or while
981/// converting the event, the stream is immediately terminated.
982pub fn event_stream_from_state_with_options<I: FidlRouteIpExt>(
983    routes_state: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
984    options: WatcherOptions,
985) -> Result<impl Stream<Item = Result<Event<I>, WatchError>>, WatcherCreationError> {
986    let watcher = get_watcher::<I>(routes_state, options)?;
987    event_stream_from_watcher(watcher)
988}
989
990/// Turns the provided watcher client into a [`Event`] stream by applying
991/// Hanging-Get watch.
992///
993/// Each call to `Watch` returns a batch of events, which are flattened into a
994/// single stream. If an error is encountered while calling `Watch` or while
995/// converting the event, the stream is immediately terminated.
996pub fn event_stream_from_watcher<I: FidlRouteIpExt>(
997    watcher: <I::WatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
998) -> Result<impl Stream<Item = Result<Event<I>, WatchError>>, WatcherCreationError> {
999    Ok(stream::ShortCircuit::new(
1000        futures::stream::try_unfold(watcher, |watcher| async {
1001            let events_batch = watch::<I>(&watcher).await.map_err(WatchError::Fidl)?;
1002            if events_batch.is_empty() {
1003                return Err(WatchError::EmptyEventBatch);
1004            }
1005            let events_batch = events_batch
1006                .into_iter()
1007                .map(|event| event.try_into().map_err(WatchError::Conversion));
1008            let event_stream = futures::stream::iter(events_batch);
1009            Ok(Some((event_stream, watcher)))
1010        })
1011        // Flatten the stream of event streams into a single event stream.
1012        .try_flatten(),
1013    ))
1014}
1015
1016/// Errors returned by [`collect_routes_until_idle`].
1017#[derive(Clone, Debug, Error)]
1018pub enum CollectRoutesUntilIdleError<I: FidlRouteIpExt> {
1019    /// There was an error in the event stream.
1020    #[error("there was an error in the event stream: {0}")]
1021    ErrorInStream(WatchError),
1022    /// There was an unexpected event in the event stream. Only `existing` or
1023    /// `idle` events are expected.
1024    #[error("there was an unexpected event in the event stream: {0:?}")]
1025    UnexpectedEvent(Event<I>),
1026    /// The event stream unexpectedly ended.
1027    #[error("the event stream unexpectedly ended")]
1028    StreamEnded,
1029}
1030
1031/// Collects all `existing` events from the stream, stopping once the `idle`
1032/// event is observed.
1033pub async fn collect_routes_until_idle<
1034    I: FidlRouteIpExt,
1035    C: Extend<InstalledRoute<I>> + Default,
1036>(
1037    event_stream: impl futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
1038) -> Result<C, CollectRoutesUntilIdleError<I>> {
1039    fold::fold_while(
1040        event_stream,
1041        Ok(C::default()),
1042        |existing_routes: Result<C, CollectRoutesUntilIdleError<I>>, event| {
1043            futures::future::ready(match existing_routes {
1044                Err(_) => {
1045                    unreachable!("`existing_routes` must be `Ok`, because we stop folding on err")
1046                }
1047                Ok(mut existing_routes) => match event {
1048                    Err(e) => {
1049                        fold::FoldWhile::Done(Err(CollectRoutesUntilIdleError::ErrorInStream(e)))
1050                    }
1051                    Ok(e) => match e {
1052                        Event::Existing(e) => {
1053                            existing_routes.extend([e]);
1054                            fold::FoldWhile::Continue(Ok(existing_routes))
1055                        }
1056                        Event::Idle => fold::FoldWhile::Done(Ok(existing_routes)),
1057                        e @ Event::Unknown | e @ Event::Added(_) | e @ Event::Removed(_) => {
1058                            fold::FoldWhile::Done(Err(
1059                                CollectRoutesUntilIdleError::UnexpectedEvent(e),
1060                            ))
1061                        }
1062                    },
1063                },
1064            })
1065        },
1066    )
1067    .await
1068    .short_circuited()
1069    .map_err(|_accumulated_thus_far: Result<C, CollectRoutesUntilIdleError<I>>| {
1070        CollectRoutesUntilIdleError::StreamEnded
1071    })?
1072}
1073
1074/// Errors returned by [`wait_for_routes`].
1075#[derive(Clone, Debug, Error)]
1076pub enum WaitForRoutesError<I: FidlRouteIpExt> {
1077    /// There was an error in the event stream.
1078    #[error("there was an error in the event stream: {0}")]
1079    ErrorInStream(WatchError),
1080    /// There was an `Added` event for an already existing route.
1081    #[error("observed an added event for an already existing route: {0:?}")]
1082    AddedAlreadyExisting(InstalledRoute<I>),
1083    /// There was a `Removed` event for a non-existent route.
1084    #[error("observed a removed event for a non-existent route: {0:?}")]
1085    RemovedNonExistent(InstalledRoute<I>),
1086    /// There was an `Unknown` event in the stream.
1087    #[error("observed an unknown event")]
1088    UnknownEvent,
1089    /// The event stream unexpectedly ended.
1090    #[error("the event stream unexpectedly ended")]
1091    StreamEnded,
1092}
1093
1094/// Wait for a condition on routing state to be satisfied, yielding a result
1095/// from the predicate.
1096///
1097/// With the given `initial_state`, take events from `event_stream` and update
1098/// the state, calling `predicate` whenever the state changes. When `predicate`
1099/// returns `Some(T)` yield `Ok(T)`. Note, this function will hang if no events
1100/// arrive on `event_stream`.
1101pub async fn wait_for_routes_map<
1102    I: FidlRouteIpExt,
1103    S: futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
1104    T,
1105    F: Fn(&HashSet<InstalledRoute<I>>) -> Option<T>,
1106>(
1107    event_stream: S,
1108    initial_state: &mut HashSet<InstalledRoute<I>>,
1109    predicate: F,
1110) -> Result<T, WaitForRoutesError<I>> {
1111    fold::try_fold_while(
1112        event_stream.map_err(WaitForRoutesError::ErrorInStream),
1113        initial_state,
1114        |accumulated_routes, event| {
1115            futures::future::ready({
1116                match event {
1117                    Event::Existing(route) | Event::Added(route) => accumulated_routes
1118                        .insert(route)
1119                        .then_some(())
1120                        .ok_or(WaitForRoutesError::AddedAlreadyExisting(route)),
1121                    Event::Removed(route) => accumulated_routes
1122                        .remove(&route)
1123                        .then_some(())
1124                        .ok_or(WaitForRoutesError::RemovedNonExistent(route)),
1125                    Event::Idle => Ok(()),
1126                    Event::Unknown => Err(WaitForRoutesError::UnknownEvent),
1127                }
1128                .map(|()| match predicate(&accumulated_routes) {
1129                    Some(t) => fold::FoldWhile::Done(t),
1130                    None => fold::FoldWhile::Continue(accumulated_routes),
1131                })
1132            })
1133        },
1134    )
1135    .await?
1136    .short_circuited()
1137    .map_err(|_accumulated_thus_far: &mut HashSet<InstalledRoute<I>>| {
1138        WaitForRoutesError::StreamEnded
1139    })
1140}
1141
1142/// Wait for a condition on routing state to be satisfied.
1143///
1144/// With the given `initial_state`, take events from `event_stream` and update
1145/// the state, calling `predicate` whenever the state changes. When predicates
1146/// returns `True` yield `Ok(())`.
1147pub async fn wait_for_routes<
1148    I: FidlRouteIpExt,
1149    S: futures::Stream<Item = Result<Event<I>, WatchError>> + Unpin,
1150    F: Fn(&HashSet<InstalledRoute<I>>) -> bool,
1151>(
1152    event_stream: S,
1153    initial_state: &mut HashSet<InstalledRoute<I>>,
1154    predicate: F,
1155) -> Result<(), WaitForRoutesError<I>> {
1156    wait_for_routes_map::<I, S, (), _>(event_stream, initial_state, |routes| {
1157        predicate(routes).then_some(())
1158    })
1159    .await
1160}
1161
1162/// Resolve options for resolving route.
1163#[derive(Debug, Default, Clone)]
1164pub struct ResolveOptions {
1165    /// The marks used for the route resolution.
1166    pub marks: fnet_ext::Marks,
1167}
1168
1169impl From<fnet_routes::ResolveOptions> for ResolveOptions {
1170    fn from(value: fnet_routes::ResolveOptions) -> Self {
1171        let fnet_routes::ResolveOptions { marks, __source_breaking } = value;
1172        Self { marks: marks.map(fnet_ext::Marks::from).unwrap_or_default() }
1173    }
1174}
1175
1176impl From<ResolveOptions> for fnet_routes::ResolveOptions {
1177    fn from(value: ResolveOptions) -> Self {
1178        let ResolveOptions { marks } = value;
1179        Self { marks: Some(marks.into()), __source_breaking: fidl::marker::SourceBreaking }
1180    }
1181}
1182
1183#[cfg(test)]
1184mod tests {
1185    use super::*;
1186    use crate::testutil::internal as internal_testutil;
1187    use assert_matches::assert_matches;
1188    use futures::{FutureExt as _, StreamExt as _};
1189    use ip_test_macro::ip_test;
1190    use net_declare::{
1191        fidl_ip_v4, fidl_ip_v4_with_prefix, fidl_ip_v6, fidl_ip_v6_with_prefix, net_ip_v4,
1192        net_ip_v6, net_subnet_v4, net_subnet_v6,
1193    };
1194    use test_case::test_case;
1195    use {fidl_fuchsia_net as _, zx_status};
1196
1197    const ARBITRARY_TABLE_ID: TableId = TableId::new(0);
1198
1199    /// Allows types to provided an arbitrary but valid value for tests.
1200    trait ArbitraryTestValue {
1201        fn arbitrary_test_value() -> Self;
1202    }
1203
1204    impl ArbitraryTestValue for fnet_routes::SpecifiedRouteProperties {
1205        fn arbitrary_test_value() -> Self {
1206            fnet_routes::SpecifiedRouteProperties {
1207                metric: Some(fnet_routes::SpecifiedMetric::ExplicitMetric(0)),
1208                ..Default::default()
1209            }
1210        }
1211    }
1212
1213    impl ArbitraryTestValue for fnet_routes::EffectiveRouteProperties {
1214        fn arbitrary_test_value() -> Self {
1215            fnet_routes::EffectiveRouteProperties { metric: Some(0), ..Default::default() }
1216        }
1217    }
1218
1219    impl ArbitraryTestValue for fnet_routes::RoutePropertiesV4 {
1220        fn arbitrary_test_value() -> Self {
1221            fnet_routes::RoutePropertiesV4 {
1222                specified_properties: Some(
1223                    fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
1224                ),
1225                ..Default::default()
1226            }
1227        }
1228    }
1229
1230    impl ArbitraryTestValue for fnet_routes::RoutePropertiesV6 {
1231        fn arbitrary_test_value() -> Self {
1232            fnet_routes::RoutePropertiesV6 {
1233                specified_properties: Some(
1234                    fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
1235                ),
1236                ..Default::default()
1237            }
1238        }
1239    }
1240
1241    impl ArbitraryTestValue for fnet_routes::RouteTargetV4 {
1242        fn arbitrary_test_value() -> Self {
1243            fnet_routes::RouteTargetV4 { outbound_interface: 1, next_hop: None }
1244        }
1245    }
1246
1247    impl ArbitraryTestValue for fnet_routes::RouteTargetV6 {
1248        fn arbitrary_test_value() -> Self {
1249            fnet_routes::RouteTargetV6 { outbound_interface: 1, next_hop: None }
1250        }
1251    }
1252
1253    impl ArbitraryTestValue for fnet_routes::RouteActionV4 {
1254        fn arbitrary_test_value() -> Self {
1255            fnet_routes::RouteActionV4::Forward(fnet_routes::RouteTargetV4::arbitrary_test_value())
1256        }
1257    }
1258
1259    impl ArbitraryTestValue for fnet_routes::RouteActionV6 {
1260        fn arbitrary_test_value() -> Self {
1261            fnet_routes::RouteActionV6::Forward(fnet_routes::RouteTargetV6::arbitrary_test_value())
1262        }
1263    }
1264
1265    impl ArbitraryTestValue for fnet_routes::RouteV4 {
1266        fn arbitrary_test_value() -> Self {
1267            fnet_routes::RouteV4 {
1268                destination: fidl_ip_v4_with_prefix!("192.168.0.0/24"),
1269                action: fnet_routes::RouteActionV4::arbitrary_test_value(),
1270                properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
1271            }
1272        }
1273    }
1274
1275    impl ArbitraryTestValue for fnet_routes::RouteV6 {
1276        fn arbitrary_test_value() -> Self {
1277            fnet_routes::RouteV6 {
1278                destination: fidl_ip_v6_with_prefix!("fe80::0/64"),
1279                action: fnet_routes::RouteActionV6::arbitrary_test_value(),
1280                properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
1281            }
1282        }
1283    }
1284
1285    impl ArbitraryTestValue for fnet_routes::InstalledRouteV4 {
1286        fn arbitrary_test_value() -> Self {
1287            fnet_routes::InstalledRouteV4 {
1288                route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
1289                effective_properties: Some(
1290                    fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1291                ),
1292                table_id: Some(ARBITRARY_TABLE_ID.get()),
1293                ..Default::default()
1294            }
1295        }
1296    }
1297
1298    impl ArbitraryTestValue for fnet_routes::InstalledRouteV6 {
1299        fn arbitrary_test_value() -> Self {
1300            fnet_routes::InstalledRouteV6 {
1301                route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
1302                effective_properties: Some(
1303                    fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1304                ),
1305                table_id: Some(ARBITRARY_TABLE_ID.get()),
1306                ..Default::default()
1307            }
1308        }
1309    }
1310
1311    #[test]
1312    fn specified_route_properties_try_from_unset_metric() {
1313        assert_eq!(
1314            SpecifiedRouteProperties::try_from(fnet_routes::SpecifiedRouteProperties::default()),
1315            Err(FidlConversionError::RequiredFieldUnset(
1316                SpecifiedRoutePropertiesRequiredFields::Metric
1317            ))
1318        )
1319    }
1320
1321    #[test]
1322    fn specified_route_properties_try_from() {
1323        let fidl_type = fnet_routes::SpecifiedRouteProperties {
1324            metric: Some(fnet_routes::SpecifiedMetric::ExplicitMetric(1)),
1325            ..Default::default()
1326        };
1327        let local_type =
1328            SpecifiedRouteProperties { metric: fnet_routes::SpecifiedMetric::ExplicitMetric(1) };
1329        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1330        assert_eq!(
1331            <SpecifiedRouteProperties as std::convert::Into<
1332                fnet_routes::SpecifiedRouteProperties,
1333            >>::into(local_type),
1334            fidl_type.clone()
1335        );
1336    }
1337
1338    #[test]
1339    fn effective_route_properties_try_from_unset_metric() {
1340        assert_eq!(
1341            EffectiveRouteProperties::try_from(fnet_routes::EffectiveRouteProperties::default()),
1342            Err(FidlConversionError::RequiredFieldUnset(
1343                EffectiveRoutePropertiesRequiredFields::Metric
1344            ))
1345        )
1346    }
1347
1348    #[test]
1349    fn effective_route_properties_try_from() {
1350        let fidl_type =
1351            fnet_routes::EffectiveRouteProperties { metric: Some(1), ..Default::default() };
1352        let local_type = EffectiveRouteProperties { metric: 1 };
1353        assert_eq!(fidl_type.clone().try_into(), Ok(EffectiveRouteProperties { metric: 1 }));
1354        assert_eq!(
1355            <EffectiveRouteProperties as std::convert::Into<
1356                fnet_routes::EffectiveRouteProperties,
1357            >>::into(local_type),
1358            fidl_type.clone()
1359        );
1360    }
1361
1362    #[test]
1363    fn route_properties_try_from_unset_specified_properties_v4() {
1364        assert_eq!(
1365            RouteProperties::try_from(fnet_routes::RoutePropertiesV4::default()),
1366            Err(FidlConversionError::RequiredFieldUnset(
1367                RoutePropertiesRequiredFields::SpecifiedProperties
1368            ))
1369        )
1370    }
1371
1372    #[test]
1373    fn route_properties_try_from_unset_specified_properties_v6() {
1374        assert_eq!(
1375            RouteProperties::try_from(fnet_routes::RoutePropertiesV6::default()),
1376            Err(FidlConversionError::RequiredFieldUnset(
1377                RoutePropertiesRequiredFields::SpecifiedProperties
1378            ))
1379        )
1380    }
1381
1382    #[test]
1383    fn route_properties_try_from_v4() {
1384        let fidl_type = fnet_routes::RoutePropertiesV4 {
1385            specified_properties: Some(
1386                fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
1387            ),
1388            ..Default::default()
1389        };
1390        let local_type = RouteProperties {
1391            specified_properties: fnet_routes::SpecifiedRouteProperties::arbitrary_test_value()
1392                .try_into()
1393                .unwrap(),
1394        };
1395        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1396        assert_eq!(
1397            <RouteProperties as std::convert::Into<fnet_routes::RoutePropertiesV4>>::into(
1398                local_type
1399            ),
1400            fidl_type.clone()
1401        );
1402    }
1403
1404    #[test]
1405    fn route_properties_try_from_v6() {
1406        let fidl_type = fnet_routes::RoutePropertiesV6 {
1407            specified_properties: Some(
1408                fnet_routes::SpecifiedRouteProperties::arbitrary_test_value(),
1409            ),
1410            ..Default::default()
1411        };
1412        let local_type = RouteProperties {
1413            specified_properties: fnet_routes::SpecifiedRouteProperties::arbitrary_test_value()
1414                .try_into()
1415                .unwrap(),
1416        };
1417        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1418        assert_eq!(
1419            <RouteProperties as std::convert::Into<fnet_routes::RoutePropertiesV6>>::into(
1420                local_type
1421            ),
1422            fidl_type.clone()
1423        );
1424    }
1425
1426    #[test]
1427    fn route_target_try_from_unspecified_next_hop_v4() {
1428        assert_eq!(
1429            RouteTarget::try_from(fnet_routes::RouteTargetV4 {
1430                outbound_interface: 1,
1431                next_hop: Some(Box::new(fidl_ip_v4!("0.0.0.0"))),
1432            }),
1433            Err(FidlConversionError::UnspecifiedNextHop)
1434        )
1435    }
1436
1437    #[test]
1438    fn route_target_try_from_unspecified_next_hop_v6() {
1439        assert_eq!(
1440            RouteTarget::try_from(fnet_routes::RouteTargetV6 {
1441                outbound_interface: 1,
1442                next_hop: Some(Box::new(fidl_ip_v6!("::"))),
1443            }),
1444            Err(FidlConversionError::UnspecifiedNextHop)
1445        );
1446    }
1447
1448    #[test]
1449    fn route_target_try_from_limited_broadcast_next_hop_v4() {
1450        assert_eq!(
1451            RouteTarget::try_from(fnet_routes::RouteTargetV4 {
1452                outbound_interface: 1,
1453                next_hop: Some(Box::new(fidl_ip_v4!("255.255.255.255"))),
1454            }),
1455            Err(FidlConversionError::NextHopNotUnicast)
1456        )
1457    }
1458
1459    #[test]
1460    fn route_target_try_from_multicast_next_hop_v6() {
1461        assert_eq!(
1462            RouteTarget::try_from(fnet_routes::RouteTargetV6 {
1463                outbound_interface: 1,
1464                next_hop: Some(Box::new(fidl_ip_v6!("ff00::1"))),
1465            }),
1466            Err(FidlConversionError::NextHopNotUnicast)
1467        )
1468    }
1469
1470    #[test]
1471    fn route_target_try_from_v4() {
1472        let fidl_type = fnet_routes::RouteTargetV4 {
1473            outbound_interface: 1,
1474            next_hop: Some(Box::new(fidl_ip_v4!("192.168.0.1"))),
1475        };
1476        let local_type = RouteTarget {
1477            outbound_interface: 1,
1478            next_hop: Some(SpecifiedAddr::new(net_ip_v4!("192.168.0.1")).unwrap()),
1479        };
1480        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1481        assert_eq!(
1482            <RouteTarget<Ipv4> as std::convert::Into<fnet_routes::RouteTargetV4>>::into(local_type),
1483            fidl_type
1484        );
1485    }
1486
1487    #[test]
1488    fn route_target_try_from_v6() {
1489        let fidl_type = fnet_routes::RouteTargetV6 {
1490            outbound_interface: 1,
1491            next_hop: Some(Box::new(fidl_ip_v6!("fe80::1"))),
1492        };
1493        let local_type = RouteTarget {
1494            outbound_interface: 1,
1495            next_hop: Some(SpecifiedAddr::new(net_ip_v6!("fe80::1")).unwrap()),
1496        };
1497        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1498        assert_eq!(
1499            <RouteTarget<Ipv6> as std::convert::Into<fnet_routes::RouteTargetV6>>::into(local_type),
1500            fidl_type
1501        );
1502    }
1503
1504    #[test]
1505    fn route_action_try_from_forward_v4() {
1506        let fidl_type =
1507            fnet_routes::RouteActionV4::Forward(fnet_routes::RouteTargetV4::arbitrary_test_value());
1508        let local_type = RouteAction::Forward(
1509            fnet_routes::RouteTargetV4::arbitrary_test_value().try_into().unwrap(),
1510        );
1511        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1512        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1513    }
1514
1515    #[test]
1516    fn route_action_try_from_forward_v6() {
1517        let fidl_type =
1518            fnet_routes::RouteActionV6::Forward(fnet_routes::RouteTargetV6::arbitrary_test_value());
1519        let local_type = RouteAction::Forward(
1520            fnet_routes::RouteTargetV6::arbitrary_test_value().try_into().unwrap(),
1521        );
1522        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1523        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1524    }
1525
1526    #[test]
1527    fn route_action_try_from_unknown_v4() {
1528        let fidl_type = fnet_routes::RouteActionV4::unknown_variant_for_testing();
1529        const LOCAL_TYPE: RouteAction<Ipv4> = RouteAction::Unknown;
1530        assert_eq!(fidl_type.try_into(), Ok(LOCAL_TYPE));
1531        assert_eq!(
1532            LOCAL_TYPE.try_into(),
1533            Err::<fnet_routes::RouteActionV4, _>(NetTypeConversionError::UnknownUnionVariant(
1534                "fuchsia.net.routes/RouteActionV4"
1535            ))
1536        );
1537    }
1538
1539    #[test]
1540    fn route_action_try_from_unknown_v6() {
1541        let fidl_type = fnet_routes::RouteActionV6::unknown_variant_for_testing();
1542        const LOCAL_TYPE: RouteAction<Ipv6> = RouteAction::Unknown;
1543        assert_eq!(fidl_type.try_into(), Ok(LOCAL_TYPE));
1544        assert_eq!(
1545            LOCAL_TYPE.try_into(),
1546            Err::<fnet_routes::RouteActionV6, _>(NetTypeConversionError::UnknownUnionVariant(
1547                "fuchsia.net.routes/RouteActionV6"
1548            ))
1549        );
1550    }
1551
1552    #[test]
1553    fn route_try_from_invalid_destination_v4() {
1554        assert_matches!(
1555            Route::try_from(fnet_routes::RouteV4 {
1556                // Invalid, because subnets should not have the "host bits" set.
1557                destination: fidl_ip_v4_with_prefix!("192.168.0.1/24"),
1558                action: fnet_routes::RouteActionV4::arbitrary_test_value(),
1559                properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
1560            }),
1561            Err(FidlConversionError::DestinationSubnet(_))
1562        );
1563    }
1564
1565    #[test]
1566    fn route_try_from_invalid_destination_v6() {
1567        assert_matches!(
1568            Route::try_from(fnet_routes::RouteV6 {
1569                // Invalid, because subnets should not have the "host bits" set.
1570                destination: fidl_ip_v6_with_prefix!("fe80::1/64"),
1571                action: fnet_routes::RouteActionV6::arbitrary_test_value(),
1572                properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
1573            }),
1574            Err(FidlConversionError::DestinationSubnet(_))
1575        );
1576    }
1577
1578    #[test]
1579    fn route_try_from_v4() {
1580        let fidl_type = fnet_routes::RouteV4 {
1581            destination: fidl_ip_v4_with_prefix!("192.168.0.0/24"),
1582            action: fnet_routes::RouteActionV4::arbitrary_test_value(),
1583            properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value(),
1584        };
1585        let local_type = Route {
1586            destination: net_subnet_v4!("192.168.0.0/24"),
1587            action: fnet_routes::RouteActionV4::arbitrary_test_value().try_into().unwrap(),
1588            properties: fnet_routes::RoutePropertiesV4::arbitrary_test_value().try_into().unwrap(),
1589        };
1590        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1591        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1592    }
1593
1594    #[test]
1595    fn route_try_from_v6() {
1596        let fidl_type = fnet_routes::RouteV6 {
1597            destination: fidl_ip_v6_with_prefix!("fe80::0/64"),
1598            action: fnet_routes::RouteActionV6::arbitrary_test_value(),
1599            properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value(),
1600        };
1601        let local_type = Route {
1602            destination: net_subnet_v6!("fe80::0/64"),
1603            action: fnet_routes::RouteActionV6::arbitrary_test_value().try_into().unwrap(),
1604            properties: fnet_routes::RoutePropertiesV6::arbitrary_test_value().try_into().unwrap(),
1605        };
1606        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1607        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1608    }
1609
1610    #[test]
1611    fn installed_route_try_from_unset_route_v4() {
1612        assert_eq!(
1613            InstalledRoute::try_from(fnet_routes::InstalledRouteV4 {
1614                route: None,
1615                effective_properties: Some(
1616                    fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1617                ),
1618                table_id: Some(ARBITRARY_TABLE_ID.get()),
1619                ..Default::default()
1620            }),
1621            Err(FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::Route))
1622        )
1623    }
1624
1625    #[test]
1626    fn installed_route_try_from_unset_route_v6() {
1627        assert_eq!(
1628            InstalledRoute::try_from(fnet_routes::InstalledRouteV6 {
1629                route: None,
1630                effective_properties: Some(
1631                    fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1632                ),
1633                table_id: Some(ARBITRARY_TABLE_ID.get()),
1634                ..Default::default()
1635            }),
1636            Err(FidlConversionError::RequiredFieldUnset(InstalledRouteRequiredFields::Route))
1637        )
1638    }
1639
1640    #[test]
1641    fn installed_route_try_from_unset_effective_properties_v4() {
1642        assert_eq!(
1643            InstalledRoute::try_from(fnet_routes::InstalledRouteV4 {
1644                route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
1645                effective_properties: None,
1646                table_id: Some(ARBITRARY_TABLE_ID.get()),
1647                ..Default::default()
1648            }),
1649            Err(FidlConversionError::RequiredFieldUnset(
1650                InstalledRouteRequiredFields::EffectiveProperties
1651            ))
1652        )
1653    }
1654
1655    #[test]
1656    fn installed_route_try_from_unset_effective_properties_v6() {
1657        assert_eq!(
1658            InstalledRoute::try_from(fnet_routes::InstalledRouteV6 {
1659                route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
1660                effective_properties: None,
1661                table_id: Some(ARBITRARY_TABLE_ID.get()),
1662                ..Default::default()
1663            }),
1664            Err(FidlConversionError::RequiredFieldUnset(
1665                InstalledRouteRequiredFields::EffectiveProperties
1666            ))
1667        )
1668    }
1669
1670    #[test]
1671    fn installed_route_try_from_v4() {
1672        let fidl_type = fnet_routes::InstalledRouteV4 {
1673            route: Some(fnet_routes::RouteV4::arbitrary_test_value()),
1674            effective_properties: Some(
1675                fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1676            ),
1677            table_id: Some(ARBITRARY_TABLE_ID.get()),
1678            ..Default::default()
1679        };
1680        let local_type = InstalledRoute {
1681            route: fnet_routes::RouteV4::arbitrary_test_value().try_into().unwrap(),
1682            effective_properties: fnet_routes::EffectiveRouteProperties::arbitrary_test_value()
1683                .try_into()
1684                .unwrap(),
1685            table_id: ARBITRARY_TABLE_ID,
1686        };
1687        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1688        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1689    }
1690
1691    #[test]
1692    fn installed_route_try_from_v6() {
1693        let fidl_type = fnet_routes::InstalledRouteV6 {
1694            route: Some(fnet_routes::RouteV6::arbitrary_test_value()),
1695            effective_properties: Some(
1696                fnet_routes::EffectiveRouteProperties::arbitrary_test_value(),
1697            ),
1698            table_id: Some(ARBITRARY_TABLE_ID.get()),
1699            ..Default::default()
1700        };
1701        let local_type = InstalledRoute {
1702            route: fnet_routes::RouteV6::arbitrary_test_value().try_into().unwrap(),
1703            effective_properties: fnet_routes::EffectiveRouteProperties::arbitrary_test_value()
1704                .try_into()
1705                .unwrap(),
1706            table_id: ARBITRARY_TABLE_ID,
1707        };
1708        assert_eq!(fidl_type.clone().try_into(), Ok(local_type));
1709        assert_eq!(local_type.try_into(), Ok(fidl_type.clone()));
1710    }
1711
1712    #[test]
1713    fn event_try_from_v4() {
1714        let fidl_route = fnet_routes::InstalledRouteV4::arbitrary_test_value();
1715        let local_route = fidl_route.clone().try_into().unwrap();
1716        assert_eq!(
1717            fnet_routes::EventV4::unknown_variant_for_testing().try_into(),
1718            Ok(Event::Unknown)
1719        );
1720        assert_eq!(
1721            Event::<Ipv4>::Unknown.try_into(),
1722            Err::<fnet_routes::EventV4, _>(NetTypeConversionError::UnknownUnionVariant(
1723                "fuchsia_net_routes.EventV4"
1724            ))
1725        );
1726        assert_eq!(
1727            fnet_routes::EventV4::Existing(fidl_route.clone()).try_into(),
1728            Ok(Event::Existing(local_route))
1729        );
1730        assert_eq!(
1731            Event::Existing(local_route).try_into(),
1732            Ok(fnet_routes::EventV4::Existing(fidl_route.clone()))
1733        );
1734
1735        assert_eq!(fnet_routes::EventV4::Idle(fnet_routes::Empty).try_into(), Ok(Event::Idle));
1736        assert_eq!(Event::Idle.try_into(), Ok(fnet_routes::EventV4::Idle(fnet_routes::Empty)));
1737        assert_eq!(
1738            fnet_routes::EventV4::Added(fidl_route.clone()).try_into(),
1739            Ok(Event::Added(local_route))
1740        );
1741        assert_eq!(
1742            Event::Added(local_route).try_into(),
1743            Ok(fnet_routes::EventV4::Added(fidl_route.clone()))
1744        );
1745        assert_eq!(
1746            fnet_routes::EventV4::Removed(fidl_route.clone()).try_into(),
1747            Ok(Event::Removed(local_route))
1748        );
1749        assert_eq!(
1750            Event::Removed(local_route).try_into(),
1751            Ok(fnet_routes::EventV4::Removed(fidl_route.clone()))
1752        );
1753    }
1754
1755    #[test]
1756    fn event_try_from_v6() {
1757        let fidl_route = fnet_routes::InstalledRouteV6::arbitrary_test_value();
1758        let local_route = fidl_route.clone().try_into().unwrap();
1759        assert_eq!(
1760            fnet_routes::EventV6::unknown_variant_for_testing().try_into(),
1761            Ok(Event::Unknown)
1762        );
1763        assert_eq!(
1764            Event::<Ipv6>::Unknown.try_into(),
1765            Err::<fnet_routes::EventV6, _>(NetTypeConversionError::UnknownUnionVariant(
1766                "fuchsia_net_routes.EventV6"
1767            ))
1768        );
1769        assert_eq!(
1770            fnet_routes::EventV6::Existing(fidl_route.clone()).try_into(),
1771            Ok(Event::Existing(local_route))
1772        );
1773        assert_eq!(
1774            Event::Existing(local_route).try_into(),
1775            Ok(fnet_routes::EventV6::Existing(fidl_route.clone()))
1776        );
1777
1778        assert_eq!(fnet_routes::EventV6::Idle(fnet_routes::Empty).try_into(), Ok(Event::Idle));
1779        assert_eq!(Event::Idle.try_into(), Ok(fnet_routes::EventV6::Idle(fnet_routes::Empty)));
1780        assert_eq!(
1781            fnet_routes::EventV6::Added(fidl_route.clone()).try_into(),
1782            Ok(Event::Added(local_route))
1783        );
1784        assert_eq!(
1785            Event::Added(local_route).try_into(),
1786            Ok(fnet_routes::EventV6::Added(fidl_route.clone()))
1787        );
1788        assert_eq!(
1789            fnet_routes::EventV6::Removed(fidl_route.clone()).try_into(),
1790            Ok(Event::Removed(local_route))
1791        );
1792        assert_eq!(
1793            Event::Removed(local_route).try_into(),
1794            Ok(fnet_routes::EventV6::Removed(fidl_route.clone()))
1795        );
1796    }
1797
1798    // Tests the `event_stream_from_state` with various "shapes". The test
1799    // parameter is a vec of ranges, where each range corresponds to the batch
1800    // of events that will be sent in response to a single call to `Watch().
1801    #[ip_test(I)]
1802    #[test_case(Vec::new(); "no events")]
1803    #[test_case(vec![0..1]; "single_batch_single_event")]
1804    #[test_case(vec![0..10]; "single_batch_many_events")]
1805    #[test_case(vec![0..10, 10..20, 20..30]; "many_batches_many_events")]
1806    #[fuchsia_async::run_singlethreaded(test)]
1807    async fn event_stream_from_state_against_shape<I: FidlRouteIpExt>(
1808        test_shape: Vec<std::ops::Range<u32>>,
1809    ) {
1810        // Build the event stream based on the `test_shape`. Use a channel
1811        // so that the stream stays open until `close_channel` is called later.
1812        let (batches_sender, batches_receiver) =
1813            futures::channel::mpsc::unbounded::<Vec<I::WatchEvent>>();
1814        for batch_shape in &test_shape {
1815            batches_sender
1816                .unbounded_send(internal_testutil::generate_events_in_range::<I>(
1817                    batch_shape.clone(),
1818                ))
1819                .expect("failed to send event batch");
1820        }
1821
1822        // Instantiate the fake Watcher implementation.
1823        let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
1824        let (mut state_request_stream, _control_handle) =
1825            state_server_end.into_stream_and_control_handle();
1826        let watcher_fut = state_request_stream
1827            .next()
1828            .then(|req| {
1829                testutil::serve_state_request::<I>(
1830                    req.expect("State request_stream unexpectedly ended"),
1831                    batches_receiver,
1832                )
1833            })
1834            .fuse();
1835
1836        let event_stream =
1837            event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
1838
1839        futures::pin_mut!(watcher_fut, event_stream);
1840
1841        for batch_shape in test_shape {
1842            for event_idx in batch_shape.into_iter() {
1843                futures::select! {
1844                    () = watcher_fut => panic!("fake watcher implementation unexpectedly finished"),
1845                    event = event_stream.next() => {
1846                        let actual_event = event
1847                            .expect("event stream unexpectedly empty")
1848                            .expect("error processing event");
1849                        let expected_event = internal_testutil::generate_event::<I>(event_idx)
1850                                .try_into()
1851                                .expect("test event is unexpectedly invalid");
1852                        assert_eq!(actual_event, expected_event);
1853                    }
1854                };
1855            }
1856        }
1857
1858        // Close `batches_sender` and observe that the `event_stream` ends.
1859        batches_sender.close_channel();
1860        let ((), mut events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
1861        assert_matches!(
1862            events.pop(),
1863            Some(Err(WatchError::Fidl(fidl::Error::ClientChannelClosed {
1864                status: zx_status::Status::PEER_CLOSED,
1865                ..
1866            })))
1867        );
1868        assert_matches!(events[..], []);
1869    }
1870
1871    // Verify that calling `event_stream_from_state` multiple times with the
1872    // same `State` proxy, results in independent `Watcher` clients.
1873    #[ip_test(I)]
1874    #[fuchsia_async::run_singlethreaded]
1875    async fn event_stream_from_state_multiple_watchers<I: FidlRouteIpExt>() {
1876        // Events for 3 watchers. Each receives one batch containing 10 events.
1877        let test_data = vec![
1878            vec![internal_testutil::generate_events_in_range::<I>(0..10)],
1879            vec![internal_testutil::generate_events_in_range::<I>(10..20)],
1880            vec![internal_testutil::generate_events_in_range::<I>(20..30)],
1881        ];
1882
1883        // Instantiate the fake Watcher implementations.
1884        let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
1885        let (state_request_stream, _control_handle) =
1886            state_server_end.into_stream_and_control_handle();
1887        let watchers_fut = state_request_stream
1888            .zip(futures::stream::iter(test_data.clone()))
1889            .for_each_concurrent(std::usize::MAX, |(request, watcher_data)| {
1890                testutil::serve_state_request::<I>(request, futures::stream::iter(watcher_data))
1891            });
1892
1893        let validate_event_streams_fut =
1894            futures::future::join_all(test_data.into_iter().map(|watcher_data| {
1895                let events_fut = event_stream_from_state::<I>(&state)
1896                    .expect("failed to connect to watcher")
1897                    .collect::<std::collections::VecDeque<_>>();
1898                events_fut.then(|mut events| {
1899                    for expected_event in watcher_data.into_iter().flatten() {
1900                        assert_eq!(
1901                            events
1902                                .pop_front()
1903                                .expect("event_stream unexpectedly empty")
1904                                .expect("error processing event"),
1905                            expected_event.try_into().expect("test event is unexpectedly invalid"),
1906                        );
1907                    }
1908                    assert_matches!(
1909                        events.pop_front(),
1910                        Some(Err(WatchError::Fidl(fidl::Error::ClientChannelClosed {
1911                            status: zx_status::Status::PEER_CLOSED,
1912                            ..
1913                        })))
1914                    );
1915                    assert_matches!(events.make_contiguous(), []);
1916                    futures::future::ready(())
1917                })
1918            }));
1919
1920        let ((), _): ((), Vec<()>) = futures::join!(watchers_fut, validate_event_streams_fut);
1921    }
1922
1923    // Verify that failing to convert an event results in an error and closes
1924    // the event stream. `trailing_event` and `trailing_batch` control whether
1925    // a good event is sent after the bad event, either as part of the same
1926    // batch or in a subsequent batch. The test expects this data to be
1927    // truncated from the resulting event_stream.
1928    #[ip_test(I)]
1929    #[test_case(false, false; "no_trailing")]
1930    #[test_case(true, false; "trailing_event")]
1931    #[test_case(false, true; "trailing_batch")]
1932    #[test_case(true, true; "trailing_event_and_batch")]
1933    #[fuchsia_async::run_singlethreaded(test)]
1934    async fn event_stream_from_state_conversion_error<I: FidlRouteIpExt>(
1935        trailing_event: bool,
1936        trailing_batch: bool,
1937    ) {
1938        // Define an event with an invalid destination subnet; receiving it
1939        // from a call to `Watch` will result in conversion errors.
1940        #[derive(GenericOverIp)]
1941        #[generic_over_ip(I, Ip)]
1942        struct EventHolder<I: FidlRouteIpExt>(I::WatchEvent);
1943        let EventHolder(bad_event) = I::map_ip(
1944            (),
1945            |()| {
1946                EventHolder(fnet_routes::EventV4::Added(fnet_routes::InstalledRouteV4 {
1947                    route: Some(fnet_routes::RouteV4 {
1948                        destination: fidl_ip_v4_with_prefix!("192.168.0.1/24"),
1949                        ..fnet_routes::RouteV4::arbitrary_test_value()
1950                    }),
1951                    ..fnet_routes::InstalledRouteV4::arbitrary_test_value()
1952                }))
1953            },
1954            |()| {
1955                EventHolder(fnet_routes::EventV6::Added(fnet_routes::InstalledRouteV6 {
1956                    route: Some(fnet_routes::RouteV6 {
1957                        destination: fidl_ip_v6_with_prefix!("fe80::1/64"),
1958                        ..fnet_routes::RouteV6::arbitrary_test_value()
1959                    }),
1960                    ..fnet_routes::InstalledRouteV6::arbitrary_test_value()
1961                }))
1962            },
1963        );
1964
1965        let batch = std::iter::once(bad_event)
1966            // Optionally append a known good event to the batch.
1967            .chain(trailing_event.then(|| internal_testutil::generate_event::<I>(0)).into_iter())
1968            .collect::<Vec<_>>();
1969        let batches = std::iter::once(batch)
1970            // Optionally append a known good batch to the sequence of batches.
1971            .chain(trailing_batch.then(|| vec![internal_testutil::generate_event::<I>(1)]))
1972            .collect::<Vec<_>>();
1973
1974        // Instantiate the fake Watcher implementation.
1975        let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
1976        let (mut state_request_stream, _control_handle) =
1977            state_server_end.into_stream_and_control_handle();
1978        let watcher_fut = state_request_stream
1979            .next()
1980            .then(|req| {
1981                testutil::serve_state_request::<I>(
1982                    req.expect("State request_stream unexpectedly ended"),
1983                    futures::stream::iter(batches),
1984                )
1985            })
1986            .fuse();
1987
1988        let event_stream =
1989            event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
1990
1991        futures::pin_mut!(watcher_fut, event_stream);
1992        let ((), events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
1993        assert_matches!(&events[..], &[Err(WatchError::Conversion(_))]);
1994    }
1995
1996    // Verify that watching an empty batch results in an error and closes
1997    // the event stream. When `trailing_batch` is true, an additional "good"
1998    // batch will be sent after the empty batch; the test expects this data to
1999    // be truncated from the resulting event_stream.
2000    #[ip_test(I)]
2001    #[test_case(false; "no_trailing_batch")]
2002    #[test_case(true; "trailing_batch")]
2003    #[fuchsia_async::run_singlethreaded(test)]
2004    async fn event_stream_from_state_empty_batch_error<I: FidlRouteIpExt>(trailing_batch: bool) {
2005        let batches = std::iter::once(Vec::new())
2006            // Optionally append a known good batch to the sequence of batches.
2007            .chain(trailing_batch.then(|| vec![internal_testutil::generate_event::<I>(0)]))
2008            .collect::<Vec<_>>();
2009
2010        // Instantiate the fake Watcher implementation.
2011        let (state, state_server_end) = fidl::endpoints::create_proxy::<I::StateMarker>();
2012        let (mut state_request_stream, _control_handle) =
2013            state_server_end.into_stream_and_control_handle();
2014        let watcher_fut = state_request_stream
2015            .next()
2016            .then(|req| {
2017                testutil::serve_state_request::<I>(
2018                    req.expect("State request_stream unexpectedly ended"),
2019                    futures::stream::iter(batches),
2020                )
2021            })
2022            .fuse();
2023
2024        let event_stream =
2025            event_stream_from_state::<I>(&state).expect("failed to connect to watcher").fuse();
2026
2027        futures::pin_mut!(watcher_fut, event_stream);
2028        let ((), events) = futures::join!(watcher_fut, event_stream.collect::<Vec<_>>());
2029        assert_matches!(&events[..], &[Err(WatchError::EmptyEventBatch)]);
2030    }
2031
2032    fn arbitrary_test_route<I: Ip + FidlRouteIpExt>() -> InstalledRoute<I> {
2033        #[derive(GenericOverIp)]
2034        #[generic_over_ip(I, Ip)]
2035        struct RouteHolder<I: FidlRouteIpExt>(InstalledRoute<I>);
2036        let RouteHolder(route) = I::map_ip(
2037            (),
2038            |()| {
2039                RouteHolder(
2040                    fnet_routes::InstalledRouteV4::arbitrary_test_value().try_into().unwrap(),
2041                )
2042            },
2043            |()| {
2044                RouteHolder(
2045                    fnet_routes::InstalledRouteV6::arbitrary_test_value().try_into().unwrap(),
2046                )
2047            },
2048        );
2049        route
2050    }
2051
2052    enum CollectRoutesUntilIdleErrorTestCase {
2053        ErrorInStream,
2054        UnexpectedEvent,
2055        StreamEnded,
2056    }
2057
2058    #[ip_test(I)]
2059    #[test_case(CollectRoutesUntilIdleErrorTestCase::ErrorInStream; "error_in_stream")]
2060    #[test_case(CollectRoutesUntilIdleErrorTestCase::UnexpectedEvent; "unexpected_event")]
2061    #[test_case(CollectRoutesUntilIdleErrorTestCase::StreamEnded; "stream_ended")]
2062    #[fuchsia_async::run_singlethreaded(test)]
2063    async fn collect_routes_until_idle_error<I: FidlRouteIpExt>(
2064        test_case: CollectRoutesUntilIdleErrorTestCase,
2065    ) {
2066        // Build up the test data and the expected outcome base on `test_case`.
2067        // Note, that `netstack_test` doesn't support test cases whose args are
2068        // generic functions (below, `test_assertion` is generic over `I`).
2069        let route = arbitrary_test_route();
2070        let (event, test_assertion): (_, Box<dyn FnOnce(_)>) = match test_case {
2071            CollectRoutesUntilIdleErrorTestCase::ErrorInStream => (
2072                Err(WatchError::EmptyEventBatch),
2073                Box::new(|result| {
2074                    assert_matches!(result, Err(CollectRoutesUntilIdleError::ErrorInStream(_)))
2075                }),
2076            ),
2077            CollectRoutesUntilIdleErrorTestCase::UnexpectedEvent => (
2078                Ok(Event::Added(route)),
2079                Box::new(|result| {
2080                    assert_matches!(result, Err(CollectRoutesUntilIdleError::UnexpectedEvent(_)))
2081                }),
2082            ),
2083            CollectRoutesUntilIdleErrorTestCase::StreamEnded => (
2084                Ok(Event::Existing(route)),
2085                Box::new(|result| {
2086                    assert_matches!(result, Err(CollectRoutesUntilIdleError::StreamEnded))
2087                }),
2088            ),
2089        };
2090
2091        let event_stream = futures::stream::once(futures::future::ready(event));
2092        futures::pin_mut!(event_stream);
2093        let result = collect_routes_until_idle::<I, Vec<_>>(event_stream).await;
2094        test_assertion(result);
2095    }
2096
2097    // Verifies that `collect_routes_until_idle` collects all existing events,
2098    // drops the idle event, and leaves all trailing events intact.
2099    #[ip_test(I)]
2100    #[fuchsia_async::run_singlethreaded]
2101    async fn collect_routes_until_idle_success<I: FidlRouteIpExt>() {
2102        let route = arbitrary_test_route();
2103        let event_stream = futures::stream::iter([
2104            Ok(Event::Existing(route)),
2105            Ok(Event::Idle),
2106            Ok(Event::Added(route)),
2107        ]);
2108
2109        futures::pin_mut!(event_stream);
2110        let existing = collect_routes_until_idle::<I, Vec<_>>(event_stream.by_ref())
2111            .await
2112            .expect("failed to collect existing routes");
2113        assert_eq!(&existing, &[route]);
2114
2115        let trailing_events = event_stream.collect::<Vec<_>>().await;
2116        assert_matches!(
2117            &trailing_events[..],
2118            &[Ok(Event::Added(found_route))] if found_route == route
2119        );
2120    }
2121
2122    #[ip_test(I)]
2123    #[fuchsia_async::run_singlethreaded]
2124    async fn wait_for_routes_errors<I: FidlRouteIpExt>() {
2125        let mut state = HashSet::new();
2126        let event_stream =
2127            futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2128        assert_matches!(
2129            wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
2130            Err(WaitForRoutesError::ErrorInStream(WatchError::EmptyEventBatch))
2131        );
2132        assert!(state.is_empty());
2133
2134        let event_stream = futures::stream::empty();
2135        assert_matches!(
2136            wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
2137            Err(WaitForRoutesError::StreamEnded)
2138        );
2139        assert!(state.is_empty());
2140
2141        let event_stream = futures::stream::once(futures::future::ready(Ok(Event::<I>::Unknown)));
2142        assert_matches!(
2143            wait_for_routes::<I, _, _>(event_stream, &mut state, |_| true).await,
2144            Err(WaitForRoutesError::UnknownEvent)
2145        );
2146        assert!(state.is_empty());
2147    }
2148
2149    #[ip_test(I)]
2150    #[fuchsia_async::run_singlethreaded]
2151    async fn wait_for_routes_add_remove<I: FidlRouteIpExt>() {
2152        let into_stream = |t| futures::stream::once(futures::future::ready(t));
2153
2154        let route = arbitrary_test_route::<I>();
2155        let mut state = HashSet::new();
2156
2157        // Verify that checking for the presence of a route blocks until the
2158        // route is added.
2159        let has_route = |routes: &HashSet<InstalledRoute<I>>| routes.contains(&route);
2160        assert_matches!(
2161            wait_for_routes::<I, _, _>(futures::stream::pending(), &mut state, has_route)
2162                .now_or_never(),
2163            None
2164        );
2165        assert!(state.is_empty());
2166        assert_matches!(
2167            wait_for_routes::<I, _, _>(into_stream(Ok(Event::Added(route))), &mut state, has_route)
2168                .now_or_never(),
2169            Some(Ok(()))
2170        );
2171        assert_eq!(state, HashSet::from_iter([route]));
2172
2173        // Re-add the route and observe an error.
2174        assert_matches!(
2175            wait_for_routes::<I, _, _>(into_stream(Ok(Event::Added(route))), &mut state, has_route)
2176                .now_or_never(),
2177            Some(Err(WaitForRoutesError::AddedAlreadyExisting(r))) if r == route
2178        );
2179        assert_eq!(state, HashSet::from_iter([route]));
2180
2181        // Verify that checking for the absence of a route blocks until the
2182        // route is removed.
2183        let does_not_have_route = |routes: &HashSet<InstalledRoute<I>>| !routes.contains(&route);
2184        assert_matches!(
2185            wait_for_routes::<I, _, _>(futures::stream::pending(), &mut state, does_not_have_route)
2186                .now_or_never(),
2187            None
2188        );
2189        assert_eq!(state, HashSet::from_iter([route]));
2190        assert_matches!(
2191            wait_for_routes::<I, _, _>(
2192                into_stream(Ok(Event::Removed(route))),
2193                &mut state,
2194                does_not_have_route
2195            )
2196            .now_or_never(),
2197            Some(Ok(()))
2198        );
2199        assert!(state.is_empty());
2200
2201        // Remove a non-existent route and observe an error.
2202        assert_matches!(
2203            wait_for_routes::<I, _, _>(
2204                into_stream(Ok(Event::Removed(route))),
2205                &mut state,
2206                does_not_have_route
2207            ).now_or_never(),
2208            Some(Err(WaitForRoutesError::RemovedNonExistent(r))) if r == route
2209        );
2210        assert!(state.is_empty());
2211    }
2212}