Skip to main content

fidl_fuchsia_net_routes_ext_fdomain/
lib.rs

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