//! Provider registration state machine. //! //! Handles the REGISTER cycle with upstream SIP providers: //! - Sends periodic REGISTER messages //! - Handles 401/407 Digest authentication challenges //! - Detects public IP from Via received= parameter //! - Emits registration state events to TypeScript //! //! Ported from ts/providerstate.ts. use crate::config::ProviderConfig; use crate::ipc::{emit_event, OutTx}; use sip_proto::helpers::{ compute_digest_auth, generate_branch, generate_call_id, generate_tag, parse_digest_challenge, }; use sip_proto::message::{RequestOptions, SipMessage}; use std::net::SocketAddr; use std::sync::Arc; use tokio::net::UdpSocket; use tokio::sync::Mutex; use tokio::time::{self, Duration}; /// Runtime state for a single SIP provider. pub struct ProviderState { pub config: ProviderConfig, pub public_ip: Option, pub is_registered: bool, pub registered_aor: String, // Registration transaction state. reg_call_id: String, reg_cseq: u32, reg_from_tag: String, // Network. lan_ip: String, lan_port: u16, } impl ProviderState { pub fn new(config: ProviderConfig, public_ip_seed: Option<&str>) -> Self { let aor = format!("sip:{}@{}", config.username, config.domain); Self { public_ip: public_ip_seed.map(|s| s.to_string()), is_registered: false, registered_aor: aor, reg_call_id: generate_call_id(None), reg_cseq: 0, reg_from_tag: generate_tag(), lan_ip: String::new(), lan_port: 0, config, } } /// Build and send a REGISTER request. pub fn build_register(&mut self) -> Vec { self.reg_cseq += 1; let pub_ip = self.public_ip.as_deref().unwrap_or(&self.lan_ip); let register = SipMessage::create_request( "REGISTER", &format!("sip:{}", self.config.domain), RequestOptions { via_host: pub_ip.to_string(), via_port: self.lan_port, via_transport: None, via_branch: Some(generate_branch()), from_uri: self.registered_aor.clone(), from_display_name: None, from_tag: Some(self.reg_from_tag.clone()), to_uri: self.registered_aor.clone(), to_display_name: None, to_tag: None, call_id: Some(self.reg_call_id.clone()), cseq: Some(self.reg_cseq), contact: Some(format!( "", self.config.username, pub_ip, self.lan_port )), max_forwards: Some(70), body: None, content_type: None, extra_headers: Some(vec![ ( "Expires".to_string(), self.config.register_interval_sec.to_string(), ), ("User-Agent".to_string(), "SipRouter/1.0".to_string()), ( "Allow".to_string(), "INVITE, ACK, OPTIONS, CANCEL, BYE, SUBSCRIBE, NOTIFY, INFO, REFER, UPDATE" .to_string(), ), ]), }, ); register.serialize() } /// Handle a SIP response that might be for this provider's REGISTER. /// Returns true if the message was consumed. pub fn handle_registration_response(&mut self, msg: &SipMessage) -> Option> { if !msg.is_response() { return None; } if msg.call_id() != self.reg_call_id { return None; } let cseq_method = msg.cseq_method().unwrap_or(""); if !cseq_method.eq_ignore_ascii_case("REGISTER") { return None; } let code = msg.status_code().unwrap_or(0); if code == 200 { self.is_registered = true; return Some(Vec::new()); // consumed, no reply needed } if code == 401 || code == 407 { let challenge_header = if code == 401 { msg.get_header("WWW-Authenticate") } else { msg.get_header("Proxy-Authenticate") }; let challenge_header = match challenge_header { Some(h) => h, None => return Some(Vec::new()), // consumed but no challenge }; let challenge = match parse_digest_challenge(challenge_header) { Some(c) => c, None => return Some(Vec::new()), }; let auth_value = compute_digest_auth( &self.config.username, &self.config.password, &challenge.realm, &challenge.nonce, "REGISTER", &format!("sip:{}", self.config.domain), challenge.algorithm.as_deref(), challenge.opaque.as_deref(), ); // Resend REGISTER with auth credentials. self.reg_cseq += 1; let pub_ip = self.public_ip.as_deref().unwrap_or(&self.lan_ip); let auth_header_name = if code == 401 { "Authorization" } else { "Proxy-Authorization" }; let register = SipMessage::create_request( "REGISTER", &format!("sip:{}", self.config.domain), RequestOptions { via_host: pub_ip.to_string(), via_port: self.lan_port, via_transport: None, via_branch: Some(generate_branch()), from_uri: self.registered_aor.clone(), from_display_name: None, from_tag: Some(self.reg_from_tag.clone()), to_uri: self.registered_aor.clone(), to_display_name: None, to_tag: None, call_id: Some(self.reg_call_id.clone()), cseq: Some(self.reg_cseq), contact: Some(format!( "", self.config.username, pub_ip, self.lan_port )), max_forwards: Some(70), body: None, content_type: None, extra_headers: Some(vec![ (auth_header_name.to_string(), auth_value), ( "Expires".to_string(), self.config.register_interval_sec.to_string(), ), ("User-Agent".to_string(), "SipRouter/1.0".to_string()), ( "Allow".to_string(), "INVITE, ACK, OPTIONS, CANCEL, BYE, SUBSCRIBE, NOTIFY, INFO, REFER, UPDATE" .to_string(), ), ]), }, ); return Some(register.serialize()); } if code >= 400 { self.is_registered = false; } Some(Vec::new()) // consumed } /// Detect public IP from Via received= parameter. pub fn detect_public_ip(&mut self, via: &str) { if let Some(m) = via.find("received=") { let rest = &via[m + 9..]; let end = rest .find(|c: char| !c.is_ascii_digit() && c != '.') .unwrap_or(rest.len()); let ip = &rest[..end]; if !ip.is_empty() && self.public_ip.as_deref() != Some(ip) { self.public_ip = Some(ip.to_string()); } } } pub fn set_network(&mut self, lan_ip: &str, lan_port: u16) { self.lan_ip = lan_ip.to_string(); self.lan_port = lan_port; } } /// Manages all provider states and their registration cycles. pub struct ProviderManager { providers: Vec>>, out_tx: OutTx, } impl ProviderManager { pub fn new(out_tx: OutTx) -> Self { Self { providers: Vec::new(), out_tx, } } /// Initialize providers from config and start registration cycles. pub async fn configure( &mut self, configs: &[ProviderConfig], public_ip_seed: Option<&str>, lan_ip: &str, lan_port: u16, socket: Arc, ) { self.providers.clear(); for cfg in configs { let mut ps = ProviderState::new(cfg.clone(), public_ip_seed); ps.set_network(lan_ip, lan_port); let ps = Arc::new(Mutex::new(ps)); self.providers.push(ps.clone()); // Start the registration cycle. let socket = socket.clone(); let out_tx = self.out_tx.clone(); tokio::spawn(async move { provider_register_loop(ps, socket, out_tx).await; }); } } /// Try to handle a SIP response as a provider registration response. /// Returns true if consumed. pub async fn handle_response( &self, msg: &SipMessage, socket: &UdpSocket, ) -> bool { for ps_arc in &self.providers { let mut ps = ps_arc.lock().await; let was_registered = ps.is_registered; if let Some(reply) = ps.handle_registration_response(msg) { // If there's a reply to send (e.g. auth retry). if !reply.is_empty() { if let Some(dest) = ps.config.outbound_proxy.to_socket_addr() { let _ = socket.send_to(&reply, dest).await; } } // Emit registration state change. if ps.is_registered != was_registered { emit_event( &self.out_tx, "provider_registered", serde_json::json!({ "provider_id": ps.config.id, "registered": ps.is_registered, "public_ip": ps.public_ip, }), ); } return true; } } false } /// Find which provider sent a packet by matching source address. pub async fn find_by_address(&self, addr: &SocketAddr) -> Option>> { for ps_arc in &self.providers { let ps = ps_arc.lock().await; let proxy_addr = format!( "{}:{}", ps.config.outbound_proxy.address, ps.config.outbound_proxy.port ); if let Ok(expected) = proxy_addr.parse::() { if expected == *addr { return Some(ps_arc.clone()); } } // Also match by IP only (port may differ). if ps.config.outbound_proxy.address == addr.ip().to_string() { return Some(ps_arc.clone()); } } None } /// Check if a provider is currently registered. pub async fn is_registered(&self, provider_id: &str) -> bool { for ps_arc in &self.providers { let ps = ps_arc.lock().await; if ps.config.id == provider_id { return ps.is_registered; } } false } } /// Registration loop for a single provider. async fn provider_register_loop( ps: Arc>, socket: Arc, _out_tx: OutTx, ) { // Initial registration. { let mut state = ps.lock().await; let register_buf = state.build_register(); if let Some(dest) = state.config.outbound_proxy.to_socket_addr() { let _ = socket.send_to(®ister_buf, dest).await; } } // Re-register periodically (85% of the interval). let interval_sec = { let state = ps.lock().await; (state.config.register_interval_sec as f64 * 0.85) as u64 }; let mut interval = time::interval(Duration::from_secs(interval_sec.max(30))); interval.tick().await; // skip first immediate tick loop { interval.tick().await; let mut state = ps.lock().await; let register_buf = state.build_register(); if let Some(dest) = state.config.outbound_proxy.to_socket_addr() { let _ = socket.send_to(®ister_buf, dest).await; } } }