use crate::core::SipError;
use std::fmt;
const MAX_URI_HEADERS: usize = 32;
const MAX_URI_HEADER_VALUE_LEN: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Scheme {
Sip,
Sips,
Tel,
}
impl Scheme {
pub fn as_str(&self) -> &'static str {
match self {
Scheme::Sip => "sip",
Scheme::Sips => "sips",
Scheme::Tel => "tel",
}
}
}
impl fmt::Display for Scheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SipUri {
scheme: Scheme,
user: Option<String>,
host: String,
port: Option<u16>,
params: Vec<(String, Option<String>)>,
headers: Vec<(String, String)>,
}
impl SipUri {
pub fn parse(s: &str) -> Result<Self, SipError> {
let s = s.trim();
let (scheme, rest) = if let Some(idx) = s.find(':') {
let scheme_str = s[..idx].to_lowercase();
let scheme = match scheme_str.as_str() {
"sip" => Scheme::Sip,
"sips" => Scheme::Sips,
"tel" => Scheme::Tel,
_ => {
return Err(SipError::Parse(format!(
"Invalid SIP URI scheme: {}",
scheme_str
)))
}
};
(scheme, &s[idx + 1..])
} else {
return Err(SipError::Parse("Missing scheme in SIP URI".to_string()));
};
let (rest, headers) = if let Some(idx) = rest.find('?') {
let (uri_part, header_part) = rest.split_at(idx);
let header_part = &header_part[1..]; let mut hs: Vec<(String, String)> = Vec::new();
for h in header_part.split('&') {
if h.is_empty() {
continue;
}
if hs.len() >= MAX_URI_HEADERS {
return Err(SipError::Parse("too many URI headers".to_string()));
}
let (k, v) = if let Some(eq) = h.find('=') {
(h[..eq].to_string(), h[eq + 1..].to_string())
} else {
(h.to_string(), String::new())
};
if v.len() > MAX_URI_HEADER_VALUE_LEN {
return Err(SipError::Parse("URI header value too long".to_string()));
}
hs.push((k, v));
}
(uri_part, hs)
} else {
(rest, Vec::new())
};
let userinfo_end = rest.find('@').map(|i| i + 1).unwrap_or(0);
let (user_host_port, params_str) = match rest[userinfo_end..].find(';') {
Some(rel_idx) => {
let abs_idx = userinfo_end + rel_idx;
(&rest[..abs_idx], Some(&rest[abs_idx + 1..]))
}
None => (rest, None),
};
let (user, host, port) = if scheme == Scheme::Tel {
(None, user_host_port.to_string(), None)
} else {
let (user, host_port) = if let Some(idx) = user_host_port.find('@') {
(
Some(user_host_port[..idx].to_string()),
&user_host_port[idx + 1..],
)
} else {
(None, user_host_port)
};
let (host, port) = if host_port.starts_with('[') {
if let Some(bracket_end) = host_port.find(']') {
let ipv6_host = host_port[..bracket_end + 1].to_string();
let after_bracket = &host_port[bracket_end + 1..];
let port = if let Some(port_str) = after_bracket.strip_prefix(':') {
port_str.parse().ok()
} else {
None
};
(ipv6_host, port)
} else {
return Err(SipError::Parse(
"Invalid IPv6 address in SIP URI".to_string(),
));
}
} else if let Some(idx) = host_port.rfind(':') {
let potential_port = &host_port[idx + 1..];
if potential_port.chars().all(|c| c.is_ascii_digit()) {
(host_port[..idx].to_string(), potential_port.parse().ok())
} else {
(host_port.to_string(), None)
}
} else {
(host_port.to_string(), None)
};
(user, host, port)
};
let mut params = Vec::new();
if let Some(params_str) = params_str {
for param in params_str.split(';') {
let param = param.trim();
if param.is_empty() {
continue;
}
if let Some(idx) = param.find('=') {
params.push((param[..idx].to_string(), Some(param[idx + 1..].to_string())));
} else {
params.push((param.to_string(), None));
}
}
}
Ok(SipUri {
scheme,
user,
host,
port,
params,
headers,
})
}
pub fn scheme(&self) -> &str {
self.scheme.as_str()
}
pub fn scheme_enum(&self) -> Scheme {
self.scheme
}
pub fn user(&self) -> Option<&str> {
self.user.as_deref()
}
pub fn host(&self) -> &str {
&self.host
}
pub fn port(&self) -> Option<u16> {
self.port
}
pub fn effective_port(&self) -> u16 {
self.port
.unwrap_or(if self.is_secure() { 5061 } else { 5060 })
}
pub fn transport(&self) -> Option<&str> {
self.get_param("transport")
}
pub fn is_secure(&self) -> bool {
self.scheme == Scheme::Sips
}
pub fn is_tel(&self) -> bool {
self.scheme == Scheme::Tel
}
pub fn user_part(&self) -> Option<&str> {
self.user.as_deref()
}
pub fn to_request_uri(&self) -> String {
self.to_string()
}
pub fn get_param(&self, name: &str) -> Option<&str> {
let name_lower = name.to_lowercase();
self.params
.iter()
.find(|(k, _)| k.to_lowercase() == name_lower)
.and_then(|(_, v)| v.as_deref())
}
pub fn has_param(&self, name: &str) -> bool {
let name_lower = name.to_lowercase();
self.params
.iter()
.any(|(k, _)| k.to_lowercase() == name_lower)
}
pub fn params(&self) -> impl Iterator<Item = (&str, Option<&str>)> {
self.params.iter().map(|(k, v)| (k.as_str(), v.as_deref()))
}
pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
self.headers.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
pub fn get_header(&self, name: &str) -> Option<&str> {
let name_lower = name.to_lowercase();
self.headers
.iter()
.find(|(k, _)| k.to_lowercase() == name_lower)
.map(|(_, v)| v.as_str())
}
pub fn has_headers(&self) -> bool {
!self.headers.is_empty()
}
pub fn is_loose_route(&self) -> bool {
self.has_param("lr")
}
pub fn builder() -> SipUriBuilder {
SipUriBuilder::new()
}
}
impl fmt::Display for SipUri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:", self.scheme.as_str())?;
if let Some(ref user) = self.user {
write!(f, "{}@", user)?;
}
write!(f, "{}", self.host)?;
if let Some(port) = self.port {
write!(f, ":{}", port)?;
}
for (key, value) in &self.params {
if let Some(v) = value {
write!(f, ";{}={}", key, v)?;
} else {
write!(f, ";{}", key)?;
}
}
if !self.headers.is_empty() {
f.write_str("?")?;
for (i, (k, v)) in self.headers.iter().enumerate() {
if i > 0 {
f.write_str("&")?;
}
write!(f, "{}={}", k, v)?;
}
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct SipUriBuilder {
scheme: Option<Scheme>,
user: Option<String>,
host: Option<String>,
port: Option<u16>,
params: Vec<(String, Option<String>)>,
headers: Vec<(String, String)>,
}
impl SipUriBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn scheme(mut self, scheme: &str) -> Self {
let s = scheme.to_lowercase();
let scheme_enum = match s.as_str() {
"sips" => Scheme::Sips,
"tel" => Scheme::Tel,
_ => Scheme::Sip,
};
self.scheme = Some(scheme_enum);
self
}
pub fn scheme_enum(mut self, scheme: Scheme) -> Self {
self.scheme = Some(scheme);
self
}
pub fn user(mut self, user: &str) -> Self {
self.user = Some(user.to_string());
self
}
pub fn host(mut self, host: &str) -> Self {
self.host = Some(host.to_string());
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn param(mut self, key: &str, value: &str) -> Self {
self.params.push((key.to_string(), Some(value.to_string())));
self
}
pub fn flag(mut self, key: &str) -> Self {
self.params.push((key.to_string(), None));
self
}
pub fn transport(self, transport: &str) -> Self {
self.param("transport", transport)
}
pub fn loose_route(self) -> Self {
self.flag("lr")
}
pub fn header(mut self, key: &str, value: &str) -> Self {
self.headers.push((key.to_string(), value.to_string()));
self
}
pub fn build(self) -> Result<SipUri, SipError> {
let host = self
.host
.ok_or_else(|| SipError::InvalidHeader("Missing host in SIP URI".to_string()))?;
Ok(SipUri {
scheme: self.scheme.unwrap_or(Scheme::Sip),
user: self.user,
host,
port: self.port,
params: self.params,
headers: self.headers,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::Write;
#[test]
fn test_parse_simple_uri() {
let uri = SipUri::parse("sip:alice@example.com").unwrap();
assert_eq!(uri.scheme(), "sip");
assert_eq!(uri.user(), Some("alice"));
assert_eq!(uri.host(), "example.com");
assert_eq!(uri.port(), None);
assert!(!uri.is_secure());
}
#[test]
fn test_parse_uri_with_port() {
let uri = SipUri::parse("sip:bob@192.168.1.1:5080").unwrap();
assert_eq!(uri.user(), Some("bob"));
assert_eq!(uri.host(), "192.168.1.1");
assert_eq!(uri.port(), Some(5080));
assert_eq!(uri.effective_port(), 5080);
}
#[test]
fn test_parse_sips_uri() {
let uri = SipUri::parse("sips:secure@example.com").unwrap();
assert_eq!(uri.scheme(), "sips");
assert!(uri.is_secure());
assert_eq!(uri.effective_port(), 5061);
}
#[test]
fn test_parse_uri_with_params() {
let uri = SipUri::parse("sip:proxy.example.com;transport=tcp;lr").unwrap();
assert_eq!(uri.user(), None);
assert_eq!(uri.host(), "proxy.example.com");
assert_eq!(uri.transport(), Some("tcp"));
assert!(uri.is_loose_route());
}
#[test]
fn test_parse_uri_host_only() {
let uri = SipUri::parse("sip:example.com").unwrap();
assert_eq!(uri.user(), None);
assert_eq!(uri.host(), "example.com");
}
#[test]
fn test_uri_to_string() {
let uri = SipUri::parse("sip:alice@example.com:5060;transport=udp").unwrap();
let s = uri.to_string();
assert!(s.starts_with("sip:"));
assert!(s.contains("alice@"));
assert!(s.contains("example.com"));
assert!(s.contains(":5060"));
assert!(s.contains("transport=udp"));
}
#[test]
fn test_uri_display_error_paths() {
struct CountingWriter {
writes: usize,
}
impl std::fmt::Write for CountingWriter {
fn write_str(&mut self, _s: &str) -> std::fmt::Result {
self.writes += 1;
Ok(())
}
}
struct FailingWriter {
fail_at: usize,
writes: usize,
}
impl FailingWriter {
fn new(fail_at: usize) -> Self {
Self { fail_at, writes: 0 }
}
}
impl std::fmt::Write for FailingWriter {
fn write_str(&mut self, _s: &str) -> std::fmt::Result {
self.writes += 1;
if self.writes == self.fail_at {
return Err(std::fmt::Error);
}
Ok(())
}
}
let uri = SipUri::builder()
.scheme("sip")
.user("alice")
.host("example.com")
.port(5060)
.transport("udp")
.loose_route()
.header("Subject", "hi")
.build()
.unwrap();
let mut counter = CountingWriter { writes: 0 };
let _ = write!(&mut counter, "{}", uri);
let total_writes = counter.writes;
for fail_at in 1..=total_writes {
let mut writer = FailingWriter::new(fail_at);
let _ = write!(&mut writer, "{}", uri);
}
}
#[test]
fn test_uri_builder() {
let uri = SipUri::builder()
.host("proxy.example.com")
.port(5060)
.transport("tcp")
.loose_route()
.build()
.unwrap();
assert_eq!(uri.host(), "proxy.example.com");
assert_eq!(uri.port(), Some(5060));
assert_eq!(uri.transport(), Some("tcp"));
assert!(uri.is_loose_route());
}
#[test]
fn test_uri_builder_with_user() {
let uri = SipUri::builder()
.scheme("sips")
.user("alice")
.host("example.com")
.build()
.unwrap();
assert_eq!(uri.scheme(), "sips");
assert_eq!(uri.user(), Some("alice"));
assert!(uri.is_secure());
}
#[test]
fn test_uri_builder_missing_host() {
let result = SipUri::builder().user("alice").build();
assert!(result.is_err());
}
#[test]
fn test_parse_invalid_scheme() {
let result = SipUri::parse("http://example.com");
assert!(result.is_err());
}
#[test]
fn test_uri_has_param() {
let uri = SipUri::parse("sip:proxy.example.com;lr;transport=tcp").unwrap();
assert!(uri.has_param("lr"));
assert!(uri.has_param("transport"));
assert!(!uri.has_param("maddr"));
}
#[test]
fn test_uri_roundtrip() {
let original = "sip:alice@example.com:5060;transport=tcp;lr";
let uri = SipUri::parse(original).unwrap();
let serialized = uri.to_string();
let reparsed = SipUri::parse(&serialized).unwrap();
assert_eq!(uri.scheme(), reparsed.scheme());
assert_eq!(uri.user(), reparsed.user());
assert_eq!(uri.host(), reparsed.host());
assert_eq!(uri.port(), reparsed.port());
assert_eq!(uri.transport(), reparsed.transport());
assert_eq!(uri.is_loose_route(), reparsed.is_loose_route());
}
#[test]
fn test_parse_ipv6_address() {
let uri = SipUri::parse("sip:alice@[::1]").unwrap();
assert_eq!(uri.host(), "[::1]");
assert_eq!(uri.port(), None);
}
#[test]
fn test_parse_ipv6_address_with_port() {
let uri = SipUri::parse("sip:alice@[2001:db8::1]:5060").unwrap();
assert_eq!(uri.host(), "[2001:db8::1]");
assert_eq!(uri.port(), Some(5060));
}
#[test]
fn test_parse_ipv6_invalid_bracket() {
let result = SipUri::parse("sip:alice@[::1");
assert!(result.is_err());
}
#[test]
fn test_parse_missing_scheme() {
let result = SipUri::parse("alice@example.com");
assert!(result.is_err());
}
#[test]
fn test_user_part() {
let uri = SipUri::parse("sip:bob@example.com").unwrap();
assert_eq!(uri.user_part(), Some("bob"));
let uri2 = SipUri::parse("sip:example.com").unwrap();
assert_eq!(uri2.user_part(), None);
}
#[test]
fn test_semicolon_in_user_part_rfc4475_semiuri() {
let uri = SipUri::parse("sip:user;par=u%40example.net@example.com").unwrap();
assert_eq!(uri.user(), Some("user;par=u%40example.net"));
assert_eq!(uri.host(), "example.com");
assert_eq!(uri.params().count(), 0, "no URI parameters");
}
#[test]
fn test_semicolon_with_no_user_is_param_boundary() {
let uri = SipUri::parse("sip:host.example.com;lr").unwrap();
assert_eq!(uri.user(), None);
assert_eq!(uri.host(), "host.example.com");
let params: Vec<_> = uri.params().collect();
assert_eq!(params.len(), 1);
assert_eq!(params[0].0, "lr");
}
#[test]
fn test_empty_host_after_at_is_lenient() {
let uri = SipUri::parse("sip:bob@;lr").unwrap();
assert_eq!(uri.user(), Some("bob"));
assert_eq!(uri.host(), "");
}
#[test]
fn test_semicolon_in_user_and_params() {
let uri = SipUri::parse("sip:user;a=1@host.example.com;lr;b=2").unwrap();
assert_eq!(uri.user(), Some("user;a=1"));
assert_eq!(uri.host(), "host.example.com");
let params: Vec<_> = uri.params().collect();
assert_eq!(params.len(), 2);
assert_eq!(params[0].0, "lr");
assert_eq!(params[1].0, "b");
assert_eq!(params[1].1, Some("2"));
}
#[test]
fn test_to_request_uri() {
let uri = SipUri::parse("sip:alice@example.com").unwrap();
let req_uri = uri.to_request_uri();
assert_eq!(req_uri, "sip:alice@example.com");
}
#[test]
fn test_effective_port_default_sip() {
let uri = SipUri::parse("sip:example.com").unwrap();
assert_eq!(uri.effective_port(), 5060);
}
#[test]
fn test_effective_port_default_sips() {
let uri = SipUri::parse("sips:example.com").unwrap();
assert_eq!(uri.effective_port(), 5061);
}
#[test]
fn test_display_host_only() {
let uri = SipUri::builder().host("example.com").build().unwrap();
let s = uri.to_string();
assert_eq!(s, "sip:example.com");
}
#[test]
fn test_display_with_flag_param() {
let uri = SipUri::builder()
.host("proxy.example.com")
.flag("lr")
.build()
.unwrap();
let s = uri.to_string();
assert!(s.contains(";lr"));
assert!(!s.contains("="));
}
#[test]
fn test_get_param_case_insensitive() {
let uri = SipUri::parse("sip:proxy.example.com;Transport=TCP").unwrap();
assert_eq!(uri.get_param("transport"), Some("TCP"));
assert_eq!(uri.get_param("TRANSPORT"), Some("TCP"));
}
#[test]
fn test_has_param_case_insensitive() {
let uri = SipUri::parse("sip:proxy.example.com;LR").unwrap();
assert!(uri.has_param("lr"));
assert!(uri.has_param("LR"));
assert!(uri.has_param("Lr"));
}
#[test]
fn test_transport_none() {
let uri = SipUri::parse("sip:example.com").unwrap();
assert_eq!(uri.transport(), None);
}
#[test]
fn test_is_loose_route_false() {
let uri = SipUri::parse("sip:example.com").unwrap();
assert!(!uri.is_loose_route());
}
#[test]
fn test_parse_empty_params() {
let uri = SipUri::parse("sip:example.com;").unwrap();
assert_eq!(uri.host(), "example.com");
}
#[test]
fn test_parse_multiple_semicolons() {
let uri = SipUri::parse("sip:example.com;;lr;;transport=tcp").unwrap();
assert!(uri.is_loose_route());
assert_eq!(uri.transport(), Some("tcp"));
}
#[test]
fn test_uri_debug() {
let uri = SipUri::parse("sip:alice@example.com").unwrap();
let debug = format!("{:?}", uri);
assert!(debug.contains("SipUri"));
}
#[test]
fn test_uri_clone() {
let uri = SipUri::parse("sip:alice@example.com:5060;transport=tcp").unwrap();
let cloned = uri.clone();
assert_eq!(uri.scheme(), cloned.scheme());
assert_eq!(uri.user(), cloned.user());
assert_eq!(uri.host(), cloned.host());
assert_eq!(uri.port(), cloned.port());
}
#[test]
fn test_uri_eq() {
let uri1 = SipUri::parse("sip:alice@example.com").unwrap();
let uri2 = SipUri::parse("sip:alice@example.com").unwrap();
assert_eq!(uri1, uri2);
let uri3 = SipUri::parse("sip:bob@example.com").unwrap();
assert_ne!(uri1, uri3);
}
#[test]
fn test_uri_builder_default() {
let builder = SipUriBuilder::default();
let result = builder.host("example.com").build();
assert!(result.is_ok());
}
#[test]
fn test_uri_builder_debug() {
let builder = SipUri::builder().host("example.com");
let debug = format!("{:?}", builder);
assert!(debug.contains("SipUriBuilder"));
}
#[test]
fn test_uri_builder_flag() {
let uri = SipUri::builder()
.host("example.com")
.flag("rport")
.flag("lr")
.build()
.unwrap();
assert!(uri.has_param("rport"));
assert!(uri.has_param("lr"));
}
#[test]
fn test_uri_builder_param() {
let uri = SipUri::builder()
.host("example.com")
.param("maddr", "proxy.example.com")
.build()
.unwrap();
assert_eq!(uri.get_param("maddr"), Some("proxy.example.com"));
}
#[test]
fn test_parse_host_with_colon_no_port() {
let uri = SipUri::parse("sip:example.com:abc").unwrap();
assert_eq!(uri.host(), "example.com:abc");
assert_eq!(uri.port(), None);
}
#[test]
fn test_parse_whitespace_trimmed() {
let uri = SipUri::parse(" sip:alice@example.com ").unwrap();
assert_eq!(uri.user(), Some("alice"));
assert_eq!(uri.host(), "example.com");
}
#[test]
fn test_scheme_case_normalized() {
let uri = SipUri::parse("SIP:alice@example.com").unwrap();
assert_eq!(uri.scheme(), "sip");
let uri2 = SipUri::parse("SIPS:alice@example.com").unwrap();
assert_eq!(uri2.scheme(), "sips");
assert!(uri2.is_secure());
}
#[test]
fn test_builder_scheme_case_normalized() {
let uri = SipUri::builder()
.scheme("SIPS")
.host("example.com")
.build()
.unwrap();
assert_eq!(uri.scheme(), "sips");
assert!(uri.is_secure());
}
#[test]
fn test_parse_tel_uri_basic() {
let uri = SipUri::parse("tel:+1-212-555-1212").unwrap();
assert_eq!(uri.scheme(), "tel");
assert_eq!(uri.scheme_enum(), Scheme::Tel);
assert!(uri.is_tel());
assert!(!uri.is_secure());
assert_eq!(uri.user(), None);
assert_eq!(uri.host(), "+1-212-555-1212");
assert_eq!(uri.port(), None);
}
#[test]
fn test_parse_tel_uri_with_params_and_colons() {
let uri = SipUri::parse("tel:+1-212-555-1212;phone-context=example.com").unwrap();
assert_eq!(uri.scheme(), "tel");
assert_eq!(uri.host(), "+1-212-555-1212");
assert_eq!(uri.port(), None);
assert_eq!(uri.get_param("phone-context"), Some("example.com"));
}
#[test]
fn test_tel_uri_round_trip_local() {
let uri = SipUri::parse("tel:7042;phone-context=example.com").unwrap();
let s = uri.to_string();
assert_eq!(s, "tel:7042;phone-context=example.com");
let reparsed = SipUri::parse(&s).unwrap();
assert_eq!(uri, reparsed);
}
#[test]
fn test_parse_uri_with_headers() {
let uri = SipUri::parse("sip:alice@example.com?Subject=Hi&Body=Hello").unwrap();
assert_eq!(uri.user(), Some("alice"));
assert_eq!(uri.host(), "example.com");
assert_eq!(uri.get_header("Subject"), Some("Hi"));
assert_eq!(uri.get_header("body"), Some("Hello")); assert!(uri.has_headers());
let collected: Vec<(&str, &str)> = uri.headers().collect();
assert_eq!(collected, vec![("Subject", "Hi"), ("Body", "Hello")]);
}
#[test]
fn test_parse_uri_params_and_headers() {
let uri =
SipUri::parse("sip:alice@example.com:5060;transport=tcp?Subject=Hi&X-Foo=Bar").unwrap();
assert_eq!(uri.port(), Some(5060));
assert_eq!(uri.transport(), Some("tcp"));
assert_eq!(uri.get_header("Subject"), Some("Hi"));
assert_eq!(uri.get_header("X-Foo"), Some("Bar"));
}
#[test]
fn test_uri_headers_display_round_trip() {
let original = "sip:alice@example.com?Subject=Hi&Body=Hello";
let uri = SipUri::parse(original).unwrap();
assert_eq!(uri.to_string(), original);
let reparsed = SipUri::parse(&uri.to_string()).unwrap();
assert_eq!(uri, reparsed);
}
#[test]
fn test_parse_uri_header_no_equals() {
let uri = SipUri::parse("sip:alice@example.com?Foo").unwrap();
assert_eq!(uri.get_header("Foo"), Some(""));
}
#[test]
fn test_parse_uri_headers_empty_tokens() {
let uri = SipUri::parse("sip:alice@example.com?A=1&&B=2").unwrap();
assert_eq!(uri.get_header("A"), Some("1"));
assert_eq!(uri.get_header("B"), Some("2"));
let count = uri.headers().count();
assert_eq!(count, 2);
}
#[test]
fn test_scheme_enum_variants() {
let sip = SipUri::parse("sip:a@b").unwrap();
let sips = SipUri::parse("sips:a@b").unwrap();
let tel = SipUri::parse("tel:+1234").unwrap();
assert_eq!(sip.scheme_enum(), Scheme::Sip);
assert_eq!(sips.scheme_enum(), Scheme::Sips);
assert_eq!(tel.scheme_enum(), Scheme::Tel);
assert_eq!(Scheme::Sip.as_str(), "sip");
assert_eq!(Scheme::Sips.as_str(), "sips");
assert_eq!(Scheme::Tel.as_str(), "tel");
assert_eq!(format!("{}", Scheme::Sip), "sip");
assert_eq!(format!("{}", Scheme::Sips), "sips");
assert_eq!(format!("{}", Scheme::Tel), "tel");
}
#[test]
fn test_builder_scheme_enum() {
let uri = SipUri::builder()
.scheme_enum(Scheme::Tel)
.host("+12125551212")
.build()
.unwrap();
assert!(uri.is_tel());
assert_eq!(uri.to_string(), "tel:+12125551212");
}
#[test]
fn test_builder_unknown_scheme_defaults_to_sip() {
let uri = SipUri::builder()
.scheme("ftp")
.host("example.com")
.build()
.unwrap();
assert_eq!(uri.scheme(), "sip");
}
#[test]
fn test_builder_with_headers() {
let uri = SipUri::builder()
.host("example.com")
.header("Subject", "Hi")
.header("Body", "Hello")
.build()
.unwrap();
assert!(uri.has_headers());
let s = uri.to_string();
assert!(s.ends_with("?Subject=Hi&Body=Hello"), "got {}", s);
}
#[test]
fn test_uri_headers_count_cap_rejects() {
let mut s = String::from("sip:alice@example.com?");
for i in 0..33 {
if i > 0 {
s.push('&');
}
s.push_str(&format!("h{i}=v"));
}
let err = SipUri::parse(&s).unwrap_err();
match err {
SipError::Parse(msg) => {
assert!(msg.contains("too many URI headers"), "got: {msg}");
}
other => panic!("expected Parse error, got {other:?}"),
}
}
#[test]
fn test_uri_headers_value_length_cap_rejects() {
let big = "x".repeat(257);
let s = format!("sip:alice@example.com?Subject={big}");
let err = SipUri::parse(&s).unwrap_err();
match err {
SipError::Parse(msg) => {
assert!(msg.contains("URI header value too long"), "got: {msg}");
}
other => panic!("expected Parse error, got {other:?}"),
}
}
#[test]
fn test_params_iterator_preserves_order_and_case() {
let uri = SipUri::parse("sip:host;Foo=1;bar;Baz=3").unwrap();
let collected: Vec<(&str, Option<&str>)> = uri.params().collect();
assert_eq!(
collected,
vec![("Foo", Some("1")), ("bar", None), ("Baz", Some("3"))]
);
}
}