use crate::sip::{RecordRoute as SipRecordRoute, SipRequest, SipResponse, Via};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DialogId {
pub call_id: String,
pub local_tag: String,
pub remote_tag: String,
}
impl DialogId {
pub fn new(
call_id: impl Into<String>,
local_tag: impl Into<String>,
remote_tag: impl Into<String>,
) -> Self {
Self {
call_id: call_id.into(),
local_tag: local_tag.into(),
remote_tag: remote_tag.into(),
}
}
pub fn from_request_uac(request: &SipRequest, remote_tag: &str) -> Option<Self> {
let call_id = request.call_id().ok()?;
let local_tag = request.from_tag().ok()?;
Some(Self {
call_id,
local_tag,
remote_tag: remote_tag.to_string(),
})
}
pub fn from_response_uac(response: &SipResponse) -> Option<Self> {
let call_id = response.call_id().ok()?;
let local_tag = response.from_tag().ok()?;
let remote_tag = response.to_tag()?;
Some(Self {
call_id,
local_tag,
remote_tag,
})
}
pub fn from_request_uas(request: &SipRequest, local_tag: &str) -> Option<Self> {
let call_id = request.call_id().ok()?;
let remote_tag = request.from_tag().ok()?;
Some(Self {
call_id,
local_tag: local_tag.to_string(),
remote_tag,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DialogState {
Early,
Confirmed,
Terminating,
Terminated,
}
#[derive(Debug, Clone, Default)]
pub struct RouteSet {
routes: Vec<String>,
loose_routing: bool,
}
impl RouteSet {
pub fn new() -> Self {
Self {
routes: Vec::new(),
loose_routing: false,
}
}
pub fn from_record_route_values(record_route_values: &[String], reverse: bool) -> Self {
let record_routes = SipRecordRoute::parse_all(record_route_values);
let mut routes: Vec<String> = record_routes
.iter()
.map(|rr| rr.to_header_value())
.collect();
if reverse {
routes.reverse();
}
let loose_routing = record_routes.first().map(|rr| rr.lr).unwrap_or(false);
Self {
routes,
loose_routing,
}
}
pub fn routes(&self) -> &[String] {
&self.routes
}
pub fn is_empty(&self) -> bool {
self.routes.is_empty()
}
pub fn is_loose_routing(&self) -> bool {
self.loose_routing
}
pub fn len(&self) -> usize {
self.routes.len()
}
}
#[derive(Debug, Clone)]
pub struct DialogInfo {
pub id: DialogId,
pub state: DialogState,
pub local_seq: u32,
pub remote_seq: Option<u32>,
pub local_uri: String,
pub remote_uri: String,
pub remote_target: String,
pub local_contact: String,
pub route_set: RouteSet,
pub secure: bool,
}
impl DialogInfo {
pub fn from_invite_response_uac(
request: &SipRequest,
response: &SipResponse,
state: DialogState,
) -> Option<Self> {
let id = DialogId::from_response_uac(response)?;
let local_uri = request.from_uri().ok()?.to_string();
let remote_uri = request.to_uri().ok()?.to_string();
let remote_target = response.contact_uri()?.to_string();
let local_seq = request.cseq().ok()?;
let record_routes = response.record_routes();
let route_set = RouteSet::from_record_route_values(&record_routes, true);
let secure = Self::detect_secure_transport(request);
let local_contact = request
.contact_uri()
.map(|u| u.to_string())
.unwrap_or_default();
Some(Self {
id,
state,
local_seq,
remote_seq: None,
local_uri,
remote_uri,
remote_target,
local_contact,
route_set,
secure,
})
}
pub fn from_invite_uas(
request: &SipRequest,
local_tag: &str,
local_contact: &str,
state: DialogState,
) -> Option<Self> {
let call_id = request.call_id().ok()?;
let (remote_tag, remote_uri) = request.from_tag_and_uri().ok()?;
let id = DialogId::new(&call_id, local_tag, &remote_tag);
let local_uri = request.to_uri().ok()?.to_string();
let remote_target = request.contact_uri()?.to_string();
let remote_seq = request.cseq().ok()?;
let record_routes = request.record_routes();
let route_set = RouteSet::from_record_route_values(&record_routes, false);
let secure = Self::detect_secure_transport(request);
Some(Self {
id,
state,
local_seq: 0, remote_seq: Some(remote_seq),
local_uri,
remote_uri: remote_uri.to_string(),
remote_target,
local_contact: local_contact.to_string(),
route_set,
secure,
})
}
fn detect_secure_transport(request: &SipRequest) -> bool {
let via_values = request.via_headers_raw();
if let Some(first_via) = via_values.first() {
let via_value = first_via
.trim()
.strip_prefix("Via:")
.unwrap_or(first_via)
.trim();
if let Ok(via) = Via::parse(via_value) {
return via.protocol.eq_ignore_ascii_case("TLS");
}
}
false
}
pub fn next_local_seq(&mut self) -> u32 {
self.local_seq += 1;
self.local_seq
}
pub fn update_remote_seq(&mut self, seq: u32) -> bool {
match self.remote_seq {
None => {
self.remote_seq = Some(seq);
true
}
Some(current) if seq > current => {
self.remote_seq = Some(seq);
true
}
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sip::{Method, SipMessage};
fn create_invite() -> SipRequest {
SipRequest::builder()
.method(Method::Invite)
.uri("sip:bob@example.com")
.via("192.168.1.1", 5060, "UDP", "z9hG4bKtest")
.from("sip:alice@example.com", "fromtag")
.to("sip:bob@example.com")
.call_id("test@example.com")
.cseq(1)
.contact("sip:alice@192.168.1.1:5060")
.build()
.unwrap()
}
fn parse_request(raw: &str) -> SipRequest {
let msg = SipMessage::parse(raw.as_bytes()).unwrap();
msg.as_request().unwrap().clone()
}
fn parse_response(raw: &str) -> SipResponse {
let msg = SipMessage::parse(raw.as_bytes()).unwrap();
msg.as_response().unwrap().clone()
}
fn create_response(request: &SipRequest) -> SipResponse {
SipResponse::builder()
.status(200, "OK")
.from_request(request)
.to_tag("totag")
.contact("sip:bob@192.168.1.2:5060")
.build()
.unwrap()
}
#[test]
fn test_dialog_id_from_response_uac() {
let invite = create_invite();
let response = create_response(&invite);
let id = DialogId::from_response_uac(&response).unwrap();
assert_eq!(id.call_id, "test@example.com");
assert_eq!(id.local_tag, "fromtag");
assert_eq!(id.remote_tag, "totag");
}
#[test]
fn test_dialog_id_from_request_uac_missing_call_id() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let id = DialogId::from_request_uac(&invite, "remotetag");
assert!(id.is_none());
}
#[test]
fn test_dialog_id_from_request_uac_missing_from_tag() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let id = DialogId::from_request_uac(&invite, "remotetag");
assert!(id.is_none());
}
#[test]
fn test_dialog_id_from_response_uac_missing_call_id() {
let response = parse_response(
"SIP/2.0 200 OK\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>;tag=totag\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:bob@192.168.1.2:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let id = DialogId::from_response_uac(&response);
assert!(id.is_none());
}
#[test]
fn test_dialog_id_from_response_uac_missing_from_tag() {
let response = parse_response(
"SIP/2.0 200 OK\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>\r\n\
To: <sip:bob@example.com>;tag=totag\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:bob@192.168.1.2:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let id = DialogId::from_response_uac(&response);
assert!(id.is_none());
}
#[test]
fn test_dialog_id_from_request_uas_missing_call_id() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let id = DialogId::from_request_uas(&invite, "localtag");
assert!(id.is_none());
}
#[test]
fn test_dialog_id_from_request_uas_missing_from_tag() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let id = DialogId::from_request_uas(&invite, "mytag");
assert!(id.is_none());
}
#[test]
fn test_dialog_id_from_request_uac() {
let invite = create_invite();
let id = DialogId::from_request_uac(&invite, "remotetag").unwrap();
assert_eq!(id.call_id, "test@example.com");
assert_eq!(id.local_tag, "fromtag");
assert_eq!(id.remote_tag, "remotetag");
}
#[test]
fn test_dialog_id_from_request_uas() {
let invite = create_invite();
let id = DialogId::from_request_uas(&invite, "mytag").unwrap();
assert_eq!(id.call_id, "test@example.com");
assert_eq!(id.local_tag, "mytag");
assert_eq!(id.remote_tag, "fromtag");
}
#[test]
fn test_dialog_info_from_invite_uas_option() {
let invite = create_invite();
let info = DialogInfo::from_invite_uas(
&invite,
"localtag",
"sip:local@example.com",
DialogState::Early,
)
.unwrap();
assert_eq!(info.state, DialogState::Early);
assert_eq!(info.remote_seq, Some(1));
assert_eq!(info.remote_uri, "sip:alice@example.com");
}
#[test]
fn test_dialog_info_from_response() {
let invite = create_invite();
let response = create_response(&invite);
let info = DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed)
.unwrap();
assert_eq!(info.state, DialogState::Confirmed);
assert_eq!(info.local_seq, 1);
assert!(info.remote_seq.is_none());
}
#[test]
fn test_dialog_info_from_response_invalid_from_uri() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@[::1>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
assert!(invite.from_uri().is_err());
let response = parse_response(
"SIP/2.0 200 OK\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>;tag=totag\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:bob@192.168.1.2:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let info = DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed);
assert!(info.is_none());
}
#[test]
fn test_dialog_info_from_response_invalid_to_uri() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@[::1>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
assert!(invite.to_uri().is_err());
let response = parse_response(
"SIP/2.0 200 OK\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>;tag=totag\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:bob@192.168.1.2:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let info = DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed);
assert!(info.is_none());
}
#[test]
fn test_dialog_info_from_response_missing_contact() {
let invite = create_invite();
let response = parse_response(
"SIP/2.0 200 OK\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>;tag=totag\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Content-Length: 0\r\n\
\r\n",
);
let info = DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed);
assert!(info.is_none());
}
#[test]
fn test_dialog_info_from_response_invalid_cseq() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: abc INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let response = create_response(&invite);
let info = DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed);
assert!(info.is_none());
}
#[test]
fn test_dialog_info_from_invite_uas_invalid_from_uri() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@[::1>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
assert!(invite.from_uri().is_err());
let info = DialogInfo::from_invite_uas(
&invite,
"localtag",
"sip:local@example.com",
DialogState::Early,
);
assert!(info.is_none());
}
#[test]
fn test_dialog_info_from_invite_uas_invalid_to_uri() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@[::1>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let info = DialogInfo::from_invite_uas(
&invite,
"localtag",
"sip:local@example.com",
DialogState::Early,
);
assert!(info.is_none());
}
#[test]
fn test_dialog_info_from_invite_uas_invalid_contact() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@[::1>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let info = DialogInfo::from_invite_uas(
&invite,
"localtag",
"sip:local@example.com",
DialogState::Early,
);
assert!(info.is_none());
}
#[test]
fn test_dialog_info_from_invite_uas_invalid_cseq() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: abc INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let info = DialogInfo::from_invite_uas(
&invite,
"localtag",
"sip:local@example.com",
DialogState::Early,
);
assert!(info.is_none());
}
#[test]
fn test_dialog_info_from_invite_uas_missing_call_id() {
let invite = parse_request(
"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n",
);
let info = DialogInfo::from_invite_uas(
&invite,
"localtag",
"sip:local@example.com",
DialogState::Early,
);
assert!(info.is_none());
}
#[test]
fn test_detect_secure_transport_prefix_via() {
let raw = "INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/TLS 192.168.1.1:5061;branch=z9hG4bKtls\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n";
let invite = parse_request(raw);
let info = DialogInfo::from_invite_uas(
&invite,
"localtag",
"sip:local@example.com",
DialogState::Early,
)
.unwrap();
assert!(info.secure);
}
#[test]
fn test_detect_secure_transport_prefix_via_lowercase() {
let raw = "INVITE sip:bob@example.com SIP/2.0\r\n\
via: SIP/2.0/TLS 192.168.1.1:5061;branch=z9hG4bKtls\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Contact: <sip:alice@192.168.1.1:5060>\r\n\
Content-Length: 0\r\n\
\r\n";
let invite = parse_request(raw);
let info = DialogInfo::from_invite_uas(
&invite,
"localtag",
"sip:local@example.com",
DialogState::Early,
)
.unwrap();
assert!(info.secure);
}
#[test]
fn test_next_local_seq() {
let invite = create_invite();
let response = create_response(&invite);
let mut info =
DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed)
.unwrap();
assert_eq!(info.next_local_seq(), 2);
assert_eq!(info.next_local_seq(), 3);
}
#[test]
fn test_update_remote_seq() {
let invite = create_invite();
let response = create_response(&invite);
let mut info =
DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed)
.unwrap();
assert!(info.update_remote_seq(1));
assert!(info.update_remote_seq(2));
assert!(!info.update_remote_seq(1)); assert!(!info.update_remote_seq(2)); }
#[test]
fn test_dialog_id_new() {
let id = DialogId::new("call123", "local456", "remote789");
assert_eq!(id.call_id, "call123");
assert_eq!(id.local_tag, "local456");
assert_eq!(id.remote_tag, "remote789");
}
#[test]
fn test_dialog_id_clone() {
let id = DialogId::new("call123", "local456", "remote789");
let cloned = id.clone();
assert_eq!(cloned.call_id, id.call_id);
assert_eq!(cloned.local_tag, id.local_tag);
assert_eq!(cloned.remote_tag, id.remote_tag);
}
#[test]
fn test_dialog_id_eq() {
let id1 = DialogId::new("call123", "local456", "remote789");
let id2 = DialogId::new("call123", "local456", "remote789");
let id3 = DialogId::new("call999", "local456", "remote789");
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_dialog_id_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(DialogId::new("call123", "local456", "remote789"));
assert!(set.contains(&DialogId::new("call123", "local456", "remote789")));
assert!(!set.contains(&DialogId::new("call999", "local456", "remote789")));
}
#[test]
fn test_dialog_id_debug() {
let id = DialogId::new("call123", "local456", "remote789");
let debug = format!("{:?}", id);
assert!(debug.contains("call123"));
assert!(debug.contains("local456"));
assert!(debug.contains("remote789"));
}
#[test]
fn test_dialog_state_all_variants() {
let states = [
DialogState::Early,
DialogState::Confirmed,
DialogState::Terminating,
DialogState::Terminated,
];
for state in states {
let _ = format!("{:?}", state);
}
}
#[test]
fn test_dialog_state_clone() {
let state = DialogState::Early;
let cloned = state;
assert_eq!(state, cloned);
}
#[test]
fn test_route_set_new() {
let route_set = RouteSet::new();
assert!(route_set.is_empty());
assert_eq!(route_set.len(), 0);
assert!(!route_set.is_loose_routing());
}
#[test]
fn test_route_set_default() {
let route_set = RouteSet::default();
assert!(route_set.is_empty());
assert!(!route_set.is_loose_routing());
}
#[test]
fn test_route_set_routes() {
let routes = RouteSet::new();
assert!(routes.routes().is_empty());
}
#[test]
fn test_route_set_from_record_route_values() {
let record_routes = vec![
"<sip:proxy1.example.com;lr>".to_string(),
"<sip:proxy2.example.com;lr>".to_string(),
];
let route_set = RouteSet::from_record_route_values(&record_routes, false);
assert!(!route_set.is_empty());
assert_eq!(route_set.len(), 2);
}
#[test]
fn test_route_set_from_record_route_values_reversed() {
let record_routes = vec![
"<sip:proxy1.example.com;lr>".to_string(),
"<sip:proxy2.example.com;lr>".to_string(),
];
let route_set = RouteSet::from_record_route_values(&record_routes, true);
assert!(!route_set.is_empty());
assert_eq!(route_set.len(), 2);
let routes = route_set.routes();
assert!(routes[0].contains("proxy2"));
assert!(routes[1].contains("proxy1"));
}
#[test]
fn test_route_set_from_record_route_values_no_lr() {
let record_routes = vec!["<sip:proxy1.example.com>".to_string()];
let route_set = RouteSet::from_record_route_values(&record_routes, false);
assert!(!route_set.is_empty());
assert!(!route_set.is_loose_routing());
}
#[test]
fn test_route_set_from_record_route_values_empty() {
let record_routes: Vec<String> = vec![];
let route_set = RouteSet::from_record_route_values(&record_routes, false);
assert!(route_set.is_empty());
assert!(!route_set.is_loose_routing());
}
#[test]
fn test_route_set_debug() {
let route_set = RouteSet::new();
let debug = format!("{:?}", route_set);
assert!(debug.contains("RouteSet"));
}
#[test]
fn test_route_set_clone() {
let record_routes = vec!["<sip:proxy.example.com;lr>".to_string()];
let route_set = RouteSet::from_record_route_values(&record_routes, false);
let cloned = route_set.clone();
assert_eq!(cloned.len(), route_set.len());
assert_eq!(cloned.is_loose_routing(), route_set.is_loose_routing());
}
#[test]
fn test_dialog_info_from_invite_uas() {
let invite = create_invite();
let info = DialogInfo::from_invite_uas(
&invite,
"mytag",
"sip:me@192.168.1.2:5060",
DialogState::Early,
);
assert!(info.is_some());
let info = info.unwrap();
assert_eq!(info.state, DialogState::Early);
assert_eq!(info.id.local_tag, "mytag");
assert_eq!(info.id.remote_tag, "fromtag");
assert_eq!(info.local_seq, 0);
assert!(info.remote_seq.is_some());
assert_eq!(info.remote_seq.unwrap(), 1);
}
#[test]
fn test_dialog_id_from_request_uac_option() {
let invite = create_invite();
let id = DialogId::from_request_uac(&invite, "remote_tag");
assert!(id.is_some());
let id = id.unwrap();
assert_eq!(id.call_id, "test@example.com");
assert_eq!(id.local_tag, "fromtag");
assert_eq!(id.remote_tag, "remote_tag");
}
#[test]
fn test_dialog_info_secure_transport_detection() {
let invite = create_invite(); let is_secure = DialogInfo::detect_secure_transport(&invite);
assert!(!is_secure); }
#[test]
fn test_dialog_info_secure_transport_tls() {
let invite = SipRequest::builder()
.method(Method::Invite)
.uri("sip:bob@example.com")
.via("192.168.1.1", 5061, "TLS", "z9hG4bKtest")
.from("sip:alice@example.com", "fromtag")
.to("sip:bob@example.com")
.call_id("test@example.com")
.cseq(1)
.build()
.unwrap();
let is_secure = DialogInfo::detect_secure_transport(&invite);
assert!(is_secure);
}
#[test]
fn test_dialog_info_secure_transport_missing_via() {
let msg = b"INVITE sip:bob@example.com SIP/2.0\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Content-Length: 0\r\n\
\r\n";
let parsed = SipMessage::parse(msg).unwrap();
let invite = parsed.as_request().expect("expected request");
let is_secure = DialogInfo::detect_secure_transport(invite);
assert!(!is_secure);
}
#[test]
fn test_dialog_info_secure_transport_invalid_via() {
let msg = b"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: INVALID\r\n\
From: <sip:alice@example.com>;tag=fromtag\r\n\
To: <sip:bob@example.com>\r\n\
Call-ID: test@example.com\r\n\
CSeq: 1 INVITE\r\n\
Content-Length: 0\r\n\
\r\n";
let parsed = SipMessage::parse(msg).unwrap();
let invite = parsed.as_request().expect("expected request");
let is_secure = DialogInfo::detect_secure_transport(invite);
assert!(!is_secure);
}
#[test]
fn test_dialog_info_secure_transport_udp() {
let invite = create_invite(); let info = DialogInfo::from_invite_uas(
&invite,
"mytag",
"sip:me@192.168.1.2:5060",
DialogState::Early,
)
.unwrap();
assert!(!info.secure);
}
#[test]
fn test_dialog_info_clone() {
let invite = create_invite();
let response = create_response(&invite);
let info = DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed)
.unwrap();
let cloned = info.clone();
assert_eq!(cloned.id.call_id, info.id.call_id);
assert_eq!(cloned.state, info.state);
assert_eq!(cloned.local_seq, info.local_seq);
assert_eq!(cloned.remote_seq, info.remote_seq);
}
#[test]
fn test_dialog_info_debug() {
let invite = create_invite();
let response = create_response(&invite);
let info = DialogInfo::from_invite_response_uac(&invite, &response, DialogState::Confirmed)
.unwrap();
let debug = format!("{:?}", info);
assert!(debug.contains("DialogInfo"));
}
}