#![deny(missing_docs)]
pub mod admin;
mod reachability;
pub use reachability::{is_globally_routable, to_reachability_stream, wait_for_reachability};
use fidl_fuchsia_net_interfaces as fnet_interfaces;
use fidl_table_validation::*;
use fuchsia_zircon_types as zx;
use futures::{Stream, TryStreamExt as _};
use std::collections::{
btree_map::{self, BTreeMap},
hash_map::{self, HashMap},
};
use std::convert::TryFrom as _;
use std::num::NonZeroU64;
use thiserror::Error;
#[derive(Clone, Debug, Eq, PartialEq, ValidFidlTable)]
#[fidl_table_src(fnet_interfaces::Properties)]
pub struct Properties {
pub id: NonZeroU64,
pub name: String,
pub device_class: fnet_interfaces::DeviceClass,
pub online: bool,
pub addresses: Vec<Address>,
pub has_default_ipv4_route: bool,
pub has_default_ipv6_route: bool,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, ValidFidlTable)]
#[fidl_table_src(fnet_interfaces::Address)]
#[fidl_table_validator(AddressValidator)]
pub struct Address {
pub addr: fidl_fuchsia_net::Subnet,
pub valid_until: zx::zx_time_t,
pub assignment_state: fnet_interfaces::AddressAssignmentState,
}
pub struct AddressValidator;
impl Validate<Address> for AddressValidator {
type Error = String;
fn validate(
Address { addr: _, valid_until, assignment_state: _ }: &Address,
) -> Result<(), Self::Error> {
if *valid_until <= 0 {
return Err(format!("non-positive value for valid_until={}", *valid_until));
}
Ok(())
}
}
#[derive(Error, Debug)]
pub enum UpdateError {
#[error("duplicate added event {0:?}")]
DuplicateAdded(fnet_interfaces::Properties),
#[error("duplicate existing event {0:?}")]
DuplicateExisting(fnet_interfaces::Properties),
#[error("failed to validate Properties FIDL table: {0}")]
InvalidProperties(#[from] PropertiesValidationError),
#[error("failed to validate Address FIDL table: {0}")]
InvalidAddress(#[from] AddressValidationError),
#[error("changed event with missing ID {0:?}")]
MissingId(fnet_interfaces::Properties),
#[error("changed event contains no changed fields {0:?}")]
EmptyChange(fnet_interfaces::Properties),
#[error("interface has been removed")]
Removed,
#[error("unknown interface changed {0:?}")]
UnknownChanged(fnet_interfaces::Properties),
#[error("unknown interface with id {0} deleted")]
UnknownRemoved(u64),
#[error("encountered 0 interface id")]
ZeroInterfaceId,
}
#[derive(Debug, PartialEq)]
pub enum UpdateResult<'a, S> {
NoChange,
Existing {
properties: &'a Properties,
state: &'a mut S,
},
Added {
properties: &'a Properties,
state: &'a mut S,
},
Changed {
previous: fnet_interfaces::Properties,
current: &'a Properties,
state: &'a mut S,
},
Removed(PropertiesAndState<S>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PropertiesAndState<S> {
pub properties: Properties,
pub state: S,
}
pub trait Update<S> {
fn update(&mut self, event: fnet_interfaces::Event)
-> Result<UpdateResult<'_, S>, UpdateError>;
}
impl<S> Update<S> for PropertiesAndState<S> {
fn update(
&mut self,
event: fnet_interfaces::Event,
) -> Result<UpdateResult<'_, S>, UpdateError> {
let Self { properties, state } = self;
match event {
fnet_interfaces::Event::Existing(existing) => {
let existing = Properties::try_from(existing)?;
if existing.id == properties.id {
return Err(UpdateError::DuplicateExisting(existing.into()));
}
}
fnet_interfaces::Event::Added(added) => {
let added = Properties::try_from(added)?;
if added.id == properties.id {
return Err(UpdateError::DuplicateAdded(added.into()));
}
}
fnet_interfaces::Event::Changed(mut change) => {
let fnet_interfaces::Properties {
id,
name: _,
device_class: _,
online,
has_default_ipv4_route,
has_default_ipv6_route,
addresses,
..
} = &mut change;
if let Some(id) = *id {
if properties.id.get() == id {
let mut changed = false;
macro_rules! swap_if_some {
($field:ident) => {
if let Some($field) = $field {
if properties.$field != *$field {
std::mem::swap(&mut properties.$field, $field);
changed = true;
}
}
};
}
swap_if_some!(online);
swap_if_some!(has_default_ipv4_route);
swap_if_some!(has_default_ipv6_route);
if let Some(addresses) = addresses {
if addresses.len() != properties.addresses.len()
|| !addresses
.iter()
.zip(
properties
.addresses
.iter()
.cloned()
.map(fnet_interfaces::Address::from),
)
.all(|(a, b)| *a == b)
{
let previous_len = properties.addresses.len();
let () = properties.addresses.reserve(addresses.len());
for address in addresses.drain(..).map(Address::try_from) {
let () = properties.addresses.push(address?);
}
let () = addresses.extend(
properties.addresses.drain(..previous_len).map(Into::into),
);
changed = true;
}
}
if changed {
change.id = None;
return Ok(UpdateResult::Changed {
previous: change,
current: properties,
state,
});
} else {
return Err(UpdateError::EmptyChange(change));
}
}
} else {
return Err(UpdateError::MissingId(change));
}
}
fnet_interfaces::Event::Removed(removed_id) => {
if properties.id.get() == removed_id {
return Err(UpdateError::Removed);
}
}
fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}) => {}
}
Ok(UpdateResult::NoChange)
}
}
impl<S: Default> Update<S> for InterfaceState<S> {
fn update(
&mut self,
event: fnet_interfaces::Event,
) -> Result<UpdateResult<'_, S>, UpdateError> {
fn get_properties<S>(state: &mut InterfaceState<S>) -> &mut PropertiesAndState<S> {
match state {
InterfaceState::Known(properties) => properties,
InterfaceState::Unknown(id) => unreachable!(
"matched `Unknown({})` immediately after assigning with `Known`",
id
),
}
}
match self {
InterfaceState::Unknown(id) => match event {
fnet_interfaces::Event::Existing(existing) => {
let properties = Properties::try_from(existing)?;
if properties.id.get() == *id {
*self = InterfaceState::Known(PropertiesAndState {
properties,
state: S::default(),
});
let PropertiesAndState { properties, state } = get_properties(self);
return Ok(UpdateResult::Existing { properties, state });
}
}
fnet_interfaces::Event::Added(added) => {
let properties = Properties::try_from(added)?;
if properties.id.get() == *id {
*self = InterfaceState::Known(PropertiesAndState {
properties,
state: S::default(),
});
let PropertiesAndState { properties, state } = get_properties(self);
return Ok(UpdateResult::Added { properties, state });
}
}
fnet_interfaces::Event::Changed(change) => {
if let Some(change_id) = change.id {
if change_id == *id {
return Err(UpdateError::UnknownChanged(change));
}
} else {
return Err(UpdateError::MissingId(change));
}
}
fnet_interfaces::Event::Removed(removed_id) => {
if removed_id == *id {
return Err(UpdateError::UnknownRemoved(removed_id));
}
}
fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}) => {}
},
InterfaceState::Known(properties) => return properties.update(event),
}
Ok(UpdateResult::NoChange)
}
}
trait TryFromMaybeNonzero: Sized {
fn try_from_maybe_nonzero(value: u64) -> Result<Self, UpdateError>;
}
impl TryFromMaybeNonzero for u64 {
fn try_from_maybe_nonzero(value: u64) -> Result<Self, UpdateError> {
Ok(value)
}
}
impl TryFromMaybeNonzero for NonZeroU64 {
fn try_from_maybe_nonzero(value: u64) -> Result<Self, UpdateError> {
NonZeroU64::new(value).ok_or(UpdateError::ZeroInterfaceId)
}
}
macro_rules! impl_map {
($map_type:ident, $map_mod:tt) => {
impl<K, S> Update<S> for $map_type<K, PropertiesAndState<S>>
where
K: TryFromMaybeNonzero + Copy + From<NonZeroU64> + Eq + Ord + std::hash::Hash,
S: Default,
{
fn update(
&mut self,
event: fnet_interfaces::Event,
) -> Result<UpdateResult<'_, S>, UpdateError> {
match event {
fnet_interfaces::Event::Existing(existing) => {
let existing = Properties::try_from(existing)?;
match self.entry(existing.id.into()) {
$map_mod::Entry::Occupied(_) => {
Err(UpdateError::DuplicateExisting(existing.into()))
}
$map_mod::Entry::Vacant(entry) => {
let PropertiesAndState { properties, state } =
entry.insert(PropertiesAndState {
properties: existing,
state: S::default(),
});
Ok(UpdateResult::Existing { properties, state })
}
}
}
fnet_interfaces::Event::Added(added) => {
let added = Properties::try_from(added)?;
match self.entry(added.id.into()) {
$map_mod::Entry::Occupied(_) => {
Err(UpdateError::DuplicateAdded(added.into()))
}
$map_mod::Entry::Vacant(entry) => {
let PropertiesAndState { properties, state } =
entry.insert(PropertiesAndState {
properties: added,
state: S::default(),
});
Ok(UpdateResult::Added { properties, state })
}
}
}
fnet_interfaces::Event::Changed(change) => {
let id = if let Some(id) = change.id {
id
} else {
return Err(UpdateError::MissingId(change));
};
if let Some(properties) = self.get_mut(&K::try_from_maybe_nonzero(id)?) {
properties.update(fnet_interfaces::Event::Changed(change))
} else {
Err(UpdateError::UnknownChanged(change))
}
}
fnet_interfaces::Event::Removed(removed_id) => {
if let Some(properties) =
self.remove(&K::try_from_maybe_nonzero(removed_id)?)
{
Ok(UpdateResult::Removed(properties))
} else {
Err(UpdateError::UnknownRemoved(removed_id))
}
}
fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}) => {
Ok(UpdateResult::NoChange)
}
}
}
}
};
}
impl_map!(BTreeMap, btree_map);
impl_map!(HashMap, hash_map);
#[derive(Error, Debug)]
pub enum WatcherOperationError<S: std::fmt::Debug, B: Update<S> + std::fmt::Debug> {
#[error("event stream error: {0}")]
EventStream(fidl::Error),
#[error("failed to update: {0}")]
Update(UpdateError),
#[error("watcher event stream ended unexpectedly, final state: {final_state:?}")]
UnexpectedEnd {
final_state: B,
marker: std::marker::PhantomData<S>,
},
#[error("unexpected event type: {0:?}")]
UnexpectedEvent(fnet_interfaces::Event),
}
#[derive(Error, Debug)]
pub enum WatcherCreationError {
#[error("failed to create interface watcher proxy: {0}")]
CreateProxy(fidl::Error),
#[error("failed to get interface watcher: {0}")]
GetWatcher(fidl::Error),
}
pub async fn wait_interface<S, B, St, F, T>(
stream: St,
init: &mut B,
mut predicate: F,
) -> Result<T, WatcherOperationError<S, B>>
where
S: std::fmt::Debug + Default,
B: Update<S> + Clone + std::fmt::Debug,
St: Stream<Item = Result<fnet_interfaces::Event, fidl::Error>>,
F: FnMut(&B) -> Option<T>,
{
async_utils::fold::try_fold_while(
stream.map_err(WatcherOperationError::EventStream),
init,
|acc, event| {
futures::future::ready(match acc.update(event) {
Ok(changed) => match changed {
UpdateResult::Existing { .. }
| UpdateResult::Added { .. }
| UpdateResult::Changed { .. }
| UpdateResult::Removed(_) => {
if let Some(rtn) = predicate(acc) {
Ok(async_utils::fold::FoldWhile::Done(rtn))
} else {
Ok(async_utils::fold::FoldWhile::Continue(acc))
}
}
UpdateResult::NoChange => Ok(async_utils::fold::FoldWhile::Continue(acc)),
},
Err(e) => Err(WatcherOperationError::Update(e)),
})
},
)
.await?
.short_circuited()
.map_err(|final_state| WatcherOperationError::UnexpectedEnd {
final_state: final_state.clone(),
marker: Default::default(),
})
}
#[derive(Clone, Debug, PartialEq)]
pub enum InterfaceState<S> {
Unknown(u64),
Known(PropertiesAndState<S>),
}
pub async fn wait_interface_with_id<S, St, F, T>(
stream: St,
init: &mut InterfaceState<S>,
mut predicate: F,
) -> Result<T, WatcherOperationError<S, InterfaceState<S>>>
where
S: Default + Clone + std::fmt::Debug,
St: Stream<Item = Result<fnet_interfaces::Event, fidl::Error>>,
F: FnMut(&PropertiesAndState<S>) -> Option<T>,
{
wait_interface(stream, init, |state| {
match state {
InterfaceState::Known(properties) => predicate(properties),
InterfaceState::Unknown(_) => None,
}
})
.await
}
pub async fn existing<S, St, B>(stream: St, init: B) -> Result<B, WatcherOperationError<S, B>>
where
S: std::fmt::Debug,
St: futures::Stream<Item = Result<fnet_interfaces::Event, fidl::Error>>,
B: Update<S> + std::fmt::Debug,
{
async_utils::fold::try_fold_while(
stream.map_err(WatcherOperationError::EventStream),
init,
|mut acc, event| {
futures::future::ready(match event {
fnet_interfaces::Event::Existing(_) => match acc.update(event) {
Ok::<UpdateResult<'_, _>, _>(_) => {
Ok(async_utils::fold::FoldWhile::Continue(acc))
}
Err(e) => Err(WatcherOperationError::Update(e)),
},
fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}) => {
Ok(async_utils::fold::FoldWhile::Done(acc))
}
fnet_interfaces::Event::Added(_)
| fnet_interfaces::Event::Removed(_)
| fnet_interfaces::Event::Changed(_) => {
Err(WatcherOperationError::UnexpectedEvent(event))
}
})
},
)
.await?
.short_circuited()
.map_err(|acc| WatcherOperationError::UnexpectedEnd {
final_state: acc,
marker: Default::default(),
})
}
pub enum IncludedAddresses {
All,
OnlyAssigned,
}
pub fn event_stream_from_state(
interface_state: &fnet_interfaces::StateProxy,
included_addresses: IncludedAddresses,
) -> Result<impl Stream<Item = Result<fnet_interfaces::Event, fidl::Error>>, WatcherCreationError> {
let (watcher, server) = ::fidl::endpoints::create_proxy::<fnet_interfaces::WatcherMarker>()
.map_err(WatcherCreationError::CreateProxy)?;
let () = interface_state
.get_watcher(
&fnet_interfaces::WatcherOptions {
address_properties_interest: Some(
fnet_interfaces::AddressPropertiesInterest::VALID_UNTIL
| fnet_interfaces::AddressPropertiesInterest::PREFERRED_LIFETIME_INFO,
),
include_non_assigned_addresses: Some(match included_addresses {
IncludedAddresses::All => true,
IncludedAddresses::OnlyAssigned => false,
}),
..Default::default()
},
server,
)
.map_err(WatcherCreationError::GetWatcher)?;
Ok(futures::stream::try_unfold(watcher, |watcher| async {
Ok(Some((watcher.watch().await?, watcher)))
}))
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fidl_fuchsia_net as fnet;
use futures::{task::Poll, FutureExt as _};
use net_declare::fidl_subnet;
use std::{cell::RefCell, convert::TryInto as _, pin::Pin, rc::Rc};
use test_case::test_case;
fn fidl_properties(id: u64) -> fnet_interfaces::Properties {
fnet_interfaces::Properties {
id: Some(id),
name: Some("test1".to_string()),
device_class: Some(fnet_interfaces::DeviceClass::Loopback(fnet_interfaces::Empty {})),
online: Some(false),
has_default_ipv4_route: Some(false),
has_default_ipv6_route: Some(false),
addresses: Some(vec![fidl_address(ADDR, zx::ZX_TIME_INFINITE)]),
..Default::default()
}
}
fn validated_properties(id: u64) -> PropertiesAndState<()> {
PropertiesAndState {
properties: fidl_properties(id).try_into().expect("failed to validate FIDL Properties"),
state: (),
}
}
fn properties_delta(id: u64) -> fnet_interfaces::Properties {
fnet_interfaces::Properties {
id: Some(id),
name: None,
device_class: None,
online: Some(true),
has_default_ipv4_route: Some(true),
has_default_ipv6_route: Some(true),
addresses: Some(vec![fidl_address(ADDR2, zx::ZX_TIME_INFINITE)]),
..Default::default()
}
}
fn fidl_properties_after_change(id: u64) -> fnet_interfaces::Properties {
fnet_interfaces::Properties {
id: Some(id),
name: Some("test1".to_string()),
device_class: Some(fnet_interfaces::DeviceClass::Loopback(fnet_interfaces::Empty {})),
online: Some(true),
has_default_ipv4_route: Some(true),
has_default_ipv6_route: Some(true),
addresses: Some(vec![fidl_address(ADDR2, zx::ZX_TIME_INFINITE)]),
..Default::default()
}
}
fn validated_properties_after_change(id: u64) -> PropertiesAndState<()> {
PropertiesAndState {
properties: fidl_properties_after_change(id)
.try_into()
.expect("failed to validate FIDL Properties"),
state: (),
}
}
fn fidl_address(addr: fnet::Subnet, valid_until: zx::zx_time_t) -> fnet_interfaces::Address {
fnet_interfaces::Address {
addr: Some(addr),
valid_until: Some(valid_until),
assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
..Default::default()
}
}
const ID: u64 = 1;
const ID2: u64 = 2;
const ADDR: fnet::Subnet = fidl_subnet!("1.2.3.4/24");
const ADDR2: fnet::Subnet = fidl_subnet!("5.6.7.8/24");
#[test_case(
&mut std::iter::once((ID, validated_properties(ID))).collect::<HashMap<_, _>>();
"hashmap"
)]
#[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
#[test_case(&mut validated_properties(ID); "properties")]
fn test_duplicate_error(state: &mut impl Update<()>) {
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Added(fidl_properties(ID))),
Err(UpdateError::DuplicateAdded(added)) if added == fidl_properties(ID)
);
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Existing(fidl_properties(ID))),
Err(UpdateError::DuplicateExisting(existing)) if existing == fidl_properties(ID)
);
}
#[test_case(&mut HashMap::<u64, _>::new(); "hashmap")]
#[test_case(&mut InterfaceState::Unknown(ID); "interface_state_unknown")]
fn test_unknown_error(state: &mut impl Update<()>) {
let unknown =
fnet_interfaces::Properties { id: Some(ID), online: Some(true), ..Default::default() };
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(unknown.clone())),
Err(UpdateError::UnknownChanged(changed)) if changed == unknown
);
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Removed(ID)),
Err(UpdateError::UnknownRemoved(id)) if id == ID
);
}
#[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
#[test_case(&mut validated_properties(ID); "properties")]
fn test_removed_error(state: &mut impl Update<()>) {
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Removed(ID)),
Err(UpdateError::Removed)
);
}
#[test_case(&mut HashMap::<u64, _>::new(); "hashmap")]
#[test_case(&mut InterfaceState::Unknown(ID); "interface_state_unknown")]
#[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
#[test_case(&mut validated_properties(ID); "properties")]
fn test_missing_id_error(state: &mut impl Update<()>) {
let missing_id = fnet_interfaces::Properties { online: Some(true), ..Default::default() };
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(missing_id.clone())),
Err(UpdateError::MissingId(properties)) if properties == missing_id
);
}
#[test_case(
&mut std::iter::once((ID, validated_properties(ID))).collect::<HashMap<_, _>>();
"hashmap"
)]
#[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
#[test_case(&mut validated_properties(ID); "properties")]
fn test_empty_change_error(state: &mut impl Update<()>) {
let empty_change = fnet_interfaces::Properties { id: Some(ID), ..Default::default() };
let net_zero_change =
fnet_interfaces::Properties { name: None, device_class: None, ..fidl_properties(ID) };
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(empty_change.clone())),
Err(UpdateError::EmptyChange(properties)) if properties == empty_change
);
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(net_zero_change.clone())),
Err(UpdateError::EmptyChange(properties)) if properties == net_zero_change
);
}
#[test_case(
&mut std::iter::once((ID, validated_properties(ID))).collect::<HashMap<_, _>>();
"hashmap"
)]
#[test_case(&mut InterfaceState::Known(validated_properties(ID)); "interface_state_known")]
#[test_case(&mut validated_properties(ID); "properties")]
fn test_update_changed_result(state: &mut impl Update<()>) {
let want_previous = fnet_interfaces::Properties {
online: Some(false),
has_default_ipv4_route: Some(false),
has_default_ipv6_route: Some(false),
addresses: Some(vec![fidl_address(ADDR, zx::ZX_TIME_INFINITE)]),
..Default::default()
};
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(properties_delta(ID).clone())),
Ok(UpdateResult::Changed { previous, current, state: _ }) => {
assert_eq!(previous, want_previous);
let PropertiesAndState { properties, state: () } =
validated_properties_after_change(ID);
assert_eq!(*current, properties);
}
);
}
#[derive(Clone)]
struct EventStream(Rc<RefCell<Vec<fnet_interfaces::Event>>>);
impl Stream for EventStream {
type Item = Result<fnet_interfaces::Event, fidl::Error>;
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut futures::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
let EventStream(events_vec) = &*self;
if events_vec.borrow().is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(events_vec.borrow_mut().remove(0))))
}
}
}
fn test_event_stream() -> EventStream {
EventStream(Rc::new(RefCell::new(vec![
fnet_interfaces::Event::Existing(fidl_properties(ID)),
fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}),
fnet_interfaces::Event::Added(fidl_properties(ID2)),
fnet_interfaces::Event::Changed(properties_delta(ID)),
fnet_interfaces::Event::Changed(properties_delta(ID2)),
fnet_interfaces::Event::Removed(ID),
fnet_interfaces::Event::Removed(ID2),
])))
}
#[test]
fn test_wait_one_interface() {
let event_stream = test_event_stream();
let mut state = InterfaceState::Unknown(ID);
for want in &[validated_properties(ID), validated_properties_after_change(ID)] {
let () = wait_interface_with_id(event_stream.clone(), &mut state, |got| {
assert_eq!(got, want);
Some(())
})
.now_or_never()
.expect("wait_interface_with_id did not complete immediately")
.expect("wait_interface_with_id error");
assert_matches!(state, InterfaceState::Known(ref got) if got == want);
}
}
fn test_wait_interface<'a, B>(state: &mut B, want_states: impl IntoIterator<Item = &'a B>)
where
B: 'a + Update<()> + Clone + std::fmt::Debug + std::cmp::PartialEq,
{
let event_stream = test_event_stream();
for want in want_states.into_iter() {
let () = wait_interface(event_stream.clone(), state, |got| {
assert_eq!(got, want);
Some(())
})
.now_or_never()
.expect("wait_interface did not complete immediately")
.expect("wait_interface error");
assert_eq!(state, want);
}
}
#[test]
fn test_wait_interface_hashmap() {
test_wait_interface(
&mut HashMap::new(),
&[
std::iter::once((ID, validated_properties(ID))).collect::<HashMap<_, _>>(),
[(ID, validated_properties(ID)), (ID2, validated_properties(ID2))]
.iter()
.cloned()
.collect::<HashMap<_, _>>(),
[(ID, validated_properties_after_change(ID)), (ID2, validated_properties(ID2))]
.iter()
.cloned()
.collect::<HashMap<_, _>>(),
[
(ID, validated_properties_after_change(ID)),
(ID2, validated_properties_after_change(ID2)),
]
.iter()
.cloned()
.collect::<HashMap<_, _>>(),
std::iter::once((ID2, validated_properties_after_change(ID2)))
.collect::<HashMap<_, _>>(),
HashMap::new(),
],
);
}
#[test]
fn test_wait_interface_interface_state() {
test_wait_interface(
&mut InterfaceState::Unknown(ID),
&[
InterfaceState::Known(validated_properties(ID)),
InterfaceState::Known(validated_properties_after_change(ID)),
],
);
}
const ID_NON_EXISTENT: u64 = 0xffffffff;
#[test_case(
InterfaceState::Unknown(ID_NON_EXISTENT),
InterfaceState::Unknown(ID_NON_EXISTENT);
"interface_state_unknown_different_id"
)]
#[test_case(
InterfaceState::Unknown(ID),
InterfaceState::Known(validated_properties(ID));
"interface_state_unknown")]
#[test_case(
HashMap::new(),
[(ID, validated_properties(ID)), (ID2, validated_properties(ID2))]
.iter()
.cloned()
.collect::<HashMap<_, _>>();
"hashmap"
)]
fn test_existing<B>(state: B, want: B)
where
B: Update<()> + std::fmt::Debug + std::cmp::PartialEq,
{
let events = [
fnet_interfaces::Event::Existing(fidl_properties(ID)),
fnet_interfaces::Event::Existing(fidl_properties(ID2)),
fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}),
];
let event_stream = futures::stream::iter(events.iter().cloned().map(Ok));
assert_eq!(
existing(event_stream, state)
.now_or_never()
.expect("existing did not complete immediately")
.expect("existing returned error"),
want,
);
}
#[test]
fn test_address_validator() {
let valid = fidl_address(ADDR, 42);
let fidl_fuchsia_net_interfaces::Address { addr, valid_until, assignment_state, .. } =
valid.clone();
assert_eq!(
Address::try_from(valid).expect("failed to create address from valid fidl table"),
Address {
addr: addr.expect("addr field missing from valid address"),
valid_until: valid_until.expect("valid_until field missing from valid address"),
assignment_state: assignment_state
.expect("assignment_state missing from valid address"),
}
);
let invalid = fidl_address(ADDR, 0);
assert_matches!(
Address::try_from(invalid),
Err(AddressValidationError::Logical(e @ String { .. }))
if e == "non-positive value for valid_until=0"
);
}
}