commit ff29c267a8d202e5eff2a0aa8bd246f8d5c5c922 Author: Maxime “pep” Buquet Date: Wed Jul 13 16:57:44 2022 +0200 Initial commit. Sync connection, send SMS; read existing SMSs Signed-off-by: Maxime “pep” Buquet diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3589c4c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "gateway_mm" +version = "0.1.0" +authors = ["Maxime “pep” Buquet "] +license = "AGPL-3.0-or-later" +edition = "2021" +categories = ["gateway", "bridge", "transport", "modemmanager"] + +[dependencies] +mmdbus = "1.18.6" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f6c07f0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,316 @@ +// 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::collections::HashMap; +use std::error::Error as StdError; +use std::fmt; +use std::sync::Arc; +use std::time::Duration; + +use mmdbus::{ + dbus::{ + arg::{PropMap, RefArg, Variant}, + blocking::{Connection, stdintf::org_freedesktop_dbus::ObjectManager, Proxy}, + // channel::MatchingReceiver, + // message::{MatchRule, Message}, + strings::{Path as DBusPath}, // Interface, Member, + // Error as DBusError, + }, + modem::Modem as ModemAccess, + modem_messaging::ModemMessaging, + sms::Sms as SmsAccess, +}; + +// 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); +const SMSC: &str = "+33609001390"; // SFR + +#[derive(Clone)] +pub struct DBus { + pub conn: Arc, +} + +impl DBus { + pub fn connect() -> Result> { + Ok(Connection::new_system() + .map(Arc::new) + .map(|conn| Self { conn })?) + } + + pub fn proxy<'a, 'b, P>(&'b self, path: P) -> Proxy<'a, &'b Connection> + where + P: Into>, + { + self.conn.with_proxy(MM_NAME, path, TIMEOUT) + } +} + +#[derive(Clone)] +pub struct ModemManager { + dbus: DBus, +} + +impl ModemManager { + pub fn connect() -> Result> { + Ok(DBus::connect().map(|dbus| Self { dbus })?) + } + + pub fn modems(&self) -> Result, Box> { + let objects = self.dbus.proxy(MM_PATH).get_managed_objects()?; + 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 { + let numbers = self.own_numbers().unwrap_or(vec![String::new()]); + write!(f, "Modem {{ {}, {:?}, .. }}", self.path, numbers) + } +} + +impl Modem { + pub fn enabled(&self) -> Result> { + let state: ModemState = ModemAccess::state(&self.dbus.proxy(&self.path)).map(Into::into)?; + Ok(match state { + ModemState::Enabling + | ModemState::Enabled + | ModemState::Searching + | ModemState::Registered + | ModemState::Connecting + | ModemState::Connected => true, + _ => false, + }) + } + + pub fn model(&self) -> Result> { + Ok(ModemAccess::model(&self.dbus.proxy(&self.path))?) + } + + pub fn manufacturer(&self) -> Result> { + Ok(ModemAccess::manufacturer(&self.dbus.proxy(&self.path))?) + } + + pub fn own_numbers(&self) -> Result, Box> { + Ok(ModemAccess::own_numbers(&self.dbus.proxy(&self.path))?) + } + + pub fn list_sms(&self) -> Result, Box> { + Ok(ModemMessaging::list(&self.dbus.proxy(&self.path))? + .into_iter() + .map(|p| Sms { + dbus: self.dbus.clone(), + modem: self.clone(), + path: p, + }) + .collect()) + } + + pub fn create_sms(&self, number: String, text: String) -> Result> { + Sms::new(self, number, text) + } + + pub fn delete_sms(&self, path: DBusPath) -> Result<(), Box> { + Ok(ModemMessaging::delete(&self.dbus.proxy(&self.path), path)?) + } +} + +#[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) } + } + } +} + +// include/ModemManager-enums.h MMSmsState +/// State of a given SMS. +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SmsState { + /// State unknown or not reportable. + Unknown = 0, + /// The message has been neither received not yet sent. + Stored = 1, + /// The message is being received but is not yet complete. + Receiving = 2, + /// The message has been completely received. + Received = 3, + /// The message is queued for delivery. + Sending = 4, + /// The message was successfully sent. + Sent = 5, +} + +impl fmt::Display for SmsState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", match self { + SmsState::Unknown => "unknown", + SmsState::Stored => "stored", + SmsState::Receiving => "receiving", + SmsState::Received => "received", + SmsState::Sending => "sending", + SmsState::Sent => "sent", + }) + } +} + +impl From for SmsState { + fn from(num: u32) -> Self { + if num > 5 { + Self::Unknown + } else { + unsafe { *(&num as *const u32 as *const Self) } + } + } +} + +pub struct Sms { + dbus: DBus, + modem: Modem, + path: DBusPath<'static>, +} + +impl Sms { + pub fn new(modem: &Modem, number: String, text: String) -> Result> { + let props: PropMap = { + let mut tmp = HashMap::new(); + tmp.insert( + String::from("number"), + Variant(Box::new(number) as Box), + ); + tmp.insert( + String::from("text"), + Variant(Box::new(text) as Box), + ); + tmp.insert( + String::from("smsc"), + Variant(Box::new(String::from(SMSC)) as Box), + ); + tmp + }; + let path = ModemMessaging::create(&modem.dbus.proxy(&modem.path), props)?; + Ok(Sms { + dbus: modem.dbus.clone(), + modem: modem.clone(), + path: path, + }) + } + + pub fn state(&self) -> Result> { + Ok(SmsAccess::state(&self.dbus.proxy(&self.path)).map(Into::into)?) + } + + pub fn send(&self) -> Result<(), Box> { + Ok(SmsAccess::send(&self.dbus.proxy(&self.path))?) + } + + pub fn number(&self) -> Result> { + Ok(SmsAccess::number(&self.dbus.proxy(&self.path))?) + } + + pub fn text(&self) -> Result> { + Ok(SmsAccess::text(&self.dbus.proxy(&self.path))?) + } +} + +impl fmt::Debug for Sms { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let state = self.state().unwrap_or(SmsState::Unknown); + write!(f, "Sms ({}) {{ {}, {:?}, .. }}", state, self.path, self.modem) + } +} + +impl fmt::Display for Sms { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let state = self.state().unwrap_or(SmsState::Unknown); + let number = self.number().unwrap_or(String::new()); + let text = self.text().unwrap_or(String::new()); + write!(f, "Sms ({}) {{ number: {}, text: {} }}", state, number, text) + } +} + +fn main() -> Result<(), Box> { + let modems: Vec = ModemManager::connect()?.modems()?; + for modem in modems { + println!("Enabled: {:?}", modem.enabled()); + println!("Model: {:?}", modem.model()); + // modem.create_sms(String::from("0123456789"), String::from("Test 1 2 3"))?.send()?; + let messages = modem.list_sms()?; + for sms in messages { + println!("{}", sms); + } + } + + Ok(()) +}