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}