use crate::sip::{Method, SipRequest, SipResponse};
use crate::transaction::client::invite::{
Action as InviteClientAction, Event as InviteClientEvent, InviteClientTransaction,
State as InviteClientState,
};
use crate::transaction::client::non_invite::{
Action as NonInviteClientAction, Event as NonInviteClientEvent, NonInviteClientTransaction,
State as NonInviteClientState,
};
use crate::transaction::server::invite::{
Action as InviteServerAction, InviteServerTransaction, State as InviteServerState,
};
use crate::transaction::server::non_invite::{
Action as NonInviteServerAction, NonInviteServerTransaction, State as NonInviteServerState,
};
use crate::transaction::timer::Timer;
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)
.build()
.unwrap()
}
fn create_register() -> SipRequest {
SipRequest::builder()
.method(Method::Register)
.uri("sip:example.com")
.via("192.168.1.1", 5060, "UDP", "z9hG4bKtest")
.from("sip:alice@example.com", "fromtag")
.to("sip:alice@example.com")
.call_id("test@example.com")
.cseq(1)
.build()
.unwrap()
}
fn create_response(request: &SipRequest, code: u16) -> SipResponse {
SipResponse::builder()
.status(code, "Test")
.from_request(request)
.to_tag("totag")
.build()
.unwrap()
}
#[cfg(test)]
mod invite_client_transaction {
use super::*;
#[test]
fn test_initial_state_calling() {
let invite = create_invite();
let tx = InviteClientTransaction::new(invite, false).unwrap();
assert_eq!(tx.state(), InviteClientState::Calling);
}
#[test]
fn test_timer_a_retransmit() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
tx.handle_timeout(Timer::A);
let actions = tx.poll_actions();
let has_send = actions
.iter()
.any(|a| matches!(a, InviteClientAction::Send(_)));
let has_timer_a = actions
.iter()
.any(|a| matches!(a, InviteClientAction::SetTimer(Timer::A, _)));
assert!(has_send, "Timer A should trigger retransmission");
assert!(has_timer_a, "Timer A should be reset");
assert_eq!(tx.state(), InviteClientState::Calling);
}
#[test]
fn test_timer_a_exponential_backoff() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
tx.handle_timeout(Timer::A);
let actions1 = tx.poll_actions();
let duration1 = actions1.iter().find_map(|a| match a {
InviteClientAction::SetTimer(Timer::A, d) => Some(d),
_ => None,
});
tx.handle_timeout(Timer::A);
let actions2 = tx.poll_actions();
let duration2 = actions2.iter().find_map(|a| match a {
InviteClientAction::SetTimer(Timer::A, d) => Some(d),
_ => None,
});
assert!(duration1.is_some() && duration2.is_some());
assert!(
duration2.unwrap() > duration1.unwrap(),
"Timer A should have exponential backoff"
);
}
#[test]
fn test_timer_b_timeout() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
tx.handle_timeout(Timer::B);
let actions = tx.poll_actions();
let has_timeout = actions
.iter()
.any(|a| matches!(a, InviteClientAction::Event(InviteClientEvent::Timeout)));
assert!(has_timeout, "Timer B should trigger timeout event");
assert_eq!(tx.state(), InviteClientState::Terminated);
}
#[test]
fn test_provisional_response_proceeding() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&invite, 180);
tx.handle_response(response.clone());
let actions = tx.poll_actions();
let has_provisional = actions.iter().any(|a| {
matches!(
a,
InviteClientAction::Event(InviteClientEvent::Provisional(_))
)
});
let has_cancel_timer_a = actions
.iter()
.any(|a| matches!(a, InviteClientAction::CancelTimer(Timer::A)));
assert!(has_provisional, "Should emit provisional event");
assert!(has_cancel_timer_a, "Should cancel Timer A");
assert_eq!(tx.state(), InviteClientState::Proceeding);
}
#[test]
fn test_multiple_provisional_responses() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
tx.handle_response(create_response(&invite, 180));
tx.poll_actions();
assert_eq!(tx.state(), InviteClientState::Proceeding);
tx.handle_response(create_response(&invite, 183));
let actions = tx.poll_actions();
let has_provisional = actions.iter().any(|a| {
matches!(
a,
InviteClientAction::Event(InviteClientEvent::Provisional(_))
)
});
assert!(has_provisional);
assert_eq!(tx.state(), InviteClientState::Proceeding);
}
#[test]
fn test_success_response_from_calling() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&invite, 200);
tx.handle_response(response);
let actions = tx.poll_actions();
let has_success = actions
.iter()
.any(|a| matches!(a, InviteClientAction::Event(InviteClientEvent::Success(_))));
let has_cancel_timer_a = actions
.iter()
.any(|a| matches!(a, InviteClientAction::CancelTimer(Timer::A)));
let has_cancel_timer_b = actions
.iter()
.any(|a| matches!(a, InviteClientAction::CancelTimer(Timer::B)));
assert!(has_success, "Should emit success event");
assert!(has_cancel_timer_a, "Should cancel Timer A");
assert!(has_cancel_timer_b, "Should cancel Timer B");
assert_eq!(tx.state(), InviteClientState::Terminated);
}
#[test]
fn test_success_response_from_proceeding() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
tx.handle_response(create_response(&invite, 180));
tx.poll_actions();
tx.handle_response(create_response(&invite, 200));
let actions = tx.poll_actions();
let has_success = actions
.iter()
.any(|a| matches!(a, InviteClientAction::Event(InviteClientEvent::Success(_))));
assert!(has_success);
assert_eq!(tx.state(), InviteClientState::Terminated);
}
#[test]
fn test_failure_response_to_completed() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&invite, 404);
tx.handle_response(response);
let actions = tx.poll_actions();
let has_failure = actions
.iter()
.any(|a| matches!(a, InviteClientAction::Event(InviteClientEvent::Failure(_))));
let has_send = actions
.iter()
.any(|a| matches!(a, InviteClientAction::Send(_)));
let has_timer_d = actions
.iter()
.any(|a| matches!(a, InviteClientAction::SetTimer(Timer::D, _)));
assert!(has_failure, "Should emit failure event");
assert!(has_send, "Should send ACK");
assert!(has_timer_d || tx.state() == InviteClientState::Terminated);
assert!(
tx.state() == InviteClientState::Completed
|| tx.state() == InviteClientState::Terminated
);
}
#[test]
fn test_timer_d_terminates() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
tx.handle_response(create_response(&invite, 404));
tx.poll_actions();
if tx.state() == InviteClientState::Completed {
tx.handle_timeout(Timer::D);
assert_eq!(tx.state(), InviteClientState::Terminated);
}
}
#[test]
fn test_retransmitted_failure_in_completed() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&invite, 404);
tx.handle_response(response.clone());
tx.poll_actions();
if tx.state() == InviteClientState::Completed {
tx.handle_response(response);
let actions = tx.poll_actions();
let has_send = actions
.iter()
.any(|a| matches!(a, InviteClientAction::Send(_)));
assert!(has_send, "Should retransmit ACK");
assert_eq!(tx.state(), InviteClientState::Completed);
}
}
#[test]
fn test_reliable_transport_no_timer_a() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, true).unwrap();
let actions = tx.poll_actions();
let has_timer_a = actions
.iter()
.any(|a| matches!(a, InviteClientAction::SetTimer(Timer::A, _)));
assert!(!has_timer_a, "Reliable transport should not set Timer A");
}
#[test]
fn test_reliable_transport_uses_timer_b() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, true).unwrap();
let actions = tx.poll_actions();
let has_timer_b = actions
.iter()
.any(|a| matches!(a, InviteClientAction::SetTimer(Timer::B, _)));
assert!(has_timer_b, "Reliable transport should still set Timer B");
}
#[test]
fn test_reliable_transport_timer_d_zero() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), true).unwrap();
tx.poll_actions();
tx.handle_response(create_response(&invite, 404));
let actions = tx.poll_actions();
let has_timer_d = actions
.iter()
.any(|a| matches!(a, InviteClientAction::SetTimer(Timer::D, _)));
if has_timer_d {
for action in actions {
if let InviteClientAction::SetTimer(Timer::D, duration) = action {
assert_eq!(
duration.as_millis(),
0,
"Timer D should be 0 for reliable transport"
);
}
}
}
}
}
#[cfg(test)]
mod non_invite_client_transaction {
use super::*;
#[test]
fn test_initial_state_trying() {
let request = create_register();
let tx = NonInviteClientTransaction::new(request, false).unwrap();
assert_eq!(tx.state(), NonInviteClientState::Trying);
}
#[test]
fn test_timer_e_retransmit() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request, false).unwrap();
tx.poll_actions();
tx.handle_timeout(Timer::E);
let actions = tx.poll_actions();
let has_send = actions
.iter()
.any(|a| matches!(a, NonInviteClientAction::Send(_)));
let has_timer_e = actions
.iter()
.any(|a| matches!(a, NonInviteClientAction::SetTimer(Timer::E, _)));
assert!(has_send, "Timer E should trigger retransmission");
assert!(has_timer_e, "Timer E should be reset");
}
#[test]
fn test_timer_f_timeout() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request, false).unwrap();
tx.poll_actions();
tx.handle_timeout(Timer::F);
let actions = tx.poll_actions();
let has_timeout = actions.iter().any(|a| {
matches!(
a,
NonInviteClientAction::Event(NonInviteClientEvent::Timeout)
)
});
assert!(has_timeout, "Timer F should trigger timeout");
assert_eq!(tx.state(), NonInviteClientState::Terminated);
}
#[test]
fn test_provisional_to_proceeding() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&request, 100);
tx.handle_response(response);
let actions = tx.poll_actions();
let has_provisional = actions.iter().any(|a| {
matches!(
a,
NonInviteClientAction::Event(NonInviteClientEvent::Provisional(_))
)
});
assert!(has_provisional);
assert_eq!(tx.state(), NonInviteClientState::Proceeding);
}
#[test]
fn test_final_response_from_trying() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&request, 200);
tx.handle_response(response);
let actions = tx.poll_actions();
let has_response = actions.iter().any(|a| {
matches!(
a,
NonInviteClientAction::Event(NonInviteClientEvent::FinalResponse(_))
)
});
assert!(has_response);
assert!(
tx.state() == NonInviteClientState::Completed
|| tx.state() == NonInviteClientState::Terminated
);
}
#[test]
fn test_final_response_from_proceeding() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
tx.handle_response(create_response(&request, 100));
tx.poll_actions();
tx.handle_response(create_response(&request, 200));
let actions = tx.poll_actions();
let has_response = actions.iter().any(|a| {
matches!(
a,
NonInviteClientAction::Event(NonInviteClientEvent::FinalResponse(_))
)
});
assert!(has_response);
assert!(
tx.state() == NonInviteClientState::Completed
|| tx.state() == NonInviteClientState::Terminated
);
}
#[test]
fn test_timer_k_terminates() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
tx.handle_response(create_response(&request, 200));
tx.poll_actions();
if tx.state() == NonInviteClientState::Completed {
tx.handle_timeout(Timer::K);
assert_eq!(tx.state(), NonInviteClientState::Terminated);
}
}
#[test]
fn test_retransmitted_response_in_completed() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&request, 200);
tx.handle_response(response.clone());
tx.poll_actions();
if tx.state() == NonInviteClientState::Completed {
tx.handle_response(response);
assert_eq!(tx.state(), NonInviteClientState::Completed);
}
}
#[test]
fn test_reliable_no_timer_e() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request, true).unwrap();
let actions = tx.poll_actions();
let has_timer_e = actions
.iter()
.any(|a| matches!(a, NonInviteClientAction::SetTimer(Timer::E, _)));
assert!(!has_timer_e, "Reliable transport should not set Timer E");
}
#[test]
fn test_reliable_uses_timer_f() {
let request = create_register();
let mut tx = NonInviteClientTransaction::new(request, true).unwrap();
let actions = tx.poll_actions();
let has_timer_f = actions
.iter()
.any(|a| matches!(a, NonInviteClientAction::SetTimer(Timer::F, _)));
assert!(has_timer_f, "Reliable transport should use Timer F");
}
}
#[cfg(test)]
mod invite_server_transaction {
use super::*;
#[test]
fn test_initial_state_proceeding() {
let invite = create_invite();
let tx = InviteServerTransaction::new(invite, false).unwrap();
assert_eq!(tx.state(), InviteServerState::Proceeding);
}
#[test]
fn test_send_provisional_stays_proceeding() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&invite, 180);
tx.send_response(response);
tx.poll_actions();
assert_eq!(tx.state(), InviteServerState::Proceeding);
}
#[test]
fn test_send_success_terminates() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&invite, 200);
tx.send_response(response);
tx.poll_actions();
assert_eq!(tx.state(), InviteServerState::Terminated);
}
#[test]
fn test_send_failure_to_completed() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&invite, 404);
tx.send_response(response);
tx.poll_actions();
assert_eq!(tx.state(), InviteServerState::Completed);
}
#[test]
fn test_retransmit_in_proceeding() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&invite, 180);
tx.send_response(response);
tx.poll_actions();
tx.handle_request(invite);
let actions = tx.poll_actions();
let has_send = actions
.iter()
.any(|a| matches!(a, InviteServerAction::Send(_)));
assert!(has_send, "Should retransmit last response");
assert_eq!(tx.state(), InviteServerState::Proceeding);
}
#[test]
fn test_ack_to_confirmed() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
tx.send_response(create_response(&invite, 404));
tx.poll_actions();
if tx.state() == InviteServerState::Completed {
let ack = SipRequest::builder()
.method(Method::Ack)
.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)
.build()
.unwrap();
tx.handle_request(ack);
assert_eq!(tx.state(), InviteServerState::Confirmed);
}
}
#[test]
fn test_timer_g_retransmits() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
tx.send_response(create_response(&invite, 404));
tx.poll_actions();
if tx.state() == InviteServerState::Completed {
tx.handle_timeout(Timer::G);
let actions = tx.poll_actions();
let has_send = actions
.iter()
.any(|a| matches!(a, InviteServerAction::Send(_)));
let has_timer_g = actions
.iter()
.any(|a| matches!(a, InviteServerAction::SetTimer(Timer::G, _)));
assert!(has_send, "Timer G should retransmit response");
assert!(has_timer_g, "Timer G should be reset");
}
}
#[test]
fn test_timer_h_timeout() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
tx.send_response(create_response(&invite, 404));
tx.poll_actions();
if tx.state() == InviteServerState::Completed {
tx.handle_timeout(Timer::H);
assert_eq!(tx.state(), InviteServerState::Terminated);
}
}
#[test]
fn test_timer_i_terminates() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
tx.send_response(create_response(&invite, 404));
tx.poll_actions();
if tx.state() == InviteServerState::Completed {
let ack = SipRequest::builder()
.method(Method::Ack)
.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)
.build()
.unwrap();
tx.handle_request(ack);
if tx.state() == InviteServerState::Confirmed {
tx.handle_timeout(Timer::I);
assert_eq!(tx.state(), InviteServerState::Terminated);
}
}
}
#[test]
fn test_reliable_no_timer_g() {
let invite = create_invite();
let mut tx = InviteServerTransaction::new(invite.clone(), true).unwrap();
tx.poll_actions();
tx.send_response(create_response(&invite, 404));
let actions = tx.poll_actions();
let has_timer_g = actions
.iter()
.any(|a| matches!(a, InviteServerAction::SetTimer(Timer::G, _)));
assert!(!has_timer_g, "Reliable transport should not set Timer G");
}
}
#[cfg(test)]
mod non_invite_server_transaction {
use super::*;
#[test]
fn test_initial_state_trying() {
let request = create_register();
let tx = NonInviteServerTransaction::new(request, false).unwrap();
assert_eq!(tx.state(), NonInviteServerState::Trying);
}
#[test]
fn test_provisional_to_proceeding() {
let request = create_register();
let mut tx = NonInviteServerTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&request, 100);
tx.send_response(response);
tx.poll_actions();
assert_eq!(tx.state(), NonInviteServerState::Proceeding);
}
#[test]
fn test_final_from_trying_to_completed() {
let request = create_register();
let mut tx = NonInviteServerTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
let response = create_response(&request, 200);
tx.send_response(response);
tx.poll_actions();
assert!(
tx.state() == NonInviteServerState::Completed
|| tx.state() == NonInviteServerState::Terminated
);
}
#[test]
fn test_final_from_proceeding_to_completed() {
let request = create_register();
let mut tx = NonInviteServerTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
tx.send_response(create_response(&request, 100));
tx.poll_actions();
tx.send_response(create_response(&request, 200));
tx.poll_actions();
assert!(
tx.state() == NonInviteServerState::Completed
|| tx.state() == NonInviteServerState::Terminated
);
}
#[test]
fn test_retransmit_sends_last_response() {
let request = create_register();
let mut tx = NonInviteServerTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
tx.send_response(create_response(&request, 200));
tx.poll_actions();
if tx.state() == NonInviteServerState::Completed {
tx.handle_request(request);
let actions = tx.poll_actions();
let has_send = actions
.iter()
.any(|a| matches!(a, NonInviteServerAction::Send(_)));
assert!(has_send, "Should retransmit last response");
}
}
#[test]
fn test_timer_j_terminates() {
let request = create_register();
let mut tx = NonInviteServerTransaction::new(request.clone(), false).unwrap();
tx.poll_actions();
tx.send_response(create_response(&request, 200));
tx.poll_actions();
if tx.state() == NonInviteServerState::Completed {
tx.handle_timeout(Timer::J);
assert_eq!(tx.state(), NonInviteServerState::Terminated);
}
}
#[test]
fn test_reliable_timer_j_zero() {
let request = create_register();
let mut tx = NonInviteServerTransaction::new(request.clone(), true).unwrap();
tx.poll_actions();
tx.send_response(create_response(&request, 200));
let actions = tx.poll_actions();
let has_timer_j = actions
.iter()
.any(|a| matches!(a, NonInviteServerAction::SetTimer(Timer::J, _)));
if has_timer_j {
for action in actions {
if let NonInviteServerAction::SetTimer(Timer::J, duration) = action {
assert_eq!(
duration.as_millis(),
0,
"Timer J should be 0 for reliable transport"
);
}
}
}
}
}