Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/api/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,11 +487,11 @@ mod tests {
}

#[tokio::test]
async fn send_message_uses_the_state_sms_sender() {
async fn send_message_uses_the_runtime_modem_path() {
let sender = RecordingSmsSender::default();
let store = crate::storage::MessageStore::open_in_memory().unwrap();
let modem = crate::modem::ModemService::new();
modem.set_verified_path(Some("/org/freedesktop/ModemManager1/Modem/0".to_string()));
modem.set_runtime_path(Some("/org/freedesktop/ModemManager1/Modem/1".to_string()));
let state = super::super::ApiState {
config: std::sync::Arc::new(crate::config::AppConfig::default()),
config_path: std::path::PathBuf::from("/tmp/not-used.toml"),
Expand Down Expand Up @@ -559,7 +559,7 @@ mod tests {
assert_eq!(
sender.calls.lock().unwrap().as_slice(),
[(
"/org/freedesktop/ModemManager1/Modem/0".to_string(),
"/org/freedesktop/ModemManager1/Modem/1".to_string(),
"+15551234567".to_string(),
"test body".to_string(),
)]
Expand Down
2 changes: 1 addition & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl ApiState {
self.delivery_wakeup.clone(),
self.sms_sender.clone(),
)
.with_verified_modem(self.modem.clone())
.with_modem_service(self.modem.clone())
}
}

Expand Down
1,030 changes: 997 additions & 33 deletions src/inbound.rs

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions src/messaging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub struct Messaging {
events: EventBus,
delivery_wakeup: DeliveryWakeup,
sms_sender: Arc<dyn SmsSender>,
verified_modem: Option<crate::modem::ModemService>,
modem_service: Option<crate::modem::ModemService>,
}

impl Messaging {
Expand All @@ -82,12 +82,12 @@ impl Messaging {
events,
delivery_wakeup,
sms_sender,
verified_modem: None,
modem_service: None,
}
}

pub fn with_verified_modem(mut self, modem: crate::modem::ModemService) -> Self {
self.verified_modem = Some(modem);
pub fn with_modem_service(mut self, modem: crate::modem::ModemService) -> Self {
self.modem_service = Some(modem);
self
}

Expand Down Expand Up @@ -115,10 +115,10 @@ impl Messaging {
mut request: SendMessage,
owner: String,
) -> anyhow::Result<SendOutcome> {
if let Some(modem) = self.verified_modem.as_ref() {
if let Some(modem) = self.modem_service.as_ref() {
request.modem_path = modem
.verified_path()
.ok_or_else(|| anyhow::anyhow!("verified modem identity is not ready"))?;
.runtime_path()
.ok_or_else(|| anyhow::anyhow!("runtime modem path is not ready"))?;
}
let SendMessage {
ref phone_number,
Expand Down Expand Up @@ -595,19 +595,19 @@ impl Messaging {
owner: &str,
modem_sms_path: &str,
) -> anyhow::Result<SmsSnapshot> {
let verified_path = match self.verified_modem.as_ref() {
let runtime_path = match self.modem_service.as_ref() {
Some(modem) => Some(
modem
.verified_path()
.ok_or_else(|| anyhow::anyhow!("verified modem identity is not ready"))?,
.runtime_path()
.ok_or_else(|| anyhow::anyhow!("runtime modem path is not ready"))?,
),
None => None,
};
self.with_outbound_lease(
message_id,
owner,
self.sms_sender
.sms_snapshot(verified_path.as_deref(), modem_sms_path),
.sms_snapshot(runtime_path.as_deref(), modem_sms_path),
)
.await
}
Expand Down Expand Up @@ -1863,7 +1863,7 @@ mod tests {
}

#[tokio::test]
async fn inbound_without_an_enrolled_fingerprint_is_not_persisted() {
async fn inbound_without_a_dedupe_namespace_is_not_persisted() {
let store = Store::open_in_memory().unwrap();
let messaging = Messaging::new(
store.clone(),
Expand Down
146 changes: 124 additions & 22 deletions src/modem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,56 @@ pub enum ModemAction {
Reset,
}

/// The independently resolved paths for SMS traffic and modem control actions.
/// Fields remain private so callers can only publish one complete snapshot.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct ModemTargets {
runtime: Option<String>,
action: Option<String>,
}

impl ModemTargets {
pub(crate) fn runtime_only(path: impl Into<String>) -> Self {
Self {
runtime: Some(path.into()),
action: None,
}
}

pub(crate) fn verified(path: impl Into<String>) -> Self {
let path = path.into();
Self {
runtime: Some(path.clone()),
action: Some(path),
}
}

pub(crate) fn separate(
runtime_path: impl Into<String>,
action_path: impl Into<String>,
) -> Self {
Self {
runtime: Some(runtime_path.into()),
action: Some(action_path.into()),
}
}

pub(crate) fn action_only(path: impl Into<String>) -> Self {
Self {
runtime: None,
action: Some(path.into()),
}
}

pub(crate) fn runtime_path(&self) -> Option<&str> {
self.runtime.as_deref()
}

pub(crate) fn action_path(&self) -> Option<&str> {
self.action.as_deref()
}
}

#[derive(Debug, Clone, Serialize)]
pub struct ActionResponse {
pub accepted: bool,
Expand Down Expand Up @@ -647,7 +697,8 @@ pub struct ModemService {
pub(crate) action_lock: Arc<tokio::sync::Mutex<()>>,
reset_limits: Arc<Mutex<HashMap<String, Instant>>>,
health_refresh_lock: Arc<tokio::sync::Mutex<()>>,
verified_path: Arc<Mutex<Option<String>>>,
modem_targets: Arc<Mutex<ModemTargets>>,
pending_identity_mismatches: Arc<Mutex<HashMap<String, String>>>,
}

impl ModemService {
Expand Down Expand Up @@ -676,16 +727,85 @@ impl ModemService {
action_lock: Arc::new(tokio::sync::Mutex::new(())),
reset_limits: Arc::new(Mutex::new(HashMap::new())),
health_refresh_lock: Arc::new(tokio::sync::Mutex::new(())),
verified_path: Arc::new(Mutex::new(None)),
modem_targets: Arc::new(Mutex::new(ModemTargets::default())),
pending_identity_mismatches: Arc::new(Mutex::new(HashMap::new())),
}
}

pub(crate) fn set_modem_targets(&self, targets: ModemTargets) {
*self.modem_targets.lock().unwrap() = targets;
}

pub(crate) fn remember_pending_identity_mismatch(
&self,
modem_path: impl Into<String>,
enrolled_fingerprint: impl Into<String>,
) {
self.pending_identity_mismatches
.lock()
.unwrap()
.insert(modem_path.into(), enrolled_fingerprint.into());
}

pub(crate) fn pending_identity_mismatch(&self, modem_path: &str) -> Option<String> {
self.pending_identity_mismatches
.lock()
.unwrap()
.get(modem_path)
.cloned()
}

pub(crate) fn pending_identity_mismatches(&self) -> Vec<(String, String)> {
self.pending_identity_mismatches
.lock()
.unwrap()
.iter()
.map(|(path, fingerprint)| (path.clone(), fingerprint.clone()))
.collect()
}

pub(crate) fn finish_pending_identity_mismatch(
&self,
modem_path: &str,
enrolled_fingerprint: &str,
) {
let mut pending = self.pending_identity_mismatches.lock().unwrap();
if pending.get(modem_path).map(String::as_str) == Some(enrolled_fingerprint) {
pending.remove(modem_path);
}
}

pub(crate) fn clear_pending_identity_mismatch(&self, modem_path: &str) {
self.pending_identity_mismatches
.lock()
.unwrap()
.remove(modem_path);
}

#[cfg(test)]
pub(crate) fn set_runtime_path(&self, path: Option<String>) {
self.set_modem_targets(path.map(ModemTargets::runtime_only).unwrap_or_default());
}

#[cfg(test)]
pub(crate) fn set_verified_path(&self, path: Option<String>) {
*self.verified_path.lock().unwrap() = path;
self.set_modem_targets(path.map(ModemTargets::verified).unwrap_or_default());
}

pub fn runtime_path(&self) -> Option<String> {
self.modem_targets
.lock()
.unwrap()
.runtime_path()
.map(ToString::to_string)
}

pub fn verified_path(&self) -> Option<String> {
self.verified_path.lock().unwrap().clone()
self.modem_targets
.lock()
.unwrap()
.action_path()
.map(ToString::to_string)
}

pub async fn status(&self, configured_path: &str) -> ModemStatus {
Expand Down Expand Up @@ -1129,24 +1249,6 @@ impl ModemService {
}
}

pub async fn scan_and_match_fingerprint(&self, target_fingerprint: &str) -> Option<String> {
let paths = self.list_all_modem_paths().await;
let mut matched: Vec<String> = Vec::new();
for path in &paths {
if let Some(identity) = self.extract_identity(path).await {
let fp = Self::compute_fingerprint(&identity);
if fp == target_fingerprint {
matched.push(path.clone());
}
}
}
if matched.len() == 1 {
Some(matched.remove(0))
} else {
None
}
}

#[allow(dead_code)]
pub fn runner_ref(&self) -> &Arc<dyn MmcliRunner> {
&self.runner
Expand Down
Loading
Loading