diff --git a/Cargo.toml b/Cargo.toml index 751d533..0f19b16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,20 +7,8 @@ edition = "2021" categories = ["gateway", "bridge", "transport", "modemmanager", "xmpp", "jabber"] [dependencies] -async-trait = "0.1.56" -mmdbus = "1.18.6" -dbus-tokio = "0.7.5" -tokio = { version = "1.19.2", features = [ "time", "macros", "rt-multi-thread" ] } +tokio = { version = "1", features = [ "macros", "rt-multi-thread" ] } futures = "0.3" -futures-util = "0.3" -xmpp = "0.3" -xmpp-parsers = "0.19" log = "0.4" -env_logger = "0.8" - -[patch.crates-io] -jid = { path = "../xmpp-rs/jid" } -minidom = { path = "../xmpp-rs/minidom" } -tokio-xmpp = { path = "../xmpp-rs/tokio-xmpp" } -xmpp = { path = "../xmpp-rs/xmpp" } -xmpp-parsers = { path = "../xmpp-rs/parsers" } +pretty_env_logger = "0.5" +zbus = "3.14.1" diff --git a/src/error.rs b/src/error.rs index 259a5f9..7e53deb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,22 +18,17 @@ use std::convert::From; use std::error::Error as StdError; -use mmdbus::dbus; -use xmpp_parsers::JidParseError; - #[derive(Debug)] pub enum Error { - DBusError(dbus::Error), - InvalidArgs, - JidParseError(JidParseError), + Zbus(zbus::Error), + ZbusFdo(zbus::fdo::Error), } impl StdError for Error { fn cause(&self) -> Option<&dyn StdError> { match self { - Error::DBusError(e) => Some(e), - Error::InvalidArgs => None, - Error::JidParseError(e) => Some(e), + Error::Zbus(e) => Some(e), + Error::ZbusFdo(e) => Some(e), } } } @@ -41,21 +36,20 @@ impl StdError for Error { impl std::fmt::Display for Error { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - Error::DBusError(e) => write!(fmt, "dbus error: {}", e), - Error::InvalidArgs => write!(fmt, "invalid arguments"), - Error::JidParseError(e) => write!(fmt, "jid parse error: {}", e), + Error::Zbus(e) => write!(fmt, "zbus error: {}", e), + Error::ZbusFdo(e) => write!(fmt, "zbus fdo error: {}", e), } } } -impl From for Error { - fn from(err: dbus::Error) -> Error { - Error::DBusError(err) +impl From for Error { + fn from(err: zbus::Error) -> Error { + Error::Zbus(err) } } -impl From for Error { - fn from(err: JidParseError) -> Error { - Error::JidParseError(err) +impl From for Error { + fn from(err: zbus::fdo::Error) -> Error { + Error::ZbusFdo(err) } } diff --git a/src/main.rs b/src/main.rs index 2fe9f3e..a10d115 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,233 +13,19 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pub mod error; -pub mod mm; +mod error; +mod modem; -use error::Error; -use mm::modem::Modem as ModemAccess; +use crate::error::Error; +use crate::modem::ModemManager; -use std::fmt; -use std::sync::Arc; -use std::str::FromStr; -use std::env::args; - -use env_logger; - -use futures_util::StreamExt; -use tokio::{ - task::JoinHandle, - time::Duration, -}; - -use mmdbus::{ - dbus::{ - message::MatchRule, - nonblock::{ - SyncConnection, Proxy, - stdintf::org_freedesktop_dbus::ObjectManager, - }, - strings::{Path as DBusPath}, - }, -}; - -use dbus_tokio::connection; - -use xmpp::{ClientBuilder, ClientType}; -use xmpp_parsers::{ - Jid, BareJid, - message::MessageType, -}; - -// The following source has been used as an example: -// https://github.com/soerenmeier/linux-info/blob/master/src/network/modem_manager.rs - -const MM_NAME: &str = "org.freedesktop.ModemManager1"; -const MM_PATH: &str = "/org/freedesktop/ModemManager1"; -const TIMEOUT: Duration = Duration::from_secs(5); - -#[derive(Clone)] -pub struct DBus { - pub conn: Arc, - pub handle: Arc>, -} - -impl DBus { - pub fn connect() -> Result { - let (resource, conn) = connection::new_system_sync()?; - - let handle: JoinHandle<()> = tokio::spawn(async { - let err = resource.await; - panic!("Lost connection to D-Bus: {}", err); - }); - - Ok(Self { conn, handle: Arc::new(handle) }) - } - - pub fn proxy<'a, 'b, P>(&'b self, path: P) -> Proxy<'a, &'b SyncConnection> - where - P: Into>, - { - Proxy::new(MM_NAME, path, TIMEOUT, &*self.conn) - } -} - -#[derive(Clone)] -pub struct ModemManager { - dbus: DBus, -} - -impl ModemManager { - pub fn connect() -> Result { - DBus::connect().map(|dbus| Self { dbus }) - } - - pub async fn modems(&self) -> Result, Error> { - let objects = self.dbus.proxy(MM_PATH).get_managed_objects().await?; - let modems = objects - .into_iter() - .map(|(path, _)| { - Modem { - dbus: self.dbus.clone(), - path, - } - }) - .collect(); - - Ok(modems) - } -} - -#[derive(Clone)] -pub struct Modem { - dbus: DBus, - pub path: DBusPath<'static>, -} - -impl fmt::Debug for Modem { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Modem {{ {}, {:?}, .. }}", self.path, "1234") - } -} - -impl Modem { - pub async fn enabled(&self) -> Result { - let state: ModemState = ModemAccess::state(&self.dbus.proxy(&self.path)).await.map(Into::into)?; - Ok(match state { - ModemState::Enabling - | ModemState::Enabled - | ModemState::Searching - | ModemState::Registered - | ModemState::Connecting - | ModemState::Connected => true, - _ => false, - }) - } - - pub async fn model(&self) -> Result { - ModemAccess::model(&self.dbus.proxy(&self.path)).await.map_err(Error::DBusError) - } -} - -#[repr(i32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ModemState { - /// The modem is unusable. - Failed = -1, - /// State unknown or not reportable. - Unknown = 0, - /// The modem is currently being initialized. - Initializing = 1, - /// The modem needs to be unlocked. - Locked = 2, - /// The modem is not enabled and is powered down. - Disabled = 3, - /// The modem is currently transitioning to the MM_MODEM_STATE_DISABLED - /// state. - Disabling = 4, - /// The modem is currently transitioning to the MM_MODEM_STATE_ENABLED - /// state. - Enabling = 5, - /// The modem is enabled and powered on but not registered with a network - /// provider and not available for data connections. - Enabled = 6, - /// The modem is searching for a network provider to register with. - Searching = 7, - /// The modem is registered with a network provider, and data connections - /// and messaging may be available for use. - Registered = 8, - /// The modem is disconnecting and deactivating the last active packet data - /// bearer. This state will not be entered if more than one packet data - /// bearer is active and one of the active bearers is deactivated. - Disconnecting = 9, - /// The modem is activating and connecting the first packet data bearer. - /// Subsequent bearer activations when another bearer is already active - /// do not cause this state to be entered. - Connecting = 10, - /// One or more packet data bearers is active and connected. - Connected = 11, -} - -impl From for ModemState { - fn from(num: i32) -> Self { - if num < -1 || num > 11 { - Self::Unknown - } else { - unsafe { *(&num as *const i32 as *const Self) } - } - } -} - -async fn print_foo(mm: ModemManager) -> Result<(), Error> { - let modems = mm.modems().await?; - println!("Modems: {:?}", modems); - for modem in modems { - println!("Modem: {:?}", modem); - println!("Enabled: {:?}", modem.enabled().await); - println!("Model: {:?}", modem.model().await); +#[tokio::main] +async fn main() -> Result<(), Error> { + let manager = ModemManager::connect().await?; + for modem in manager { + println!("FOO0: {:?}", modem); + println!("FOO0: {:?}", modem.manufacturer().await?); + println!("FOO0: {:?}", modem.model().await?); } Ok(()) } - -#[tokio::main] -pub async fn main() -> Result<(), Error> { - env_logger::init(); - - let args: Vec = args().collect(); - if args.len() != 3 { - println!("Usage: {} ", args[0]); - return Err(Error::InvalidArgs) - } - - // ModemManager - let dbus = DBus::connect()?; - - let mut interval = tokio::time::interval(Duration::from_secs(2)); - - let mr = MatchRule::new_signal("org.freedesktop.DBus.Properties", "PropertiesChanged"); - - - // XMPP - let jid_str = args[1].as_str(); - let jid = Jid::Bare(BareJid::from_str(jid_str).unwrap()); - let passwd = args[2].as_str(); - - // Client instance - let mut xmpp = ClientBuilder::new(jid_str, passwd) - .set_client(ClientType::Bot, "gateway_mm") - .set_website("https://codeberg.org/pep./gateway_mm") - .build() - .unwrap(); - - loop { - tokio::select! { - events = xmpp.wait_for_events() => { - println!("Events: {:?}", events); - }, - _ = interval.tick() => { - let mm = ModemManager { dbus: dbus.clone() }; - let _ = print_foo(mm).await; - }, - } - } -} diff --git a/src/mm/mod.rs b/src/mm/mod.rs deleted file mode 100644 index 733a648..0000000 --- a/src/mm/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2022 Maxime “pep” Buquet -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU Affero General Public License as published by the -// Free Software Foundation, either version 3 of the License, or (at your -// option) any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License -// for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pub mod modem; diff --git a/src/mm/modem.rs b/src/mm/modem.rs deleted file mode 100644 index 4ef7ff0..0000000 --- a/src/mm/modem.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2022 Maxime “pep” Buquet -// -// This program is free software: you can redistribute it and/or modify it -// under the terms of the GNU Affero General Public License as published by the -// Free Software Foundation, either version 3 of the License, or (at your -// option) any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License -// for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use std::ops::Deref; -use std::marker::Sync; - -use async_trait::async_trait; -use mmdbus::dbus::{nonblock, Error as DBusError}; - -#[async_trait] -pub trait Modem { - async fn enable(&self, enable: bool) -> Result<(), DBusError>; - async fn state(&self) -> Result; - async fn model(&self) -> Result; -} - -#[async_trait] -impl<'a, T: nonblock::NonblockReply, C: Deref + Sync> Modem for nonblock::Proxy<'a, C> { - async fn enable(&self, enable: bool) -> Result<(), DBusError> { - self.method_call("org.freedesktop.ModemManager1.Modem", "Enable", (enable,)).await - } - - async fn state(&self) -> Result { - ::get(&self, "org.freedesktop.ModemManager1.Modem", "State").await - } - - async fn model(&self) -> Result { - ::get(&self, "org.freedesktop.ModemManager1.Modem", "Model").await - } -} diff --git a/src/modem/introspect.rs b/src/modem/introspect.rs new file mode 100644 index 0000000..b2dec81 --- /dev/null +++ b/src/modem/introspect.rs @@ -0,0 +1,316 @@ +//! DBus interface proxies for: +//! +//! - `org.freedesktop.ModemManager1.Modem`, +//! - `org.freedesktop.ModemManager1.Modem.Messaging`, +//! - `org.freedesktop.ModemManager1.Modem.Simple`, +//! - `org.freedesktop.ModemManager1.Modem.Voice` +//! +//! This code was generated by `zbus-xmlgen` `3.1.1` from DBus introspection data. +//! Source: `ModemManager1.xml`. + +use zbus::dbus_proxy; + +#[dbus_proxy( + interface = "org.freedesktop.ModemManager1.Modem.Messaging", + assume_defaults = true +)] +trait Messaging { + /// Create method + fn create( + &self, + properties: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + ) -> zbus::Result; + + /// Delete method + fn delete(&self, path: &zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// List method + fn list(&self) -> zbus::Result>; + + /// Added signal + #[dbus_proxy(signal)] + fn added(&self, path: zbus::zvariant::ObjectPath<'_>, received: bool) -> zbus::Result<()>; + + /// Deleted signal + #[dbus_proxy(signal)] + fn deleted(&self, path: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// DefaultStorage property + #[dbus_proxy(property)] + fn default_storage(&self) -> zbus::Result; + + /// Messages property + #[dbus_proxy(property)] + fn messages(&self) -> zbus::Result>; + + /// SupportedStorages property + #[dbus_proxy(property)] + fn supported_storages(&self) -> zbus::Result>; +} + +#[dbus_proxy( + interface = "org.freedesktop.ModemManager1.Modem", + assume_defaults = true +)] +trait Modem { + /// Command method + fn command(&self, cmd: &str, timeout: u32) -> zbus::Result; + + /// CreateBearer method + fn create_bearer( + &self, + properties: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + ) -> zbus::Result; + + /// DeleteBearer method + fn delete_bearer(&self, bearer: &zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// Enable method + fn enable(&self, enable: bool) -> zbus::Result<()>; + + /// FactoryReset method + fn factory_reset(&self, code: &str) -> zbus::Result<()>; + + /// GetCellInfo method + fn get_cell_info( + &self, + ) -> zbus::Result>>; + + /// ListBearers method + fn list_bearers(&self) -> zbus::Result>; + + /// Reset method + fn reset(&self) -> zbus::Result<()>; + + /// SetCurrentBands method + fn set_current_bands(&self, bands: &[u32]) -> zbus::Result<()>; + + /// SetCurrentCapabilities method + fn set_current_capabilities(&self, capabilities: u32) -> zbus::Result<()>; + + /// SetCurrentModes method + fn set_current_modes(&self, modes: &(u32, u32)) -> zbus::Result<()>; + + /// SetPowerState method + fn set_power_state(&self, state: u32) -> zbus::Result<()>; + + /// SetPrimarySimSlot method + fn set_primary_sim_slot(&self, sim_slot: u32) -> zbus::Result<()>; + + // TODO: This fails to compile: duplicate definitions for `receive_state_changed` + // /// StateChanged signal + // #[dbus_proxy(signal)] + // fn state_changed(&self, old: i32, new: i32, reason: u32) -> zbus::Result<()>; + + /// AccessTechnologies property + #[dbus_proxy(property)] + fn access_technologies(&self) -> zbus::Result; + + /// Bearers property + #[dbus_proxy(property)] + fn bearers(&self) -> zbus::Result>; + + /// CarrierConfiguration property + #[dbus_proxy(property)] + fn carrier_configuration(&self) -> zbus::Result; + + /// CarrierConfigurationRevision property + #[dbus_proxy(property)] + fn carrier_configuration_revision(&self) -> zbus::Result; + + /// CurrentBands property + #[dbus_proxy(property)] + fn current_bands(&self) -> zbus::Result>; + + /// CurrentCapabilities property + #[dbus_proxy(property)] + fn current_capabilities(&self) -> zbus::Result; + + /// CurrentModes property + #[dbus_proxy(property)] + fn current_modes(&self) -> zbus::Result<(u32, u32)>; + + /// Device property + #[dbus_proxy(property)] + fn device(&self) -> zbus::Result; + + /// DeviceIdentifier property + #[dbus_proxy(property)] + fn device_identifier(&self) -> zbus::Result; + + /// Drivers property + #[dbus_proxy(property)] + fn drivers(&self) -> zbus::Result>; + + /// EquipmentIdentifier property + #[dbus_proxy(property)] + fn equipment_identifier(&self) -> zbus::Result; + + /// HardwareRevision property + #[dbus_proxy(property)] + fn hardware_revision(&self) -> zbus::Result; + + /// Manufacturer property + #[dbus_proxy(property)] + fn manufacturer(&self) -> zbus::Result; + + /// MaxActiveBearers property + #[dbus_proxy(property)] + fn max_active_bearers(&self) -> zbus::Result; + + /// MaxActiveMultiplexedBearers property + #[dbus_proxy(property)] + fn max_active_multiplexed_bearers(&self) -> zbus::Result; + + /// MaxBearers property + #[dbus_proxy(property)] + fn max_bearers(&self) -> zbus::Result; + + /// Model property + #[dbus_proxy(property)] + fn model(&self) -> zbus::Result; + + /// OwnNumbers property + #[dbus_proxy(property)] + fn own_numbers(&self) -> zbus::Result>; + + /// Plugin property + #[dbus_proxy(property)] + fn plugin(&self) -> zbus::Result; + + /// Ports property + #[dbus_proxy(property)] + fn ports(&self) -> zbus::Result>; + + /// PowerState property + #[dbus_proxy(property)] + fn power_state(&self) -> zbus::Result; + + /// PrimaryPort property + #[dbus_proxy(property)] + fn primary_port(&self) -> zbus::Result; + + /// PrimarySimSlot property + #[dbus_proxy(property)] + fn primary_sim_slot(&self) -> zbus::Result; + + /// Revision property + #[dbus_proxy(property)] + fn revision(&self) -> zbus::Result; + + /// SignalQuality property + #[dbus_proxy(property)] + fn signal_quality(&self) -> zbus::Result<(u32, bool)>; + + /// Sim property + #[dbus_proxy(property)] + fn sim(&self) -> zbus::Result; + + /// SimSlots property + #[dbus_proxy(property)] + fn sim_slots(&self) -> zbus::Result>; + + /// State property + #[dbus_proxy(property)] + fn state(&self) -> zbus::Result; + + /// StateFailedReason property + #[dbus_proxy(property)] + fn state_failed_reason(&self) -> zbus::Result; + + /// SupportedBands property + #[dbus_proxy(property)] + fn supported_bands(&self) -> zbus::Result>; + + /// SupportedCapabilities property + #[dbus_proxy(property)] + fn supported_capabilities(&self) -> zbus::Result>; + + /// SupportedIpFamilies property + #[dbus_proxy(property)] + fn supported_ip_families(&self) -> zbus::Result; + + /// SupportedModes property + #[dbus_proxy(property)] + fn supported_modes(&self) -> zbus::Result>; + + /// UnlockRequired property + #[dbus_proxy(property)] + fn unlock_required(&self) -> zbus::Result; + + /// UnlockRetries property + #[dbus_proxy(property)] + fn unlock_retries(&self) -> zbus::Result>; +} + +#[dbus_proxy( + interface = "org.freedesktop.ModemManager1.Modem.Simple", + assume_defaults = true +)] +trait Simple { + /// Connect method + fn connect( + &self, + properties: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + ) -> zbus::Result; + + /// Disconnect method + fn disconnect(&self, bearer: &zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// GetStatus method + fn get_status( + &self, + ) -> zbus::Result>; +} + +#[dbus_proxy( + interface = "org.freedesktop.ModemManager1.Modem.Voice", + assume_defaults = true +)] +trait Voice { + /// CallWaitingQuery method + fn call_waiting_query(&self) -> zbus::Result; + + /// CallWaitingSetup method + fn call_waiting_setup(&self, enable: bool) -> zbus::Result<()>; + + /// CreateCall method + fn create_call( + &self, + properties: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + ) -> zbus::Result; + + /// DeleteCall method + fn delete_call(&self, path: &zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// HangupAll method + fn hangup_all(&self) -> zbus::Result<()>; + + /// HangupAndAccept method + fn hangup_and_accept(&self) -> zbus::Result<()>; + + /// HoldAndAccept method + fn hold_and_accept(&self) -> zbus::Result<()>; + + /// ListCalls method + fn list_calls(&self) -> zbus::Result>; + + /// Transfer method + fn transfer(&self) -> zbus::Result<()>; + + /// CallAdded signal + #[dbus_proxy(signal)] + fn call_added(&self, path: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// CallDeleted signal + #[dbus_proxy(signal)] + fn call_deleted(&self, path: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// Calls property + #[dbus_proxy(property)] + fn calls(&self) -> zbus::Result>; + + /// EmergencyOnly property + #[dbus_proxy(property)] + fn emergency_only(&self) -> zbus::Result; +} diff --git a/src/modem/mod.rs b/src/modem/mod.rs new file mode 100644 index 0000000..18fef60 --- /dev/null +++ b/src/modem/mod.rs @@ -0,0 +1,116 @@ +// Copyright (C) 2022 Maxime “pep” Buquet +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +// for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +mod introspect; + +use crate::error::Error; +use crate::modem::introspect::ModemProxy; + +use std::fmt; +use std::ops::{Deref, DerefMut}; + +use futures::future::join_all; +use zbus::{fdo::ObjectManagerProxy, zvariant::ObjectPath, Connection}; + +const MM_NAME: &str = "org.freedesktop.ModemManager1"; +const MM_PATH: &str = "/org/freedesktop/ModemManager1"; + +#[derive(Clone)] +pub struct ModemManager<'a> { + connection: Connection, + modems: Vec>, +} + +impl<'a> ModemManager<'a> { + pub async fn connect() -> Result, Error> { + let connection = Connection::system().await?; + Ok(ModemManager { + modems: ModemManager::modems(&connection).await?, + connection, + }) + } + + pub async fn modems(conn: &Connection) -> Result>, Error> { + let proxy = ObjectManagerProxy::builder(&conn) + .destination(MM_NAME)? + .path(MM_PATH)? + .build() + .await?; + let modems = proxy + .get_managed_objects() + .await? + .into_iter() + .map(|(path, _)| Modem::new(&conn, path.into())) + .collect::>(); + join_all(modems) + .await + .into_iter() + .try_fold(vec![], |mut acc, res| { + let modem = res?; + acc.push(modem); + Ok::>, Error>(acc) + }) + } +} + +impl fmt::Debug for ModemManager<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ModemManager") + .field("modems", &self.modems) + .finish_non_exhaustive() + } +} + +impl<'a> IntoIterator for ModemManager<'a> { + type Item = Modem<'a>; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.modems.into_iter() + } +} + +#[derive(Clone)] +pub struct Modem<'a>(ModemProxy<'a>); + +impl<'a> Modem<'a> { + pub async fn new(conn: &Connection, path: ObjectPath<'a>) -> Result, Error> { + Ok(Modem(ModemProxy::builder(&conn).path(path)?.build().await?)) + } +} + +impl<'a> Deref for Modem<'a> { + type Target = ModemProxy<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for Modem<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Debug for Modem<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Modem") + .field("destination", &self.inner().destination()) + .field("path", &self.inner().path()) + .field("interface", &self.inner().interface()) + .finish_non_exhaustive() + } +}