1#![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#[derive(Clone, Copy, Debug, Error, PartialEq)]
35pub enum FidlConversionError<UnsetFieldSpecifier: Debug + Display> {
36 #[error("required field is unset: {0}")]
39 RequiredFieldUnset(UnsetFieldSpecifier),
40 #[error("failed to convert `destination` to net_types subnet: {0:?}")]
42 DestinationSubnet(net_types::ip::SubnetError),
43 #[error("failed to convert `next_hop` to a specified addr")]
45 UnspecifiedNextHop,
46 #[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#[derive(Clone, Copy, Debug, Error, PartialEq)]
97pub enum NetTypeConversionError {
98 #[error("Union type is of the `Unknown` variant: {0}")]
100 UnknownUnionVariant(&'static str),
101}
102
103#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
106pub struct SpecifiedRouteProperties {
107 pub metric: fnet_routes::SpecifiedMetric,
109}
110
111#[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#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
144pub struct EffectiveRouteProperties {
145 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#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
181pub struct RouteProperties {
182 pub specified_properties: SpecifiedRouteProperties,
184}
185
186impl RouteProperties {
187 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#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
268pub struct RouteTarget<I: Ip> {
269 pub outbound_interface: u64,
271 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#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
340pub enum RouteAction<I: Ip> {
341 Unknown,
343 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#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
409pub struct Route<I: Ip> {
410 pub destination: Subnet<I::Addr>,
412 pub action: RouteAction<I>,
414 pub properties: RouteProperties,
416}
417
418impl<I: Ip> Route<I> {
419 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 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 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#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
533pub struct InstalledRoute<I: Ip> {
534 pub route: Route<I>,
536 pub effective_properties: EffectiveRouteProperties,
538 pub table_id: TableId,
540}
541
542impl<I: Ip> InstalledRoute<I> {
543 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#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
551pub struct TableId(u32);
552
553impl TableId {
554 pub const fn new(id: u32) -> Self {
556 Self(id)
557 }
558
559 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#[derive(Clone, Copy, Debug, PartialEq)]
678pub enum Event<I: Ip> {
679 Unknown,
681 Existing(InstalledRoute<I>),
683 Idle,
685 Added(InstalledRoute<I>),
687 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#[derive(Clone, Debug, Error)]
749pub enum WatcherCreationError {
750 #[error("failed to create route watcher proxy: {0}")]
752 CreateProxy(fidl::Error),
753 #[error("failed to get route watcher: {0}")]
755 GetWatcher(fidl::Error),
756}
757
758#[derive(Clone, Debug, Error)]
760pub enum WatchError {
761 #[error("the call to `Watch()` failed: {0}")]
763 Fidl(fidl::Error),
764 #[error("failed to convert event returned by `Watch()`: {0}")]
766 Conversion(FidlConversionError<InstalledRouteRequiredFields>),
767 #[error("the call to `Watch()` returned an empty batch of events")]
769 EmptyEventBatch,
770}
771
772pub trait FidlRouteIpExt: Ip {
774 type StateMarker: flex_client::fidl::DiscoverableProtocolMarker;
776 type WatcherMarker: flex_client::fidl::ProtocolMarker;
778 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 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
806pub trait Responder: flex_client::fidl::Responder + Debug + Send {
808 type Payload;
810
811 fn send(self, result: Self::Payload) -> Result<(), fidl::Error>;
813}
814
815pub trait SliceResponder<Payload>: flex_client::fidl::Responder + Debug + Send {
821 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#[derive(Default, Clone)]
847pub struct WatcherOptions {
848 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
880pub 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
910pub 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
932pub 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
939pub 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
953pub 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 .try_flatten(),
976 ))
977}
978
979#[derive(Clone, Debug, Error)]
981pub enum CollectRoutesUntilIdleError<I: FidlRouteIpExt> {
982 #[error("there was an error in the event stream: {0}")]
984 ErrorInStream(WatchError),
985 #[error("there was an unexpected event in the event stream: {0:?}")]
988 UnexpectedEvent(Event<I>),
989 #[error("the event stream unexpectedly ended")]
991 StreamEnded,
992}
993
994pub 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#[derive(Clone, Debug, Error)]
1039pub enum WaitForRoutesError<I: FidlRouteIpExt> {
1040 #[error("there was an error in the event stream: {0}")]
1042 ErrorInStream(WatchError),
1043 #[error("observed an added event for an already existing route: {0:?}")]
1045 AddedAlreadyExisting(InstalledRoute<I>),
1046 #[error("observed a removed event for a non-existent route: {0:?}")]
1048 RemovedNonExistent(InstalledRoute<I>),
1049 #[error("observed an unknown event")]
1051 UnknownEvent,
1052 #[error("the event stream unexpectedly ended")]
1054 StreamEnded,
1055}
1056
1057pub 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
1105pub 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#[derive(Debug, Default, Clone)]
1127pub struct ResolveOptions {
1128 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 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 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 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 #[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 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 #[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 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 #[ip_test(I)]
1841 #[fuchsia_async::run_singlethreaded]
1842 async fn event_stream_from_state_multiple_watchers<I: FidlRouteIpExt>() {
1843 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 #[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 #[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 #[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 .chain(trailing_event.then(|| internal_testutil::generate_event::<I>(0)).into_iter())
1939 .collect::<Vec<_>>();
1940 let batches = std::iter::once(batch)
1941 .chain(trailing_batch.then(|| vec![internal_testutil::generate_event::<I>(1)]))
1943 .collect::<Vec<_>>();
1944
1945 #[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 #[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 .chain(trailing_batch.then(|| vec![internal_testutil::generate_event::<I>(0)]))
1983 .collect::<Vec<_>>();
1984
1985 #[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 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 #[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 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 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 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 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}