//! SIP URI and SDP body rewriting helpers. //! //! Ported from ts/sip/rewrite.ts. use crate::Endpoint; /// Replaces the host:port in every `sip:` / `sips:` URI found in `value`. pub fn rewrite_sip_uri(value: &str, host: &str, port: u16) -> String { let mut result = String::with_capacity(value.len()); let mut i = 0; let bytes = value.as_bytes(); while i < bytes.len() { // Look for "sip:" or "sips:" let scheme_len = if i + 4 <= bytes.len() && (bytes[i..].starts_with(b"sip:") || bytes[i..].starts_with(b"SIP:")) { 4 } else if i + 5 <= bytes.len() && (bytes[i..].starts_with(b"sips:") || bytes[i..].starts_with(b"SIPS:")) { 5 } else { result.push(value[i..].chars().next().unwrap()); i += value[i..].chars().next().unwrap().len_utf8(); continue; }; let scheme = &value[i..i + scheme_len]; let rest = &value[i + scheme_len..]; // Check for userpart (contains '@') let (userpart, host_start) = if let Some(at) = rest.find('@') { // Make sure @ comes before any delimiters let delim = rest.find(|c: char| c == '>' || c == ';' || c == ',' || c.is_whitespace()); if delim.is_none() || at < delim.unwrap() { (&rest[..=at], at + 1) } else { ("", 0) } } else { ("", 0) }; // Find the end of the host:port portion let host_rest = &rest[host_start..]; let end = host_rest .find(|c: char| c == '>' || c == ';' || c == ',' || c.is_whitespace()) .unwrap_or(host_rest.len()); result.push_str(scheme); result.push_str(userpart); result.push_str(&format!("{host}:{port}")); i += scheme_len + host_start + end; } result } /// Rewrites the connection address (`c=`) and audio media port (`m=audio`) /// in an SDP body. Returns the rewritten body together with the original /// endpoint that was replaced (if any). pub fn rewrite_sdp(body: &str, ip: &str, port: u16) -> (String, Option) { let mut orig_addr: Option = None; let mut orig_port: Option = None; let lines: Vec = body .replace("\r\n", "\n") .split('\n') .map(|line| { if let Some(rest) = line.strip_prefix("c=IN IP4 ") { orig_addr = Some(rest.trim().to_string()); format!("c=IN IP4 {ip}") } else if line.starts_with("m=audio ") { let parts: Vec<&str> = line.split(' ').collect(); if parts.len() >= 2 { orig_port = parts[1].parse().ok(); let mut rebuilt = parts[0].to_string(); rebuilt.push(' '); rebuilt.push_str(&port.to_string()); for part in &parts[2..] { rebuilt.push(' '); rebuilt.push_str(part); } return rebuilt; } line.to_string() } else { line.to_string() } }) .collect(); let original = match (orig_addr, orig_port) { (Some(a), Some(p)) => Some(Endpoint { address: a, port: p, codec_pt: None }), _ => None, }; (lines.join("\r\n"), original) } #[cfg(test)] mod tests { use super::*; #[test] fn test_rewrite_sip_uri() { let input = ""; let result = rewrite_sip_uri(input, "192.168.1.1", 5070); assert_eq!(result, ""); } #[test] fn test_rewrite_sip_uri_no_port() { let input = "sip:user@10.0.0.1"; let result = rewrite_sip_uri(input, "192.168.1.1", 5070); assert_eq!(result, "sip:user@192.168.1.1:5070"); } #[test] fn test_rewrite_sdp() { let sdp = "v=0\r\nc=IN IP4 10.0.0.1\r\nm=audio 5060 RTP/AVP 0 9\r\na=sendrecv\r\n"; let (rewritten, orig) = rewrite_sdp(sdp, "192.168.1.1", 20000); assert!(rewritten.contains("c=IN IP4 192.168.1.1")); assert!(rewritten.contains("m=audio 20000 RTP/AVP 0 9")); let ep = orig.unwrap(); assert_eq!(ep.address, "10.0.0.1"); assert_eq!(ep.port, 5060); } }