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