use crate::sip::{Method, SipRequest, SipResponse, Via};
use crate::transaction::timer::{Timer, TimerValues};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TransactionId {
pub branch: String,
pub method: Method,
}
impl TransactionId {
pub fn from_request(req: &SipRequest) -> Option<Self> {
let branch = req.via_branch().ok()?;
Some(Self {
branch,
method: req.method(),
})
}
pub fn from_response(resp: &SipResponse) -> Option<Self> {
let branch = resp.via_branch().ok()?;
let method = resp.cseq_method().ok()?;
Some(Self { branch, method })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State {
Calling,
Proceeding,
Completed,
Terminated,
}
#[derive(Debug, Clone)]
pub enum Action {
Send(bytes::Bytes),
Event(Event),
SetTimer(Timer, Duration),
CancelTimer(Timer),
}
#[derive(Debug, Clone)]
pub enum Event {
Provisional(SipResponse),
Success(SipResponse),
Failure(SipResponse),
Timeout,
TransportError,
}
#[derive(Debug)]
pub struct InviteClientTransaction {
id: TransactionId,
state: State,
request: SipRequest,
timers: TimerValues,
reliable: bool,
retransmit_interval: Duration,
actions: Vec<Action>,
}
impl InviteClientTransaction {
pub fn new(request: SipRequest, reliable: bool) -> Option<Self> {
if request.method() != Method::Invite {
return None;
}
let id = TransactionId::from_request(&request)?;
let timers = TimerValues::default();
let retransmit_interval = timers.timer_a();
let mut tx = Self {
id,
state: State::Calling,
request,
timers,
reliable,
retransmit_interval,
actions: Vec::new(),
};
tx.actions.push(Action::Send(tx.request.to_bytes()));
if !reliable {
tx.actions
.push(Action::SetTimer(Timer::A, tx.retransmit_interval));
}
tx.actions
.push(Action::SetTimer(Timer::B, tx.timers.timer_b()));
Some(tx)
}
pub fn id(&self) -> &TransactionId {
&self.id
}
pub fn state(&self) -> State {
self.state
}
pub fn is_terminated(&self) -> bool {
self.state == State::Terminated
}
pub fn handle_timeout(&mut self, timer: Timer) {
match (self.state, timer) {
(State::Calling, Timer::A) => {
self.actions.push(Action::Send(self.request.to_bytes()));
self.retransmit_interval = self.timers.next_retransmit(self.retransmit_interval);
self.actions
.push(Action::SetTimer(Timer::A, self.retransmit_interval));
}
(State::Calling, Timer::B) => {
self.state = State::Terminated;
self.actions.push(Action::Event(Event::Timeout));
}
(State::Proceeding, Timer::B) => {
self.state = State::Terminated;
self.actions.push(Action::Event(Event::Timeout));
}
(State::Completed, Timer::D) => {
self.state = State::Terminated;
}
_ => {
}
}
}
pub fn handle_response(&mut self, response: SipResponse) {
let code = response.status_code();
match self.state {
State::Calling => {
if (100..200).contains(&code) {
self.state = State::Proceeding;
if !self.reliable {
self.actions.push(Action::CancelTimer(Timer::A));
}
self.actions
.push(Action::Event(Event::Provisional(response)));
} else if (200..300).contains(&code) {
self.state = State::Terminated;
self.actions.push(Action::CancelTimer(Timer::A));
self.actions.push(Action::CancelTimer(Timer::B));
self.actions.push(Action::Event(Event::Success(response)));
} else if code >= 300 {
self.state = State::Completed;
self.actions.push(Action::CancelTimer(Timer::A));
self.actions.push(Action::CancelTimer(Timer::B));
self.send_ack(&response);
self.actions.push(Action::Event(Event::Failure(response)));
let timer_d = if self.reliable {
Duration::ZERO
} else {
self.timers.timer_d()
};
if timer_d.is_zero() {
self.state = State::Terminated;
} else {
self.actions.push(Action::SetTimer(Timer::D, timer_d));
}
}
}
State::Proceeding => {
if (100..200).contains(&code) {
self.actions
.push(Action::Event(Event::Provisional(response)));
} else if (200..300).contains(&code) {
self.state = State::Terminated;
self.actions.push(Action::CancelTimer(Timer::B));
self.actions.push(Action::Event(Event::Success(response)));
} else if code >= 300 {
self.state = State::Completed;
self.actions.push(Action::CancelTimer(Timer::B));
self.send_ack(&response);
self.actions.push(Action::Event(Event::Failure(response)));
let timer_d = if self.reliable {
Duration::ZERO
} else {
self.timers.timer_d()
};
if timer_d.is_zero() {
self.state = State::Terminated;
} else {
self.actions.push(Action::SetTimer(Timer::D, timer_d));
}
}
}
State::Completed => {
if code >= 300 {
self.send_ack(&response);
}
}
State::Terminated => {
if (200..300).contains(&code) {
self.actions.push(Action::Event(Event::Success(response)));
}
}
}
}
fn send_ack(&mut self, _response: &SipResponse) {
let ack = build_ack_for_non_2xx(&self.request);
if let Some(ack) = ack {
self.actions.push(Action::Send(ack.to_bytes()));
}
}
pub fn poll_actions(&mut self) -> Vec<Action> {
std::mem::take(&mut self.actions)
}
pub fn handle_transport_error(&mut self) {
match self.state {
State::Calling | State::Proceeding => {
self.state = State::Terminated;
self.actions.push(Action::Event(Event::TransportError));
}
_ => {}
}
}
}
fn parse_via_or_default(via_raw: Option<&str>, branch: &str) -> Via {
via_raw
.and_then(|v| Via::parse(v).ok())
.unwrap_or_else(|| Via {
protocol: "UDP".to_string(),
host: "0.0.0.0".to_string(),
port: 5060,
branch: branch.to_string(),
received: None,
rport: None,
})
}
fn build_ack_for_non_2xx(invite: &SipRequest) -> Option<SipRequest> {
let branch = invite.via_branch().ok()?;
let call_id = invite.call_id().ok()?;
let (from_tag, from_uri) = invite.from_tag_and_uri().ok()?;
let via_raw = invite.via_headers_raw();
let via = parse_via_or_default(via_raw.first().map(String::as_str), &branch);
SipRequest::builder()
.method(Method::Ack)
.uri(&invite.uri().to_string())
.via(&via.host, via.port, &via.protocol, &branch)
.from(&from_uri.to_string(), &from_tag)
.to(&invite.to_uri().ok()?.to_string())
.call_id(&call_id)
.cseq(invite.cseq().ok()?)
.build()
.ok()
}
#[cfg(test)]
mod tests {
use super::*;
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 parse_request(raw: &[u8]) -> SipRequest {
let msg = crate::sip::SipMessage::parse(raw).unwrap();
msg.as_request().unwrap().clone()
}
fn parse_response(raw: &[u8]) -> SipResponse {
let msg = crate::sip::SipMessage::parse(raw).unwrap();
msg.as_response().unwrap().clone()
}
fn create_response(code: u16) -> SipResponse {
let invite = create_invite();
SipResponse::builder()
.status(code, "Test")
.from_request(&invite)
.to_tag("totag")
.build()
.unwrap()
}
#[test]
fn test_new_transaction() {
let invite = create_invite();
let tx = InviteClientTransaction::new(invite, false).unwrap();
assert_eq!(tx.state(), State::Calling);
assert!(!tx.is_terminated());
}
#[test]
fn test_provisional_response() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(180);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Proceeding);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::Provisional(_)))));
}
#[test]
fn test_success_response() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(200);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::Success(_)))));
}
#[test]
fn test_forked_2xx_after_first_2xx_reaches_tu() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite.clone(), false).unwrap();
tx.poll_actions();
let first = SipResponse::builder()
.status(200, "OK")
.from_request(&invite)
.to_tag("fork-A")
.build()
.unwrap();
tx.handle_response(first);
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
let first_tag = actions
.iter()
.find_map(|a| match a {
Action::Event(Event::Success(r)) => r.to_tag(),
_ => None,
})
.expect("first 2xx must produce a Success event");
assert_eq!(first_tag, "fork-A");
let second = SipResponse::builder()
.status(200, "OK")
.from_request(&invite)
.to_tag("fork-B")
.build()
.unwrap();
tx.handle_response(second);
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
let second_tag = actions
.iter()
.find_map(|a| match a {
Action::Event(Event::Success(r)) => r.to_tag(),
_ => None,
})
.expect("forked 2xx (fork-B) must reach the TU per RFC 3261 §13.2.2.4");
assert_eq!(second_tag, "fork-B");
}
#[test]
fn test_calling_response_below_100_ignored() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(99);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Calling);
assert!(tx.poll_actions().is_empty());
}
#[test]
fn test_failure_response_unreliable() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(404);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Completed);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::Failure(_)))));
assert!(actions
.iter()
.any(|a| matches!(a, Action::SetTimer(Timer::D, _))));
}
#[test]
fn test_proceeding_response_below_100_ignored() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let provisional = create_response(180);
tx.handle_response(provisional);
tx.poll_actions();
let resp = create_response(99);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Proceeding);
assert!(tx.poll_actions().is_empty());
}
#[test]
fn test_failure_response_reliable() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, true).unwrap();
tx.poll_actions();
let resp = create_response(404);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Terminated);
}
#[test]
fn test_timer_b_timeout() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, true).unwrap();
tx.poll_actions();
tx.handle_timeout(Timer::B);
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::Timeout))));
}
#[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);
assert_eq!(tx.state(), State::Calling);
let actions = tx.poll_actions();
assert!(actions.iter().any(|a| matches!(a, Action::Send(_))));
}
#[test]
fn test_timer_d_terminates() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(404);
tx.handle_response(resp);
tx.poll_actions();
assert_eq!(tx.state(), State::Completed);
tx.handle_timeout(Timer::D);
assert_eq!(tx.state(), State::Terminated);
}
#[test]
fn test_transaction_id_from_request() {
let invite = create_invite();
let id = TransactionId::from_request(&invite).unwrap();
assert_eq!(id.branch, "z9hG4bKtest");
assert_eq!(id.method, Method::Invite);
}
#[test]
fn test_transaction_id_from_response() {
let resp = create_response(200);
let id = TransactionId::from_response(&resp).expect("Expected TransactionId");
assert_eq!(id.branch, "z9hG4bKtest");
assert_eq!(id.method, Method::Invite);
}
#[test]
fn test_transaction_id_eq() {
let id1 = TransactionId {
branch: "z9hG4bKtest".to_string(),
method: Method::Invite,
};
let id2 = TransactionId {
branch: "z9hG4bKtest".to_string(),
method: Method::Invite,
};
assert_eq!(id1, id2);
}
#[test]
fn test_transaction_id_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
let id = TransactionId {
branch: "z9hG4bKtest".to_string(),
method: Method::Invite,
};
set.insert(id.clone());
assert!(set.contains(&id));
}
#[test]
fn test_transaction_id_debug() {
let id = TransactionId {
branch: "z9hG4bKtest".to_string(),
method: Method::Invite,
};
let debug = format!("{:?}", id);
assert!(debug.contains("TransactionId"));
}
#[test]
fn test_new_non_invite_returns_none() {
let req = SipRequest::builder()
.method(Method::Register)
.uri("sip:registrar@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();
let result = InviteClientTransaction::new(req, false);
assert!(result.is_none());
}
#[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();
assert!(actions.iter().any(|a| matches!(a, Action::Send(_))));
assert!(actions
.iter()
.any(|a| matches!(a, Action::SetTimer(Timer::B, _))));
assert!(!actions
.iter()
.any(|a| matches!(a, Action::SetTimer(Timer::A, _))));
}
#[test]
fn test_transaction_id_accessor() {
let invite = create_invite();
let tx = InviteClientTransaction::new(invite, false).unwrap();
let id = tx.id();
assert_eq!(id.branch, "z9hG4bKtest");
}
#[test]
fn test_handle_transport_error_calling() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
tx.handle_transport_error();
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::TransportError))));
}
#[test]
fn test_handle_transport_error_proceeding() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(180);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Proceeding);
tx.poll_actions();
tx.handle_transport_error();
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::TransportError))));
}
#[test]
fn test_handle_transport_error_completed_ignored() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(404);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Completed);
tx.poll_actions();
tx.handle_transport_error();
assert_eq!(tx.state(), State::Completed);
}
#[test]
fn test_timer_b_timeout_in_proceeding() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(180);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Proceeding);
tx.poll_actions();
tx.handle_timeout(Timer::B);
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::Timeout))));
}
#[test]
fn test_unexpected_timer_ignored() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
tx.handle_timeout(Timer::D);
assert_eq!(tx.state(), State::Calling);
}
#[test]
fn test_proceeding_another_provisional() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp1 = create_response(100);
tx.handle_response(resp1);
assert_eq!(tx.state(), State::Proceeding);
tx.poll_actions();
let resp2 = create_response(180);
tx.handle_response(resp2);
assert_eq!(tx.state(), State::Proceeding);
let actions = tx.poll_actions();
let has_provisional = actions
.iter()
.any(|a| matches!(a, Action::Event(Event::Provisional(_))));
assert!(has_provisional);
}
#[test]
fn test_proceeding_success_response() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp1 = create_response(180);
tx.handle_response(resp1);
assert_eq!(tx.state(), State::Proceeding);
tx.poll_actions();
let resp2 = create_response(200);
tx.handle_response(resp2);
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::Success(_)))));
assert!(actions
.iter()
.any(|a| matches!(a, Action::CancelTimer(Timer::B))));
}
#[test]
fn test_proceeding_failure_response_unreliable() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp1 = create_response(180);
tx.handle_response(resp1);
assert_eq!(tx.state(), State::Proceeding);
tx.poll_actions();
let resp2 = create_response(486);
tx.handle_response(resp2);
assert_eq!(tx.state(), State::Completed);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::Event(Event::Failure(_)))));
assert!(actions
.iter()
.any(|a| matches!(a, Action::SetTimer(Timer::D, _))));
}
#[test]
fn test_proceeding_failure_response_reliable() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, true).unwrap();
tx.poll_actions();
let resp1 = create_response(180);
tx.handle_response(resp1);
assert_eq!(tx.state(), State::Proceeding);
tx.poll_actions();
let resp2 = create_response(486);
tx.handle_response(resp2);
assert_eq!(tx.state(), State::Terminated);
}
#[test]
fn test_completed_retransmitted_response() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(404);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Completed);
tx.poll_actions();
let resp2 = create_response(404);
tx.handle_response(resp2);
assert_eq!(tx.state(), State::Completed);
let actions = tx.poll_actions();
assert!(actions.iter().any(|a| matches!(a, Action::Send(_))));
}
#[test]
fn test_terminated_response_ignored() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(200);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Terminated);
tx.poll_actions();
let resp2 = create_response(180);
tx.handle_response(resp2);
assert_eq!(tx.state(), State::Terminated);
let actions = tx.poll_actions();
assert!(actions.is_empty());
}
#[test]
#[allow(clippy::clone_on_copy)] fn test_state_enum_clone() {
let state = State::Calling;
let cloned = state.clone();
assert_eq!(state, cloned);
}
#[test]
fn test_state_enum_debug() {
assert!(format!("{:?}", State::Calling).contains("Calling"));
assert!(format!("{:?}", State::Proceeding).contains("Proceeding"));
assert!(format!("{:?}", State::Completed).contains("Completed"));
assert!(format!("{:?}", State::Terminated).contains("Terminated"));
}
#[test]
fn test_state_enum_copy() {
let state = State::Proceeding;
let copied: State = state; assert_eq!(state, copied);
}
#[test]
fn test_action_debug() {
let action = Action::SetTimer(Timer::A, Duration::from_millis(500));
let debug = format!("{:?}", action);
assert!(debug.contains("SetTimer"));
}
#[test]
fn test_action_clone() {
let action = Action::CancelTimer(Timer::B);
let cloned = action.clone();
assert!(format!("{cloned:?}").contains("CancelTimer"));
}
#[test]
fn test_event_debug() {
let event = Event::Timeout;
let debug = format!("{:?}", event);
assert!(debug.contains("Timeout"));
}
#[test]
fn test_event_clone() {
let event = Event::TransportError;
let cloned = event.clone();
assert!(format!("{cloned:?}").contains("TransportError"));
}
#[test]
fn test_invite_client_transaction_debug() {
let invite = create_invite();
let tx = InviteClientTransaction::new(invite, false).unwrap();
let debug = format!("{:?}", tx);
assert!(debug.contains("InviteClientTransaction"));
}
#[test]
fn test_calling_2xx_cancels_timers() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(200);
tx.handle_response(resp);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::CancelTimer(Timer::A))));
assert!(actions
.iter()
.any(|a| matches!(a, Action::CancelTimer(Timer::B))));
}
#[test]
fn test_calling_3xx_cancels_timers() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(302);
tx.handle_response(resp);
let actions = tx.poll_actions();
assert!(actions
.iter()
.any(|a| matches!(a, Action::CancelTimer(Timer::A))));
assert!(actions
.iter()
.any(|a| matches!(a, Action::CancelTimer(Timer::B))));
}
#[test]
fn test_proceeding_3xx_transitions_completed() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let provisional = create_response(180);
tx.handle_response(provisional);
assert_eq!(tx.state(), State::Proceeding);
tx.poll_actions();
let resp = create_response(404);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Completed);
}
#[test]
fn test_send_ack_missing_headers() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let raw = b"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060\r\n\
To: <sip:bob@example.com>\r\n\
From: <sip:alice@example.com>;tag=fromtag\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 req = parse_request(raw);
tx.request = req.clone();
let resp = create_response(404);
tx.send_ack(&resp);
let actions = tx.poll_actions();
assert!(actions.is_empty());
}
#[test]
fn test_transaction_id_from_response_missing_cseq() {
let raw = b"SIP/2.0 200 OK\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bK123\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\
Content-Length: 0\r\n\
\r\n";
let resp = parse_response(raw);
assert!(TransactionId::from_response(&resp).is_none());
}
#[test]
fn test_invite_client_transaction_new_missing_branch() {
let raw = b"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060\r\n\
To: <sip:bob@example.com>\r\n\
From: <sip:alice@example.com>;tag=fromtag\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 req = parse_request(raw);
let tx = InviteClientTransaction::new(req, false);
assert!(tx.is_none());
}
#[test]
fn test_build_ack_missing_from_tag() {
let raw = b"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
To: <sip:bob@example.com>\r\n\
From: <sip:alice@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 req = parse_request(raw);
assert!(build_ack_for_non_2xx(&req).is_none());
}
#[test]
fn test_build_ack_missing_call_id() {
let raw = b"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
To: <sip:bob@example.com>\r\n\
From: <sip:alice@example.com>;tag=fromtag\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 req = parse_request(raw);
assert!(build_ack_for_non_2xx(&req).is_none());
}
#[test]
fn test_build_ack_invalid_from_uri() {
let raw = b"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
To: <sip:bob@example.com>\r\n\
From: <sip:alice@[::1>;tag=fromtag\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 req = parse_request(raw);
assert!(build_ack_for_non_2xx(&req).is_none());
}
#[test]
fn test_build_ack_invalid_to_uri() {
let raw = b"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
To: <sip:bob@[::1>\r\n\
From: <sip:alice@example.com>;tag=fromtag\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 req = parse_request(raw);
assert!(build_ack_for_non_2xx(&req).is_none());
}
#[test]
fn test_build_ack_invalid_cseq() {
let raw = b"INVITE sip:bob@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.168.1.1:5060;branch=z9hG4bKtest\r\n\
To: <sip:bob@example.com>\r\n\
From: <sip:alice@example.com>;tag=fromtag\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 req = parse_request(raw);
assert!(build_ack_for_non_2xx(&req).is_none());
}
#[test]
fn test_parse_via_or_default_fallback() {
let via = parse_via_or_default(Some("invalid"), "z9hG4bK-test");
assert_eq!(via.protocol, "UDP");
assert_eq!(via.host, "0.0.0.0");
assert_eq!(via.port, 5060);
assert_eq!(via.branch, "z9hG4bK-test");
}
#[test]
fn test_completed_response_under_300_ignored() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(404);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Completed);
tx.poll_actions();
let resp2 = create_response(200);
tx.handle_response(resp2);
assert_eq!(tx.state(), State::Completed);
let actions = tx.poll_actions();
assert!(actions.is_empty());
}
#[test]
fn test_multiple_retransmits() {
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();
assert!(actions.iter().any(|a| matches!(a, Action::Send(_))));
assert!(actions
.iter()
.any(|a| matches!(a, Action::SetTimer(Timer::A, _))));
tx.handle_timeout(Timer::A);
let actions = tx.poll_actions();
assert!(actions.iter().any(|a| matches!(a, Action::Send(_))));
}
#[test]
fn test_timer_a_ignored_in_proceeding() {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(180);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Proceeding);
tx.poll_actions();
tx.handle_timeout(Timer::A);
assert_eq!(tx.state(), State::Proceeding);
let actions = tx.poll_actions();
assert!(actions.is_empty());
}
#[test]
fn test_various_failure_codes() {
for code in [300, 400, 500, 600, 603, 699] {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(code);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Completed);
}
}
#[test]
fn test_various_provisional_codes() {
for code in [100, 180, 181, 182, 183, 199] {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(code);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Proceeding);
}
}
#[test]
fn test_various_success_codes() {
for code in [200, 201, 202, 299] {
let invite = create_invite();
let mut tx = InviteClientTransaction::new(invite, false).unwrap();
tx.poll_actions();
let resp = create_response(code);
tx.handle_response(resp);
assert_eq!(tx.state(), State::Terminated);
}
}
}