netstack3_device/
id.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Common device identifier types.
6
7use alloc::sync::Arc;
8use core::fmt::{self, Debug};
9use core::hash::Hash;
10use core::num::NonZeroU64;
11
12use derivative::Derivative;
13use netstack3_base::sync::{DynDebugReferences, PrimaryRc, StrongRc};
14use netstack3_base::{
15    Device, DeviceIdentifier, DeviceWithName, StrongDeviceIdentifier, WeakDeviceIdentifier,
16};
17use netstack3_filter as filter;
18
19use crate::blackhole::{BlackholeDevice, BlackholeDeviceId, BlackholeWeakDeviceId};
20use crate::internal::base::{
21    DeviceClassMatcher as _, DeviceIdAndNameMatcher as _, DeviceLayerTypes, OriginTracker,
22};
23use crate::internal::ethernet::EthernetLinkDevice;
24use crate::internal::loopback::{LoopbackDevice, LoopbackDeviceId, LoopbackWeakDeviceId};
25use crate::internal::pure_ip::{PureIpDevice, PureIpDeviceId, PureIpWeakDeviceId};
26use crate::internal::state::{BaseDeviceState, DeviceStateSpec, IpLinkDeviceState, WeakCookie};
27
28/// A weak ID identifying a device.
29///
30/// This device ID makes no claim about the live-ness of the underlying device.
31/// See [`DeviceId`] for a device ID that acts as a witness to the live-ness of
32/// a device.
33#[derive(Derivative)]
34#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Hash(bound = ""))]
35#[allow(missing_docs)]
36pub enum WeakDeviceId<BT: DeviceLayerTypes> {
37    Ethernet(EthernetWeakDeviceId<BT>),
38    Loopback(LoopbackWeakDeviceId<BT>),
39    PureIp(PureIpWeakDeviceId<BT>),
40    Blackhole(BlackholeWeakDeviceId<BT>),
41}
42
43impl<BT: DeviceLayerTypes> PartialEq<DeviceId<BT>> for WeakDeviceId<BT> {
44    fn eq(&self, other: &DeviceId<BT>) -> bool {
45        <DeviceId<BT> as PartialEq<WeakDeviceId<BT>>>::eq(other, self)
46    }
47}
48
49impl<BT: DeviceLayerTypes> From<EthernetWeakDeviceId<BT>> for WeakDeviceId<BT> {
50    fn from(id: EthernetWeakDeviceId<BT>) -> WeakDeviceId<BT> {
51        WeakDeviceId::Ethernet(id)
52    }
53}
54
55impl<BT: DeviceLayerTypes> From<LoopbackWeakDeviceId<BT>> for WeakDeviceId<BT> {
56    fn from(id: LoopbackWeakDeviceId<BT>) -> WeakDeviceId<BT> {
57        WeakDeviceId::Loopback(id)
58    }
59}
60
61impl<BT: DeviceLayerTypes> From<PureIpWeakDeviceId<BT>> for WeakDeviceId<BT> {
62    fn from(id: PureIpWeakDeviceId<BT>) -> WeakDeviceId<BT> {
63        WeakDeviceId::PureIp(id)
64    }
65}
66
67impl<BT: DeviceLayerTypes> From<BlackholeWeakDeviceId<BT>> for WeakDeviceId<BT> {
68    fn from(id: BlackholeWeakDeviceId<BT>) -> WeakDeviceId<BT> {
69        WeakDeviceId::Blackhole(id)
70    }
71}
72
73impl<BT: DeviceLayerTypes> WeakDeviceId<BT> {
74    /// Attempts to upgrade the ID.
75    pub fn upgrade(&self) -> Option<DeviceId<BT>> {
76        for_any_device_id!(WeakDeviceId, self, id => id.upgrade().map(Into::into))
77    }
78
79    /// Creates a [`DebugReferences`] instance for this device.
80    pub fn debug_references(&self) -> DynDebugReferences {
81        for_any_device_id!(
82            WeakDeviceId,
83            self,
84            BaseWeakDeviceId { cookie } => cookie.weak_ref.debug_references().into_dyn()
85        )
86    }
87
88    /// Returns the bindings identifier associated with the device.
89    pub fn bindings_id(&self) -> &BT::DeviceIdentifier {
90        for_any_device_id!(WeakDeviceId, self, id => id.bindings_id())
91    }
92}
93
94impl<BT: DeviceLayerTypes> DeviceIdentifier for WeakDeviceId<BT> {
95    fn is_loopback(&self) -> bool {
96        match self {
97            WeakDeviceId::Loopback(_) => true,
98            WeakDeviceId::Ethernet(_) | WeakDeviceId::PureIp(_) | WeakDeviceId::Blackhole(_) => {
99                false
100            }
101        }
102    }
103}
104
105impl<BT: DeviceLayerTypes> WeakDeviceIdentifier for WeakDeviceId<BT> {
106    type Strong = DeviceId<BT>;
107
108    fn upgrade(&self) -> Option<Self::Strong> {
109        self.upgrade()
110    }
111}
112
113impl<BT: DeviceLayerTypes> Debug for WeakDeviceId<BT> {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        for_any_device_id!(WeakDeviceId, self, id => Debug::fmt(id, f))
116    }
117}
118
119/// A strong ID identifying a device.
120///
121/// Holders may safely assume that the underlying device is "alive" in the sense
122/// that the device is still recognized by the stack. That is, operations that
123/// use this device ID will never fail as a result of "unrecognized device"-like
124/// errors.
125#[derive(Derivative)]
126#[derivative(Eq(bound = ""), PartialEq(bound = ""), Hash(bound = ""))]
127#[allow(missing_docs)]
128pub enum DeviceId<BT: DeviceLayerTypes> {
129    Ethernet(EthernetDeviceId<BT>),
130    Loopback(LoopbackDeviceId<BT>),
131    PureIp(PureIpDeviceId<BT>),
132    Blackhole(BlackholeDeviceId<BT>),
133}
134
135/// Evaluates the expression for the given device_id, regardless of its variant.
136///
137/// This macro supports multiple forms.
138///
139/// Form #1: for_any_device_id!(device_id_enum, device_id,
140///          variable => expression)
141///   - `device_id_enum`: the type of device ID, either [`DeviceId`] or
142///     [`WeakDeviceId`].
143///   - `device_id`: The id to match on. I.e. a value of type `device_id_enum`.
144///   - `variable`: The local variable to bind the inner device id type to. This
145///     variable may be referenced from `expression`.
146///   - `expression`: The expression to evaluate against the given `device_id`.
147///
148/// Example: for_any_device_id!(DeviceId, my_device, id => println!({:?}, id))
149///
150/// Form #2: for_any_device_id!(device_id_enum, provider_trait, type_param,
151///          device_id, variable => expression)
152///   - `device_id_enum`, `device_id`, `variable`, and `expression`: Same as for
153///     Form #1.
154///   - `provider_trait`: The [`DeviceProvider`] trait.
155///   - `type_param`: The name of the type parameter to hold the values provided
156///     by `provider_trait`. This type parameter may be referenced from
157///     `expression`.
158///
159/// The second form is useful in situations where the compiler cannot infer a
160/// device type that differs for each variant of `device_id_enum`.
161///
162/// Example: for_any_device_id!(
163///     DeviceId,
164///     DeviceProvider,
165///     D,
166///     my_device, id => fn_with_id::<D>(id)
167/// )
168#[macro_export]
169macro_rules! for_any_device_id {
170    // Form #1
171    ($device_id_enum_type:ident, $device_id:expr, $variable:pat => $expression:expr) => {
172        match $device_id {
173            $device_id_enum_type::Loopback($variable) => $expression,
174            $device_id_enum_type::Ethernet($variable) => $expression,
175            $device_id_enum_type::PureIp($variable) => $expression,
176            $device_id_enum_type::Blackhole($variable) => $expression,
177        }
178    };
179    // Form #2
180    (
181        $device_id_enum_type:ident,
182        $provider_trait:ident,
183        $type_param:ident,
184        $device_id:expr, $variable:pat => $expression:expr) => {
185        match $device_id {
186            $device_id_enum_type::Loopback($variable) => {
187                type $type_param = <() as $provider_trait>::Loopback;
188                $expression
189            }
190            $device_id_enum_type::Ethernet($variable) => {
191                type $type_param = <() as $provider_trait>::Ethernet;
192                $expression
193            }
194            $device_id_enum_type::PureIp($variable) => {
195                type $type_param = <() as $provider_trait>::PureIp;
196                $expression
197            }
198            $device_id_enum_type::Blackhole($variable) => {
199                type $type_param = <() as $provider_trait>::Blackhole;
200                $expression
201            }
202        }
203    };
204}
205pub(crate) use crate::for_any_device_id;
206
207/// Provides the [`Device`] type for each device domain.
208pub trait DeviceProvider {
209    /// The [`Device`] type for Ethernet devices.
210    type Ethernet: Device;
211    /// The [`Device`] type for Loopback devices.
212    type Loopback: Device;
213    /// The [`Device`] type for pure IP devices.
214    type PureIp: Device;
215    /// The [`Device`] type for Blackhole devices.
216    type Blackhole: Device;
217}
218
219/// This implementation is used in the `for_any_device_id` macro.
220impl DeviceProvider for () {
221    type Ethernet = EthernetLinkDevice;
222    type Loopback = LoopbackDevice;
223    type PureIp = PureIpDevice;
224    type Blackhole = BlackholeDevice;
225}
226
227impl<BT: DeviceLayerTypes> Clone for DeviceId<BT> {
228    #[cfg_attr(feature = "instrumented", track_caller)]
229    fn clone(&self) -> Self {
230        for_any_device_id!(DeviceId, self, id => id.clone().into())
231    }
232}
233
234impl<BT: DeviceLayerTypes> PartialEq<WeakDeviceId<BT>> for DeviceId<BT> {
235    fn eq(&self, other: &WeakDeviceId<BT>) -> bool {
236        match (self, other) {
237            (DeviceId::Ethernet(strong), WeakDeviceId::Ethernet(weak)) => strong == weak,
238            (DeviceId::Loopback(strong), WeakDeviceId::Loopback(weak)) => strong == weak,
239            (DeviceId::PureIp(strong), WeakDeviceId::PureIp(weak)) => strong == weak,
240            (DeviceId::Blackhole(strong), WeakDeviceId::Blackhole(weak)) => strong == weak,
241            (DeviceId::Ethernet(_), _)
242            | (DeviceId::Loopback(_), _)
243            | (DeviceId::PureIp(_), _)
244            | (DeviceId::Blackhole(_), _) => false,
245        }
246    }
247}
248
249impl<BT: DeviceLayerTypes> PartialOrd for DeviceId<BT> {
250    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
251        Some(self.cmp(other))
252    }
253}
254
255impl<BT: DeviceLayerTypes> Ord for DeviceId<BT> {
256    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
257        // Assigns an arbitrary but orderable identifier, `u8`, to each variant
258        // of `DeviceId`.
259        fn discriminant<BT: DeviceLayerTypes>(d: &DeviceId<BT>) -> u8 {
260            match d {
261                // Each variant must provide a unique discriminant!
262                DeviceId::Ethernet(_) => 0,
263                DeviceId::Loopback(_) => 1,
264                DeviceId::PureIp(_) => 2,
265                DeviceId::Blackhole(_) => 3,
266            }
267        }
268        match (self, other) {
269            (DeviceId::Ethernet(me), DeviceId::Ethernet(other)) => me.cmp(other),
270            (DeviceId::Loopback(me), DeviceId::Loopback(other)) => me.cmp(other),
271            (DeviceId::PureIp(me), DeviceId::PureIp(other)) => me.cmp(other),
272            (DeviceId::Blackhole(me), DeviceId::Blackhole(other)) => me.cmp(other),
273            (me @ DeviceId::Ethernet(_), other)
274            | (me @ DeviceId::Loopback(_), other)
275            | (me @ DeviceId::PureIp(_), other)
276            | (me @ DeviceId::Blackhole(_), other) => discriminant(me).cmp(&discriminant(other)),
277        }
278    }
279}
280
281impl<BT: DeviceLayerTypes> From<EthernetDeviceId<BT>> for DeviceId<BT> {
282    fn from(id: EthernetDeviceId<BT>) -> DeviceId<BT> {
283        DeviceId::Ethernet(id)
284    }
285}
286
287impl<BT: DeviceLayerTypes> From<LoopbackDeviceId<BT>> for DeviceId<BT> {
288    fn from(id: LoopbackDeviceId<BT>) -> DeviceId<BT> {
289        DeviceId::Loopback(id)
290    }
291}
292
293impl<BT: DeviceLayerTypes> From<PureIpDeviceId<BT>> for DeviceId<BT> {
294    fn from(id: PureIpDeviceId<BT>) -> DeviceId<BT> {
295        DeviceId::PureIp(id)
296    }
297}
298
299impl<BT: DeviceLayerTypes> From<BlackholeDeviceId<BT>> for DeviceId<BT> {
300    fn from(id: BlackholeDeviceId<BT>) -> DeviceId<BT> {
301        DeviceId::Blackhole(id)
302    }
303}
304
305impl<BT: DeviceLayerTypes> DeviceId<BT> {
306    /// Downgrade to a [`WeakDeviceId`].
307    pub fn downgrade(&self) -> WeakDeviceId<BT> {
308        for_any_device_id!(DeviceId, self, id => id.downgrade().into())
309    }
310
311    /// Returns the bindings identifier associated with the device.
312    pub fn bindings_id(&self) -> &BT::DeviceIdentifier {
313        for_any_device_id!(DeviceId, self, id => id.bindings_id())
314    }
315}
316
317impl<BT: DeviceLayerTypes> DeviceIdentifier for DeviceId<BT> {
318    fn is_loopback(&self) -> bool {
319        match self {
320            DeviceId::Loopback(_) => true,
321            DeviceId::Ethernet(_) | DeviceId::PureIp(_) | DeviceId::Blackhole(_) => false,
322        }
323    }
324}
325
326impl<BT: DeviceLayerTypes> StrongDeviceIdentifier for DeviceId<BT> {
327    type Weak = WeakDeviceId<BT>;
328
329    fn downgrade(&self) -> Self::Weak {
330        self.downgrade()
331    }
332}
333
334impl<BT: DeviceLayerTypes> Debug for DeviceId<BT> {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336        for_any_device_id!(DeviceId, self, id => Debug::fmt(id, f))
337    }
338}
339
340impl<BT: DeviceLayerTypes> DeviceWithName for DeviceId<BT> {
341    fn name_matches(&self, name: &str) -> bool {
342        self.bindings_id().name_matches(name)
343    }
344}
345
346impl<BT: DeviceLayerTypes> filter::InterfaceProperties<BT::DeviceClass> for DeviceId<BT> {
347    fn id_matches(&self, id: &NonZeroU64) -> bool {
348        self.bindings_id().id_matches(id)
349    }
350
351    fn device_class_matches(&self, device_class: &BT::DeviceClass) -> bool {
352        for_any_device_id!(
353            DeviceId,
354            self,
355            id => id.external_state().device_class_matches(device_class)
356        )
357    }
358}
359
360/// A base weak device identifier.
361///
362/// Allows multiple device implementations to share the same shape for
363/// maintaining reference identifiers.
364#[derive(Derivative)]
365#[derivative(Clone(bound = ""))]
366pub struct BaseWeakDeviceId<T: DeviceStateSpec, BT: DeviceLayerTypes> {
367    // NB: This is not a tuple struct because regular structs play nicer with
368    // type aliases, which is how we use BaseDeviceId.
369    cookie: Arc<WeakCookie<T, BT>>,
370}
371
372impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialEq for BaseWeakDeviceId<T, BT> {
373    fn eq(&self, other: &Self) -> bool {
374        self.cookie.weak_ref.ptr_eq(&other.cookie.weak_ref)
375    }
376}
377
378impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Eq for BaseWeakDeviceId<T, BT> {}
379
380impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Hash for BaseWeakDeviceId<T, BT> {
381    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
382        self.cookie.weak_ref.hash(state)
383    }
384}
385
386impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialEq<BaseDeviceId<T, BT>>
387    for BaseWeakDeviceId<T, BT>
388{
389    fn eq(&self, other: &BaseDeviceId<T, BT>) -> bool {
390        <BaseDeviceId<T, BT> as PartialEq<BaseWeakDeviceId<T, BT>>>::eq(other, self)
391    }
392}
393
394impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Debug for BaseWeakDeviceId<T, BT> {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        let Self { cookie } = self;
397        write!(f, "Weak{}({:?})", T::DEBUG_TYPE, &cookie.bindings_id)
398    }
399}
400
401impl<T: DeviceStateSpec, BT: DeviceLayerTypes> DeviceIdentifier for BaseWeakDeviceId<T, BT> {
402    fn is_loopback(&self) -> bool {
403        T::IS_LOOPBACK
404    }
405}
406
407impl<T: DeviceStateSpec, BT: DeviceLayerTypes> WeakDeviceIdentifier for BaseWeakDeviceId<T, BT> {
408    type Strong = BaseDeviceId<T, BT>;
409
410    fn upgrade(&self) -> Option<Self::Strong> {
411        self.upgrade()
412    }
413}
414
415impl<T: DeviceStateSpec, BT: DeviceLayerTypes> BaseWeakDeviceId<T, BT> {
416    /// Attempts to upgrade the ID to a strong ID, failing if the
417    /// device no longer exists.
418    pub fn upgrade(&self) -> Option<BaseDeviceId<T, BT>> {
419        let Self { cookie } = self;
420        cookie.weak_ref.upgrade().map(|rc| BaseDeviceId { rc })
421    }
422
423    /// Returns the bindings identifier associated with the device.
424    pub fn bindings_id(&self) -> &BT::DeviceIdentifier {
425        &self.cookie.bindings_id
426    }
427}
428
429/// A base device identifier.
430///
431/// Allows multiple device implementations to share the same shape for
432/// maintaining reference identifiers.
433#[derive(Derivative)]
434#[derivative(Hash(bound = ""), Eq(bound = ""), PartialEq(bound = ""))]
435pub struct BaseDeviceId<T: DeviceStateSpec, BT: DeviceLayerTypes> {
436    // NB: This is not a tuple struct because regular structs play nicer with
437    // type aliases, which is how we use BaseDeviceId.
438    rc: StrongRc<BaseDeviceState<T, BT>>,
439}
440
441impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Clone for BaseDeviceId<T, BT> {
442    #[cfg_attr(feature = "instrumented", track_caller)]
443    fn clone(&self) -> Self {
444        let Self { rc } = self;
445        Self { rc: StrongRc::clone(rc) }
446    }
447}
448
449impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialEq<BaseWeakDeviceId<T, BT>>
450    for BaseDeviceId<T, BT>
451{
452    fn eq(&self, BaseWeakDeviceId { cookie }: &BaseWeakDeviceId<T, BT>) -> bool {
453        let Self { rc: me_rc } = self;
454        StrongRc::weak_ptr_eq(me_rc, &cookie.weak_ref)
455    }
456}
457
458impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialEq<BasePrimaryDeviceId<T, BT>>
459    for BaseDeviceId<T, BT>
460{
461    fn eq(&self, BasePrimaryDeviceId { rc: other_rc }: &BasePrimaryDeviceId<T, BT>) -> bool {
462        let Self { rc: me_rc } = self;
463        PrimaryRc::ptr_eq(other_rc, me_rc)
464    }
465}
466
467impl<T: DeviceStateSpec, BT: DeviceLayerTypes> PartialOrd for BaseDeviceId<T, BT> {
468    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
469        Some(self.cmp(other))
470    }
471}
472
473impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Ord for BaseDeviceId<T, BT> {
474    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
475        let Self { rc: me } = self;
476        let Self { rc: other } = other;
477
478        StrongRc::ptr_cmp(me, other)
479    }
480}
481
482impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Debug for BaseDeviceId<T, BT> {
483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484        let Self { rc } = self;
485        write!(f, "{}({:?})", T::DEBUG_TYPE, &rc.weak_cookie.bindings_id)
486    }
487}
488
489impl<T: DeviceStateSpec, BT: DeviceLayerTypes> DeviceIdentifier for BaseDeviceId<T, BT> {
490    fn is_loopback(&self) -> bool {
491        T::IS_LOOPBACK
492    }
493}
494
495impl<T: DeviceStateSpec, BT: DeviceLayerTypes> StrongDeviceIdentifier for BaseDeviceId<T, BT> {
496    type Weak = BaseWeakDeviceId<T, BT>;
497
498    fn downgrade(&self) -> Self::Weak {
499        self.downgrade()
500    }
501}
502
503impl<T: DeviceStateSpec, BT: DeviceLayerTypes> BaseDeviceId<T, BT> {
504    /// Returns a reference to the device state.
505    ///
506    /// Requires an OriginTracker to ensure this is being access from the proper
507    /// context and disallow usage in bindings.
508    pub fn device_state(&self, tracker: &OriginTracker) -> &IpLinkDeviceState<T, BT> {
509        debug_assert_eq!(tracker, &self.rc.ip.origin);
510        &self.rc.ip
511    }
512
513    /// Returns a reference to the external state for the device.
514    pub fn external_state(&self) -> &T::External<BT> {
515        &self.rc.external_state
516    }
517
518    /// Returns the bindings identifier associated with the device.
519    pub fn bindings_id(&self) -> &BT::DeviceIdentifier {
520        &self.rc.weak_cookie.bindings_id
521    }
522
523    /// Downgrades the ID to an [`EthernetWeakDeviceId`].
524    pub fn downgrade(&self) -> BaseWeakDeviceId<T, BT> {
525        let Self { rc } = self;
526        BaseWeakDeviceId { cookie: Arc::clone(&rc.weak_cookie) }
527    }
528}
529
530/// The primary reference to a device.
531pub struct BasePrimaryDeviceId<T: DeviceStateSpec, BT: DeviceLayerTypes> {
532    // NB: This is not a tuple struct because regular structs play nicer with
533    // type aliases, which is how we use BaseDeviceId.
534    rc: PrimaryRc<BaseDeviceState<T, BT>>,
535}
536
537impl<T: DeviceStateSpec, BT: DeviceLayerTypes> Debug for BasePrimaryDeviceId<T, BT> {
538    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539        let Self { rc } = self;
540        write!(f, "Primary{}({:?})", T::DEBUG_TYPE, &rc.weak_cookie.bindings_id)
541    }
542}
543
544impl<T: DeviceStateSpec, BT: DeviceLayerTypes> BasePrimaryDeviceId<T, BT> {
545    /// Returns a strong clone of this primary ID.
546    #[cfg_attr(feature = "instrumented", track_caller)]
547    pub fn clone_strong(&self) -> BaseDeviceId<T, BT> {
548        let Self { rc } = self;
549        BaseDeviceId { rc: PrimaryRc::clone_strong(rc) }
550    }
551
552    pub(crate) fn new<F: FnOnce(BaseWeakDeviceId<T, BT>) -> IpLinkDeviceState<T, BT>>(
553        ip: F,
554        external_state: T::External<BT>,
555        bindings_id: BT::DeviceIdentifier,
556    ) -> Self {
557        Self {
558            rc: PrimaryRc::new_cyclic(move |weak_ref| {
559                let weak_cookie = Arc::new(WeakCookie { bindings_id, weak_ref });
560                let ip = ip(BaseWeakDeviceId { cookie: Arc::clone(&weak_cookie) });
561                BaseDeviceState { ip, external_state, weak_cookie }
562            }),
563        }
564    }
565
566    pub(crate) fn into_inner(self) -> PrimaryRc<BaseDeviceState<T, BT>> {
567        self.rc
568    }
569}
570
571/// A strong device ID identifying an ethernet device.
572///
573/// This device ID is like [`DeviceId`] but specifically for ethernet devices.
574pub type EthernetDeviceId<BT> = BaseDeviceId<EthernetLinkDevice, BT>;
575/// A weak device ID identifying an ethernet device.
576pub type EthernetWeakDeviceId<BT> = BaseWeakDeviceId<EthernetLinkDevice, BT>;
577/// The primary Ethernet device reference.
578pub type EthernetPrimaryDeviceId<BT> = BasePrimaryDeviceId<EthernetLinkDevice, BT>;
579
580#[cfg(any(test, feature = "testutils"))]
581mod testutil {
582    use super::*;
583
584    impl<BT: DeviceLayerTypes> TryFrom<DeviceId<BT>> for EthernetDeviceId<BT> {
585        type Error = DeviceId<BT>;
586        fn try_from(id: DeviceId<BT>) -> Result<EthernetDeviceId<BT>, DeviceId<BT>> {
587            match id {
588                DeviceId::Ethernet(id) => Ok(id),
589                DeviceId::Loopback(_) | DeviceId::PureIp(_) | DeviceId::Blackhole(_) => Err(id),
590            }
591        }
592    }
593
594    impl<BT: DeviceLayerTypes> DeviceId<BT> {
595        /// Extracts an ethernet device from self or panics.
596        pub fn unwrap_ethernet(self) -> EthernetDeviceId<BT> {
597            assert_matches::assert_matches!(self, DeviceId::Ethernet(e) => e)
598        }
599    }
600}