Skip to main content

fidl_fuchsia_net_routes_ext_fdomain/
lib.rs

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