blob: ceff0e18947028b0cebae35e9ddde563edb1cc3d [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(https://fxbug.dev/42062192): Provide facilities that work with a watcher
// disinterested in all address properties.
//! Extensions for the fuchsia.net.interfaces FIDL library.
#![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;
// TODO(https://fxbug.dev/42144953): Prevent this type from becoming stale.
/// Properties of a network interface.
#[derive(Clone, Debug, Eq, PartialEq, ValidFidlTable)]
#[fidl_table_src(fnet_interfaces::Properties)]
pub struct Properties {
/// An opaque identifier for the interface. Its value will not be reused
/// even if the device is removed and subsequently re-added. Immutable.
pub id: NonZeroU64,
/// The name of the interface. Immutable.
pub name: String,
/// The device class of the interface. Immutable.
pub device_class: fnet_interfaces::DeviceClass,
/// The device is enabled and its physical state is online.
pub online: bool,
/// The addresses currently assigned to the interface.
pub addresses: Vec<Address>,
/// Whether there is a default IPv4 route through this interface.
pub has_default_ipv4_route: bool,
/// Whether there is a default IPv6 route through this interface.
pub has_default_ipv6_route: bool,
}
// TODO(https://fxbug.dev/42144953): Prevent this type from becoming stale.
/// An address and its properties.
#[derive(Clone, Debug, Eq, Hash, PartialEq, ValidFidlTable)]
#[fidl_table_src(fnet_interfaces::Address)]
#[fidl_table_validator(AddressValidator)]
pub struct Address {
/// The address and prefix length.
pub addr: fidl_fuchsia_net::Subnet,
/// The time after which the address will no longer be valid.
///
/// Its value must be greater than 0. A value of zx.time.INFINITE indicates
/// that the address will always be valid.
// TODO(https://fxbug.dev/42155335): Replace with zx::Time once there is support for custom
// conversion functions.
pub valid_until: zx::zx_time_t,
/// The address's assignment state.
pub assignment_state: fnet_interfaces::AddressAssignmentState,
}
/// Helper struct implementing Address validation.
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(())
}
}
/// Interface watcher event update errors.
#[derive(Error, Debug)]
pub enum UpdateError {
/// The update attempted to add an already-known added interface into local state.
#[error("duplicate added event {0:?}")]
DuplicateAdded(fnet_interfaces::Properties),
/// The update attempted to add an already-known existing interface into local state.
#[error("duplicate existing event {0:?}")]
DuplicateExisting(fnet_interfaces::Properties),
/// The event contained one or more invalid properties.
#[error("failed to validate Properties FIDL table: {0}")]
InvalidProperties(#[from] PropertiesValidationError),
/// The event contained one or more invalid addresses.
#[error("failed to validate Address FIDL table: {0}")]
InvalidAddress(#[from] AddressValidationError),
/// The event was required to have contained an ID, but did not.
#[error("changed event with missing ID {0:?}")]
MissingId(fnet_interfaces::Properties),
/// The event did not contain any changes.
#[error("changed event contains no changed fields {0:?}")]
EmptyChange(fnet_interfaces::Properties),
/// The update removed the only interface in the local state.
#[error("interface has been removed")]
Removed,
/// The event contained changes for an interface that did not exist in local state.
#[error("unknown interface changed {0:?}")]
UnknownChanged(fnet_interfaces::Properties),
/// The event removed an interface that did not exist in local state.
#[error("unknown interface with id {0} deleted")]
UnknownRemoved(u64),
/// The event included an interface id = 0, which should never happen.
#[error("encountered 0 interface id")]
ZeroInterfaceId,
}
/// The result of updating network interface state with an event.
#[derive(Debug, PartialEq)]
pub enum UpdateResult<'a, S> {
/// The update did not change the local state.
NoChange,
/// The update inserted an existing interface into the local state.
Existing {
/// The properties,
properties: &'a Properties,
/// The state.
state: &'a mut S,
},
/// The update inserted an added interface into the local state.
Added {
/// The properties,
properties: &'a Properties,
/// The state.
state: &'a mut S,
},
/// The update changed an existing interface in the local state.
Changed {
/// The previous values of any properties which changed.
///
/// This is sparsely populated: none of the immutable properties are present (they can
/// all be found on `current`), and a mutable property is present with its value pre-update
/// iff it has changed as a result of the update.
previous: fnet_interfaces::Properties,
/// The properties of the interface post-update.
current: &'a Properties,
/// The state of the interface.
state: &'a mut S,
},
/// The update removed an interface from the local state.
Removed(PropertiesAndState<S>),
}
/// The properties and state for an interface.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PropertiesAndState<S> {
/// Properties.
pub properties: Properties,
/// State.
pub state: S,
}
/// A trait for types holding interface state that can be updated by change events.
pub trait Update<S> {
/// Update state with the interface change event.
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 {
// NB The following iterator comparison assumes that the server is
// well-behaved and will not send a permutation of the existing
// addresses with no actual changes (additions or removals). Making the
// comparison via set equality is possible, but more expensive than
// it's worth.
// TODO(https://github.com/rust-lang/rust/issues/64295) Use `eq_by` to
// compare the iterators once stabilized.
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();
// NB This is equivalent to Vec::try_extend, if such a method
// existed.
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);
/// Interface watcher operational errors.
#[derive(Error, Debug)]
pub enum WatcherOperationError<S: std::fmt::Debug, B: Update<S> + std::fmt::Debug> {
/// Watcher event stream yielded an error.
#[error("event stream error: {0}")]
EventStream(fidl::Error),
/// Watcher event stream yielded an event that could not be applied to the local state.
#[error("failed to update: {0}")]
Update(UpdateError),
/// Watcher event stream ended unexpectedly.
#[error("watcher event stream ended unexpectedly, final state: {final_state:?}")]
UnexpectedEnd {
/// The local state at the time of the watcher event stream's end.
final_state: B,
/// Marker for the state held alongside interface properties.
marker: std::marker::PhantomData<S>,
},
/// Watcher event stream yielded an event with unexpected type.
#[error("unexpected event type: {0:?}")]
UnexpectedEvent(fnet_interfaces::Event),
}
/// Interface watcher creation errors.
#[derive(Error, Debug)]
pub enum WatcherCreationError {
/// Proxy creation failed.
#[error("failed to create interface watcher proxy: {0}")]
CreateProxy(fidl::Error),
/// Watcher acquisition failed.
#[error("failed to get interface watcher: {0}")]
GetWatcher(fidl::Error),
}
/// Wait for a condition on interface state to be satisfied.
///
/// Note that `stream` must be created from a watcher with interest in all
/// fields, such as one created from [`event_stream_from_state`].
///
/// With the initial state in `init`, take events from `stream` and update the state, calling
/// `predicate` whenever the state changes. When `predicate` returns `Some(T)`, yield `Ok(T)`.
///
/// Since the state passed via `init` is mutably updated for every event, when this function
/// returns successfully, the state can be used as the initial state in a subsequent call with a
/// stream of events from the same watcher.
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(),
})
}
/// The local state of an interface's properties.
#[derive(Clone, Debug, PartialEq)]
pub enum InterfaceState<S> {
/// Not yet known.
Unknown(u64),
/// Locally known.
Known(PropertiesAndState<S>),
}
/// Wait for a condition on a specific interface to be satisfied.
///
/// Note that `stream` must be created from a watcher with interest in all
/// fields, such as one created from [`event_stream_from_state`].
///
/// With the initial state in `init`, take events from `stream` and update the state, calling
/// `predicate` whenever the state changes. When `predicate` returns `Some(T)`, yield `Ok(T)`.
///
/// Since the state passed via `init` is mutably updated for every event, when this function
/// returns successfully, the state can be used as the initial state in a subsequent call with a
/// stream of events from the same watcher.
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),
// NB This is technically unreachable because a successful update will always change
// `Unknown` to `Known` (and `Known` will stay `Known`).
InterfaceState::Unknown(_) => None,
}
})
.await
}
/// Read Existing interface events from `stream`, updating `init` until the Idle event is detected,
/// returning the resulting state.
///
/// Note that `stream` must be created from a watcher with interest in all
/// fields, such as one created from [`event_stream_from_state`].
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(),
})
}
/// The kind of addresses included from the watcher.
pub enum IncludedAddresses {
/// All addresses are returned from the watcher.
All,
/// Only assigned addresses are returned rom the watcher.
OnlyAssigned,
}
/// Initialize a watcher with interest in all fields and return its events as a
/// stream.
///
/// If `include_non_assigned_addresses` is true, then all addresses will be
/// returned, not just assigned addresses.
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(
// Register interest in all fields so that the strong validation
// witness type can be used and the stream returned is compatible
// with other methods in this crate.
&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"
);
}
}