Skip to main content

fidl_fuchsia_net_routes_ext/
lib.rs

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