#![deny(missing_docs)]
pub mod admin;
mod reachability;
pub use reachability::{is_globally_routable, to_reachability_stream, wait_for_reachability};
use anyhow::Context as _;
use derivative::Derivative;
use fidl_table_validation::*;
use futures::{Stream, TryStreamExt as _};
use std::collections::btree_map::{self, BTreeMap};
use std::collections::hash_map::{self, HashMap};
use std::convert::TryFrom as _;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::num::NonZeroU64;
use thiserror::Error;
use {
fidl_fuchsia_hardware_network as fhardware_network,
fidl_fuchsia_net_interfaces as fnet_interfaces,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum PortClass {
Loopback,
Virtual,
Ethernet,
WlanClient,
WlanAp,
Ppp,
Bridge,
Lowpan,
}
impl PortClass {
pub fn is_loopback(&self) -> bool {
match self {
PortClass::Loopback => true,
PortClass::Virtual
| PortClass::Ethernet
| PortClass::WlanClient
| PortClass::WlanAp
| PortClass::Ppp
| PortClass::Bridge
| PortClass::Lowpan => false,
}
}
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum UnknownPortClassError {
#[error(transparent)]
NetInterfaces(UnknownNetInterfacesPortClassError),
#[error(transparent)]
HardwareNetwork(UnknownHardwareNetworkPortClassError),
}
#[derive(Debug, Error)]
#[error("unknown fuchsia.net.interfaces/PortClass ordinal: {unknown_ordinal}")]
pub struct UnknownNetInterfacesPortClassError {
unknown_ordinal: u64,
}
#[derive(Debug, Error)]
#[error("unknown fuchsia.hardware.network/PortClass ordinal: {unknown_ordinal}")]
pub struct UnknownHardwareNetworkPortClassError {
unknown_ordinal: u16,
}
impl TryFrom<fnet_interfaces::PortClass> for PortClass {
type Error = UnknownPortClassError;
fn try_from(port_class: fnet_interfaces::PortClass) -> Result<Self, Self::Error> {
match port_class {
fnet_interfaces::PortClass::Loopback(fnet_interfaces::Empty) => Ok(PortClass::Loopback),
fnet_interfaces::PortClass::Device(port_class) => {
PortClass::try_from(port_class).map_err(UnknownPortClassError::HardwareNetwork)
}
fnet_interfaces::PortClass::__SourceBreaking { unknown_ordinal } => {
Err(UnknownPortClassError::NetInterfaces(UnknownNetInterfacesPortClassError {
unknown_ordinal,
}))
}
}
}
}
impl From<PortClass> for fnet_interfaces::PortClass {
fn from(port_class: PortClass) -> Self {
match port_class {
PortClass::Loopback => fnet_interfaces::PortClass::Loopback(fnet_interfaces::Empty),
PortClass::Virtual => {
fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Virtual)
}
PortClass::Ethernet => {
fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Ethernet)
}
PortClass::WlanClient => {
fnet_interfaces::PortClass::Device(fhardware_network::PortClass::WlanClient)
}
PortClass::WlanAp => {
fnet_interfaces::PortClass::Device(fhardware_network::PortClass::WlanAp)
}
PortClass::Ppp => fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Ppp),
PortClass::Bridge => {
fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Bridge)
}
PortClass::Lowpan => {
fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Lowpan)
}
}
}
}
impl TryFrom<fhardware_network::PortClass> for PortClass {
type Error = UnknownHardwareNetworkPortClassError;
fn try_from(port_class: fhardware_network::PortClass) -> Result<Self, Self::Error> {
match port_class {
fhardware_network::PortClass::Virtual => Ok(PortClass::Virtual),
fhardware_network::PortClass::Ethernet => Ok(PortClass::Ethernet),
fhardware_network::PortClass::WlanClient => Ok(PortClass::WlanClient),
fhardware_network::PortClass::WlanAp => Ok(PortClass::WlanAp),
fhardware_network::PortClass::Ppp => Ok(PortClass::Ppp),
fhardware_network::PortClass::Bridge => Ok(PortClass::Bridge),
fhardware_network::PortClass::Lowpan => Ok(PortClass::Lowpan),
fhardware_network::PortClass::__SourceBreaking { unknown_ordinal } => {
Err(UnknownHardwareNetworkPortClassError { unknown_ordinal })
}
}
}
}
#[derive(Derivative, ValidFidlTable)]
#[derivative(Clone(bound = ""), Debug(bound = ""), Eq(bound = ""), PartialEq(bound = ""))]
#[fidl_table_src(fnet_interfaces::Properties)]
#[fidl_table_strict(device_class)]
pub struct Properties<I: FieldInterests> {
pub id: NonZeroU64,
pub name: String,
pub online: bool,
pub addresses: Vec<Address<I>>,
pub has_default_ipv4_route: bool,
pub has_default_ipv6_route: bool,
pub port_class: PortClass,
}
#[derive(Derivative, ValidFidlTable)]
#[derivative(
Clone(bound = ""),
Debug(bound = ""),
Eq(bound = ""),
PartialEq(bound = ""),
Hash(bound = "")
)]
#[fidl_table_src(fnet_interfaces::Address)]
#[fidl_table_strict]
pub struct Address<I: FieldInterests> {
pub addr: fidl_fuchsia_net::Subnet,
#[fidl_field_type(optional_converter = InterestConverter::<I, ValidUntilInterest>)]
pub valid_until: FromInterest<I, ValidUntilInterest>,
#[fidl_field_type(optional_converter = InterestConverter::<I, PreferredLifetimeInfoInterest>)]
pub preferred_lifetime_info: FromInterest<I, PreferredLifetimeInfoInterest>,
pub assignment_state: fnet_interfaces::AddressAssignmentState,
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Copy, Clone, Hash)]
#[allow(missing_docs)]
pub enum PreferredLifetimeInfo {
PreferredUntil(PositiveMonotonicInstant),
Deprecated,
}
impl PreferredLifetimeInfo {
pub const fn preferred_forever() -> Self {
Self::PreferredUntil(PositiveMonotonicInstant::INFINITE_FUTURE)
}
pub const fn to_fidl(self) -> fnet_interfaces::PreferredLifetimeInfo {
match self {
PreferredLifetimeInfo::Deprecated => {
fnet_interfaces::PreferredLifetimeInfo::Deprecated(fnet_interfaces::Empty)
}
PreferredLifetimeInfo::PreferredUntil(v) => {
fnet_interfaces::PreferredLifetimeInfo::PreferredUntil(v.into_nanos())
}
}
}
}
impl TryFrom<fnet_interfaces::PreferredLifetimeInfo> for PreferredLifetimeInfo {
type Error = NotPositiveMonotonicInstantError;
fn try_from(value: fnet_interfaces::PreferredLifetimeInfo) -> Result<Self, Self::Error> {
match value {
fnet_interfaces::PreferredLifetimeInfo::Deprecated(fnet_interfaces::Empty) => {
Ok(Self::Deprecated)
}
fnet_interfaces::PreferredLifetimeInfo::PreferredUntil(v) => {
Ok(Self::PreferredUntil(v.try_into()?))
}
}
}
}
impl From<PreferredLifetimeInfo> for fnet_interfaces::PreferredLifetimeInfo {
fn from(value: PreferredLifetimeInfo) -> Self {
value.to_fidl()
}
}
#[derive(Error, Debug)]
#[error("{0} is not a positive monotonic instant")]
pub struct NotPositiveMonotonicInstantError(i64);
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Copy, Clone, Hash)]
pub struct PositiveMonotonicInstant(i64);
impl PositiveMonotonicInstant {
pub const INFINITE_FUTURE: Self = Self(zx_types::ZX_TIME_INFINITE);
pub const fn into_nanos(self) -> i64 {
let Self(v) = self;
v
}
pub const fn from_nanos(v: i64) -> Option<Self> {
if v > 0 {
Some(Self(v))
} else {
None
}
}
pub fn is_infinite(&self) -> bool {
self == &Self::INFINITE_FUTURE
}
}
#[cfg(target_os = "fuchsia")]
impl From<PositiveMonotonicInstant> for zx::MonotonicInstant {
fn from(PositiveMonotonicInstant(v): PositiveMonotonicInstant) -> Self {
zx::MonotonicInstant::from_nanos(v)
}
}
#[cfg(target_os = "fuchsia")]
impl TryFrom<zx::MonotonicInstant> for PositiveMonotonicInstant {
type Error = NotPositiveMonotonicInstantError;
fn try_from(value: zx::MonotonicInstant) -> Result<Self, Self::Error> {
Self::try_from(value.into_nanos())
}
}
impl From<PositiveMonotonicInstant> for zx_types::zx_time_t {
fn from(value: PositiveMonotonicInstant) -> Self {
value.into_nanos()
}
}
impl TryFrom<zx_types::zx_time_t> for PositiveMonotonicInstant {
type Error = NotPositiveMonotonicInstantError;
fn try_from(value: zx_types::zx_time_t) -> Result<Self, Self::Error> {
Self::from_nanos(value).ok_or(NotPositiveMonotonicInstantError(value))
}
}
#[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(Derivative)]
#[derivative(Debug(bound = "S: Debug"), PartialEq(bound = "S: PartialEq"))]
pub enum UpdateResult<'a, S, I: FieldInterests> {
NoChange,
Existing {
properties: &'a Properties<I>,
state: &'a mut S,
},
Added {
properties: &'a Properties<I>,
state: &'a mut S,
},
Changed {
previous: fnet_interfaces::Properties,
current: &'a Properties<I>,
state: &'a mut S,
},
Removed(PropertiesAndState<S, I>),
}
#[derive(Derivative)]
#[derivative(
Clone(bound = "S: Clone"),
Debug(bound = "S: Debug"),
Eq(bound = "S: Eq"),
PartialEq(bound = "S: PartialEq")
)]
pub struct PropertiesAndState<S, I: FieldInterests> {
pub properties: Properties<I>,
pub state: S,
}
pub trait Update<S> {
type Interest: FieldInterests;
fn update(
&mut self,
event: EventWithInterest<Self::Interest>,
) -> Result<UpdateResult<'_, S, Self::Interest>, UpdateError>;
}
impl<S, I: FieldInterests> Update<S> for PropertiesAndState<S, I> {
type Interest = I;
fn update(
&mut self,
event: EventWithInterest<I>,
) -> Result<UpdateResult<'_, S, I>, UpdateError> {
let Self { properties, state } = self;
match event.into_inner() {
fnet_interfaces::Event::Existing(existing) => {
let existing = Properties::<I>::try_from(existing)?;
if existing.id == properties.id {
return Err(UpdateError::DuplicateExisting(existing.into()));
}
}
fnet_interfaces::Event::Added(added) => {
let added = Properties::<I>::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: _,
port_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, I: FieldInterests> Update<S> for InterfaceState<S, I> {
type Interest = I;
fn update(
&mut self,
event: EventWithInterest<I>,
) -> Result<UpdateResult<'_, S, I>, UpdateError> {
fn get_properties<S, I: FieldInterests>(
state: &mut InterfaceState<S, I>,
) -> &mut PropertiesAndState<S, I> {
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.into_inner() {
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)
}
}
pub struct ZeroError {}
pub trait TryFromMaybeNonZero: Sized {
fn try_from(value: u64) -> Result<Self, ZeroError>;
}
impl TryFromMaybeNonZero for u64 {
fn try_from(value: u64) -> Result<Self, ZeroError> {
Ok(value)
}
}
impl TryFromMaybeNonZero for NonZeroU64 {
fn try_from(value: u64) -> Result<Self, ZeroError> {
NonZeroU64::new(value).ok_or(ZeroError {})
}
}
macro_rules! impl_map {
($map_type:ident, $map_mod:tt) => {
impl<K, S, I> Update<S> for $map_type<K, PropertiesAndState<S, I>>
where
K: TryFromMaybeNonZero + Copy + From<NonZeroU64> + Eq + Ord + std::hash::Hash,
S: Default,
I: FieldInterests,
{
type Interest = I;
fn update(
&mut self,
event: EventWithInterest<I>,
) -> Result<UpdateResult<'_, S, I>, UpdateError> {
match event.into_inner() {
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(id)
.map_err(|ZeroError {}| UpdateError::ZeroInterfaceId)?,
) {
properties.update(EventWithInterest::new(
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(removed_id)
.map_err(|ZeroError {}| UpdateError::ZeroInterfaceId)?,
) {
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: Debug, B: Update<S> + 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: Debug + Default,
B: Update<S> + Clone + Debug,
St: Stream<Item = Result<EventWithInterest<B::Interest>, 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(Derivative)]
#[derivative(
Clone(bound = "S: Clone"),
Debug(bound = "S: Debug"),
PartialEq(bound = "S: PartialEq")
)]
pub enum InterfaceState<S, I: FieldInterests> {
Unknown(u64),
Known(PropertiesAndState<S, I>),
}
pub async fn wait_interface_with_id<S, St, F, T, I>(
stream: St,
init: &mut InterfaceState<S, I>,
mut predicate: F,
) -> Result<T, WatcherOperationError<S, InterfaceState<S, I>>>
where
S: Default + Clone + Debug,
St: Stream<Item = Result<EventWithInterest<I>, fidl::Error>>,
F: FnMut(&PropertiesAndState<S, I>) -> Option<T>,
I: FieldInterests,
{
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: Debug,
St: futures::Stream<Item = Result<EventWithInterest<B::Interest>, fidl::Error>>,
B: Update<S> + Debug,
{
async_utils::fold::try_fold_while(
stream.map_err(WatcherOperationError::EventStream),
init,
|mut acc, event| {
futures::future::ready(match event.inner() {
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.into_inner()))
}
})
},
)
.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<I: FieldInterests>(
interface_state: &fnet_interfaces::StateProxy,
included_addresses: IncludedAddresses,
) -> Result<impl Stream<Item = Result<EventWithInterest<I>, fidl::Error>>, WatcherCreationError> {
let (watcher, server) = ::fidl::endpoints::create_proxy::<fnet_interfaces::WatcherMarker>();
let () = interface_state
.get_watcher(
&fnet_interfaces::WatcherOptions {
address_properties_interest: Some(interest_from_params::<I>()),
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((EventWithInterest::new(watcher.watch().await?), watcher)))
}))
}
fn interest_from_params<I: FieldInterests>() -> fnet_interfaces::AddressPropertiesInterest {
let mut interest = fnet_interfaces::AddressPropertiesInterest::empty();
if <I::ValidUntil as MaybeInterest<_>>::ENABLED {
interest |= fnet_interfaces::AddressPropertiesInterest::VALID_UNTIL;
}
if <I::PreferredLifetimeInfo as MaybeInterest<_>>::ENABLED {
interest |= fnet_interfaces::AddressPropertiesInterest::PREFERRED_LIFETIME_INFO;
}
interest
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Default)]
pub struct NoInterest;
mod interest {
use super::*;
use std::hash::Hash;
use Debug;
pub trait FieldInterests {
type PreferredLifetimeInfo: MaybeInterest<PreferredLifetimeInfo>;
type ValidUntil: MaybeInterest<PositiveMonotonicInstant>;
}
pub trait MaybeInterest<T> {
const ENABLED: bool;
type Ty: Clone + Debug + Eq + Hash + PartialEq + 'static;
fn try_from_fidl<F: TryInto<T, Error: Into<anyhow::Error>>>(
fidl: Option<F>,
) -> Result<Self::Ty, anyhow::Error>;
fn into_fidl<F: From<T>>(value: Self::Ty) -> Option<F>;
}
pub struct EnableInterest;
impl<T: Clone + Debug + Eq + Hash + PartialEq + 'static> MaybeInterest<T> for EnableInterest {
const ENABLED: bool = true;
type Ty = T;
fn try_from_fidl<F: TryInto<T, Error: Into<anyhow::Error>>>(
fidl: Option<F>,
) -> Result<Self::Ty, anyhow::Error> {
fidl.map(|f| f.try_into().map_err(Into::into))
.unwrap_or_else(|| Err(anyhow::anyhow!("missing field with registered interest")))
}
fn into_fidl<F: From<T>>(value: Self::Ty) -> Option<F> {
Some(value.into())
}
}
pub struct DisableInterest;
impl<T> MaybeInterest<T> for DisableInterest {
const ENABLED: bool = false;
type Ty = NoInterest;
fn try_from_fidl<F: TryInto<T, Error: Into<anyhow::Error>>>(
fidl: Option<F>,
) -> Result<Self::Ty, anyhow::Error> {
match fidl {
Some(_) => Err(anyhow::anyhow!("unexpected set field with no registered interest")),
None => Ok(NoInterest),
}
}
fn into_fidl<F: From<T>>(_value: Self::Ty) -> Option<F> {
None
}
}
pub(super) type FromInterest<I, T> =
<<T as FieldSpec>::Interest<I> as MaybeInterest<<T as FieldSpec>::Present>>::Ty;
pub trait FieldSpec {
type Interest<I: FieldInterests>: MaybeInterest<Self::Present>;
type Fidl: From<Self::Present>;
type Present: TryFrom<Self::Fidl, Error: Into<anyhow::Error>>;
const FIELD_NAME: &'static str;
}
pub struct InterestConverter<I, P>(PhantomData<(I, P)>);
impl<I, P> fidl_table_validation::Converter for InterestConverter<I, P>
where
I: FieldInterests,
P: FieldSpec,
{
type Fidl = Option<P::Fidl>;
type Validated = <P::Interest<I> as MaybeInterest<P::Present>>::Ty;
type Error = anyhow::Error;
fn try_from_fidl(value: Self::Fidl) -> std::result::Result<Self::Validated, Self::Error> {
<P::Interest<I> as MaybeInterest<_>>::try_from_fidl(value).context(P::FIELD_NAME)
}
fn from_validated(validated: Self::Validated) -> Self::Fidl {
<P::Interest<I> as MaybeInterest<_>>::into_fidl(validated)
}
}
pub struct ValidUntilInterest;
impl FieldSpec for ValidUntilInterest {
type Interest<I: FieldInterests> = I::ValidUntil;
type Fidl = zx_types::zx_time_t;
type Present = PositiveMonotonicInstant;
const FIELD_NAME: &'static str = "valid_until";
}
pub struct PreferredLifetimeInfoInterest;
impl FieldSpec for PreferredLifetimeInfoInterest {
type Interest<I: FieldInterests> = I::PreferredLifetimeInfo;
type Fidl = fnet_interfaces::PreferredLifetimeInfo;
type Present = PreferredLifetimeInfo;
const FIELD_NAME: &'static str = "preferred_lifetime_info";
}
}
pub use interest::{DisableInterest, EnableInterest, FieldInterests};
use interest::{
FromInterest, InterestConverter, MaybeInterest, PreferredLifetimeInfoInterest,
ValidUntilInterest,
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct AllInterest;
impl FieldInterests for AllInterest {
type PreferredLifetimeInfo = EnableInterest;
type ValidUntil = EnableInterest;
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct DefaultInterest;
impl FieldInterests for DefaultInterest {
type PreferredLifetimeInfo = DisableInterest;
type ValidUntil = DisableInterest;
}
#[derive(Derivative)]
#[derivative(Clone(bound = ""), Debug(bound = ""), Eq(bound = ""), PartialEq(bound = ""))]
pub struct EventWithInterest<I: FieldInterests> {
event: fnet_interfaces::Event,
#[derivative(Debug = "ignore")]
_marker: PhantomData<I>,
}
impl<I: FieldInterests> EventWithInterest<I> {
pub fn new(event: fnet_interfaces::Event) -> Self {
Self { event, _marker: PhantomData }
}
pub fn into_inner(self) -> fnet_interfaces::Event {
self.event
}
pub fn inner(&self) -> &fnet_interfaces::Event {
&self.event
}
}
impl<I: FieldInterests> From<fnet_interfaces::Event> for EventWithInterest<I> {
fn from(value: fnet_interfaces::Event) -> Self {
Self::new(value)
}
}
impl<I: FieldInterests> From<EventWithInterest<I>> for fnet_interfaces::Event {
fn from(value: EventWithInterest<I>) -> Self {
value.into_inner()
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fidl_fuchsia_net as fnet;
use futures::task::Poll;
use futures::FutureExt as _;
use net_declare::fidl_subnet;
use std::cell::RefCell;
use std::convert::TryInto as _;
use std::pin::Pin;
use std::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()),
port_class: Some(fnet_interfaces::PortClass::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_types::ZX_TIME_INFINITE)]),
..Default::default()
}
}
fn validated_properties(id: u64) -> PropertiesAndState<(), AllInterest> {
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,
port_class: None,
online: Some(true),
has_default_ipv4_route: Some(true),
has_default_ipv6_route: Some(true),
addresses: Some(vec![fidl_address(ADDR2, zx_types::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()),
port_class: Some(fnet_interfaces::PortClass::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_types::ZX_TIME_INFINITE)]),
..Default::default()
}
}
fn validated_properties_after_change(id: u64) -> PropertiesAndState<(), AllInterest> {
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_types::zx_time_t,
) -> fnet_interfaces::Address {
fnet_interfaces::Address {
addr: Some(addr),
valid_until: Some(valid_until.try_into().unwrap()),
assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
preferred_lifetime_info: Some(PreferredLifetimeInfo::preferred_forever().into()),
__source_breaking: 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<(), Interest = AllInterest>) {
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Added(fidl_properties(ID)).into()),
Err(UpdateError::DuplicateAdded(added)) if added == fidl_properties(ID)
);
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Existing(fidl_properties(ID)).into()),
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<(), Interest = AllInterest>) {
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()).into()),
Err(UpdateError::UnknownChanged(changed)) if changed == unknown
);
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Removed(ID).into()),
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).into()),
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<(), Interest = AllInterest>) {
let missing_id = fnet_interfaces::Properties { online: Some(true), ..Default::default() };
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(missing_id.clone()).into()),
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, port_class: None, ..fidl_properties(ID) };
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(empty_change.clone()).into()),
Err(UpdateError::EmptyChange(properties)) if properties == empty_change
);
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(net_zero_change.clone()).into()),
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<(), Interest = AllInterest>) {
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_types::ZX_TIME_INFINITE)]),
..Default::default()
};
assert_matches::assert_matches!(
state.update(fnet_interfaces::Event::Changed(properties_delta(ID).clone()).into()),
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(Derivative)]
#[derivative(Clone(bound = ""))]
struct EventStream<I: FieldInterests>(Rc<RefCell<Vec<fnet_interfaces::Event>>>, PhantomData<I>);
impl<I: FieldInterests> Stream for EventStream<I> {
type Item = Result<EventWithInterest<I>, fidl::Error>;
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut futures::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
let EventStream(events_vec, _marker) = &*self;
if events_vec.borrow().is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(EventWithInterest::new(events_vec.borrow_mut().remove(0)))))
}
}
}
fn test_event_stream<I: FieldInterests>() -> EventStream<I> {
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),
])),
PhantomData,
)
}
#[test]
fn test_wait_one_interface() {
let event_stream = test_event_stream::<AllInterest>();
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 + Debug + std::cmp::PartialEq,
{
let event_stream = test_event_stream::<B::Interest>();
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<(), Interest = AllInterest> + 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(|e| Ok(EventWithInterest::new(e))));
assert_eq!(
existing(event_stream, state)
.now_or_never()
.expect("existing did not complete immediately")
.expect("existing returned error"),
want,
);
}
#[test]
fn positive_instant() {
assert_eq!(PositiveMonotonicInstant::from_nanos(-1), None);
assert_eq!(PositiveMonotonicInstant::from_nanos(0), None);
assert_eq!(PositiveMonotonicInstant::from_nanos(1), Some(PositiveMonotonicInstant(1)));
}
}