#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
// `caller`/`callee` are the words for these two roles, and renaming them to satisfy a
// similarity heuristic would make the test harder to read than the heuristic is worth.
clippy::similar_names
)]
use std::time::Duration;
use bytes::Bytes;
use sipx_sip::transaction::{
Dispatch, Output, Reliability, Timer, TransactionKey, TransactionLayer, TuEvent,
};
use sipx_sip::{HeaderName, Limits, Method, Request, StatusCode, parse_datagram};
use sipx_testkit::link::{Faults, Link, Side};
use sipx_transport::timers::TimerQueue;
use tokio::time::Instant;
struct Stack {
layer: TransactionLayer,
timers: TimerQueue<(TransactionKey, Timer)>,
}
impl Stack {
fn new() -> Self {
Self {
layer: TransactionLayer::new(sipx_sip::transaction::Timers::default()),
timers: TimerQueue::new(),
}
}
fn perform(
&mut self,
key: &TransactionKey,
outputs: Vec<Output>,
now: Instant,
events: &mut Vec<TuEvent>,
) -> Vec<Bytes> {
let mut wire = Vec::new();
for output in outputs {
match output {
Output::Send(message) => wire.push(message.to_bytes()),
Output::SetTimer { timer, after } => {
self.timers.set((key.clone(), timer), now, after);
}
Output::ClearTimer(timer) => self.timers.clear(&(key.clone(), timer)),
Output::ToTu(event) => events.push(*event),
Output::Terminated(_) => self.timers.forget_matching(|(k, _)| k == key),
}
}
wire
}
fn on_timers(&mut self, now: Instant, events: &mut Vec<TuEvent>) -> Vec<Bytes> {
let mut wire = Vec::new();
for (key, timer) in self.timers.take_due(now) {
let outputs = self.layer.on_timer(&key, timer);
wire.extend(self.perform(&key, outputs, now, events));
}
wire
}
fn receive(&mut self, bytes: &Bytes, now: Instant, events: &mut Vec<TuEvent>) -> Vec<Bytes> {
let Ok(message) = parse_datagram(bytes.clone(), &Limits::datagram()) else {
return Vec::new();
};
match self.layer.receive(message, Reliability::Unreliable) {
Dispatch::Created { key, outputs } | Dispatch::Matched { key, outputs } => {
self.perform(&key, outputs, now, events)
}
Dispatch::Unmatched(_) => Vec::new(),
}
}
}
fn invite() -> Request {
sipx_sip::build::RequestBuilder::new(
Method::Invite,
sipx_sip::Uri::sip(sipx_sip::Host::Name(
sipx_sip::HostName::new("callee.example").expect("valid"),
)),
)
.header(
HeaderName::Via,
Bytes::from_static(b"SIP/2.0/UDP caller.example;branch=z9hG4bK-loopback-1"),
)
.expect("via")
.header(HeaderName::To, Bytes::from_static(b"<sip:callee.example>"))
.expect("to")
.header(
HeaderName::From,
Bytes::from_static(b"<sip:caller@example.net>;tag=abc"),
)
.expect("from")
.header(HeaderName::CallId, Bytes::from_static(b"loopback@sipx"))
.expect("call-id")
.cseq(1, &Method::Invite)
.expect("cseq")
.max_forwards(70)
.build()
}
#[tokio::test(start_paused = true)]
async fn a_retransmission_gets_through_a_link_that_drops_the_first_datagram() {
let mut caller = Stack::new();
let mut callee = Stack::new();
let mut link = Link::new(1, Faults::losing(1.0));
let epoch = Instant::now();
let mut caller_events = Vec::new();
let mut callee_events = Vec::new();
let (key, outputs) = caller
.layer
.send_request(invite(), Reliability::Unreliable)
.expect("a client transaction");
for bytes in caller.perform(&key, outputs, epoch, &mut caller_events) {
link.send(Side::Left, bytes, epoch);
}
assert_eq!(link.dropped(), 1, "the first INVITE is lost");
assert!(
link.take_due(epoch).is_empty(),
"and nothing arrives at the callee"
);
link = Link::perfect();
let at_t1 = epoch + Duration::from_millis(500);
let retransmitted = caller.on_timers(at_t1, &mut caller_events);
assert_eq!(
retransmitted.len(),
1,
"Timer A must produce exactly one retransmission"
);
for bytes in retransmitted {
link.send(Side::Left, bytes, at_t1);
}
let arrived = link.take_due(at_t1);
assert_eq!(arrived.len(), 1, "the retransmission gets through");
for delivery in arrived {
assert_eq!(delivery.to, Side::Right);
let _ = callee.receive(&delivery.bytes, at_t1, &mut callee_events);
}
assert!(
callee_events.iter().any(
|event| matches!(event, TuEvent::Request(request) if request.method == Method::Invite)
),
"the callee's transaction user should see the INVITE that the retransmission carried"
);
}
#[tokio::test(start_paused = true)]
async fn a_full_exchange_crosses_a_perfect_link() {
let mut caller = Stack::new();
let mut callee = Stack::new();
let mut link = Link::perfect();
let now = Instant::now();
let mut caller_events = Vec::new();
let mut callee_events = Vec::new();
let (key, outputs) = caller
.layer
.send_request(invite(), Reliability::Unreliable)
.expect("a client transaction");
for bytes in caller.perform(&key, outputs, now, &mut caller_events) {
link.send(Side::Left, bytes, now);
}
for _ in 0..8u32 {
let arrived = link.take_due(now);
if arrived.is_empty() {
break;
}
for delivery in arrived {
let (stack, events) = match delivery.to {
Side::Right => (&mut callee, &mut callee_events),
Side::Left => (&mut caller, &mut caller_events),
};
let before = events.len();
for bytes in stack.receive(&delivery.bytes, now, events) {
link.send(delivery.to, bytes, now);
}
if delivery.to == Side::Right && events.len() > before {
if let Some(key) = &TransactionKey::from_request(&invite()) {
let ok = sipx_sip::build::ResponseBuilder::to_request(
&invite(),
StatusCode::new(200).expect("valid"),
"OK",
)
.expect("builds")
.header(
HeaderName::Contact,
Bytes::from_static(b"<sip:callee@callee.example>"),
)
.expect("contact")
.build();
let outputs = callee.layer.send_response(key, ok);
for bytes in callee.perform(key, outputs, now, &mut callee_events) {
link.send(Side::Right, bytes, now);
}
}
}
}
}
assert!(
caller_events.iter().any(|event| matches!(
event,
TuEvent::Response(response) if response.status.code() == 200
)),
"the caller's transaction user should see the 200: {caller_events:?}"
);
}
#[tokio::test(start_paused = true)]
async fn a_request_survives_a_link_that_loses_half_of_everything() {
let mut caller = Stack::new();
let mut callee = Stack::new();
let mut link = Link::new(11, Faults::losing(0.5));
let mut now = Instant::now();
let mut caller_events = Vec::new();
let mut callee_events = Vec::new();
let (key, outputs) = caller
.layer
.send_request(invite(), Reliability::Unreliable)
.expect("a client transaction");
for bytes in caller.perform(&key, outputs, now, &mut caller_events) {
link.send(Side::Left, bytes, now);
}
for _ in 0..8u32 {
for delivery in link.take_due(now) {
if delivery.to == Side::Right {
let _ = callee.receive(&delivery.bytes, now, &mut callee_events);
}
}
if !callee_events.is_empty() {
break;
}
now += Duration::from_millis(500);
for bytes in caller.on_timers(now, &mut caller_events) {
link.send(Side::Left, bytes, now);
}
}
assert!(
!callee_events.is_empty(),
"over eight retransmissions a link losing half its datagrams should still deliver one; \
dropped {} so far",
link.dropped()
);
assert!(link.dropped() > 0, "and the link really did lose some");
}