1#![deny(missing_docs)]
8
9pub mod admin;
10mod reachability;
11
12pub use reachability::{is_globally_routable, to_reachability_stream, wait_for_reachability};
13
14use anyhow::Context as _;
15use derivative::Derivative;
16use fidl_table_validation::*;
17use flex_client::ProxyHasDomain;
18use flex_fuchsia_hardware_network as fhardware_network;
19use flex_fuchsia_net_interfaces as fnet_interfaces;
20use futures::{Stream, TryStreamExt as _};
21use std::collections::btree_map::{self, BTreeMap};
22use std::collections::hash_map::{self, HashMap};
23use std::convert::{Infallible as Never, TryFrom as _};
24use std::fmt::Debug;
25use std::marker::PhantomData;
26use std::num::NonZeroU64;
27use thiserror::Error;
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
34#[allow(missing_docs)]
35pub enum PortClass {
36 Loopback,
37 Virtual,
38 Ethernet,
39 WlanClient,
40 WlanAp,
41 Ppp,
42 Bridge,
43 Lowpan,
44 Blackhole,
45}
46
47impl PortClass {
48 pub fn is_loopback(&self) -> bool {
50 match self {
51 PortClass::Loopback => true,
52 PortClass::Virtual
53 | PortClass::Blackhole
54 | PortClass::Ethernet
55 | PortClass::WlanClient
56 | PortClass::WlanAp
57 | PortClass::Ppp
58 | PortClass::Bridge
59 | PortClass::Lowpan => false,
60 }
61 }
62}
63
64#[derive(Debug, Error, PartialEq, Eq)]
67#[allow(missing_docs)]
68pub enum UnknownPortClassError {
69 #[error(transparent)]
70 NetInterfaces(UnknownNetInterfacesPortClassError),
71 #[error(transparent)]
72 HardwareNetwork(UnknownHardwareNetworkPortClassError),
73}
74
75#[derive(Debug, Error, PartialEq, Eq)]
77#[error("unknown fuchsia.net.interfaces/PortClass ordinal: {unknown_ordinal}")]
78pub struct UnknownNetInterfacesPortClassError {
79 unknown_ordinal: u64,
80}
81
82#[derive(Debug, Error, PartialEq, Eq)]
84#[error("unknown fuchsia.hardware.network/PortClass ordinal: {unknown_ordinal}")]
85pub struct UnknownHardwareNetworkPortClassError {
86 unknown_ordinal: u16,
87}
88
89impl TryFrom<fnet_interfaces::PortClass> for PortClass {
90 type Error = UnknownPortClassError;
91 fn try_from(port_class: fnet_interfaces::PortClass) -> Result<Self, Self::Error> {
92 match port_class {
93 fnet_interfaces::PortClass::Loopback(fnet_interfaces::Empty) => Ok(PortClass::Loopback),
94 fnet_interfaces::PortClass::Blackhole(fnet_interfaces::Empty) => {
95 Ok(PortClass::Blackhole)
96 }
97 fnet_interfaces::PortClass::Device(port_class) => {
98 PortClass::try_from(port_class).map_err(UnknownPortClassError::HardwareNetwork)
99 }
100 fnet_interfaces::PortClass::__SourceBreaking { unknown_ordinal } => {
101 Err(UnknownPortClassError::NetInterfaces(UnknownNetInterfacesPortClassError {
102 unknown_ordinal,
103 }))
104 }
105 }
106 }
107}
108
109impl From<PortClass> for fnet_interfaces::PortClass {
110 fn from(port_class: PortClass) -> Self {
111 match port_class {
112 PortClass::Loopback => fnet_interfaces::PortClass::Loopback(fnet_interfaces::Empty),
113 PortClass::Blackhole => fnet_interfaces::PortClass::Blackhole(fnet_interfaces::Empty),
114 PortClass::Virtual => {
115 fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Virtual)
116 }
117 PortClass::Ethernet => {
118 fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Ethernet)
119 }
120 PortClass::WlanClient => {
121 fnet_interfaces::PortClass::Device(fhardware_network::PortClass::WlanClient)
122 }
123 PortClass::WlanAp => {
124 fnet_interfaces::PortClass::Device(fhardware_network::PortClass::WlanAp)
125 }
126 PortClass::Ppp => fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Ppp),
127 PortClass::Bridge => {
128 fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Bridge)
129 }
130 PortClass::Lowpan => {
131 fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Lowpan)
132 }
133 }
134 }
135}
136
137impl TryFrom<fhardware_network::PortClass> for PortClass {
138 type Error = UnknownHardwareNetworkPortClassError;
139 fn try_from(port_class: fhardware_network::PortClass) -> Result<Self, Self::Error> {
140 match port_class {
141 fhardware_network::PortClass::Virtual => Ok(PortClass::Virtual),
142 fhardware_network::PortClass::Ethernet => Ok(PortClass::Ethernet),
143 fhardware_network::PortClass::WlanClient => Ok(PortClass::WlanClient),
144 fhardware_network::PortClass::WlanAp => Ok(PortClass::WlanAp),
145 fhardware_network::PortClass::Ppp => Ok(PortClass::Ppp),
146 fhardware_network::PortClass::Bridge => Ok(PortClass::Bridge),
147 fhardware_network::PortClass::Lowpan => Ok(PortClass::Lowpan),
148 fhardware_network::PortClass::__SourceBreaking { unknown_ordinal } => {
149 Err(UnknownHardwareNetworkPortClassError { unknown_ordinal })
150 }
151 }
152 }
153}
154
155#[cfg(not(target_os = "fuchsia"))]
161#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
162pub struct PortIdentityKoid(zx_types::zx_koid_t);
163
164#[cfg(not(target_os = "fuchsia"))]
165impl PortIdentityKoid {
166 pub fn from_raw(koid: zx_types::zx_koid_t) -> Self {
168 Self(koid)
169 }
170
171 pub fn raw_koid(&self) -> zx_types::zx_koid_t {
173 let Self(raw) = self;
174 *raw
175 }
176}
177
178#[cfg(target_os = "fuchsia")]
180pub type PortIdentityKoid = zx::Koid;
181
182#[derive(Derivative, ValidFidlTable)]
184#[derivative(Clone(bound = ""), Debug(bound = ""), Eq(bound = ""), PartialEq(bound = ""))]
185#[fidl_table_src(fnet_interfaces::Properties)]
186pub struct Properties<I: FieldInterests> {
187 pub id: NonZeroU64,
190 pub name: String,
192 pub online: bool,
194 pub addresses: Vec<Address<I>>,
196 pub has_default_ipv4_route: bool,
198 pub has_default_ipv6_route: bool,
200 pub port_class: PortClass,
202 #[fidl_field_type(optional_converter = PortIdentityKoidConverter)]
211 pub port_identity_koid: Option<PortIdentityKoid>,
212}
213
214#[derive(Derivative, ValidFidlTable)]
216#[derivative(
217 Clone(bound = ""),
218 Debug(bound = ""),
219 Eq(bound = ""),
220 PartialEq(bound = ""),
221 Hash(bound = "")
222)]
223#[fidl_table_src(fnet_interfaces::Address)]
224#[fidl_table_strict]
225pub struct Address<I: FieldInterests> {
226 pub addr: flex_fuchsia_net::Subnet,
228 #[fidl_field_type(optional_converter = InterestConverter::<I, ValidUntilInterest>)]
233 pub valid_until: FromInterest<I, ValidUntilInterest>,
234 #[fidl_field_type(optional_converter = InterestConverter::<I, PreferredLifetimeInfoInterest>)]
236 pub preferred_lifetime_info: FromInterest<I, PreferredLifetimeInfoInterest>,
237 pub assignment_state: fnet_interfaces::AddressAssignmentState,
239}
240
241#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Copy, Clone, Hash)]
246#[allow(missing_docs)]
247pub enum PreferredLifetimeInfo {
248 PreferredUntil(PositiveMonotonicInstant),
249 Deprecated,
250}
251
252impl PreferredLifetimeInfo {
253 pub const fn preferred_forever() -> Self {
255 Self::PreferredUntil(PositiveMonotonicInstant::INFINITE_FUTURE)
256 }
257
258 pub const fn to_fidl(self) -> fnet_interfaces::PreferredLifetimeInfo {
260 match self {
261 PreferredLifetimeInfo::Deprecated => {
262 fnet_interfaces::PreferredLifetimeInfo::Deprecated(fnet_interfaces::Empty)
263 }
264 PreferredLifetimeInfo::PreferredUntil(v) => {
265 fnet_interfaces::PreferredLifetimeInfo::PreferredUntil(v.into_nanos())
266 }
267 }
268 }
269}
270
271impl TryFrom<fnet_interfaces::PreferredLifetimeInfo> for PreferredLifetimeInfo {
272 type Error = NotPositiveMonotonicInstantError;
273
274 fn try_from(value: fnet_interfaces::PreferredLifetimeInfo) -> Result<Self, Self::Error> {
275 match value {
276 fnet_interfaces::PreferredLifetimeInfo::Deprecated(fnet_interfaces::Empty) => {
277 Ok(Self::Deprecated)
278 }
279 fnet_interfaces::PreferredLifetimeInfo::PreferredUntil(v) => {
280 Ok(Self::PreferredUntil(v.try_into()?))
281 }
282 }
283 }
284}
285
286impl From<PreferredLifetimeInfo> for fnet_interfaces::PreferredLifetimeInfo {
287 fn from(value: PreferredLifetimeInfo) -> Self {
288 value.to_fidl()
289 }
290}
291
292#[derive(Error, Debug)]
295#[error("{0} is not a positive monotonic instant")]
296pub struct NotPositiveMonotonicInstantError(i64);
297
298#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Copy, Clone, Hash)]
300pub struct PositiveMonotonicInstant(i64);
301
302impl PositiveMonotonicInstant {
303 pub const INFINITE_FUTURE: Self = Self(zx_types::ZX_TIME_INFINITE);
305
306 pub const fn into_nanos(self) -> i64 {
308 let Self(v) = self;
309 v
310 }
311
312 pub const fn from_nanos(v: i64) -> Option<Self> {
315 if v > 0 { Some(Self(v)) } else { None }
316 }
317
318 pub fn is_infinite(&self) -> bool {
320 self == &Self::INFINITE_FUTURE
321 }
322}
323
324#[cfg(target_os = "fuchsia")]
325impl From<PositiveMonotonicInstant> for zx::MonotonicInstant {
326 fn from(PositiveMonotonicInstant(v): PositiveMonotonicInstant) -> Self {
327 zx::MonotonicInstant::from_nanos(v)
328 }
329}
330
331#[cfg(target_os = "fuchsia")]
332impl TryFrom<zx::MonotonicInstant> for PositiveMonotonicInstant {
333 type Error = NotPositiveMonotonicInstantError;
334
335 fn try_from(value: zx::MonotonicInstant) -> Result<Self, Self::Error> {
336 Self::try_from(value.into_nanos())
337 }
338}
339
340impl From<PositiveMonotonicInstant> for zx_types::zx_time_t {
341 fn from(value: PositiveMonotonicInstant) -> Self {
342 value.into_nanos()
343 }
344}
345
346impl TryFrom<zx_types::zx_time_t> for PositiveMonotonicInstant {
347 type Error = NotPositiveMonotonicInstantError;
348
349 fn try_from(value: zx_types::zx_time_t) -> Result<Self, Self::Error> {
350 Self::from_nanos(value).ok_or(NotPositiveMonotonicInstantError(value))
351 }
352}
353
354#[derive(Error, Debug)]
356pub enum UpdateError {
357 #[error("duplicate added event {0:?}")]
359 DuplicateAdded(fnet_interfaces::Properties),
360 #[error("duplicate existing event {0:?}")]
362 DuplicateExisting(fnet_interfaces::Properties),
363 #[error("failed to validate Properties FIDL table: {0}")]
365 InvalidProperties(#[from] PropertiesValidationError),
366 #[error("failed to validate Address FIDL table: {0}")]
368 InvalidAddress(#[from] AddressValidationError),
369 #[error("changed event with missing ID {0:?}")]
371 MissingId(fnet_interfaces::Properties),
372 #[error("changed event contains no changed fields {0:?}")]
374 EmptyChange(fnet_interfaces::Properties),
375 #[error("interface has been removed")]
377 Removed,
378 #[error("unknown interface changed {0:?}")]
380 UnknownChanged(fnet_interfaces::Properties),
381 #[error("unknown interface with id {0} deleted")]
383 UnknownRemoved(u64),
384 #[error("encountered 0 interface id")]
386 ZeroInterfaceId,
387}
388
389#[derive(Derivative)]
391#[derivative(Debug(bound = "S: Debug"), PartialEq(bound = "S: PartialEq"))]
392pub enum UpdateResult<'a, S, I: FieldInterests> {
393 NoChange,
395 Existing {
397 properties: &'a Properties<I>,
399 state: &'a mut S,
401 },
402 Added {
404 properties: &'a Properties<I>,
406 state: &'a mut S,
408 },
409 Changed {
411 previous: fnet_interfaces::Properties,
417 current: &'a Properties<I>,
419 state: &'a mut S,
421 },
422 Removed(PropertiesAndState<S, I>),
424}
425
426#[derive(Derivative)]
428#[derivative(
429 Clone(bound = "S: Clone"),
430 Debug(bound = "S: Debug"),
431 Eq(bound = "S: Eq"),
432 PartialEq(bound = "S: PartialEq")
433)]
434pub struct PropertiesAndState<S, I: FieldInterests> {
435 pub properties: Properties<I>,
437 pub state: S,
439}
440
441pub trait Update<S> {
443 type Interest: FieldInterests;
445
446 fn update(
448 &mut self,
449 event: EventWithInterest<Self::Interest>,
450 ) -> Result<UpdateResult<'_, S, Self::Interest>, UpdateError>;
451}
452
453impl<S, I: FieldInterests> Update<S> for PropertiesAndState<S, I> {
454 type Interest = I;
455 fn update(
456 &mut self,
457 event: EventWithInterest<I>,
458 ) -> Result<UpdateResult<'_, S, I>, UpdateError> {
459 let Self { properties, state } = self;
460 match event.into_inner() {
461 fnet_interfaces::Event::Existing(existing) => {
462 let existing = Properties::<I>::try_from(existing)?;
463 if existing.id == properties.id {
464 return Err(UpdateError::DuplicateExisting(existing.into()));
465 }
466 }
467 fnet_interfaces::Event::Added(added) => {
468 let added = Properties::<I>::try_from(added)?;
469 if added.id == properties.id {
470 return Err(UpdateError::DuplicateAdded(added.into()));
471 }
472 }
473 fnet_interfaces::Event::Changed(mut change) => {
474 let fnet_interfaces::Properties {
475 id,
476 name: _,
477 port_class: _,
478 online,
479 has_default_ipv4_route,
480 has_default_ipv6_route,
481 addresses,
482 port_identity_koid: _,
483 __source_breaking: _,
484 } = &mut change;
485 if let Some(id) = *id {
486 if properties.id.get() == id {
487 let mut changed = false;
488 macro_rules! swap_if_some {
489 ($field:ident) => {
490 if let Some($field) = $field {
491 if properties.$field != *$field {
492 std::mem::swap(&mut properties.$field, $field);
493 changed = true;
494 }
495 }
496 };
497 }
498 swap_if_some!(online);
499 swap_if_some!(has_default_ipv4_route);
500 swap_if_some!(has_default_ipv6_route);
501 if let Some(addresses) = addresses {
502 if addresses.len() != properties.addresses.len()
510 || !addresses
511 .iter()
512 .zip(
513 properties
514 .addresses
515 .iter()
516 .cloned()
517 .map(fnet_interfaces::Address::from),
518 )
519 .all(|(a, b)| *a == b)
520 {
521 let previous_len = properties.addresses.len();
522 properties.addresses.reserve(addresses.len());
525 for address in addresses.drain(..).map(Address::try_from) {
526 properties.addresses.push(address?);
527 }
528 addresses.extend(
529 properties.addresses.drain(..previous_len).map(Into::into),
530 );
531 changed = true;
532 }
533 }
534 if changed {
535 change.id = None;
536 return Ok(UpdateResult::Changed {
537 previous: change,
538 current: properties,
539 state,
540 });
541 } else {
542 return Err(UpdateError::EmptyChange(change));
543 }
544 }
545 } else {
546 return Err(UpdateError::MissingId(change));
547 }
548 }
549 fnet_interfaces::Event::Removed(removed_id) => {
550 if properties.id.get() == removed_id {
551 return Err(UpdateError::Removed);
552 }
553 }
554 fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}) => {}
555 }
556 Ok(UpdateResult::NoChange)
557 }
558}
559
560impl<S: Default, I: FieldInterests> Update<S> for InterfaceState<S, I> {
561 type Interest = I;
562 fn update(
563 &mut self,
564 event: EventWithInterest<I>,
565 ) -> Result<UpdateResult<'_, S, I>, UpdateError> {
566 fn get_properties<S, I: FieldInterests>(
567 state: &mut InterfaceState<S, I>,
568 ) -> &mut PropertiesAndState<S, I> {
569 match state {
570 InterfaceState::Known(properties) => properties,
571 InterfaceState::Unknown(id) => unreachable!(
572 "matched `Unknown({})` immediately after assigning with `Known`",
573 id
574 ),
575 }
576 }
577 match self {
578 InterfaceState::Unknown(id) => match event.into_inner() {
579 fnet_interfaces::Event::Existing(existing) => {
580 let properties = Properties::try_from(existing)?;
581 if properties.id.get() == *id {
582 *self = InterfaceState::Known(PropertiesAndState {
583 properties,
584 state: S::default(),
585 });
586 let PropertiesAndState { properties, state } = get_properties(self);
587 return Ok(UpdateResult::Existing { properties, state });
588 }
589 }
590 fnet_interfaces::Event::Added(added) => {
591 let properties = Properties::try_from(added)?;
592 if properties.id.get() == *id {
593 *self = InterfaceState::Known(PropertiesAndState {
594 properties,
595 state: S::default(),
596 });
597 let PropertiesAndState { properties, state } = get_properties(self);
598 return Ok(UpdateResult::Added { properties, state });
599 }
600 }
601 fnet_interfaces::Event::Changed(change) => {
602 if let Some(change_id) = change.id {
603 if change_id == *id {
604 return Err(UpdateError::UnknownChanged(change));
605 }
606 } else {
607 return Err(UpdateError::MissingId(change));
608 }
609 }
610 fnet_interfaces::Event::Removed(removed_id) => {
611 if removed_id == *id {
612 return Err(UpdateError::UnknownRemoved(removed_id));
613 }
614 }
615 fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}) => {}
616 },
617 InterfaceState::Known(properties) => return properties.update(event),
618 }
619 Ok(UpdateResult::NoChange)
620 }
621}
622
623pub struct ZeroError {}
625
626pub trait TryFromMaybeNonZero: Sized {
628 fn try_from(value: u64) -> Result<Self, ZeroError>;
630}
631
632impl TryFromMaybeNonZero for u64 {
633 fn try_from(value: u64) -> Result<Self, ZeroError> {
634 Ok(value)
635 }
636}
637
638impl TryFromMaybeNonZero for NonZeroU64 {
639 fn try_from(value: u64) -> Result<Self, ZeroError> {
640 NonZeroU64::new(value).ok_or(ZeroError {})
641 }
642}
643
644macro_rules! impl_map {
645 ($map_type:ident, $map_mod:tt) => {
646 impl<K, S, I> Update<S> for $map_type<K, PropertiesAndState<S, I>>
647 where
648 K: TryFromMaybeNonZero + Copy + From<NonZeroU64> + Eq + Ord + std::hash::Hash,
649 S: Default,
650 I: FieldInterests,
651 {
652 type Interest = I;
653
654 fn update(
655 &mut self,
656 event: EventWithInterest<I>,
657 ) -> Result<UpdateResult<'_, S, I>, UpdateError> {
658 match event.into_inner() {
659 fnet_interfaces::Event::Existing(existing) => {
660 let existing = Properties::try_from(existing)?;
661 match self.entry(existing.id.into()) {
662 $map_mod::Entry::Occupied(_) => {
663 Err(UpdateError::DuplicateExisting(existing.into()))
664 }
665 $map_mod::Entry::Vacant(entry) => {
666 let PropertiesAndState { properties, state } =
667 entry.insert(PropertiesAndState {
668 properties: existing,
669 state: S::default(),
670 });
671 Ok(UpdateResult::Existing { properties, state })
672 }
673 }
674 }
675 fnet_interfaces::Event::Added(added) => {
676 let added = Properties::try_from(added)?;
677 match self.entry(added.id.into()) {
678 $map_mod::Entry::Occupied(_) => {
679 Err(UpdateError::DuplicateAdded(added.into()))
680 }
681 $map_mod::Entry::Vacant(entry) => {
682 let PropertiesAndState { properties, state } =
683 entry.insert(PropertiesAndState {
684 properties: added,
685 state: S::default(),
686 });
687 Ok(UpdateResult::Added { properties, state })
688 }
689 }
690 }
691 fnet_interfaces::Event::Changed(change) => {
692 let id = if let Some(id) = change.id {
693 id
694 } else {
695 return Err(UpdateError::MissingId(change));
696 };
697 if let Some(properties) = self.get_mut(
698 &K::try_from(id)
699 .map_err(|ZeroError {}| UpdateError::ZeroInterfaceId)?,
700 ) {
701 properties.update(EventWithInterest::new(
702 fnet_interfaces::Event::Changed(change),
703 ))
704 } else {
705 Err(UpdateError::UnknownChanged(change))
706 }
707 }
708 fnet_interfaces::Event::Removed(removed_id) => {
709 if let Some(properties) = self.remove(
710 &K::try_from(removed_id)
711 .map_err(|ZeroError {}| UpdateError::ZeroInterfaceId)?,
712 ) {
713 Ok(UpdateResult::Removed(properties))
714 } else {
715 Err(UpdateError::UnknownRemoved(removed_id))
716 }
717 }
718 fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}) => {
719 Ok(UpdateResult::NoChange)
720 }
721 }
722 }
723 }
724 };
725}
726
727impl_map!(BTreeMap, btree_map);
728impl_map!(HashMap, hash_map);
729
730#[derive(Error, Debug)]
732pub enum WatcherOperationError<S: Debug, B: Update<S> + Debug> {
733 #[error("event stream error: {0}")]
735 EventStream(fidl::Error),
736 #[error("failed to update: {0}")]
738 Update(UpdateError),
739 #[error("watcher event stream ended unexpectedly, final state: {final_state:?}")]
741 UnexpectedEnd {
742 final_state: B,
744 marker: std::marker::PhantomData<S>,
746 },
747 #[error("unexpected event type: {0:?}")]
749 UnexpectedEvent(fnet_interfaces::Event),
750}
751
752#[derive(Error, Debug)]
754pub enum WatcherCreationError {
755 #[error("failed to create interface watcher proxy: {0}")]
757 CreateProxy(fidl::Error),
758 #[error("failed to get interface watcher: {0}")]
760 GetWatcher(fidl::Error),
761}
762
763pub async fn wait_interface<S, B, St, F, T>(
772 stream: St,
773 init: &mut B,
774 mut predicate: F,
775) -> Result<T, WatcherOperationError<S, B>>
776where
777 S: Debug + Default,
778 B: Update<S> + Clone + Debug,
779 St: Stream<Item = Result<EventWithInterest<B::Interest>, fidl::Error>>,
780 F: FnMut(&B) -> Option<T>,
781{
782 async_utils::fold::try_fold_while(
783 stream.map_err(WatcherOperationError::EventStream),
784 init,
785 |acc, event| {
786 futures::future::ready(match acc.update(event) {
787 Ok(changed) => match changed {
788 UpdateResult::Existing { .. }
789 | UpdateResult::Added { .. }
790 | UpdateResult::Changed { .. }
791 | UpdateResult::Removed(_) => {
792 if let Some(rtn) = predicate(acc) {
793 Ok(async_utils::fold::FoldWhile::Done(rtn))
794 } else {
795 Ok(async_utils::fold::FoldWhile::Continue(acc))
796 }
797 }
798 UpdateResult::NoChange => Ok(async_utils::fold::FoldWhile::Continue(acc)),
799 },
800 Err(e) => Err(WatcherOperationError::Update(e)),
801 })
802 },
803 )
804 .await?
805 .short_circuited()
806 .map_err(|final_state| WatcherOperationError::UnexpectedEnd {
807 final_state: final_state.clone(),
808 marker: Default::default(),
809 })
810}
811
812#[derive(Derivative)]
814#[derivative(
815 Clone(bound = "S: Clone"),
816 Debug(bound = "S: Debug"),
817 PartialEq(bound = "S: PartialEq")
818)]
819pub enum InterfaceState<S, I: FieldInterests> {
820 Unknown(u64),
822 Known(PropertiesAndState<S, I>),
824}
825
826pub async fn wait_interface_with_id<S, St, F, T, I>(
838 stream: St,
839 init: &mut InterfaceState<S, I>,
840 mut predicate: F,
841) -> Result<T, WatcherOperationError<S, InterfaceState<S, I>>>
842where
843 S: Default + Clone + Debug,
844 St: Stream<Item = Result<EventWithInterest<I>, fidl::Error>>,
845 F: FnMut(&PropertiesAndState<S, I>) -> Option<T>,
846 I: FieldInterests,
847{
848 wait_interface(stream, init, |state| {
849 match state {
850 InterfaceState::Known(properties) => predicate(properties),
851 InterfaceState::Unknown(_) => None,
854 }
855 })
856 .await
857}
858
859pub async fn existing<S, St, B>(stream: St, init: B) -> Result<B, WatcherOperationError<S, B>>
865where
866 S: Debug,
867 St: futures::Stream<Item = Result<EventWithInterest<B::Interest>, fidl::Error>>,
868 B: Update<S> + Debug,
869{
870 async_utils::fold::try_fold_while(
871 stream.map_err(WatcherOperationError::EventStream),
872 init,
873 |mut acc, event| {
874 futures::future::ready(match event.inner() {
875 fnet_interfaces::Event::Existing(_) => match acc.update(event) {
876 Ok::<UpdateResult<'_, _, _>, _>(_) => {
877 Ok(async_utils::fold::FoldWhile::Continue(acc))
878 }
879 Err(e) => Err(WatcherOperationError::Update(e)),
880 },
881 fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}) => {
882 Ok(async_utils::fold::FoldWhile::Done(acc))
883 }
884 fnet_interfaces::Event::Added(_)
885 | fnet_interfaces::Event::Removed(_)
886 | fnet_interfaces::Event::Changed(_) => {
887 Err(WatcherOperationError::UnexpectedEvent(event.into_inner()))
888 }
889 })
890 },
891 )
892 .await?
893 .short_circuited()
894 .map_err(|acc| WatcherOperationError::UnexpectedEnd {
895 final_state: acc,
896 marker: Default::default(),
897 })
898}
899
900#[derive(Default)]
902pub enum IncludedAddresses {
903 All,
905 #[default]
907 OnlyAssigned,
908}
909
910#[derive(Default)]
913pub struct WatchOptions {
914 pub included_addresses: IncludedAddresses,
916 pub port_identity_koid_filter: Option<PortIdentityKoid>,
918}
919
920pub fn event_stream_from_state<I: FieldInterests>(
926 interface_state: &fnet_interfaces::StateProxy,
927 options: WatchOptions,
928) -> Result<
929 impl Stream<Item = Result<EventWithInterest<I>, fidl::Error>> + use<I>,
930 WatcherCreationError,
931> {
932 let (watcher, server) =
933 interface_state.domain().create_proxy::<fnet_interfaces::WatcherMarker>();
934 let WatchOptions { included_addresses, port_identity_koid_filter } = options;
935 interface_state
936 .get_watcher(
937 &fnet_interfaces::WatcherOptions {
938 address_properties_interest: Some(interest_from_params::<I>()),
939 include_non_assigned_addresses: Some(match included_addresses {
940 IncludedAddresses::All => true,
941 IncludedAddresses::OnlyAssigned => false,
942 }),
943 port_identity_koid_filter: port_identity_koid_filter.map(|p| p.raw_koid()),
944 ..Default::default()
945 },
946 server,
947 )
948 .map_err(WatcherCreationError::GetWatcher)?;
949 Ok(futures::stream::try_unfold(watcher, |watcher| async {
950 Ok(Some((EventWithInterest::new(watcher.watch().await?), watcher)))
951 }))
952}
953
954fn interest_from_params<I: FieldInterests>() -> fnet_interfaces::AddressPropertiesInterest {
955 let mut interest = fnet_interfaces::AddressPropertiesInterest::empty();
956 if <I::ValidUntil as MaybeInterest<_>>::ENABLED {
957 interest |= fnet_interfaces::AddressPropertiesInterest::VALID_UNTIL;
958 }
959 if <I::PreferredLifetimeInfo as MaybeInterest<_>>::ENABLED {
960 interest |= fnet_interfaces::AddressPropertiesInterest::PREFERRED_LIFETIME_INFO;
961 }
962 interest
963}
964
965#[derive(Clone, Debug, Eq, Hash, PartialEq, Default)]
967pub struct NoInterest;
968
969mod interest {
970 use super::*;
971
972 use Debug;
973 use std::hash::Hash;
974
975 pub trait FieldInterests {
981 type PreferredLifetimeInfo: MaybeInterest<PreferredLifetimeInfo>;
983 type ValidUntil: MaybeInterest<PositiveMonotonicInstant>;
985 }
986
987 pub trait MaybeInterest<T> {
989 const ENABLED: bool;
991
992 type Ty: Clone + Debug + Eq + Hash + PartialEq + 'static;
994
995 fn try_from_fidl<F: TryInto<T, Error: Into<anyhow::Error>>>(
997 fidl: Option<F>,
998 ) -> Result<Self::Ty, anyhow::Error>;
999
1000 fn into_fidl<F: From<T>>(value: Self::Ty) -> Option<F>;
1003 }
1004
1005 pub struct EnableInterest;
1009
1010 impl<T: Clone + Debug + Eq + Hash + PartialEq + 'static> MaybeInterest<T> for EnableInterest {
1011 const ENABLED: bool = true;
1012 type Ty = T;
1013
1014 fn try_from_fidl<F: TryInto<T, Error: Into<anyhow::Error>>>(
1015 fidl: Option<F>,
1016 ) -> Result<Self::Ty, anyhow::Error> {
1017 fidl.map(|f| f.try_into().map_err(Into::into))
1018 .unwrap_or_else(|| Err(anyhow::anyhow!("missing field with registered interest")))
1019 }
1020
1021 fn into_fidl<F: From<T>>(value: Self::Ty) -> Option<F> {
1022 Some(value.into())
1023 }
1024 }
1025
1026 pub struct DisableInterest;
1030 impl<T> MaybeInterest<T> for DisableInterest {
1031 const ENABLED: bool = false;
1032
1033 type Ty = NoInterest;
1034
1035 fn try_from_fidl<F: TryInto<T, Error: Into<anyhow::Error>>>(
1036 fidl: Option<F>,
1037 ) -> Result<Self::Ty, anyhow::Error> {
1038 match fidl {
1039 Some(_) => Err(anyhow::anyhow!("unexpected set field with no registered interest")),
1040 None => Ok(NoInterest),
1041 }
1042 }
1043
1044 fn into_fidl<F: From<T>>(_value: Self::Ty) -> Option<F> {
1045 None
1046 }
1047 }
1048
1049 pub(super) type FromInterest<I, T> =
1052 <<T as FieldSpec>::Interest<I> as MaybeInterest<<T as FieldSpec>::Present>>::Ty;
1053
1054 pub trait FieldSpec {
1060 type Interest<I: FieldInterests>: MaybeInterest<Self::Present>;
1062
1063 type Fidl: From<Self::Present>;
1065
1066 type Present: TryFrom<Self::Fidl, Error: Into<anyhow::Error>>;
1069
1070 const FIELD_NAME: &'static str;
1073 }
1074
1075 pub struct InterestConverter<I, P>(PhantomData<(I, P)>);
1076
1077 impl<I, P> fidl_table_validation::Converter for InterestConverter<I, P>
1078 where
1079 I: FieldInterests,
1080 P: FieldSpec,
1081 {
1082 type Fidl = Option<P::Fidl>;
1083 type Validated = <P::Interest<I> as MaybeInterest<P::Present>>::Ty;
1084 type Error = anyhow::Error;
1085
1086 fn try_from_fidl(value: Self::Fidl) -> std::result::Result<Self::Validated, Self::Error> {
1087 <P::Interest<I> as MaybeInterest<_>>::try_from_fidl(value).context(P::FIELD_NAME)
1088 }
1089
1090 fn from_validated(validated: Self::Validated) -> Self::Fidl {
1091 <P::Interest<I> as MaybeInterest<_>>::into_fidl(validated)
1092 }
1093 }
1094
1095 pub struct ValidUntilInterest;
1096
1097 impl FieldSpec for ValidUntilInterest {
1098 type Interest<I: FieldInterests> = I::ValidUntil;
1099 type Fidl = zx_types::zx_time_t;
1100 type Present = PositiveMonotonicInstant;
1101 const FIELD_NAME: &'static str = "valid_until";
1102 }
1103
1104 pub struct PreferredLifetimeInfoInterest;
1105
1106 impl FieldSpec for PreferredLifetimeInfoInterest {
1107 type Interest<I: FieldInterests> = I::PreferredLifetimeInfo;
1108 type Fidl = fnet_interfaces::PreferredLifetimeInfo;
1109 type Present = PreferredLifetimeInfo;
1110 const FIELD_NAME: &'static str = "preferred_lifetime_info";
1111 }
1112}
1113pub use interest::{DisableInterest, EnableInterest, FieldInterests};
1114use interest::{
1115 FromInterest, InterestConverter, MaybeInterest, PreferredLifetimeInfoInterest,
1116 ValidUntilInterest,
1117};
1118
1119#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1121pub struct AllInterest;
1122impl FieldInterests for AllInterest {
1123 type PreferredLifetimeInfo = EnableInterest;
1124 type ValidUntil = EnableInterest;
1125}
1126
1127#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1130pub struct DefaultInterest;
1131impl FieldInterests for DefaultInterest {
1132 type PreferredLifetimeInfo = DisableInterest;
1133 type ValidUntil = DisableInterest;
1134}
1135
1136#[derive(Derivative)]
1139#[derivative(Clone(bound = ""), Debug(bound = ""), Eq(bound = ""), PartialEq(bound = ""))]
1140pub struct EventWithInterest<I: FieldInterests> {
1141 event: fnet_interfaces::Event,
1142 #[derivative(Debug = "ignore")]
1143 _marker: PhantomData<I>,
1144}
1145
1146impl<I: FieldInterests> EventWithInterest<I> {
1147 pub fn new(event: fnet_interfaces::Event) -> Self {
1154 Self { event, _marker: PhantomData }
1155 }
1156
1157 pub fn into_inner(self) -> fnet_interfaces::Event {
1159 self.event
1160 }
1161
1162 pub fn inner(&self) -> &fnet_interfaces::Event {
1164 &self.event
1165 }
1166}
1167
1168impl<I: FieldInterests> From<fnet_interfaces::Event> for EventWithInterest<I> {
1169 fn from(value: fnet_interfaces::Event) -> Self {
1170 Self::new(value)
1171 }
1172}
1173
1174impl<I: FieldInterests> From<EventWithInterest<I>> for fnet_interfaces::Event {
1175 fn from(value: EventWithInterest<I>) -> Self {
1176 value.into_inner()
1177 }
1178}
1179
1180struct PortIdentityKoidConverter;
1181
1182impl From<Never> for PropertiesValidationError {
1183 fn from(value: Never) -> Self {
1184 match value {}
1185 }
1186}
1187
1188impl fidl_table_validation::Converter for PortIdentityKoidConverter {
1189 type Fidl = Option<u64>;
1190 type Validated = Option<PortIdentityKoid>;
1191 type Error = Never;
1192
1193 fn try_from_fidl(value: Self::Fidl) -> Result<Self::Validated, Self::Error> {
1194 Ok(value.map(|value| PortIdentityKoid::from_raw(value)))
1195 }
1196
1197 fn from_validated(validated: Self::Validated) -> Self::Fidl {
1198 validated.map(|validated| validated.raw_koid())
1199 }
1200}
1201
1202#[cfg(test)]
1203mod tests {
1204 use super::*;
1205 use assert_matches::assert_matches;
1206 use flex_fuchsia_net as fnet;
1207 use futures::FutureExt as _;
1208 use futures::task::Poll;
1209 use net_declare::fidl_subnet;
1210 use std::cell::RefCell;
1211 use std::convert::TryInto as _;
1212 use std::pin::Pin;
1213 use std::rc::Rc;
1214 use test_case::test_case;
1215
1216 fn fidl_properties(id: u64) -> fnet_interfaces::Properties {
1217 fnet_interfaces::Properties {
1218 id: Some(id),
1219 name: Some("test1".to_string()),
1220 port_class: Some(fnet_interfaces::PortClass::Loopback(fnet_interfaces::Empty {})),
1221 online: Some(false),
1222 has_default_ipv4_route: Some(false),
1223 has_default_ipv6_route: Some(false),
1224 addresses: Some(vec![fidl_address(ADDR, zx_types::ZX_TIME_INFINITE)]),
1225 ..Default::default()
1226 }
1227 }
1228
1229 fn validated_properties(id: u64) -> PropertiesAndState<(), AllInterest> {
1230 PropertiesAndState {
1231 properties: fidl_properties(id).try_into().expect("failed to validate FIDL Properties"),
1232 state: (),
1233 }
1234 }
1235
1236 fn properties_delta(id: u64) -> fnet_interfaces::Properties {
1237 fnet_interfaces::Properties {
1238 id: Some(id),
1239 name: None,
1240 port_class: None,
1241 online: Some(true),
1242 has_default_ipv4_route: Some(true),
1243 has_default_ipv6_route: Some(true),
1244 addresses: Some(vec![fidl_address(ADDR2, zx_types::ZX_TIME_INFINITE)]),
1245 ..Default::default()
1246 }
1247 }
1248
1249 fn fidl_properties_after_change(id: u64) -> fnet_interfaces::Properties {
1250 fnet_interfaces::Properties {
1251 id: Some(id),
1252 name: Some("test1".to_string()),
1253 port_class: Some(fnet_interfaces::PortClass::Loopback(fnet_interfaces::Empty {})),
1254 online: Some(true),
1255 has_default_ipv4_route: Some(true),
1256 has_default_ipv6_route: Some(true),
1257 addresses: Some(vec![fidl_address(ADDR2, zx_types::ZX_TIME_INFINITE)]),
1258 ..Default::default()
1259 }
1260 }
1261
1262 fn validated_properties_after_change(id: u64) -> PropertiesAndState<(), AllInterest> {
1263 PropertiesAndState {
1264 properties: fidl_properties_after_change(id)
1265 .try_into()
1266 .expect("failed to validate FIDL Properties"),
1267 state: (),
1268 }
1269 }
1270
1271 fn fidl_address(
1272 addr: fnet::Subnet,
1273 valid_until: zx_types::zx_time_t,
1274 ) -> fnet_interfaces::Address {
1275 fnet_interfaces::Address {
1276 addr: Some(addr),
1277 valid_until: Some(valid_until.try_into().unwrap()),
1278 assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
1279 preferred_lifetime_info: Some(PreferredLifetimeInfo::preferred_forever().into()),
1280 __source_breaking: Default::default(),
1281 }
1282 }
1283
1284 const ID: u64 = 1;
1285 const ID2: u64 = 2;
1286 const ADDR: fnet::Subnet = fidl_subnet!("1.2.3.4/24");
1287 const ADDR2: fnet::Subnet = fidl_subnet!("5.6.7.8/24");
1288
1289 #[test_case(
1290 &mut std::iter::once((ID, validated_properties(ID))).collect::<HashMap<_, _>>();
1291 "hashmap"
1292 )]
1293 #[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
1294 #[test_case(&mut validated_properties(ID); "properties")]
1295 fn test_duplicate_error(state: &mut impl Update<(), Interest = AllInterest>) {
1296 assert_matches::assert_matches!(
1297 state.update(fnet_interfaces::Event::Added(fidl_properties(ID)).into()),
1298 Err(UpdateError::DuplicateAdded(added)) if added == fidl_properties(ID)
1299 );
1300 assert_matches::assert_matches!(
1301 state.update(fnet_interfaces::Event::Existing(fidl_properties(ID)).into()),
1302 Err(UpdateError::DuplicateExisting(existing)) if existing == fidl_properties(ID)
1303 );
1304 }
1305
1306 #[test_case(&mut HashMap::<u64, _>::new(); "hashmap")]
1307 #[test_case(&mut InterfaceState::Unknown(ID); "interface_state_unknown")]
1308 fn test_unknown_error(state: &mut impl Update<(), Interest = AllInterest>) {
1309 let unknown =
1310 fnet_interfaces::Properties { id: Some(ID), online: Some(true), ..Default::default() };
1311 assert_matches::assert_matches!(
1312 state.update(fnet_interfaces::Event::Changed(unknown.clone()).into()),
1313 Err(UpdateError::UnknownChanged(changed)) if changed == unknown
1314 );
1315 assert_matches::assert_matches!(
1316 state.update(fnet_interfaces::Event::Removed(ID).into()),
1317 Err(UpdateError::UnknownRemoved(id)) if id == ID
1318 );
1319 }
1320
1321 #[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
1322 #[test_case(&mut validated_properties(ID); "properties")]
1323 fn test_removed_error(state: &mut impl Update<()>) {
1324 assert_matches::assert_matches!(
1325 state.update(fnet_interfaces::Event::Removed(ID).into()),
1326 Err(UpdateError::Removed)
1327 );
1328 }
1329
1330 #[test_case(&mut HashMap::<u64, _>::new(); "hashmap")]
1331 #[test_case(&mut InterfaceState::Unknown(ID); "interface_state_unknown")]
1332 #[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
1333 #[test_case(&mut validated_properties(ID); "properties")]
1334 fn test_missing_id_error(state: &mut impl Update<(), Interest = AllInterest>) {
1335 let missing_id = fnet_interfaces::Properties { online: Some(true), ..Default::default() };
1336 assert_matches::assert_matches!(
1337 state.update(fnet_interfaces::Event::Changed(missing_id.clone()).into()),
1338 Err(UpdateError::MissingId(properties)) if properties == missing_id
1339 );
1340 }
1341
1342 #[test_case(
1343 &mut std::iter::once((ID, validated_properties(ID))).collect::<HashMap<_, _>>();
1344 "hashmap"
1345 )]
1346 #[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
1347 #[test_case(&mut validated_properties(ID); "properties")]
1348 fn test_empty_change_error(state: &mut impl Update<()>) {
1349 let empty_change = fnet_interfaces::Properties { id: Some(ID), ..Default::default() };
1350 let net_zero_change =
1351 fnet_interfaces::Properties { name: None, port_class: None, ..fidl_properties(ID) };
1352 assert_matches::assert_matches!(
1353 state.update(fnet_interfaces::Event::Changed(empty_change.clone()).into()),
1354 Err(UpdateError::EmptyChange(properties)) if properties == empty_change
1355 );
1356 assert_matches::assert_matches!(
1357 state.update(fnet_interfaces::Event::Changed(net_zero_change.clone()).into()),
1358 Err(UpdateError::EmptyChange(properties)) if properties == net_zero_change
1359 );
1360 }
1361
1362 #[test_case(
1363 &mut std::iter::once((ID, validated_properties(ID))).collect::<HashMap<_, _>>();
1364 "hashmap"
1365 )]
1366 #[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
1367 #[test_case(&mut validated_properties(ID); "properties")]
1368 fn test_update_changed_result(state: &mut impl Update<(), Interest = AllInterest>) {
1369 let want_previous = fnet_interfaces::Properties {
1370 online: Some(false),
1371 has_default_ipv4_route: Some(false),
1372 has_default_ipv6_route: Some(false),
1373 addresses: Some(vec![fidl_address(ADDR, zx_types::ZX_TIME_INFINITE)]),
1374 ..Default::default()
1375 };
1376 assert_matches::assert_matches!(
1377 state.update(fnet_interfaces::Event::Changed(properties_delta(ID).clone()).into()),
1378 Ok(UpdateResult::Changed { previous, current, state: _ }) => {
1379 assert_eq!(previous, want_previous);
1380 let PropertiesAndState { properties, state: () } =
1381 validated_properties_after_change(ID);
1382 assert_eq!(*current, properties);
1383 }
1384 );
1385 }
1386
1387 #[derive(Derivative)]
1388 #[derivative(Clone(bound = ""))]
1389 struct EventStream<I: FieldInterests>(Rc<RefCell<Vec<fnet_interfaces::Event>>>, PhantomData<I>);
1390
1391 impl<I: FieldInterests> Stream for EventStream<I> {
1392 type Item = Result<EventWithInterest<I>, fidl::Error>;
1393
1394 fn poll_next(
1395 self: Pin<&mut Self>,
1396 _cx: &mut futures::task::Context<'_>,
1397 ) -> Poll<Option<Self::Item>> {
1398 let EventStream(events_vec, _marker) = &*self;
1399 if events_vec.borrow().is_empty() {
1400 Poll::Ready(None)
1401 } else {
1402 Poll::Ready(Some(Ok(EventWithInterest::new(events_vec.borrow_mut().remove(0)))))
1403 }
1404 }
1405 }
1406
1407 fn test_event_stream<I: FieldInterests>() -> EventStream<I> {
1408 EventStream(
1409 Rc::new(RefCell::new(vec![
1410 fnet_interfaces::Event::Existing(fidl_properties(ID)),
1411 fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}),
1412 fnet_interfaces::Event::Added(fidl_properties(ID2)),
1413 fnet_interfaces::Event::Changed(properties_delta(ID)),
1414 fnet_interfaces::Event::Changed(properties_delta(ID2)),
1415 fnet_interfaces::Event::Removed(ID),
1416 fnet_interfaces::Event::Removed(ID2),
1417 ])),
1418 PhantomData,
1419 )
1420 }
1421
1422 #[test]
1423 fn test_wait_one_interface() {
1424 let event_stream = test_event_stream::<AllInterest>();
1425 let mut state = InterfaceState::Unknown(ID);
1426 for want in &[validated_properties(ID), validated_properties_after_change(ID)] {
1427 wait_interface_with_id(event_stream.clone(), &mut state, |got| {
1428 assert_eq!(got, want);
1429 Some(())
1430 })
1431 .now_or_never()
1432 .expect("wait_interface_with_id did not complete immediately")
1433 .expect("wait_interface_with_id error");
1434 assert_matches!(state, InterfaceState::Known(ref got) if got == want);
1435 }
1436 }
1437
1438 fn test_wait_interface<'a, B>(state: &mut B, want_states: impl IntoIterator<Item = &'a B>)
1439 where
1440 B: 'a + Update<()> + Clone + Debug + std::cmp::PartialEq,
1441 {
1442 let event_stream = test_event_stream::<B::Interest>();
1443 for want in want_states.into_iter() {
1444 wait_interface(event_stream.clone(), state, |got| {
1445 assert_eq!(got, want);
1446 Some(())
1447 })
1448 .now_or_never()
1449 .expect("wait_interface did not complete immediately")
1450 .expect("wait_interface error");
1451 assert_eq!(state, want);
1452 }
1453 }
1454
1455 #[test]
1456 fn test_wait_interface_hashmap() {
1457 test_wait_interface(
1458 &mut HashMap::new(),
1459 &[
1460 std::iter::once((ID, validated_properties(ID))).collect::<HashMap<_, _>>(),
1461 [(ID, validated_properties(ID)), (ID2, validated_properties(ID2))]
1462 .iter()
1463 .cloned()
1464 .collect::<HashMap<_, _>>(),
1465 [(ID, validated_properties_after_change(ID)), (ID2, validated_properties(ID2))]
1466 .iter()
1467 .cloned()
1468 .collect::<HashMap<_, _>>(),
1469 [
1470 (ID, validated_properties_after_change(ID)),
1471 (ID2, validated_properties_after_change(ID2)),
1472 ]
1473 .iter()
1474 .cloned()
1475 .collect::<HashMap<_, _>>(),
1476 std::iter::once((ID2, validated_properties_after_change(ID2)))
1477 .collect::<HashMap<_, _>>(),
1478 HashMap::new(),
1479 ],
1480 );
1481 }
1482
1483 #[test]
1484 fn test_wait_interface_interface_state() {
1485 test_wait_interface(
1486 &mut InterfaceState::Unknown(ID),
1487 &[
1488 InterfaceState::Known(validated_properties(ID)),
1489 InterfaceState::Known(validated_properties_after_change(ID)),
1490 ],
1491 );
1492 }
1493
1494 const ID_NON_EXISTENT: u64 = 0xffffffff;
1495 #[test_case(
1496 InterfaceState::Unknown(ID_NON_EXISTENT),
1497 InterfaceState::Unknown(ID_NON_EXISTENT);
1498 "interface_state_unknown_different_id"
1499 )]
1500 #[test_case(
1501 InterfaceState::Unknown(ID),
1502 InterfaceState::Known(validated_properties(ID));
1503 "interface_state_unknown")]
1504 #[test_case(
1505 HashMap::new(),
1506 [(ID, validated_properties(ID)), (ID2, validated_properties(ID2))]
1507 .iter()
1508 .cloned()
1509 .collect::<HashMap<_, _>>();
1510 "hashmap"
1511 )]
1512 fn test_existing<B>(state: B, want: B)
1513 where
1514 B: Update<(), Interest = AllInterest> + Debug + std::cmp::PartialEq,
1515 {
1516 let events = [
1517 fnet_interfaces::Event::Existing(fidl_properties(ID)),
1518 fnet_interfaces::Event::Existing(fidl_properties(ID2)),
1519 fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}),
1520 ];
1521 let event_stream =
1522 futures::stream::iter(events.iter().cloned().map(|e| Ok(EventWithInterest::new(e))));
1523 assert_eq!(
1524 existing(event_stream, state)
1525 .now_or_never()
1526 .expect("existing did not complete immediately")
1527 .expect("existing returned error"),
1528 want,
1529 );
1530 }
1531
1532 #[test]
1533 fn positive_instant() {
1534 assert_eq!(PositiveMonotonicInstant::from_nanos(-1), None);
1535 assert_eq!(PositiveMonotonicInstant::from_nanos(0), None);
1536 assert_eq!(PositiveMonotonicInstant::from_nanos(1), Some(PositiveMonotonicInstant(1)));
1537 }
1538}