Skip to main content

nmrs/api/models/
device.rs

1use std::fmt::{Display, Formatter};
2
3use zvariant::OwnedObjectPath;
4
5/// Represents a network device managed by NetworkManager.
6///
7/// A device can be a WiFi adapter, Ethernet interface, or other network hardware.
8///
9/// # Examples
10///
11/// ```no_run
12/// use nmrs::NetworkManager;
13///
14/// # async fn example() -> nmrs::Result<()> {
15/// let nm = NetworkManager::new().await?;
16/// let devices = nm.list_devices().await?;
17///
18/// for device in devices {
19///     println!("Interface: {}", device.interface);
20///     println!("  Type: {}", device.device_type);
21///     println!("  State: {}", device.state);
22///     println!("  MAC: {}", device.identity.current_mac);
23///
24///     if device.is_wireless() {
25///         println!("  This is a WiFi device");
26///     } else if device.is_wired() {
27///         println!("  This is an Ethernet device");
28///     } else if device.is_bluetooth() {
29///         println!("  This is a Bluetooth device");
30///     } else if device.is_loopback() {
31///         println!("  This is a loopback device");
32///     }
33///
34///     if let Some(driver) = &device.driver {
35///         println!("  Driver: {}", driver);
36///     }
37/// }
38/// # Ok(())
39/// # }
40/// ```
41#[non_exhaustive]
42#[derive(Debug, Clone)]
43pub struct Device {
44    /// D-Bus object path
45    pub path: String,
46    /// Interface name (e.g., "wlan0", "eth0")
47    pub interface: String,
48    /// Device hardware identity (MAC addresses)
49    pub identity: DeviceIdentity,
50    /// Type of device (WiFi, Ethernet, etc.)
51    pub device_type: DeviceType,
52    /// Current device state
53    pub state: DeviceState,
54    /// Whether NetworkManager manages this device
55    pub managed: Option<bool>,
56    /// Kernel driver name
57    pub driver: Option<String>,
58    /// Assigned IPv4 address with CIDR notation (only present when connected)
59    pub ip4_address: Option<String>,
60    /// Assigned IPv6 address with CIDR notation (only present when connected)
61    pub ip6_address: Option<String>,
62    /// Operating frequency in MHz for the active Wi-Fi connection, if known.
63    pub frequency: Option<u32>,
64    /// Link speed in megabits per second for Ethernet devices, if known.
65    ///
66    /// This is the raw value reported by NetworkManager. Some drivers report
67    /// `0` when no carrier is present.
68    pub speed_mbps: Option<u32>,
69}
70
71/// A Wi-Fi device summary returned by
72/// [`list_wifi_devices`](crate::NetworkManager::list_wifi_devices).
73///
74/// Use this on multi-radio machines (laptops with USB dongles, docks with a
75/// second wireless adapter, etc.) to discover the available interfaces and
76/// pick one to scope subsequent operations to. Pair with
77/// [`NetworkManager::wifi`](crate::NetworkManager::wifi) for ergonomic
78/// per-interface calls.
79#[non_exhaustive]
80#[derive(Debug, Clone)]
81pub struct WifiDevice {
82    /// D-Bus object path of the device.
83    pub path: OwnedObjectPath,
84    /// Interface name (e.g. `"wlan0"`).
85    pub interface: String,
86    /// Current MAC address (may be randomized).
87    pub hw_address: String,
88    /// Permanent (factory-burned) MAC, if NM exposes it.
89    pub permanent_hw_address: Option<String>,
90    /// Kernel driver name, if available.
91    pub driver: Option<String>,
92    /// Current device state.
93    pub state: DeviceState,
94    /// Whether NetworkManager manages this device.
95    pub managed: bool,
96    /// Whether NM will autoconnect known networks on this device.
97    pub autoconnect: bool,
98    /// `true` if the device currently has an active access point.
99    pub is_active: bool,
100    /// SSID of the currently active AP, if any.
101    pub active_ssid: Option<String>,
102    /// Operating frequency in MHz of the currently active AP, if any.
103    pub active_frequency_mhz: Option<u32>,
104}
105
106/// A wired Ethernet device summary returned by
107/// [`list_wired_device_details`](crate::NetworkManager::list_wired_device_details).
108///
109/// Use this when Ethernet-specific details such as link speed, hardware
110/// address, or active connection id are needed without falling back to raw
111/// D-Bus calls.
112#[non_exhaustive]
113#[derive(Debug, Clone)]
114pub struct WiredDevice {
115    /// D-Bus object path of the device.
116    pub path: String,
117    /// Interface name (e.g. `"eth0"`).
118    pub interface: String,
119    /// Current MAC address.
120    pub hw_address: String,
121    /// Permanent (factory-burned) MAC, if NM exposes it.
122    pub permanent_hw_address: Option<String>,
123    /// Link speed in megabits per second, if NM exposes it.
124    ///
125    /// This is the raw NetworkManager value. Some drivers report `0` when no
126    /// carrier is present.
127    pub speed_mbps: Option<u32>,
128    /// Active connection profile id, if this device is connected.
129    pub active_connection_id: Option<String>,
130    /// Current device state.
131    pub state: DeviceState,
132    /// Assigned IPv4 address with CIDR notation, if connected.
133    pub ip4_address: Option<String>,
134    /// Assigned IPv6 address with CIDR notation, if connected.
135    pub ip6_address: Option<String>,
136}
137
138/// Represents the hardware identity of a network device.
139///
140/// Contains MAC addresses that uniquely identify the device. The permanent
141/// MAC is burned into the hardware, while the current MAC may be different
142/// if MAC address randomization or spoofing is enabled.
143#[non_exhaustive]
144#[derive(Debug, Clone, PartialEq, Eq, Hash)]
145pub struct DeviceIdentity {
146    /// The permanent (factory-assigned) MAC address.
147    pub permanent_mac: String,
148    /// The current MAC address in use (may differ if randomized/spoofed).
149    pub current_mac: String,
150}
151
152impl DeviceIdentity {
153    /// Creates a new `DeviceIdentity`.
154    ///
155    /// # Arguments
156    ///
157    /// * `permanent_mac` - The permanent (factory-assigned) MAC address
158    /// * `current_mac` - The current MAC address in use
159    #[must_use]
160    pub fn new(permanent_mac: String, current_mac: String) -> Self {
161        Self {
162            permanent_mac,
163            current_mac,
164        }
165    }
166}
167
168/// NetworkManager device types.
169///
170/// Represents the type of network hardware managed by NetworkManager.
171/// This enum uses a registry-based system to support adding new device
172/// types without breaking the API.
173#[non_exhaustive]
174#[derive(Debug, Clone, PartialEq)]
175pub enum DeviceType {
176    /// Wired Ethernet device.
177    Ethernet,
178    /// Wi-Fi (802.11) wireless device.
179    Wifi,
180    /// Wi-Fi P2P (peer-to-peer) device.
181    WifiP2P,
182    /// Loopback device (localhost).
183    Loopback,
184    /// Bluetooth
185    Bluetooth,
186    /// VLAN (802.1Q) virtual device.
187    Vlan,
188    /// Unknown or unsupported device type with raw code.
189    ///
190    /// Use the methods on `DeviceType` to query capabilities of unknown device types,
191    /// which will consult the internal device type registry.
192    Other(u32),
193}
194
195impl DeviceType {
196    /// Returns whether this device type supports network scanning.
197    ///
198    /// Currently only WiFi and WiFi P2P devices support scanning.
199    /// For unknown device types, consults the internal device type registry.
200    #[must_use]
201    pub fn supports_scanning(&self) -> bool {
202        match self {
203            Self::Wifi | Self::WifiP2P => true,
204            Self::Other(code) => crate::types::device_type_registry::supports_scanning(*code),
205            _ => false,
206        }
207    }
208
209    /// Returns whether this device type requires a specific object (like an access point).
210    ///
211    /// WiFi devices require an access point to connect to, while Ethernet can connect
212    /// without a specific target.
213    /// For unknown device types, consults the internal device type registry.
214    #[must_use]
215    pub fn requires_specific_object(&self) -> bool {
216        match self {
217            Self::Wifi | Self::WifiP2P => true,
218            Self::Other(code) => {
219                crate::types::device_type_registry::requires_specific_object(*code)
220            }
221            _ => false,
222        }
223    }
224
225    /// Returns whether this device type has a global enabled/disabled state.
226    ///
227    /// WiFi has a global radio killswitch that can enable/disable all WiFi devices.
228    /// For unknown device types, consults the internal device type registry.
229    #[must_use]
230    pub fn has_global_enabled_state(&self) -> bool {
231        match self {
232            Self::Wifi => true,
233            Self::Other(code) => {
234                crate::types::device_type_registry::has_global_enabled_state(*code)
235            }
236            _ => false,
237        }
238    }
239
240    /// Returns the NetworkManager connection type string for this device.
241    ///
242    /// This is used when creating connection profiles for this device type.
243    /// For unknown device types, consults the internal device type registry.
244    #[must_use]
245    pub fn connection_type_str(&self) -> &'static str {
246        match self {
247            Self::Ethernet => "802-3-ethernet",
248            Self::Wifi => "802-11-wireless",
249            Self::WifiP2P => "wifi-p2p",
250            Self::Loopback => "loopback",
251            Self::Bluetooth => "bluetooth",
252            Self::Vlan => "vlan",
253            Self::Other(code) => {
254                crate::types::device_type_registry::connection_type_for_code(*code)
255                    .unwrap_or("generic")
256            }
257        }
258    }
259
260    /// Returns the raw NetworkManager type code for this device.
261    #[must_use]
262    pub fn to_code(&self) -> u32 {
263        match self {
264            Self::Ethernet => 1,
265            Self::Wifi => 2,
266            Self::WifiP2P => 30,
267            Self::Loopback => 32,
268            Self::Bluetooth => 5,
269            Self::Vlan => 11,
270            Self::Other(code) => *code,
271        }
272    }
273}
274
275/// NetworkManager device states.
276///
277/// Represents the current operational state of a network device.
278#[non_exhaustive]
279#[derive(Debug, Clone, PartialEq)]
280pub enum DeviceState {
281    /// Device is not managed by NetworkManager.
282    Unmanaged,
283    /// Device is managed but not yet available (e.g., Wi-Fi disabled).
284    Unavailable,
285    /// Device is available but not connected.
286    Disconnected,
287    /// Device is preparing to connect.
288    Prepare,
289    /// Device is being configured.
290    Config,
291    /// Device requires authentication credentials.
292    NeedAuth,
293    /// Device is requesting IP configuration.
294    IpConfig,
295    /// Device is verifying IP connectivity.
296    IpCheck,
297    /// Device is waiting for secondary connections.
298    Secondaries,
299    /// Device is fully connected and operational.
300    Activated,
301    /// Device is disconnecting.
302    Deactivating,
303    /// Device connection failed.
304    Failed,
305    /// Unknown or unsupported state with raw code.
306    Other(u32),
307}
308
309impl DeviceState {
310    /// Returns `true` if the device is in a transitional (in-progress) state.
311    ///
312    /// Transitional states indicate an active connection or disconnection
313    /// operation: Prepare, Config, NeedAuth, IpConfig, IpCheck, Secondaries,
314    /// or Deactivating.
315    #[must_use]
316    pub fn is_transitional(&self) -> bool {
317        matches!(
318            self,
319            Self::Prepare
320                | Self::Config
321                | Self::NeedAuth
322                | Self::IpConfig
323                | Self::IpCheck
324                | Self::Secondaries
325                | Self::Deactivating
326        )
327    }
328
329    /// Returns `true` if the device state indicates the device is usable.
330    ///
331    /// This is derived only from the NetworkManager device state. For actual
332    /// Wi-Fi radio power and rfkill state, use
333    /// [`NetworkManager::wifi_state`](crate::NetworkManager::wifi_state).
334    #[must_use]
335    pub fn is_enabled(&self) -> bool {
336        matches!(
337            self,
338            Self::Disconnected
339                | Self::Prepare
340                | Self::Config
341                | Self::NeedAuth
342                | Self::IpConfig
343                | Self::IpCheck
344                | Self::Secondaries
345                | Self::Activated
346                | Self::Deactivating
347        )
348    }
349}
350
351impl Device {
352    /// Returns `true` if this is a wired (Ethernet) device.
353    #[must_use]
354    pub fn is_wired(&self) -> bool {
355        crate::types::device_type_registry::is_wired(self.device_type.to_code())
356    }
357
358    /// Returns `true` if this is a wireless (Wi-Fi) device.
359    #[must_use]
360    pub fn is_wireless(&self) -> bool {
361        matches!(self.device_type, DeviceType::Wifi)
362    }
363
364    /// Returns 'true' if this is a Bluetooth (DUN or PANU) device.
365    #[must_use]
366    pub fn is_bluetooth(&self) -> bool {
367        matches!(self.device_type, DeviceType::Bluetooth)
368    }
369
370    /// Returns `true` if this is a loopback device (e.g., `lo`).
371    #[must_use]
372    pub fn is_loopback(&self) -> bool {
373        matches!(self.device_type, DeviceType::Loopback)
374    }
375
376    /// Returns `true` if this is a VLAN (802.1Q) device.
377    #[must_use]
378    pub fn is_vlan(&self) -> bool {
379        matches!(self.device_type, DeviceType::Vlan)
380    }
381}
382
383impl Display for Device {
384    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
385        write!(
386            f,
387            "{} ({}) [{}]",
388            self.interface, self.device_type, self.state
389        )
390    }
391}
392
393impl From<u32> for DeviceType {
394    fn from(value: u32) -> Self {
395        match value {
396            1 => DeviceType::Ethernet,
397            2 => DeviceType::Wifi,
398            5 => DeviceType::Bluetooth,
399            11 => DeviceType::Vlan,
400            30 => DeviceType::WifiP2P,
401            32 => DeviceType::Loopback,
402            v => DeviceType::Other(v),
403        }
404    }
405}
406
407impl From<u32> for DeviceState {
408    fn from(value: u32) -> Self {
409        match value {
410            10 => Self::Unmanaged,
411            20 => Self::Unavailable,
412            30 => Self::Disconnected,
413            40 => Self::Prepare,
414            50 => Self::Config,
415            60 => Self::NeedAuth,
416            70 => Self::IpConfig,
417            80 => Self::IpCheck,
418            90 => Self::Secondaries,
419            100 => Self::Activated,
420            110 => Self::Deactivating,
421            120 => Self::Failed,
422            v => Self::Other(v),
423        }
424    }
425}
426
427impl Display for DeviceType {
428    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
429        match self {
430            DeviceType::Ethernet => write!(f, "Ethernet"),
431            DeviceType::Wifi => write!(f, "Wi-Fi"),
432            DeviceType::WifiP2P => write!(f, "Wi-Fi P2P"),
433            DeviceType::Loopback => write!(f, "Loopback"),
434            DeviceType::Bluetooth => write!(f, "Bluetooth"),
435            DeviceType::Vlan => write!(f, "VLAN"),
436            DeviceType::Other(v) => write!(
437                f,
438                "{}",
439                crate::types::device_type_registry::display_name_for_code(*v)
440            ),
441        }
442    }
443}
444
445impl Display for DeviceState {
446    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
447        match self {
448            Self::Unmanaged => write!(f, "Unmanaged"),
449            Self::Unavailable => write!(f, "Unavailable"),
450            Self::Disconnected => write!(f, "Disconnected"),
451            Self::Prepare => write!(f, "Preparing"),
452            Self::Config => write!(f, "Configuring"),
453            Self::NeedAuth => write!(f, "NeedAuth"),
454            Self::IpConfig => write!(f, "IpConfig"),
455            Self::IpCheck => write!(f, "IpCheck"),
456            Self::Secondaries => write!(f, "Secondaries"),
457            Self::Activated => write!(f, "Activated"),
458            Self::Deactivating => write!(f, "Deactivating"),
459            Self::Failed => write!(f, "Failed"),
460            Self::Other(v) => write!(f, "Other({v})"),
461        }
462    }
463}