blob: 4be1e4f29ec24742a75e3261181b0654dae5467c [file] [log] [blame]
// Copyright 2023 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.
//! Common device identifier types.
use alloc::sync::Arc;
use core::fmt::{self, Debug};
use core::hash::Hash;
use core::num::NonZeroU64;
use derivative::Derivative;
use crate::{
device::{
ethernet::EthernetLinkDevice,
loopback::{LoopbackDevice, LoopbackDeviceId, LoopbackWeakDeviceId},
pure_ip::{PureIpDevice, PureIpDeviceId, PureIpWeakDeviceId},
state::{BaseDeviceState, DeviceStateSpec, IpLinkDeviceState, WeakCookie},
Device, DeviceClassMatcher as _, DeviceIdAndNameMatcher as _, DeviceLayerTypes,
},
sync::{PrimaryRc, StrongRc},
};
/// An identifier for a device.
pub trait Id: Clone + Debug + Eq + Hash + PartialEq + Send + Sync + 'static {
/// Returns true if the device is a loopback device.
fn is_loopback(&self) -> bool;
}
/// A strong device reference.
///
/// [`StrongId`] indicates that the referenced device is alive while the
/// instance exists.
pub trait StrongId: Id {
/// The weak version of this identifier.
type Weak: WeakId<Strong = Self>;
/// Returns a weak ID for this strong ID.
fn downgrade(&self) -> Self::Weak;
}
/// A weak device reference.
///
/// This is the weak reference equivalent of [`StrongId`].
pub trait WeakId: Id + PartialEq<Self::Strong> {
/// The strong version of this identifier.
type Strong: StrongId<Weak = Self>;
/// Attempts to upgrade this weak ID to a strong ID.
///
/// Returns `None` if the resource has been destroyed.
fn upgrade(&self) -> Option<Self::Strong>;
}
/// A weak ID identifying a device.
///
/// This device ID makes no claim about the live-ness of the underlying device.
/// See [`DeviceId`] for a device ID that acts as a witness to the live-ness of
/// a device.
#[derive(Derivative)]
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Hash(bound = ""))]
#[allow(missing_docs)]
pub enum WeakDeviceId<BT: DeviceLayerTypes> {
Ethernet(EthernetWeakDeviceId<BT>),
Loopback(LoopbackWeakDeviceId<BT>),
PureIp(PureIpWeakDeviceId<BT>),
}
impl<BT: DeviceLayerTypes> PartialEq<DeviceId<BT>> for WeakDeviceId<BT> {
fn eq(&self, other: &DeviceId<BT>) -> bool {
<DeviceId<BT> as PartialEq<WeakDeviceId<BT>>>::eq(other, self)
}
}
impl<BT: DeviceLayerTypes> From<EthernetWeakDeviceId<BT>> for WeakDeviceId<BT> {
fn from(id: EthernetWeakDeviceId<BT>) -> WeakDeviceId<BT> {
WeakDeviceId::Ethernet(id)
}
}
impl<BT: DeviceLayerTypes> From<LoopbackWeakDeviceId<BT>> for WeakDeviceId<BT> {
fn from(id: LoopbackWeakDeviceId<BT>) -> WeakDeviceId<BT> {
WeakDeviceId::Loopback(id)
}
}
impl<BT: DeviceLayerTypes> From<PureIpWeakDeviceId<BT>> for WeakDeviceId<BT> {
fn from(id: PureIpWeakDeviceId<BT>) -> WeakDeviceId<BT> {
WeakDeviceId::PureIp(id)
}
}
impl<BT: DeviceLayerTypes> WeakDeviceId<BT> {
/// Attempts to upgrade the ID.
pub fn upgrade(&self) -> Option<DeviceId<BT>> {
for_any_device_id!(WeakDeviceId, self, id => id.upgrade().map(Into::into))
}
/// Creates a [`DebugReferences`] instance for this device.
pub fn debug_references(&self) -> DebugReferences<BT> {
DebugReferences(for_any_device_id!(
WeakDeviceId,
self,
BaseWeakDeviceId { cookie } => cookie.weak_ref.debug_references().into()
))
}
/// Returns the bindings identifier associated with the device.
pub fn bindings_id(&self) -> &BT::DeviceIdentifier {
for_any_device_id!(WeakDeviceId, self, id => id.bindings_id())
}
}
enum DebugReferencesInner<BT: DeviceLayerTypes> {
Loopback(crate::sync::DebugReferences<BaseDeviceState<LoopbackDevice, BT>>),
Ethernet(crate::sync::DebugReferences<BaseDeviceState<EthernetLinkDevice, BT>>),
PureIp(crate::sync::DebugReferences<BaseDeviceState<PureIpDevice, BT>>),
}
impl<BT: DeviceLayerTypes> From<crate::sync::DebugReferences<BaseDeviceState<LoopbackDevice, BT>>>
for DebugReferencesInner<BT>
{
fn from(inner: crate::sync::DebugReferences<BaseDeviceState<LoopbackDevice, BT>>) -> Self {
DebugReferencesInner::Loopback(inner)
}
}
impl<BT: DeviceLayerTypes>
From<crate::sync::DebugReferences<BaseDeviceState<EthernetLinkDevice, BT>>>
for DebugReferencesInner<BT>
{
fn from(inner: crate::sync::DebugReferences<BaseDeviceState<EthernetLinkDevice, BT>>) -> Self {
DebugReferencesInner::Ethernet(inner)
}
}
impl<BT: DeviceLayerTypes> From<crate::sync::DebugReferences<BaseDeviceState<PureIpDevice, BT>>>
for DebugReferencesInner<BT>
{
fn from(inner: crate::sync::DebugReferences<BaseDeviceState<PureIpDevice, BT>>) -> Self {
DebugReferencesInner::PureIp(inner)
}
}
/// A type offering a [`Debug`] implementation that helps debug dangling device
/// references.
pub struct DebugReferences<BT: DeviceLayerTypes>(DebugReferencesInner<BT>);
impl<BT: DeviceLayerTypes> Debug for DebugReferences<BT> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self(inner) = self;
match inner {
DebugReferencesInner::Loopback(d) => write!(f, "Loopback({d:?})"),
DebugReferencesInner::Ethernet(d) => write!(f, "Ethernet({d:?})"),
DebugReferencesInner::PureIp(d) => write!(f, "PureIp({d:?})"),
}
}
}
impl<BT: DeviceLayerTypes> Id for WeakDeviceId<BT> {
fn is_loopback(&self) -> bool {
match self {
WeakDeviceId::Loopback(_) => true,
WeakDeviceId::Ethernet(_) | WeakDeviceId::PureIp(_) => false,
}
}
}
impl<BT: DeviceLayerTypes> WeakId for WeakDeviceId<BT> {
type Strong = DeviceId<BT>;
fn upgrade(&self) -> Option<Self::Strong> {
self.upgrade()
}
}
impl<BT: DeviceLayerTypes> Debug for WeakDeviceId<BT> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for_any_device_id!(WeakDeviceId, self, id => Debug::fmt(id, f))
}
}
/// A strong ID identifying a device.
///
/// Holders may safely assume that the underlying device is "alive" in the sense
/// that the device is still recognized by the stack. That is, operations that
/// use this device ID will never fail as a result of "unrecognized device"-like
/// errors.
#[derive(Derivative)]
#[derivative(Eq(bound = ""), PartialEq(bound = ""), Hash(bound = ""))]
#[allow(missing_docs)]
pub enum DeviceId<BT: DeviceLayerTypes> {
Ethernet(EthernetDeviceId<BT>),
Loopback(LoopbackDeviceId<BT>),
PureIp(PureIpDeviceId<BT>),
}
/// Evaluates the expression for the given device_id, regardless of its variant.
///
/// This macro supports multiple forms.
///
/// Form #1: for_any_device_id!(device_id_enum, device_id,
/// variable => expression)
/// - `device_id_enum`: the type of device ID, either [`DeviceId`] or
/// [`WeakDeviceId`].
/// - `device_id`: The id to match on. I.e. a value of type `device_id_enum`.
/// - `variable`: The local variable to bind the inner device id type to. This
/// variable may be referenced from `expression`.
/// - `expression`: The expression to evaluate against the given `device_id`.
///
/// Example: for_any_device_id!(DeviceId, my_device, id => println!({:?}, id))
///
/// Form #2: for_any_device_id!(device_id_enum, provider_trait, type_param,
/// device_id, variable => expression)
/// - `device_id_enum`, `device_id`, `variable`, and `expression`: Same as for
/// Form #1.
/// - `provider_trait`: The [`DeviceProvider`] trait.
/// - `type_param`: The name of the type parameter to hold the values provided
/// by `provider_trait`. This type parameter may be referenced from
/// `expression`.
///
/// The second form is useful in situations where the compiler cannot infer a
/// device type that differs for each variant of `device_id_enum`.
///
/// Example: for_any_device_id!(
/// DeviceId,
/// DeviceProvider,
/// D,
/// my_device, id => fn_with_id::<D>(id)
/// )
#[macro_export]
macro_rules! for_any_device_id {
// Form #1
($device_id_enum_type:ident, $device_id:expr, $variable:pat => $expression:expr) => {
match $device_id {
$device_id_enum_type::Loopback($variable) => $expression,
$device_id_enum_type::Ethernet($variable) => $expression,
$device_id_enum_type::PureIp($variable) => $expression,
}
};
// Form #2
(
$device_id_enum_type:ident,
$provider_trait:ident,
$type_param:ident,
$device_id:expr, $variable:pat => $expression:expr) => {
match $device_id {
$device_id_enum_type::Loopback($variable) => {
type $type_param = <() as $provider_trait>::Loopback;
$expression
}
$device_id_enum_type::Ethernet($variable) => {
type $type_param = <() as $provider_trait>::Ethernet;
$expression
}
$device_id_enum_type::PureIp($variable) => {
type $type_param = <() as $provider_trait>::PureIp;
$expression
}
}
};
}
use crate::for_any_device_id;
/// Provides the [`Device`] type for each device domain.
pub trait DeviceProvider {
/// The [`Device`] type for Ethernet devices.
type Ethernet: Device;
/// The [`Device`] type for Loopback devices.
type Loopback: Device;
/// The [`Device`] type for pure IP devices.
type PureIp: Device;
}
/// This implementation is used in the `for_any_device_id` macro.
impl DeviceProvider for () {
type Ethernet = EthernetLinkDevice;
type Loopback = LoopbackDevice;
type PureIp = PureIpDevice;
}
impl<BT: DeviceLayerTypes> Clone for DeviceId<BT> {
#[cfg_attr(feature = "instrumented", track_caller)]
fn clone(&self) -> Self {
for_any_device_id!(DeviceId, self, id => id.clone().into())
}
}
impl<BT: DeviceLayerTypes> PartialEq<WeakDeviceId<BT>> for DeviceId<BT> {
fn eq(&self, other: &WeakDeviceId<BT>) -> bool {
match (self, other) {
(DeviceId::Ethernet(strong), WeakDeviceId::Ethernet(weak)) => strong == weak,
(DeviceId::Loopback(strong), WeakDeviceId::Loopback(weak)) => strong == weak,
(DeviceId::PureIp(strong), WeakDeviceId::PureIp(weak)) => strong == weak,
(DeviceId::Ethernet(_), _) | (DeviceId::Loopback(_), _) | (DeviceId::PureIp(_), _) => {
false
}
}
}
}
impl<BT: DeviceLayerTypes> PartialOrd for DeviceId<BT> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<BT: DeviceLayerTypes> Ord for DeviceId<BT> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
// Assigns an arbitrary but orderable identifier, `u8`, to each variant
// of `DeviceId`.
fn discriminant<BT: DeviceLayerTypes>(d: &DeviceId<BT>) -> u8 {
match d {
// Each variant must provide a unique discriminant!
DeviceId::Ethernet(_) => 0,
DeviceId::Loopback(_) => 1,
DeviceId::PureIp(_) => 2,
}
}
match (self, other) {
(DeviceId::Ethernet(me), DeviceId::Ethernet(other)) => me.cmp(other),
(DeviceId::Loopback(me), DeviceId::Loopback(other)) => me.cmp(other),
(DeviceId::PureIp(me), DeviceId::PureIp(other)) => me.cmp(other),
(me @ DeviceId::Ethernet(_), other)
| (me @ DeviceId::Loopback(_), other)
| (me @ DeviceId::PureIp(_), other) => discriminant(me).cmp(&discriminant(other)),
}
}
}
impl<BT: DeviceLayerTypes> From<EthernetDeviceId<BT>> for DeviceId<BT> {
fn from(id: EthernetDeviceId<BT>) -> DeviceId<BT> {
DeviceId::Ethernet(id)
}
}
impl<BT: DeviceLayerTypes> From<LoopbackDeviceId<BT>> for DeviceId<BT> {
fn from(id: LoopbackDeviceId<BT>) -> DeviceId<BT> {
DeviceId::Loopback(id)
}
}
impl<BT: DeviceLayerTypes> From<PureIpDeviceId<BT>> for DeviceId<BT> {
fn from(id: PureIpDeviceId<BT>) -> DeviceId<BT> {
DeviceId::PureIp(id)
}
}
impl<BT: DeviceLayerTypes> DeviceId<BT> {
/// Downgrade to a [`WeakDeviceId`].
pub fn downgrade(&self) -> WeakDeviceId<BT> {
for_any_device_id!(DeviceId, self, id => id.downgrade().into())
}
/// Returns the bindings identifier associated with the device.
pub fn bindings_id(&self) -> &BT::DeviceIdentifier {
for_any_device_id!(DeviceId, self, id => id.bindings_id())
}
#[cfg(test)]
pub(crate) fn unwrap_ethernet(self) -> EthernetDeviceId<BT> {
assert_matches::assert_matches!(self, DeviceId::Ethernet(e) => e)
}
}
impl<BT: DeviceLayerTypes> Id for DeviceId<BT> {
fn is_loopback(&self) -> bool {
match self {
DeviceId::Loopback(_) => true,
DeviceId::Ethernet(_) | DeviceId::PureIp(_) => false,
}
}
}
impl<BT: DeviceLayerTypes> StrongId for DeviceId<BT> {
type Weak = WeakDeviceId<BT>;
fn downgrade(&self) -> Self::Weak {
self.downgrade()
}
}
impl<BT: DeviceLayerTypes> Debug for DeviceId<BT> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for_any_device_id!(DeviceId, self, id => Debug::fmt(id, f))
}
}
impl<BT: DeviceLayerTypes> crate::filter::InterfaceProperties<BT::DeviceClass> for DeviceId<BT> {
fn id_matches(&self, id: &NonZeroU64) -> bool {
self.bindings_id().id_matches(id)
}
fn name_matches(&self, name: &str) -> bool {
self.bindings_id().name_matches(name)
}
fn device_class_matches(&self, device_class: &BT::DeviceClass) -> bool {
for_any_device_id!(
DeviceId,
self,
id => id.external_state().device_class_matches(device_class)
)
}
}
/// A base weak device identifier.
///
/// Allows multiple device implementations to share the same shape for
/// maintaining reference identifiers.
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
pub struct BaseWeakDeviceId<T: DeviceStateSpec, BT: DeviceLayerTypes> {
// NB: This is not a tuple struct because regular structs play nicer with
// type aliases, which is how we use BaseDeviceId.
cookie: Arc<WeakCookie<T, BT>>,
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialEq for BaseWeakDeviceId<T, BT> {
fn eq(&self, other: &Self) -> bool {
self.cookie.weak_ref.ptr_eq(&other.cookie.weak_ref)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Eq for BaseWeakDeviceId<T, BT> {}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Hash for BaseWeakDeviceId<T, BT> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.cookie.weak_ref.hash(state)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialEq<BaseDeviceId<T, BT>>
for BaseWeakDeviceId<T, BT>
{
fn eq(&self, other: &BaseDeviceId<T, BT>) -> bool {
<BaseDeviceId<T, BT> as PartialEq<BaseWeakDeviceId<T, BT>>>::eq(other, self)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Debug for BaseWeakDeviceId<T, BT> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { cookie } = self;
write!(f, "Weak{}({:?})", T::DEBUG_TYPE, &cookie.bindings_id)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Id for BaseWeakDeviceId<T, BT> {
fn is_loopback(&self) -> bool {
T::IS_LOOPBACK
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> WeakId for BaseWeakDeviceId<T, BT> {
type Strong = BaseDeviceId<T, BT>;
fn upgrade(&self) -> Option<Self::Strong> {
self.upgrade()
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> BaseWeakDeviceId<T, BT> {
/// Attempts to upgrade the ID to a strong ID, failing if the
/// device no longer exists.
pub fn upgrade(&self) -> Option<BaseDeviceId<T, BT>> {
let Self { cookie } = self;
cookie.weak_ref.upgrade().map(|rc| BaseDeviceId { rc })
}
/// Returns the bindings identifier associated with the device.
pub fn bindings_id(&self) -> &BT::DeviceIdentifier {
&self.cookie.bindings_id
}
}
/// A base device identifier.
///
/// Allows multiple device implementations to share the same shape for
/// maintaining reference identifiers.
#[derive(Derivative)]
#[derivative(Hash(bound = ""), Eq(bound = ""), PartialEq(bound = ""))]
pub struct BaseDeviceId<T: DeviceStateSpec, BT: DeviceLayerTypes> {
// NB: This is not a tuple struct because regular structs play nicer with
// type aliases, which is how we use BaseDeviceId.
rc: StrongRc<BaseDeviceState<T, BT>>,
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Clone for BaseDeviceId<T, BT> {
#[cfg_attr(feature = "instrumented", track_caller)]
fn clone(&self) -> Self {
let Self { rc } = self;
Self { rc: StrongRc::clone(rc) }
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialEq<BaseWeakDeviceId<T, BT>>
for BaseDeviceId<T, BT>
{
fn eq(&self, BaseWeakDeviceId { cookie }: &BaseWeakDeviceId<T, BT>) -> bool {
let Self { rc: me_rc } = self;
StrongRc::weak_ptr_eq(me_rc, &cookie.weak_ref)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialEq<BasePrimaryDeviceId<T, BT>>
for BaseDeviceId<T, BT>
{
fn eq(&self, BasePrimaryDeviceId { rc: other_rc }: &BasePrimaryDeviceId<T, BT>) -> bool {
let Self { rc: me_rc } = self;
PrimaryRc::ptr_eq(other_rc, me_rc)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialOrd for BaseDeviceId<T, BT> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Ord for BaseDeviceId<T, BT> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
let Self { rc: me } = self;
let Self { rc: other } = other;
StrongRc::ptr_cmp(me, other)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Debug for BaseDeviceId<T, BT> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { rc } = self;
write!(f, "{}({:?})", T::DEBUG_TYPE, &rc.weak_cookie.bindings_id)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Id for BaseDeviceId<T, BT> {
fn is_loopback(&self) -> bool {
T::IS_LOOPBACK
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> StrongId for BaseDeviceId<T, BT> {
type Weak = BaseWeakDeviceId<T, BT>;
fn downgrade(&self) -> Self::Weak {
self.downgrade()
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> BaseDeviceId<T, BT> {
pub(crate) fn device_state(&self) -> &IpLinkDeviceState<T, BT> {
&self.rc.ip
}
/// Returns a reference to the external state for the device.
pub fn external_state(&self) -> &T::External<BT> {
&self.rc.external_state
}
/// Returns the bindings identifier associated with the device.
pub fn bindings_id(&self) -> &BT::DeviceIdentifier {
&self.rc.weak_cookie.bindings_id
}
/// Downgrades the ID to an [`EthernetWeakDeviceId`].
pub fn downgrade(&self) -> BaseWeakDeviceId<T, BT> {
let Self { rc } = self;
BaseWeakDeviceId { cookie: Arc::clone(&rc.weak_cookie) }
}
}
/// The primary reference to a device.
pub struct BasePrimaryDeviceId<T: DeviceStateSpec, BT: DeviceLayerTypes> {
// NB: This is not a tuple struct because regular structs play nicer with
// type aliases, which is how we use BaseDeviceId.
rc: PrimaryRc<BaseDeviceState<T, BT>>,
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Debug for BasePrimaryDeviceId<T, BT> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { rc } = self;
write!(f, "Primary{}({:?})", T::DEBUG_TYPE, &rc.weak_cookie.bindings_id)
}
}
impl<T: DeviceStateSpec, BT: DeviceLayerTypes> BasePrimaryDeviceId<T, BT> {
#[cfg_attr(feature = "instrumented", track_caller)]
pub(crate) fn clone_strong(&self) -> BaseDeviceId<T, BT> {
let Self { rc } = self;
BaseDeviceId { rc: PrimaryRc::clone_strong(rc) }
}
pub(crate) fn new<F: FnOnce(BaseWeakDeviceId<T, BT>) -> IpLinkDeviceState<T, BT>>(
ip: F,
external_state: T::External<BT>,
bindings_id: BT::DeviceIdentifier,
) -> Self {
Self {
rc: PrimaryRc::new_cyclic(move |weak_ref| {
let weak_cookie = Arc::new(WeakCookie { bindings_id, weak_ref });
let ip = ip(BaseWeakDeviceId { cookie: Arc::clone(&weak_cookie) });
BaseDeviceState { ip, external_state, weak_cookie }
}),
}
}
pub(crate) fn into_inner(self) -> PrimaryRc<BaseDeviceState<T, BT>> {
self.rc
}
}
/// A strong device ID identifying an ethernet device.
///
/// This device ID is like [`DeviceId`] but specifically for ethernet devices.
pub type EthernetDeviceId<BT> = BaseDeviceId<EthernetLinkDevice, BT>;
/// A weak device ID identifying an ethernet device.
pub type EthernetWeakDeviceId<BT> = BaseWeakDeviceId<EthernetLinkDevice, BT>;
/// The primary Ethernet device reference.
pub(crate) type EthernetPrimaryDeviceId<BT> = BasePrimaryDeviceId<EthernetLinkDevice, BT>;