use crate::dialog::{dialog_layer::DialogLayer, DialogId};
use crate::sip::{headers::*, Request};
use crate::transaction::{
endpoint::EndpointBuilder,
key::{TransactionKey, TransactionRole},
transaction::Transaction,
};
use crate::transport::{udp::UdpConnection, TransportLayer};
use tokio::sync::mpsc::unbounded_channel;
use tokio_util::sync::CancellationToken;
async fn create_test_endpoint() -> crate::Result<crate::transaction::endpoint::Endpoint> {
let token = CancellationToken::new();
let tl = TransportLayer::new(token.child_token());
let endpoint = EndpointBuilder::new()
.with_user_agent("rsipstack-test")
.with_transport_layer(tl)
.build();
Ok(endpoint)
}
fn create_invite_request(from_tag: &str, to_tag: &str, call_id: &str, branch: &str) -> Request {
Request {
method: crate::sip::Method::Invite,
uri: crate::sip::Uri::try_from("sip:bob@example.com:5060").unwrap(),
headers: vec![
Via::new(&format!(
"SIP/2.0/UDP alice.example.com:5060;branch={}",
branch
))
.into(),
CSeq::new("1 INVITE").into(),
From::new(&format!("Alice <sip:alice@example.com>;tag={}", from_tag)).into(),
To::new(&format!("Bob <sip:bob@example.com>;tag={}", to_tag)).into(),
CallId::new(call_id).into(),
Contact::new("<sip:alice@alice.example.com:5060>").into(),
MaxForwards::new("70").into(),
]
.into(),
version: crate::sip::Version::V2,
body: b"v=0\r\no=alice 2890844526 2890844527 IN IP4 host.atlanta.com\r\n".to_vec(),
}
}
async fn create_mock_connection() -> crate::Result<crate::transport::SipConnection> {
let udp_conn = UdpConnection::create_connection("127.0.0.1:0".parse()?, None, None).await?;
Ok(udp_conn.into())
}
#[tokio::test]
async fn test_dialog_layer_creation() -> crate::Result<()> {
let endpoint = create_test_endpoint().await?;
let dialog_layer = DialogLayer::new(endpoint.inner.clone());
assert_eq!(dialog_layer.len(), 0);
let seq1 = dialog_layer.increment_last_seq();
let seq2 = dialog_layer.increment_last_seq();
assert_eq!(seq2, seq1 + 1);
Ok(())
}
#[tokio::test]
async fn test_server_invite_dialog_creation() -> crate::Result<()> {
let endpoint = create_test_endpoint().await?;
let dialog_layer = DialogLayer::new(endpoint.inner.clone());
let mock_conn = create_mock_connection().await?;
let invite_req = create_invite_request("alice-tag-123", "", "call-id-456", "z9hG4bKnashds");
let key = TransactionKey::from_request(&invite_req, TransactionRole::Server)?;
let tx = Transaction::new_server(
key,
invite_req.clone(),
endpoint.inner.clone(),
Some(mock_conn),
);
let (state_sender, _state_receiver) = unbounded_channel();
let dialog = dialog_layer.get_or_create_server_invite(
&tx,
state_sender,
None,
Some(crate::sip::Uri::try_from("sip:bob@bob.example.com:5060")?),
)?;
assert_eq!(dialog_layer.len(), 1);
let dialog_id = dialog.id();
assert_eq!(dialog_id.call_id, "call-id-456");
assert_eq!(dialog_id.remote_tag, "alice-tag-123");
assert!(!dialog_id.local_tag.is_empty());
Ok(())
}
#[tokio::test]
async fn test_existing_server_invite_dialog_retrieval() -> crate::Result<()> {
let endpoint = create_test_endpoint().await?;
let dialog_layer = DialogLayer::new(endpoint.inner.clone());
let mock_conn = create_mock_connection().await?;
let invite_req1 = create_invite_request("alice-tag-123", "", "call-id-456", "z9hG4bKnashds1");
let key1 = TransactionKey::from_request(&invite_req1, TransactionRole::Server)?;
let tx1 = Transaction::new_server(
key1,
invite_req1,
endpoint.inner.clone(),
Some(mock_conn.clone()),
);
let (state_sender, _) = unbounded_channel();
let dialog1 = dialog_layer.get_or_create_server_invite(
&tx1,
state_sender.clone(),
None,
Some(crate::sip::Uri::try_from("sip:bob@bob.example.com:5060")?),
)?;
let dialog_id = dialog1.id();
let invite_req2 = create_invite_request(
"alice-tag-123",
&dialog_id.local_tag,
"call-id-456",
"z9hG4bKnashds2",
);
let key2 = TransactionKey::from_request(&invite_req2, TransactionRole::Server)?;
let tx2 = Transaction::new_server(key2, invite_req2, endpoint.inner.clone(), Some(mock_conn));
let dialog2 = dialog_layer.get_or_create_server_invite(
&tx2,
state_sender,
None,
Some(crate::sip::Uri::try_from("sip:bob@bob.example.com:5060")?),
)?;
assert_eq!(dialog1.id(), dialog2.id());
assert_eq!(dialog_layer.len(), 1);
Ok(())
}
#[tokio::test]
async fn test_dialog_retrieval_and_matching() -> crate::Result<()> {
let endpoint = create_test_endpoint().await?;
let dialog_layer = DialogLayer::new(endpoint.inner.clone());
let mock_conn = create_mock_connection().await?;
let invite_req = create_invite_request("alice-tag-123", "", "call-id-456", "z9hG4bKnashds");
let key = TransactionKey::from_request(&invite_req, TransactionRole::Server)?;
let tx = Transaction::new_server(
key,
invite_req.clone(),
endpoint.inner.clone(),
Some(mock_conn.clone()),
);
let (state_sender, _) = unbounded_channel();
let dialog = dialog_layer.get_or_create_server_invite(
&tx,
state_sender,
None,
Some(crate::sip::Uri::try_from("sip:bob@bob.example.com:5060")?),
)?;
let dialog_id = dialog.id();
let retrieved_dialog = dialog_layer.get_dialog(&dialog_id);
assert!(retrieved_dialog.is_some());
let bye_req = Request {
method: crate::sip::Method::Bye,
uri: crate::sip::Uri::try_from("sip:bob@example.com:5060")?,
headers: vec![
Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKbye").into(),
CSeq::new("2 BYE").into(),
From::new(&format!(
"Alice <sip:alice@example.com>;tag={}",
dialog_id.remote_tag
))
.into(),
To::new(&format!(
"Bob <sip:bob@example.com>;tag={}",
dialog_id.local_tag
))
.into(),
CallId::new(&dialog_id.call_id).into(),
]
.into(),
version: crate::sip::Version::V2,
body: vec![],
};
let bye_key = TransactionKey::from_request(&bye_req, TransactionRole::Server)?;
let bye_tx = Transaction::new_server(bye_key, bye_req, endpoint.inner.clone(), Some(mock_conn));
let matched_dialog = dialog_layer.match_dialog(&bye_tx);
assert!(matched_dialog.is_some());
Ok(())
}
#[tokio::test]
async fn test_dialog_removal() -> crate::Result<()> {
let endpoint = create_test_endpoint().await?;
let dialog_layer = DialogLayer::new(endpoint.inner.clone());
let mock_conn = create_mock_connection().await?;
let invite_req = create_invite_request("alice-tag-123", "", "call-id-456", "z9hG4bKnashds");
let key = TransactionKey::from_request(&invite_req, TransactionRole::Server)?;
let tx = Transaction::new_server(key, invite_req, endpoint.inner.clone(), Some(mock_conn));
let (state_sender, _) = unbounded_channel();
let dialog = dialog_layer.get_or_create_server_invite(
&tx,
state_sender,
None,
Some(crate::sip::Uri::try_from("sip:bob@bob.example.com:5060")?),
)?;
let dialog_id = dialog.id();
assert_eq!(dialog_layer.len(), 1);
assert!(dialog_layer.get_dialog(&dialog_id).is_some());
dialog_layer.remove_dialog(&dialog_id);
assert_eq!(dialog_layer.len(), 0);
assert!(dialog_layer.get_dialog(&dialog_id).is_none());
Ok(())
}
#[tokio::test]
async fn test_dialog_layer_with_swapped_tags() -> crate::Result<()> {
let endpoint = create_test_endpoint().await?;
let dialog_layer = DialogLayer::new(endpoint.inner.clone());
let mock_conn = create_mock_connection().await?;
let invite_req = create_invite_request("alice-tag-123", "", "call-id-456", "z9hG4bKnashds");
let key = TransactionKey::from_request(&invite_req, TransactionRole::Server)?;
let tx = Transaction::new_server(key, invite_req, endpoint.inner.clone(), Some(mock_conn));
let (state_sender, _) = unbounded_channel();
let dialog = dialog_layer.get_or_create_server_invite(
&tx,
state_sender,
None,
Some(crate::sip::Uri::try_from("sip:bob@bob.example.com:5060")?),
)?;
let dialog_id = dialog.id();
let swapped_id = DialogId {
call_id: dialog_id.call_id.clone(),
local_tag: dialog_id.remote_tag.clone(),
remote_tag: dialog_id.local_tag.clone(),
};
let found_dialog = dialog_layer.get_dialog(&swapped_id);
assert!(found_dialog.is_none());
Ok(())
}
#[tokio::test]
async fn test_multiple_dialogs_management() -> crate::Result<()> {
let endpoint = create_test_endpoint().await?;
let dialog_layer = DialogLayer::new(endpoint.inner.clone());
let mock_conn = create_mock_connection().await?;
let (state_sender, _) = unbounded_channel();
for i in 0..5 {
let call_id = format!("call-id-{}", i);
let from_tag = format!("alice-tag-{}", i);
let branch = format!("z9hG4bKnashds{}", i);
let invite_req = create_invite_request(&from_tag, "", &call_id, &branch);
let key = TransactionKey::from_request(&invite_req, TransactionRole::Server)?;
let tx = Transaction::new_server(
key,
invite_req,
endpoint.inner.clone(),
Some(mock_conn.clone()),
);
dialog_layer.get_or_create_server_invite(
&tx,
state_sender.clone(),
None,
Some(crate::sip::Uri::try_from("sip:bob@bob.example.com:5060")?),
)?;
}
assert_eq!(dialog_layer.len(), 5);
let _test_id = DialogId {
call_id: "test-call-2".to_string(),
local_tag: "".to_string(),
remote_tag: "alice-tag-2".to_string(), };
let mut dialog_ids = vec![];
for i in 0..5 {
let call_id = format!("call-id-{}", i);
let from_tag = format!("alice-tag-{}", i);
let _partial_id = DialogId {
call_id: call_id.clone(),
local_tag: "".to_string(),
remote_tag: from_tag.clone(),
};
let test_req = create_invite_request(&from_tag, "", &call_id, "test-branch");
let test_key = TransactionKey::from_request(&test_req, TransactionRole::Server)?;
let test_tx = Transaction::new_server(
test_key,
test_req,
endpoint.inner.clone(),
Some(mock_conn.clone()),
);
if let Some(dialog) = dialog_layer.match_dialog(&test_tx) {
dialog_ids.push(dialog.id());
}
}
if let Some(dialog_id) = dialog_ids.first() {
dialog_layer.remove_dialog(dialog_id);
assert_eq!(dialog_layer.len(), 4);
}
Ok(())
}
#[tokio::test]
async fn test_dialog_error_cases() -> crate::Result<()> {
let endpoint = create_test_endpoint().await?;
let dialog_layer = DialogLayer::new(endpoint.inner.clone());
let mock_conn = create_mock_connection().await?;
let invite_req = create_invite_request(
"alice-tag-123",
"non-existent-tag",
"call-id-456",
"z9hG4bKnashds",
);
let key = TransactionKey::from_request(&invite_req, TransactionRole::Server)?;
let tx = Transaction::new_server(key, invite_req, endpoint.inner.clone(), Some(mock_conn));
let (state_sender, _) = unbounded_channel();
let result = dialog_layer.get_or_create_server_invite(
&tx,
state_sender,
None,
Some(crate::sip::Uri::try_from("sip:bob@bob.example.com:5060")?),
);
assert!(result.is_err());
Ok(())
}