sipx_call/dispatch.rs
1//! One endpoint's requests, routed to any number of concurrent calls (story `C-4`).
2//!
3//! An endpoint hands out exactly one [`Receiver<Incoming>`](tokio::sync::mpsc::Receiver). Every
4//! request that arrives, for any call in any dialog, comes out of that one stream — so an
5//! application holding more than one call had to write its own demultiplexer, and
6//! [`serve`](crate::serve) dropped whatever the single call it drove did not claim. Both are
7//! ways to lose an ACK, which is the loss that leaks calls: nothing retransmits it once Timer H
8//! expires and no timer reaps the dialog it would have completed.
9//!
10//! This is that demultiplexer, written once. It owns the receiver, routes each request to the
11//! call it belongs to, and gives every request that belongs to no call a defined answer instead
12//! of silence. The decision table, the counters and the vectors the tests are derived from are
13//! in [`docs/specs/call-dispatch.md`](../../../docs/specs/call-dispatch.md).
14//!
15//! ```no_run
16//! # async fn example(endpoint: sipx_transport::Handle,
17//! # incoming: tokio::sync::mpsc::Receiver<sipx_transport::Incoming>)
18//! # -> Result<(), Box<dyn std::error::Error>> {
19//! use std::net::IpAddr;
20//! use sipx_call::{Dispatched, Dispatcher, serve};
21//! use tokio::task::JoinSet;
22//!
23//! const MAX_CALLS: usize = 64;
24//! let media_address: IpAddr = "203.0.113.7".parse()?;
25//! let mut dispatcher = Dispatcher::new(endpoint.clone(), incoming);
26//! let mut calls = JoinSet::new();
27//! let outcome: Result<(), Box<dyn std::error::Error>> = async {
28//! loop {
29//! tokio::select! {
30//! event = dispatcher.next() => {
31//! let Some(event) = event else { break };
32//! if let Dispatched::Invitation(invitation) = event {
33//! if calls.len() >= MAX_CALLS {
34//! invitation.refuse(&endpoint, 503, "Service Unavailable").await?;
35//! continue;
36//! }
37//! let mut call = invitation.answer(&endpoint, media_address).await?;
38//! let (_, mut requests) = invitation.into_parts();
39//! calls.spawn(async move { serve(&mut call, &mut requests).await });
40//! }
41//! }
42//! joined = calls.join_next(), if !calls.is_empty() => {
43//! if let Some(joined) = joined { joined??; }
44//! }
45//! }
46//! }
47//! Ok(())
48//! }.await;
49//! calls.shutdown().await;
50//! outcome
51//! # }
52//! ```
53
54use std::collections::{BTreeMap, HashMap};
55use std::net::IpAddr;
56use std::sync::atomic::{AtomicU64, Ordering};
57use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
58use std::time::Duration;
59
60use bytes::Bytes;
61use futures_util::stream::{FuturesUnordered, StreamExt};
62use sipx_sip::build::ResponseBuilder;
63use sipx_sip::headers::CSeq;
64use sipx_sip::transaction::TransactionKey;
65use sipx_sip::{HeaderName, Method, Request, StatusCode};
66use sipx_transport::{Handle, Incoming};
67use tokio::sync::{Notify, mpsc, watch};
68
69use crate::call::{Call, token};
70use crate::dialog::{Dialog, cseq_number, from_tag, to_tag};
71use crate::error::{Error, Result};
72use crate::event::{CallEvents, EndCause, EventSink};
73use crate::identity::InboundIdentityPolicy;
74use crate::media_policy::{Codecs, MediaPolicy};
75use crate::notifier::Notifier;
76use crate::publication::Publications;
77use crate::subscriber::EventSubscriptions;
78
79/// How many requests one call's inbox holds before the dispatcher sheds for it.
80///
81/// Deep enough that a call busy with one signalling exchange does not shed the next request
82/// behind it, shallow enough that a task which has stopped reading is noticed rather than
83/// buffered for. Override with [`Dispatcher::with_queue`].
84pub const DEFAULT_QUEUE: usize = 16;
85
86/// Amortize dead-route collection instead of walking both routing indexes for every INVITE.
87///
88/// The interval is a memory bound as well as a CPU budget: at most this many newly dead routes
89/// can accumulate between insertion-path sweeps. Explicit [`Calls::forget`] and observations
90/// through [`Calls::len`] still remove routes immediately or on demand.
91const DEAD_ROUTE_SWEEP_INTERVAL: usize = 256;
92
93/// The `Retry-After` a shed request carries, in seconds.
94///
95/// The same value [`sipx_transport`] sheds with, for the same reason: the peer is being told the
96/// moment was wrong, not that the call is gone, and a number lets it act on that.
97const RETRY_AFTER: &[u8] = b"5";
98
99/// What the dispatcher could not place itself, handed to the application.
100#[derive(Debug)]
101#[non_exhaustive]
102pub enum Dispatched {
103 /// An INVITE that belongs to no live call: an incoming call.
104 ///
105 /// Answering, ringing or rejecting it is the application's decision — the dispatcher takes
106 /// none of them. Its inbox is already routed, so nothing that arrives while the application
107 /// decides can be missed.
108 Invitation(Invitation),
109 /// A request outside any dialog whose method this stack advertises but that matched no
110 /// route: an OPTIONS ping.
111 ///
112 /// Surfaced rather than refused because the `Allow` a 405 would carry
113 /// ([`sipx_sip::update::ALLOW`]) names it, and answering 405 to a method the same message
114 /// says is supported tells the peer two different things at once. What answers an OPTIONS is
115 /// a user agent (`sipx_ua::Agent::answer`), which the call framework does not have.
116 ///
117 /// **A CANCEL never arrives here** (`S-23`). It is the one advertised method the dispatcher
118 /// can place itself, because RFC 3261 §9.2 says exactly what to do with it and both halves of
119 /// the answer are the dispatcher's to give: the `200 OK` on the CANCEL's own transaction, and
120 /// the `487 Request Terminated` on the INVITE transaction it withdraws. One that matches no
121 /// pending INVITE transaction is answered `481` rather than handed over, because there is
122 /// nothing an application could usefully decide about a transaction this stack does not have.
123 OutOfDialog(Incoming),
124}
125
126/// An incoming call: the INVITE, and the inbox of the call it may become.
127///
128/// The inbox exists before the application has decided anything, and that is the point. The ACK
129/// to our own 2xx can arrive before `answer` has returned, so a route installed only once a
130/// [`Call`] existed would have nowhere to put it.
131///
132/// Dropping this without answering releases the route: the next request for that dialog is
133/// answered as an unknown one rather than queued for a call that will never exist.
134///
135/// It is also what a CANCEL for this INVITE ends (RFC 3261 §9.2). The dispatcher answers the
136/// CANCEL itself — [`is_cancelled`](Self::is_cancelled) and [`events`](Self::events) are how an
137/// application finds out, and [`answer`](Self::answer) is how it is stopped from accepting an
138/// invitation the caller has already withdrawn.
139#[derive(Debug)]
140pub struct Invitation {
141 incoming: Incoming,
142 requests: mpsc::Receiver<Incoming>,
143 /// Shared with the dispatcher's table: the INVITE server transaction a CANCEL names.
144 pending: Arc<Pending>,
145 /// Handed out once by [`Self::events`], as [`Call::events`](crate::Call::events) does.
146 events: Option<CallEvents>,
147}
148
149impl Invitation {
150 /// The INVITE, to answer, ring or refuse.
151 #[must_use]
152 pub fn request(&self) -> &Incoming {
153 &self.incoming
154 }
155
156 /// Whether the caller withdrew this invitation before it was answered (RFC 3261 §9.2).
157 ///
158 /// True from the moment the dispatcher has answered a matching CANCEL, which is also the
159 /// moment it sent the `487` that ended the INVITE transaction. There is nothing left to
160 /// accept: [`Self::answer`] refuses with [`Error::InvitationCancelled`] from here on.
161 ///
162 /// This is the poll. [`Self::events`] is the push, and an application that is *ringing* wants
163 /// that one — it has to be told to stop, not remember to ask.
164 #[must_use]
165 pub fn is_cancelled(&self) -> bool {
166 self.pending.is_cancelled()
167 }
168
169 /// This invitation's event stream, handed out exactly once.
170 ///
171 /// Returns `None` on every call after the first, the same contract
172 /// [`Call::events`](crate::Call::events) has and for the same reason: there is one consumer
173 /// by construction rather than a value a second reader could be cloned from.
174 ///
175 /// Exactly one event is ever emitted on it —
176 /// [`CallEvent::Ended`](crate::CallEvent::Ended)`(`[`EndCause::RemoteCancel`]`)`, when the
177 /// caller gives up. An invitation that is *answered* produces no event here: it becomes a
178 /// [`Call`], which has a stream of its own that starts with `Answered`.
179 #[must_use]
180 pub fn events(&mut self) -> Option<CallEvents> {
181 self.events.take()
182 }
183
184 /// Answer this invitation, unless the caller has already withdrawn it.
185 ///
186 /// [`crate::answer`] with two things the free function cannot know, both of which come from
187 /// the invitation owning the INVITE's server transaction:
188 ///
189 /// - It fails with [`Error::InvitationCancelled`] once a CANCEL has ended the transaction,
190 /// rather than putting a `200` on a transaction that already carried a `487`.
191 /// - It records that a final response has gone out, which is what makes a CANCEL arriving
192 /// afterwards the no-op RFC 3261 §9.2 requires instead of a teardown.
193 ///
194 /// The `To` tag is the invitation's own, so the `200` accepting it and the `200` answering a
195 /// late CANCEL agree on one, which is §9.2's `SHOULD`.
196 ///
197 /// Prefer this to [`crate::answer`] on anything a [`Dispatcher`] surfaced. The free function
198 /// still works and still answers correctly — it simply cannot tell the dispatcher what it
199 /// did, so a CANCEL that arrives around it is judged on the transaction's last known state.
200 ///
201 /// Answers from the default codec set, [`Codecs::G711`]. [`Self::answer_with`] takes a
202 /// selection.
203 pub async fn answer(&self, endpoint: &Handle, media_address: IpAddr) -> Result<Call> {
204 self.answer_with(endpoint, media_address, Codecs::default())
205 .await
206 }
207
208 /// [`Self::answer`], from a chosen codec set rather than the default one (`M-30`).
209 ///
210 /// The dispatcher's counterpart of [`crate::answer_with`]. This exists rather than being left
211 /// to the free function because this is the path the docs above tell an application to prefer:
212 /// a selection reachable only through [`crate::answer_with`] would be a selection every
213 /// dispatched call has to give up cancellation bookkeeping to make.
214 pub async fn answer_with(
215 &self,
216 endpoint: &Handle,
217 media_address: IpAddr,
218 codecs: Codecs,
219 ) -> Result<Call> {
220 self.answer_with_policy(
221 endpoint,
222 media_address,
223 MediaPolicy::default().with_codecs(codecs),
224 )
225 .await
226 }
227
228 /// [`Self::answer`], using one coherent codec and ICE policy.
229 pub async fn answer_with_policy(
230 &self,
231 endpoint: &Handle,
232 media_address: IpAddr,
233 policy: MediaPolicy,
234 ) -> Result<Call> {
235 self.answer_with_policy_at(endpoint, crate::MediaAddress::new(media_address), policy)
236 .await
237 }
238
239 /// [`Self::answer_with_policy`] with independent advertised and bound media addresses.
240 pub async fn answer_with_policy_at(
241 &self,
242 endpoint: &Handle,
243 media_address: crate::MediaAddress,
244 policy: MediaPolicy,
245 ) -> Result<Call> {
246 let tag = self.pending.tag();
247 // Handed down rather than taken here, so that the invitation is taken immediately before
248 // the `200` leaves rather than before the work that builds it — every step of which can
249 // fail with nothing sent, and an invitation taken by one of those is one no CANCEL can
250 // end. `answer_negotiated` documents the placement.
251 crate::call::answer_tagged(
252 endpoint,
253 &self.incoming,
254 media_address,
255 &tag,
256 Some(&|| self.pending.claim()),
257 policy,
258 &[],
259 )
260 .await
261 }
262
263 /// Send the optional `100 Trying` used by a signalling workload.
264 ///
265 /// A `100` creates neither an early dialog nor a final-response claim, so a matching CANCEL
266 /// may still end this invitation afterwards. It deliberately carries no `To` tag (RFC 3261
267 /// §8.2.6.2).
268 pub async fn trying(&self, endpoint: &Handle) -> Result<()> {
269 let status = StatusCode::new(100).ok_or_else(|| Error::Rejected {
270 status: 100,
271 reason: "invalid Trying status".to_owned(),
272 })?;
273 let response =
274 ResponseBuilder::to_request(&self.incoming.request, status, "Trying")?.build();
275 endpoint.respond(&self.incoming.key, response).await?;
276 Ok(())
277 }
278
279 /// Accept an SDP-free INVITE as a signalling-only confirmed dialog.
280 ///
281 /// No media socket or task is created. The returned [`SignallingCall`](crate::SignallingCall)
282 /// owns the reserved per-dialog inbox, retransmits this 2xx until a valid ACK, validates BYE,
283 /// and can originate a bounded BYE of its own. `contact` is the local dialog target advertised
284 /// in the final response.
285 pub async fn answer_signalling(
286 self,
287 endpoint: &Handle,
288 contact: impl Into<Bytes>,
289 ) -> Result<crate::SignallingCall> {
290 let tag = self.pending.tag();
291 self.answer_signalling_inner(endpoint, contact.into(), tag)
292 .await
293 }
294
295 /// [`Self::answer_signalling`] with an application-selected, validated dialog tag.
296 ///
297 /// This exists for deterministic protocol fixtures. The tag is claimed atomically with the
298 /// invitation, so a crossing CANCEL either wins with the old pending tag or loses and uses this
299 /// same tag in its own response; the two transactions cannot disagree.
300 pub async fn answer_signalling_with_tag(
301 self,
302 endpoint: &Handle,
303 contact: impl Into<Bytes>,
304 tag: impl Into<String>,
305 ) -> Result<crate::SignallingCall> {
306 self.answer_signalling_inner(endpoint, contact.into(), tag.into())
307 .await
308 }
309
310 async fn answer_signalling_inner(
311 self,
312 endpoint: &Handle,
313 contact: Bytes,
314 tag: String,
315 ) -> Result<crate::SignallingCall> {
316 // Every fallible shape check happens before the transaction is claimed. A malformed
317 // Contact or dialog cannot consume an invitation that a later CANCEL could still end.
318 let prepared = crate::signalling::prepare(endpoint, &self.incoming, &tag, contact)?;
319 self.pending.claim_with_tag(&tag)?;
320 crate::signalling::establish(endpoint.clone(), self.incoming, self.requests, prepared).await
321 }
322
323 /// Refuse this pending invitation with a final response.
324 ///
325 /// The dispatcher's cancellation state is claimed before the response leaves, so a crossing
326 /// CANCEL receives its own 200 but cannot also replace this final response with 487.
327 pub async fn refuse(
328 &self,
329 endpoint: &Handle,
330 status: u16,
331 reason: impl Into<Bytes>,
332 ) -> Result<()> {
333 self.pending.claim()?;
334 let tag = self.pending.tag();
335 final_response(endpoint, &self.incoming, &tag, status, reason).await
336 }
337
338 /// Split into the INVITE and the inbox, ready for
339 /// [`answer`](crate::answer) and [`serve`](crate::serve).
340 ///
341 /// What is given up is the cancellation state: [`Self::is_cancelled`] and [`Self::answer`] go
342 /// with it. The dispatcher keeps answering CANCELs for this transaction either way — the
343 /// table owns that, not this handle — so what is lost is the application's view of it, which
344 /// is why this is the call to make *after* answering rather than instead of it.
345 #[must_use]
346 pub fn into_parts(self) -> (Incoming, mpsc::Receiver<Incoming>) {
347 (self.incoming, self.requests)
348 }
349
350 /// Transfer this pending invitation to the two-dialog coupling driver.
351 pub(crate) fn into_coupling(self) -> CouplingInvitation {
352 CouplingInvitation {
353 incoming: self.incoming,
354 requests: self.requests,
355 pending: self.pending,
356 }
357 }
358}
359
360/// The invitation state retained by an early two-dialog coupling.
361#[derive(Debug)]
362pub(crate) struct CouplingInvitation {
363 pub(crate) incoming: Incoming,
364 pub(crate) requests: mpsc::Receiver<Incoming>,
365 pending: Arc<Pending>,
366}
367
368impl CouplingInvitation {
369 pub(crate) fn cancellation(&self) -> CouplingCancellation {
370 CouplingCancellation(Arc::clone(&self.pending))
371 }
372
373 pub(crate) fn claim(&self) -> Result<()> {
374 self.pending.claim()
375 }
376
377 pub(crate) async fn refuse(
378 &self,
379 endpoint: &Handle,
380 status: u16,
381 reason: impl Into<Bytes>,
382 ) -> Result<()> {
383 self.pending.claim()?;
384 let tag = self.pending.tag();
385 final_response(endpoint, &self.incoming, &tag, status, reason).await
386 }
387}
388
389async fn final_response(
390 endpoint: &Handle,
391 incoming: &Incoming,
392 tag: &str,
393 status: u16,
394 reason: impl Into<Bytes>,
395) -> Result<()> {
396 let status = StatusCode::new(status).ok_or_else(|| Error::Rejected {
397 status,
398 reason: "invalid final response status".to_owned(),
399 })?;
400 let response = ResponseBuilder::to_request(&incoming.request, status, reason)
401 .and_then(|builder| with_to_tag(builder, &incoming.request, Some(tag)))?
402 .build();
403 endpoint.respond(&incoming.key, response).await?;
404 Ok(())
405}
406
407#[derive(Debug, Clone)]
408pub(crate) struct CouplingCancellation(Arc<Pending>);
409
410impl CouplingCancellation {
411 pub(crate) async fn cancelled(&self) {
412 loop {
413 let notified = self.0.cancelled.notified();
414 if self.0.is_cancelled() {
415 return;
416 }
417 notified.await;
418 }
419 }
420}
421
422/// One INVITE server transaction the dispatcher surfaced, and what RFC 3261 §9.2 needs to end it.
423///
424/// Shared between the [`Invitation`] the application holds and the dispatcher's table, because
425/// both have half the question: the dispatcher sees the CANCEL arrive, the application decides
426/// whether the invitation was answered first, and §9.2's rule is about which of those happened.
427#[derive(Debug)]
428struct Pending {
429 /// The INVITE's own server transaction — where the `487` goes.
430 ///
431 /// Not the CANCEL's. The whole difficulty of §9.2 is that the answer is two responses on two
432 /// transactions, and a stack that keeps only one key can only ever send one of them.
433 transaction: TransactionKey,
434 /// The INVITE, which the `487` is built from.
435 request: Request,
436 /// The route this invitation reserved, kept only so a finished one can be swept.
437 ///
438 /// A transaction whose call has dropped its inbox is gone as far as anything here is
439 /// concerned, and the table would otherwise hold its INVITE for the life of the dispatcher.
440 route: mpsc::Sender<Incoming>,
441 state: Mutex<State>,
442 cancelled: Notify,
443}
444
445/// Where an invitation is, and where its one event goes.
446///
447/// One mutex over both because they change together: the transition to `Cancelled` *is* the
448/// emission of `Ended`, and a reader that saw one without the other would see a cancelled
449/// invitation nobody was told about, or a report of an end that had not happened yet.
450#[derive(Debug)]
451struct State {
452 phase: Phase,
453 events: EventSink,
454 /// The `To` tag every response this side sends about this invitation carries (§9.2).
455 ///
456 /// Kept under the phase lock so a deterministic signalling answer can replace it atomically
457 /// with claiming the invitation, while CANCEL observes either the complete before or after
458 /// state.
459 tag: String,
460}
461
462/// The three states RFC 3261 §9.2 distinguishes, and the only three it needs.
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464enum Phase {
465 /// No final response has been sent. A CANCEL ends it.
466 Ringing,
467 /// A final response has been sent. §9.2: "if it has already sent a final response ... the
468 /// CANCEL request has no effect" — BYE is what ends what was answered.
469 Answered,
470 /// A CANCEL ended it, and the `487` has gone out. It cannot be answered.
471 Cancelled,
472}
473
474impl Pending {
475 fn tag(&self) -> String {
476 self.lock().tag.clone()
477 }
478
479 /// The state, whether or not a previous holder panicked.
480 ///
481 /// The same reasoning as [`Calls::lock`]: the critical section is a field write and a
482 /// non-blocking send, so a poisoned lock cannot mean a half-made transition — and refusing to
483 /// answer a CANCEL because some unrelated task unwound would be the worse failure.
484 fn lock(&self) -> MutexGuard<'_, State> {
485 self.state.lock().unwrap_or_else(PoisonError::into_inner)
486 }
487
488 fn is_cancelled(&self) -> bool {
489 self.lock().phase == Phase::Cancelled
490 }
491
492 /// Take the invitation for a final response of our own.
493 ///
494 /// Marked *before* the `200` is built rather than after it is sent, and the asymmetry is
495 /// deliberate: a CANCEL that arrives mid-answer must not draw a `487` chasing a `200` down
496 /// the wire, which is the one ordering that leaves the caller and the callee disagreeing
497 /// about whether there is a call. The other way round — an answer that then fails — costs
498 /// the caller a CANCEL that says `200` and ends nothing, which its own Timer B resolves.
499 fn claim(&self) -> Result<()> {
500 let mut state = self.lock();
501 if state.phase == Phase::Cancelled {
502 return Err(Error::InvitationCancelled);
503 }
504 state.phase = Phase::Answered;
505 Ok(())
506 }
507
508 /// Claim the invitation and select its final dialog tag as one atomic state transition.
509 fn claim_with_tag(&self, tag: &str) -> Result<()> {
510 let mut state = self.lock();
511 if state.phase == Phase::Cancelled {
512 return Err(Error::InvitationCancelled);
513 }
514 state.tag.clear();
515 state.tag.push_str(tag);
516 state.phase = Phase::Answered;
517 Ok(())
518 }
519
520 /// End the invitation, if it has not already answered.
521 ///
522 /// Returns whether the `487` is owed — that is, whether this call was the transition. §9.2
523 /// asks for it only "if the transaction for the original request still exists", and both
524 /// things that make it not exist come through here: an answer, and an earlier CANCEL whose
525 /// retransmission this is.
526 fn cancel(&self) -> (bool, String) {
527 let mut state = self.lock();
528 if state.phase != Phase::Ringing {
529 return (false, state.tag.clone());
530 }
531 state.phase = Phase::Cancelled;
532 state.events.end(EndCause::RemoteCancel);
533 self.cancelled.notify_waiters();
534 (true, state.tag.clone())
535 }
536}
537
538/// What a dispatcher has refused, shed or could not place.
539///
540/// The same shape as [`sipx_transport::ShedCounts`] and for the same reason (`T-19`): loss that
541/// cannot be counted from outside is loss nobody is told about.
542#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
543#[non_exhaustive]
544pub struct DispatchCounts {
545 /// Requests answered `503` because the call they belong to was not reading its inbox.
546 pub shed: u64,
547 /// ACKs that could not be delivered — to a full inbox, or to no call at all.
548 ///
549 /// Counted apart because an ACK cannot be refused: SIP has no response to one, so nothing
550 /// retransmits it after Timer H and the dialog it would have completed is not reaped unless
551 /// RFC 4028 session timers happen to be running. This is the one that leaks calls.
552 pub acks: u64,
553 /// Requests answered `481 Call/Transaction Does Not Exist`.
554 ///
555 /// Two kinds, counted together because they are one fact — something named a dialog or a
556 /// transaction this endpoint does not have: an in-dialog request for no live call (RFC 3261
557 /// §12.2.2), and a CANCEL matching no pending INVITE transaction (§9.2).
558 pub unmatched: u64,
559 /// Out-of-dialog requests answered `405 Method Not Allowed` (RFC 3261 §8.2.1).
560 pub unsupported: u64,
561 /// Requests answered `400 Bad Request` for naming no dialog at all — no `Call-ID`, or no
562 /// `From` tag, both of which RFC 3261 §8.1.1 makes mandatory.
563 pub malformed: u64,
564 /// INVITEs answered `482 Loop Detected` as merged requests (RFC 3261 §8.2.2.2).
565 pub merged: u64,
566 /// Initial INVITEs refused by the caller-selected authenticated-identity policy.
567 pub identity: u64,
568 /// Valid new work refused because graceful drain intentionally closed admission.
569 pub draining: u64,
570}
571
572impl DispatchCounts {
573 /// Everything the dispatcher did not deliver, of every kind.
574 ///
575 /// Every field above, and every refusal the dispatcher makes is on one of them. That is a
576 /// property worth stating rather than assuming: two of these fields exist because the first
577 /// version of this type had four, and the `400` and `482` branches moved no counter at all —
578 /// two refusals invisible to the counters this story added to make loss visible.
579 #[must_use]
580 pub fn total(self) -> u64 {
581 self.shed
582 .saturating_add(self.acks)
583 .saturating_add(self.unmatched)
584 .saturating_add(self.unsupported)
585 .saturating_add(self.malformed)
586 .saturating_add(self.merged)
587 .saturating_add(self.identity)
588 .saturating_add(self.draining)
589 }
590}
591
592/// Work observed while a dispatcher is draining.
593#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
594#[non_exhaustive]
595pub struct DrainProgress {
596 /// Live per-dialog routes whose receivers have not closed.
597 pub dialogs: usize,
598 /// Transaction and transaction-owned entries still held by the endpoint.
599 pub transactions: usize,
600}
601
602/// Terminal result of [`Dispatcher::drain`].
603#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
604#[non_exhaustive]
605pub struct DrainReport {
606 /// True when every route and transaction ended before the deadline.
607 pub completed: bool,
608 /// Last state observed before natural completion or forced cleanup.
609 pub remaining: DrainProgress,
610 /// Dialog routes explicitly closed at deadline expiry.
611 pub terminated_dialogs: usize,
612 /// Endpoint transaction state explicitly terminated at deadline expiry.
613 pub terminated_transactions: usize,
614 /// Dispatcher decisions accumulated through the drain.
615 pub counts: DispatchCounts,
616}
617
618/// What identifies a route: a `Call-ID` and the tag of the party at the other end.
619///
620/// The local tag is deliberately absent — see
621/// [`docs/specs/call-dispatch.md`](../../../docs/specs/call-dispatch.md) §2. It is what lets a
622/// route be reserved from an INVITE, before this side has chosen a tag of its own.
623#[derive(Debug, Clone, PartialEq, Eq, Hash)]
624struct RouteKey {
625 call_id: Vec<u8>,
626 peer_tag: Vec<u8>,
627}
628
629impl RouteKey {
630 /// The route an arriving request belongs to, if it names one at all.
631 ///
632 /// `None` when the `Call-ID` or the `From` tag is missing, which RFC 3261 §8.1.1 makes
633 /// mandatory on every request: such a message cannot be placed in any dialog, present or
634 /// future.
635 fn of(request: &Request) -> Option<Self> {
636 Some(Self {
637 call_id: request.headers.value(&HeaderName::CallId)?.into_owned(),
638 peer_tag: from_tag(&request.headers)?,
639 })
640 }
641
642 /// The route a call is reached at.
643 fn of_dialog(dialog: &Dialog) -> Self {
644 Self {
645 call_id: dialog.id.call_id.clone(),
646 peer_tag: dialog.id.remote_tag.clone(),
647 }
648 }
649}
650
651/// One live route: where a call's requests go, and what reserved it.
652#[derive(Debug)]
653struct Route {
654 tx: mpsc::Sender<Incoming>,
655 /// The `CSeq` of the INVITE this route was reserved from, when it was reserved from one.
656 ///
657 /// Kept because RFC 3261 §8.2.2.2 makes a merged request one whose `From` tag, `Call-ID`
658 /// **and `CSeq`** all match — the third term is what tells a request arriving twice by two
659 /// paths from the ordinary retry of §8.1.3.5, which keeps the first two and increments this.
660 /// `None` for a route registered from a dialog that already exists ([`Calls::register`]),
661 /// which no out-of-dialog INVITE can be a merged copy of.
662 invite_cseq: Option<u32>,
663}
664
665/// The routing table, and the counters that describe what missed it.
666#[derive(Debug)]
667struct Table {
668 routes: Mutex<Routing>,
669 counts: Counters,
670 responses: Mutex<BTreeMap<u16, u64>>,
671 queue: usize,
672 /// Changes to route identity; receiver closure is awaited from each snapshot directly.
673 route_generation: watch::Sender<u64>,
674}
675
676/// Two indexes over the same set of calls, under one lock.
677///
678/// They answer different questions and are keyed differently on purpose. `by_dialog` answers
679/// "which call does this request belong to", which SIP keys on the dialog. `invites` answers "which
680/// transaction does this CANCEL name", which RFC 3261 §9.2 keys on the transaction and nothing
681/// else. Serving the second from the first would mean matching a CANCEL by `Call-ID`, which §9.2
682/// does not do — see [`Dispatcher::cancel`].
683#[derive(Debug, Default)]
684struct Routing {
685 /// Where each live call's in-dialog requests go.
686 by_dialog: HashMap<RouteKey, Route>,
687 /// Every INVITE server transaction this dispatcher has surfaced and not yet swept, keyed by
688 /// [`TransactionKey::for_cancelled_invite`]'s answer for the CANCEL that would name it.
689 invites: HashMap<TransactionKey, Arc<Pending>>,
690 /// Registrations since the last insertion-path dead-route sweep.
691 registrations_since_sweep: usize,
692}
693
694impl Routing {
695 fn sweep_dead(&mut self) {
696 self.by_dialog.retain(|_, route| !route.tx.is_closed());
697 self.invites.retain(|_, pending| !pending.route.is_closed());
698 self.registrations_since_sweep = 0;
699 }
700
701 fn sweep_dead_if_due(&mut self) {
702 self.registrations_since_sweep = self.registrations_since_sweep.saturating_add(1);
703 if self.registrations_since_sweep >= DEAD_ROUTE_SWEEP_INTERVAL {
704 self.sweep_dead();
705 }
706 }
707}
708
709#[derive(Debug, Default)]
710struct Counters {
711 shed: AtomicU64,
712 acks: AtomicU64,
713 unmatched: AtomicU64,
714 unsupported: AtomicU64,
715 malformed: AtomicU64,
716 merged: AtomicU64,
717 identity: AtomicU64,
718 draining: AtomicU64,
719}
720
721/// Which counter a request that was not delivered belongs on.
722#[derive(Debug, Clone, Copy)]
723enum Kind {
724 Shed,
725 Ack,
726 Unmatched,
727 Unsupported,
728 Malformed,
729 Merged,
730 Identity,
731 Draining,
732}
733
734/// The set of calls a dispatcher routes to: a cheap, cloneable handle to its routing table.
735///
736/// Needed because the dispatcher's loop and the code that places outbound calls are not the same
737/// task. A call made with [`dial`](crate::dial) is registered through this from wherever it was
738/// dialled; an inbound one needs nothing, because [`Dispatcher::next`] reserved its route before
739/// the application ever saw the INVITE.
740#[derive(Debug, Clone)]
741pub struct Calls(Arc<Table>);
742
743impl Calls {
744 /// Route this dialog's in-dialog requests to the returned inbox.
745 ///
746 /// Hand the inbox to [`serve`](crate::serve). Dropping it — which is what ending a call and
747 /// returning from `serve` does — releases the route.
748 ///
749 /// There is a window on the outbound path this cannot close: [`dial`](crate::dial) returns
750 /// only once the 2xx has arrived, so a BYE that overtakes it is answered `481`. Closing it
751 /// needs the `Call-ID` to be known before the INVITE is sent, which is a change to `dial`
752 /// this story does not make.
753 ///
754 /// Registering a dialog that already has a route replaces it, and the previous inbox stops
755 /// receiving.
756 pub fn register(&self, dialog: &Dialog) -> mpsc::Receiver<Incoming> {
757 self.install(RouteKey::of_dialog(dialog), None).1
758 }
759
760 /// Stop routing to this dialog.
761 ///
762 /// Rarely needed — dropping the inbox does the same thing lazily — but explicit when an
763 /// application tears a call down without dropping the receiver it was serving from.
764 pub fn forget(&self, dialog: &Dialog) {
765 let removed = self.lock().by_dialog.remove(&RouteKey::of_dialog(dialog));
766 if removed.is_some() {
767 self.route_changed();
768 }
769 }
770
771 /// How many calls are currently routed.
772 #[must_use]
773 pub fn len(&self) -> usize {
774 let mut routing = self.lock();
775 routing.sweep_dead();
776 routing.by_dialog.len()
777 }
778
779 /// Whether no call is routed at all.
780 #[must_use]
781 pub fn is_empty(&self) -> bool {
782 self.len() == 0
783 }
784
785 /// What has been refused, shed or left unplaced.
786 #[must_use]
787 pub fn counts(&self) -> DispatchCounts {
788 let counts = &self.0.counts;
789 DispatchCounts {
790 shed: counts.shed.load(Ordering::Relaxed),
791 acks: counts.acks.load(Ordering::Relaxed),
792 unmatched: counts.unmatched.load(Ordering::Relaxed),
793 unsupported: counts.unsupported.load(Ordering::Relaxed),
794 malformed: counts.malformed.load(Ordering::Relaxed),
795 merged: counts.merged.load(Ordering::Relaxed),
796 identity: counts.identity.load(Ordering::Relaxed),
797 draining: counts.draining.load(Ordering::Relaxed),
798 }
799 }
800
801 /// Responses the dispatcher successfully handed to the endpoint, keyed by status code.
802 ///
803 /// Unlike [`Self::counts`], this is wire evidence rather than decision evidence: a response
804 /// that could not be built or handed off is absent.
805 #[must_use]
806 pub fn responses(&self) -> BTreeMap<u16, u64> {
807 self.0
808 .responses
809 .lock()
810 .unwrap_or_else(PoisonError::into_inner)
811 .clone()
812 }
813
814 fn counted_response(&self, status: u16) {
815 let mut responses = self
816 .0
817 .responses
818 .lock()
819 .unwrap_or_else(PoisonError::into_inner);
820 *responses.entry(status).or_default() += 1;
821 }
822
823 /// Record one request the dispatcher did not deliver.
824 ///
825 /// One method per kind rather than a field reached through from the dispatcher, because the
826 /// counters exist so that loss is visible and a caller that had to name a path to reach them
827 /// is a caller that can quietly name the wrong one.
828 fn counted(&self, kind: Kind) {
829 let counts = &self.0.counts;
830 match kind {
831 Kind::Shed => &counts.shed,
832 Kind::Ack => &counts.acks,
833 Kind::Unmatched => &counts.unmatched,
834 Kind::Unsupported => &counts.unsupported,
835 Kind::Malformed => &counts.malformed,
836 Kind::Merged => &counts.merged,
837 Kind::Identity => &counts.identity,
838 Kind::Draining => &counts.draining,
839 }
840 .fetch_add(1, Ordering::Relaxed);
841 }
842
843 /// The table, whether or not a previous holder panicked.
844 ///
845 /// Every critical section here is a map operation with no `await` and nothing fallible in
846 /// it, so a poisoned lock cannot mean a half-updated table — and refusing to route the rest
847 /// of an endpoint's calls because one unrelated task unwound would be a far worse failure
848 /// than the one poisoning guards against.
849 fn lock(&self) -> MutexGuard<'_, Routing> {
850 self.0.routes.lock().unwrap_or_else(PoisonError::into_inner)
851 }
852
853 /// The sender for a route, if there is one. Cloned rather than borrowed so the lock is not
854 /// held across the send that follows.
855 fn sender(&self, key: &RouteKey) -> Option<mpsc::Sender<Incoming>> {
856 self.lock().by_dialog.get(key).map(|route| route.tx.clone())
857 }
858
859 /// The INVITE transaction a CANCEL names, if this dispatcher has it (RFC 3261 §9.2).
860 ///
861 /// Cloned out of the table rather than borrowed, because answering a CANCEL takes two `await`
862 /// points and holding a `std::sync::Mutex` across one of those is how a routing table stops
863 /// routing.
864 fn pending_invite(&self, key: &TransactionKey) -> Option<Arc<Pending>> {
865 self.lock().invites.get(key).map(Arc::clone)
866 }
867
868 /// Reserve a route for an invitation that does not exist yet, and remember its INVITE
869 /// transaction so a CANCEL naming it can be answered (RFC 3261 §9.2).
870 ///
871 /// The transaction is keyed by [`TransactionKey::from_request`] over the *received* INVITE
872 /// rather than by [`Incoming::key`], so that both sides of the eventual comparison are derived
873 /// from a request the transport has already applied `received` and `rport` to. `Incoming::key`
874 /// is kept too, but for responding rather than for matching.
875 fn reserve(
876 &self,
877 key: RouteKey,
878 incoming: &Incoming,
879 ) -> (mpsc::Receiver<Incoming>, Arc<Pending>, CallEvents) {
880 let (tx, rx) = self.install(key, cseq_number(&incoming.request.headers));
881 let (events, stream) = EventSink::new();
882 let pending = Arc::new(Pending {
883 transaction: incoming.key.clone(),
884 request: incoming.request.clone(),
885 route: tx,
886 state: Mutex::new(State {
887 phase: Phase::Ringing,
888 events,
889 tag: token(),
890 }),
891 cancelled: Notify::new(),
892 });
893 if let Some(matched) = TransactionKey::from_request(&incoming.request) {
894 self.lock().invites.insert(matched, Arc::clone(&pending));
895 }
896 (rx, pending, stream)
897 }
898
899 /// Put a route in the table, replacing whatever was there, and periodically sweep the dead.
900 ///
901 /// A call that has ended dropped its inbox, so its route is dead weight until something
902 /// arrives for it. Sweeping on a bounded fraction of the operations that already take the
903 /// lock keeps a long-lived dispatcher from accumulating them without turning N concurrent
904 /// invitations into N full walks over both routing indexes.
905 ///
906 /// An INVITE transaction is swept with the route it reserved, and only then: a CANCEL for an
907 /// invitation that has been answered still has to draw the `200` of RFC 3261 §9.2, and that
908 /// invitation is long past being a `Dispatched::Invitation` by the time it arrives.
909 fn install(
910 &self,
911 key: RouteKey,
912 invite_cseq: Option<u32>,
913 ) -> (mpsc::Sender<Incoming>, mpsc::Receiver<Incoming>) {
914 let (tx, rx) = mpsc::channel(self.0.queue);
915 let mut routing = self.lock();
916 routing.sweep_dead_if_due();
917 routing.by_dialog.insert(
918 key,
919 Route {
920 tx: tx.clone(),
921 invite_cseq,
922 },
923 );
924 drop(routing);
925 self.route_changed();
926 (tx, rx)
927 }
928
929 fn remove(&self, key: &RouteKey) {
930 let removed = self.lock().by_dialog.remove(key);
931 if removed.is_some() {
932 self.route_changed();
933 }
934 }
935
936 fn route_changed(&self) {
937 self.0
938 .route_generation
939 .send_modify(|generation| *generation = generation.wrapping_add(1));
940 }
941
942 async fn wait_for_route_change(&self) {
943 let mut generation = self.0.route_generation.subscribe();
944 let senders: Vec<_> = {
945 let mut routing = self.lock();
946 routing.sweep_dead();
947 routing
948 .by_dialog
949 .values()
950 .map(|route| route.tx.clone())
951 .collect()
952 };
953 if senders.is_empty() {
954 return;
955 }
956 let closed = FuturesUnordered::new();
957 for sender in senders {
958 closed.push(async move { sender.closed().await });
959 }
960 tokio::pin!(closed);
961 tokio::select! {
962 _ = closed.next() => {}
963 _ = generation.changed() => {}
964 }
965 }
966
967 fn terminate_routes(&self) -> usize {
968 let count = {
969 let mut routing = self.lock();
970 routing.sweep_dead();
971 let count = routing.by_dialog.len();
972 routing.by_dialog.clear();
973 routing.invites.clear();
974 count
975 };
976 if count != 0 {
977 self.route_changed();
978 }
979 count
980 }
981
982 /// Whether this INVITE is a merged copy of one already accepted (RFC 3261 §8.2.2.2).
983 ///
984 /// All three of the section's terms, and no fewer. `Call-ID` and the `From` tag are the route
985 /// key; the `CSeq` is what separates the same request arriving twice by two paths — which is
986 /// what §8.2.2.2 is about — from the retry of §8.1.3.5, which keeps both of those and
987 /// increments the `CSeq`. That retry is the ordinary answer to a 401, 407, 413, 415, 420 or
988 /// 484, and RFC 4028 §7.3's 422; refusing it 482 would mean a challenged call could never be
989 /// placed at all, including by sipx's own UAC, which retries in exactly that shape.
990 ///
991 /// A **closed** route is not a match either, whatever its `CSeq`. The application dropped
992 /// that inbox, which is what refusing an invitation does, so there is no accepted request
993 /// left for a second copy to be merged with — and treating one as live would let a refused
994 /// invitation poison its key for every later attempt from the same peer.
995 fn is_merged(&self, key: &RouteKey, cseq: Option<u32>) -> bool {
996 let routing = self.lock();
997 routing.by_dialog.get(key).is_some_and(|route| {
998 !route.tx.is_closed() && cseq.is_some() && route.invite_cseq == cseq
999 })
1000 }
1001}
1002
1003/// One endpoint's incoming requests, routed to any number of concurrent calls.
1004///
1005/// Owns the endpoint's `Receiver<Incoming>`, which is the whole reason it can promise anything:
1006/// there is exactly one of those, so anything else reading it would be reading requests this
1007/// cannot then route.
1008#[derive(Debug)]
1009pub struct Dispatcher {
1010 endpoint: Handle,
1011 incoming: mpsc::Receiver<Incoming>,
1012 calls: Calls,
1013 identity: Option<InboundIdentityPolicy>,
1014 notifier: Option<Notifier>,
1015 event_subscriptions: Option<EventSubscriptions>,
1016 publications: Option<Publications>,
1017 draining: bool,
1018}
1019
1020impl Dispatcher {
1021 /// Take over an endpoint's incoming requests, with the default per-call queue depth.
1022 #[must_use]
1023 pub fn new(endpoint: Handle, incoming: mpsc::Receiver<Incoming>) -> Self {
1024 Self::with_queue(endpoint, incoming, DEFAULT_QUEUE)
1025 }
1026
1027 /// The same, with a per-call queue depth of your own.
1028 ///
1029 /// A depth of zero is raised to one: an unbuffered channel would shed every request that
1030 /// arrived while the call was between `recv` calls, which is most of them.
1031 #[must_use]
1032 pub fn with_queue(endpoint: Handle, incoming: mpsc::Receiver<Incoming>, queue: usize) -> Self {
1033 let (route_generation, _) = watch::channel(0);
1034 Self {
1035 endpoint,
1036 incoming,
1037 calls: Calls(Arc::new(Table {
1038 routes: Mutex::new(Routing::default()),
1039 counts: Counters::default(),
1040 responses: Mutex::new(BTreeMap::new()),
1041 queue: queue.max(1),
1042 route_generation,
1043 })),
1044 identity: None,
1045 notifier: None,
1046 event_subscriptions: None,
1047 publications: None,
1048 draining: false,
1049 }
1050 }
1051
1052 /// Verify new inbound INVITEs before they become answerable application invitations.
1053 ///
1054 /// A verification failure is sent on the INVITE transaction with its RFC 8224 status and the
1055 /// request is not surfaced. With no selected policy, dispatch remains wire-compatible and
1056 /// performs no credential acquisition or time read.
1057 #[must_use]
1058 pub fn with_identity(mut self, identity: InboundIdentityPolicy) -> Self {
1059 self.identity = Some(identity);
1060 self
1061 }
1062
1063 /// Serve inbound RFC 6665 SUBSCRIBE requests through this bounded notifier.
1064 #[must_use]
1065 pub fn with_notifier(mut self, mut notifier: Notifier) -> Self {
1066 notifier.attach(self.endpoint.clone());
1067 self.notifier = Some(notifier);
1068 self
1069 }
1070
1071 /// Route inbound NOTIFY requests to a bounded outbound event-subscription client.
1072 #[must_use]
1073 pub fn with_event_subscriptions(mut self, subscriptions: EventSubscriptions) -> Self {
1074 subscriptions.attach(self.endpoint.clone());
1075 self.event_subscriptions = Some(subscriptions);
1076 self
1077 }
1078
1079 /// Serve inbound PUBLISH and attach outbound publication transactions to this endpoint.
1080 #[must_use]
1081 pub fn with_publications(mut self, mut publications: Publications) -> Self {
1082 publications.attach(self.endpoint.clone());
1083 self.publications = Some(publications);
1084 self
1085 }
1086
1087 /// A handle for registering calls this dispatcher did not itself surface.
1088 #[must_use]
1089 pub fn calls(&self) -> Calls {
1090 self.calls.clone()
1091 }
1092
1093 /// Route this dialog's requests to the returned inbox — [`Calls::register`], for an
1094 /// application that holds the dispatcher directly.
1095 pub fn register(&self, dialog: &Dialog) -> mpsc::Receiver<Incoming> {
1096 self.calls.register(dialog)
1097 }
1098
1099 /// What has been refused, shed or left unplaced.
1100 #[must_use]
1101 pub fn counts(&self) -> DispatchCounts {
1102 self.calls.counts()
1103 }
1104
1105 /// Atomically close new-dialog admission for this dispatcher and its transport endpoint.
1106 ///
1107 /// Existing routes and transactions remain live. Calling this more than once is harmless.
1108 pub fn begin_drain(&mut self) {
1109 self.draining = true;
1110 self.endpoint.begin_drain();
1111 }
1112
1113 /// Whether new-dialog admission has been closed.
1114 #[must_use]
1115 pub const fn is_draining(&self) -> bool {
1116 self.draining
1117 }
1118
1119 /// Observe the two completion dimensions used by [`Self::drain`].
1120 pub async fn drain_progress(&self) -> Result<DrainProgress> {
1121 Ok(DrainProgress {
1122 dialogs: self.calls.len(),
1123 transactions: self.endpoint.outstanding().await?,
1124 })
1125 }
1126
1127 /// Close admission, drive existing dialogs and transactions, and stop the endpoint.
1128 ///
1129 /// Completion is event-driven: route receiver closure and the endpoint's transaction-terminal
1130 /// barrier. `within` only bounds failure. At expiry every remaining route is closed, the live
1131 /// counts are logged and returned, and the ordinary endpoint shutdown path cancels and joins
1132 /// transport-owned tasks.
1133 pub async fn drain(&mut self, within: Duration) -> Result<DrainReport> {
1134 self.begin_drain();
1135 let deadline = tokio::time::Instant::now().checked_add(within);
1136
1137 loop {
1138 if self.calls.is_empty() {
1139 let endpoint = self.endpoint.clone();
1140 let settled = async move { endpoint.settled().await };
1141 tokio::pin!(settled);
1142 tokio::select! {
1143 result = &mut settled => {
1144 result?;
1145 self.shutdown_services().await;
1146 self.endpoint.shutdown().await;
1147 return Ok(DrainReport {
1148 completed: true,
1149 counts: self.counts(),
1150 ..DrainReport::default()
1151 });
1152 }
1153 incoming = self.incoming.recv() => {
1154 let Some(incoming) = incoming else {
1155 return Err(sipx_transport::Error::EndpointClosed.into());
1156 };
1157 self.route_while_draining(incoming).await;
1158 }
1159 () = wait_for_drain_deadline(deadline) => break,
1160 }
1161 } else {
1162 let calls = self.calls.clone();
1163 let route_changed = async move { calls.wait_for_route_change().await };
1164 tokio::pin!(route_changed);
1165 tokio::select! {
1166 () = &mut route_changed => {}
1167 incoming = self.incoming.recv() => {
1168 let Some(incoming) = incoming else {
1169 return Err(sipx_transport::Error::EndpointClosed.into());
1170 };
1171 self.route_while_draining(incoming).await;
1172 }
1173 () = wait_for_drain_deadline(deadline) => break,
1174 }
1175 }
1176 }
1177
1178 let remaining = self.drain_progress().await?;
1179 let terminated_dialogs = self.calls.terminate_routes();
1180 let terminated_transactions = remaining.transactions;
1181 tracing::warn!(
1182 terminated_dialogs,
1183 terminated_transactions,
1184 "graceful drain deadline expired; terminating remaining work"
1185 );
1186 self.endpoint.shutdown().await;
1187 self.shutdown_services().await;
1188 Ok(DrainReport {
1189 completed: false,
1190 remaining,
1191 terminated_dialogs,
1192 terminated_transactions,
1193 counts: self.counts(),
1194 })
1195 }
1196
1197 async fn route_while_draining(&mut self, incoming: Incoming) {
1198 if let Some(dispatched) = self.route(incoming).await {
1199 match dispatched {
1200 Dispatched::OutOfDialog(incoming) => self.refuse_draining(&incoming).await,
1201 Dispatched::Invitation(invitation) => {
1202 // The draining check in `route_new_invite` makes this unreachable without a
1203 // future new `Dispatched` variant changing the decision table. Keep the safe
1204 // response here so that such a change cannot silently reopen admission.
1205 self.refuse_draining(invitation.request()).await;
1206 }
1207 }
1208 }
1209 }
1210
1211 async fn shutdown_services(&mut self) {
1212 if let Some(notifier) = self.notifier.as_mut() {
1213 notifier.shutdown().await;
1214 }
1215 if let Some(subscriptions) = self.event_subscriptions.as_mut() {
1216 subscriptions.shutdown().await;
1217 }
1218 if let Some(publications) = self.publications.as_mut() {
1219 publications.shutdown().await;
1220 }
1221 }
1222
1223 /// The next thing the dispatcher cannot place itself.
1224 ///
1225 /// Routes everything else on the way, so this must be called in a loop for a dispatcher to
1226 /// do its job at all — the ACKs and BYEs of every call it has already handed out move only
1227 /// while it is being polled. `None` once the endpoint has shut down.
1228 pub async fn next(&mut self) -> Option<Dispatched> {
1229 loop {
1230 let Some(incoming) = self.incoming.recv().await else {
1231 if let Some(subscriptions) = self.event_subscriptions.as_mut() {
1232 subscriptions.shutdown().await;
1233 }
1234 if let Some(publications) = self.publications.as_mut() {
1235 publications.shutdown().await;
1236 }
1237 return None;
1238 };
1239 if let Some(surfaced) = self.route(incoming).await {
1240 return Some(surfaced);
1241 }
1242 }
1243 }
1244
1245 /// Place one request: the decision table of
1246 /// [`docs/specs/call-dispatch.md`](../../../docs/specs/call-dispatch.md) §3, in order.
1247 async fn route(&mut self, incoming: Incoming) -> Option<Dispatched> {
1248 let Some(key) = RouteKey::of(&incoming.request) else {
1249 // RFC 3261 §8.1.1 makes `Call-ID` and the `From` tag mandatory on every request.
1250 // Without them this cannot be placed in any dialog, now or later.
1251 self.calls.counted(Kind::Malformed);
1252 self.refuse(&incoming, 400, "Bad Request", None).await;
1253 return None;
1254 };
1255
1256 // An INVITE with no `To` tag is a new call, and has to be recognised as one *before* the
1257 // route lookup: routed by key alone it would land in an existing call, whose
1258 // `Dialog::matches` would then reject it and leave nothing to answer it.
1259 if incoming.request.method == Method::Invite && to_tag(&incoming.request.headers).is_none()
1260 {
1261 return self.route_new_invite(key, incoming).await;
1262 }
1263
1264 // Before the route lookup, because a CANCEL does not belong to a *dialog* — it belongs to
1265 // the INVITE transaction whose branch it carries (RFC 3261 §9.1), which may well be an
1266 // invitation nobody has answered and so a call that does not exist yet. Routing it by key
1267 // would put it in an inbox where the two responses §9.2 owes could not be sent from.
1268 if incoming.request.method == Method::Cancel {
1269 self.cancel(&incoming).await;
1270 return None;
1271 }
1272
1273 // These methods can create a dialog without an INVITE. A tagged request belongs to an
1274 // existing service dialog and remains legal; an untagged one is new admission just as an
1275 // initial INVITE is. Keep this before the optional notifier so the service cannot reopen
1276 // admission behind the dispatcher's drain barrier.
1277 if self.draining
1278 && to_tag(&incoming.request.headers).is_none()
1279 && matches!(incoming.request.method, Method::Subscribe | Method::Refer)
1280 {
1281 self.refuse_draining(&incoming).await;
1282 return None;
1283 }
1284
1285 // SUBSCRIBE owns a dialog of its own and therefore cannot be routed by the call table.
1286 // A tagged refresh is matched inside the notifier against its subscription dialog.
1287 if incoming.request.method == Method::Subscribe
1288 && let Some(notifier) = self.notifier.as_mut()
1289 {
1290 notifier.receive(&incoming).await;
1291 return None;
1292 }
1293
1294 // PUBLISH creates no dialog. The publication service serializes and authorizes its own
1295 // resource state before anything can be mistaken for an INVITE-dialog request.
1296 if incoming.request.method == Method::Publish
1297 && let Some(publications) = self.publications.as_mut()
1298 {
1299 publications.receive(&incoming).await;
1300 return None;
1301 }
1302
1303 // NOTIFY owns the subscription dialog established by an outbound SUBSCRIBE, not an INVITE
1304 // dialog in the call table. The event client validates its exact tags, Event and CSeq.
1305 if incoming.request.method == Method::Notify
1306 && let Some(subscriptions) = &self.event_subscriptions
1307 && subscriptions.receive(&incoming).await
1308 {
1309 return None;
1310 }
1311
1312 if let Some(sender) = self.calls.sender(&key) {
1313 self.deliver(&key, sender, incoming).await;
1314 return None;
1315 }
1316
1317 match incoming.request.method {
1318 // Nothing to answer: SIP has no response to an ACK, and an ACK for a 2xx is a
1319 // transaction of its own (RFC 3261 §17.1.1.3). Counted and logged instead, because
1320 // a stray one means a dialog somewhere completed against a call that is not here.
1321 Method::Ack => {
1322 self.calls.counted(Kind::Ack);
1323 tracing::warn!(
1324 source = %incoming.source,
1325 "an ACK arrived for no live call and cannot be refused"
1326 );
1327 None
1328 }
1329 // RFC 3261 §12.2.2. Either it says it is in a dialog, or its method exists only
1330 // inside one — an orphan of a dialog that is gone, not an invitation to open a new
1331 // exchange.
1332 ref method if to_tag(&incoming.request.headers).is_some() || dialog_only(method) => {
1333 self.calls.counted(Kind::Unmatched);
1334 self.refuse(&incoming, 481, "Call/Transaction Does Not Exist", None)
1335 .await;
1336 None
1337 }
1338 // On the `Allow` the 405 below would carry, so refusing it there would have one
1339 // message say two different things. The application decides.
1340 ref method if advertised(method) => Some(Dispatched::OutOfDialog(incoming)),
1341 // RFC 3261 §8.2.1: "the UAS MUST generate a 405 ... and MUST add an Allow header
1342 // field".
1343 _ => {
1344 self.calls.counted(Kind::Unsupported);
1345 self.refuse(
1346 &incoming,
1347 405,
1348 "Method Not Allowed",
1349 Some((
1350 HeaderName::Allow,
1351 Bytes::from_static(sipx_sip::update::ALLOW.as_bytes()),
1352 )),
1353 )
1354 .await;
1355 None
1356 }
1357 }
1358 }
1359
1360 async fn route_new_invite(&mut self, key: RouteKey, incoming: Incoming) -> Option<Dispatched> {
1361 let invite_cseq = incoming
1362 .request
1363 .headers
1364 .typed::<CSeq>()
1365 .and_then(std::result::Result::ok)
1366 .filter(|value| value.method == Method::Invite);
1367 let unique_required = [
1368 HeaderName::CallId,
1369 HeaderName::From,
1370 HeaderName::To,
1371 HeaderName::CSeq,
1372 HeaderName::Contact,
1373 ]
1374 .iter()
1375 .all(|name| incoming.request.headers.count(name) == 1);
1376 if invite_cseq.is_none()
1377 || !unique_required
1378 || Dialog::from_request(&incoming.request, "validation").is_none()
1379 {
1380 self.calls.counted(Kind::Malformed);
1381 self.refuse(&incoming, 400, "Bad Request", None).await;
1382 return None;
1383 }
1384 let cseq = cseq_number(&incoming.request.headers);
1385 if self.calls.is_merged(&key, cseq) {
1386 // RFC 3261 §8.2.2.2, all three of its terms: the same `Call-ID`, `From` tag *and*
1387 // `CSeq` as a request already accepted here, which means this copy reached us by a
1388 // second path. A retransmission never gets this far — the server transaction absorbs
1389 // those — so a match here is always a different branch.
1390 self.calls.counted(Kind::Merged);
1391 self.refuse(&incoming, 482, "Loop Detected", None).await;
1392 return None;
1393 }
1394 if self.draining {
1395 self.refuse_draining(&incoming).await;
1396 return None;
1397 }
1398 let verification = self
1399 .identity
1400 .as_mut()
1401 .map(|identity| identity.verify(&incoming.request));
1402 if let Some(Err(failure)) = verification {
1403 self.calls.counted(Kind::Identity);
1404 self.refuse(&incoming, failure.status(), failure.reason(), None)
1405 .await;
1406 return None;
1407 }
1408 // Anything else is a fresh call attempt, and that includes the §8.1.3.5 retry that follows
1409 // a 401, 407, 413, 415, 420, 484 or RFC 4028 §7.3's 422 — same `Call-ID` and `From` tag, one
1410 // higher `CSeq`. It reserves the key afresh, replacing a route whose invitation has been
1411 // answered and abandoned; anything still holding that inbox stops receiving, which is what
1412 // it already was.
1413 let (requests, pending, events) = self.calls.reserve(key, &incoming);
1414 Some(Dispatched::Invitation(Invitation {
1415 incoming,
1416 requests,
1417 pending,
1418 events: Some(events),
1419 }))
1420 }
1421
1422 /// Answer a CANCEL — both halves of RFC 3261 §9.2, or the 481 that says there was nothing to
1423 /// cancel.
1424 ///
1425 /// **The matching is §9.2's and not an approximation of it.** A CANCEL names the transaction
1426 /// it withdraws by carrying that request's topmost `Via` branch (§9.1), so the match is the
1427 /// server transaction match of §17.2.3 — branch, sent-by, and the method of the transaction
1428 /// being cancelled — which is exactly what
1429 /// [`TransactionKey::for_cancelled_invite`] builds. In particular the `Call-ID` is *not* part
1430 /// of it: a table keyed on that would answer a CANCEL for the wrong branch of a dialog it
1431 /// happens to know, which is a stack that stops ringing when it should not have.
1432 ///
1433 /// The answer is two responses on two transactions, and that is the part that is easy to get
1434 /// half-right:
1435 ///
1436 /// 1. `200 OK` on the CANCEL's own transaction — a MUST, and unconditional. It is sent even
1437 /// when the INVITE has already been answered, because it says "I got your CANCEL", not "I
1438 /// stopped".
1439 /// 2. `487 Request Terminated` on the INVITE transaction it withdraws, and only "if the
1440 /// transaction for the original request still exists". A final response already sent means
1441 /// it does not, and §9.2 is explicit that the CANCEL then has no effect — BYE is the
1442 /// request for ending a call that was answered.
1443 ///
1444 /// Both carry the invitation's `To` tag, which is §9.2's `SHOULD` that the two agree.
1445 ///
1446 /// One term is added to §9.2's own, and it is §9.1's rather than an invention: a CANCEL "MUST
1447 /// have the same `Call-ID`, `To`, `From` and `CSeq` as the INVITE", so one whose dialog
1448 /// identifiers disagree with the transaction its branch names cannot be a legitimate CANCEL
1449 /// for it. Every well-formed CANCEL passes the check, which is what makes it free; what it
1450 /// costs an off-path attacker is that guessing or observing a branch is no longer enough to
1451 /// stop somebody else's phone ringing, since the sent-by in a `Via` is the attacker's to
1452 /// write.
1453 async fn cancel(&self, incoming: &Incoming) {
1454 let matched = TransactionKey::for_cancelled_invite(&incoming.request)
1455 .and_then(|key| self.calls.pending_invite(&key))
1456 .filter(|pending| RouteKey::of(&pending.request) == RouteKey::of(&incoming.request));
1457 let Some(pending) = matched else {
1458 // §9.2: "If the UAS did not find a matching transaction for the CANCEL according to
1459 // the procedure above, it SHOULD respond to the CANCEL with a 481." Not dropped, and
1460 // not the 405 an unadvertised method would draw — the method is fine, the transaction
1461 // is the thing that is not here.
1462 self.calls.counted(Kind::Unmatched);
1463 self.refuse(incoming, 481, "Call/Transaction Does Not Exist", None)
1464 .await;
1465 return;
1466 };
1467
1468 let (cancelled, tag) = pending.cancel();
1469 self.answer_request(
1470 &incoming.key,
1471 &incoming.request,
1472 200,
1473 "OK",
1474 None,
1475 Some(&tag),
1476 )
1477 .await;
1478
1479 if cancelled {
1480 self.answer_request(
1481 &pending.transaction,
1482 &pending.request,
1483 487,
1484 "Request Terminated",
1485 None,
1486 Some(&tag),
1487 )
1488 .await;
1489 }
1490 }
1491
1492 /// Hand a request to the call it belongs to, or say why it could not be.
1493 ///
1494 /// Never awaits room. The whole promise of a per-call queue is that one application task
1495 /// which has stopped reading cannot stop the loop that serves every other call, and a
1496 /// dispatcher that blocked here would trade a shed request for every call on the endpoint.
1497 async fn deliver(&self, key: &RouteKey, sender: mpsc::Sender<Incoming>, incoming: Incoming) {
1498 let is_ack = incoming.request.method == Method::Ack;
1499 match sender.try_send(incoming) {
1500 Ok(()) => {}
1501 Err(mpsc::error::TrySendError::Full(incoming)) => {
1502 if is_ack {
1503 self.calls.counted(Kind::Ack);
1504 tracing::error!(
1505 source = %incoming.source,
1506 "a call's queue is full; an ACK was dropped and cannot be refused — \
1507 the dialog it would have completed will not be reaped"
1508 );
1509 } else {
1510 self.calls.counted(Kind::Shed);
1511 tracing::warn!(
1512 source = %incoming.source,
1513 method = %incoming.request.method,
1514 "a call's queue is full; refusing the transaction for that call"
1515 );
1516 self.refuse(
1517 &incoming,
1518 503,
1519 "Service Unavailable",
1520 Some((HeaderName::RetryAfter, Bytes::from_static(RETRY_AFTER))),
1521 )
1522 .await;
1523 }
1524 }
1525 Err(mpsc::error::TrySendError::Closed(incoming)) => {
1526 // The application dropped the inbox, which is what ending a call does. The
1527 // route is stale, so it goes, and the request gets the answer any request for a
1528 // dialog this endpoint does not have gets.
1529 self.calls.remove(key);
1530 if is_ack {
1531 self.calls.counted(Kind::Ack);
1532 } else {
1533 self.calls.counted(Kind::Unmatched);
1534 self.refuse(&incoming, 481, "Call/Transaction Does Not Exist", None)
1535 .await;
1536 }
1537 }
1538 }
1539 }
1540
1541 /// Answer a request the dispatcher will not route.
1542 ///
1543 /// Failures are logged rather than returned: this is the path that exists so that nothing is
1544 /// dropped in silence, and giving it an error for the caller to ignore would put the silence
1545 /// back one level up.
1546 async fn refuse(
1547 &self,
1548 incoming: &Incoming,
1549 status: u16,
1550 reason: &'static str,
1551 extra: Option<(HeaderName, Bytes)>,
1552 ) {
1553 self.answer_request(
1554 &incoming.key,
1555 &incoming.request,
1556 status,
1557 reason,
1558 extra,
1559 None,
1560 )
1561 .await;
1562 }
1563
1564 async fn refuse_draining(&self, incoming: &Incoming) {
1565 self.calls.counted(Kind::Draining);
1566 tracing::info!(
1567 source = %incoming.source,
1568 method = %incoming.request.method,
1569 "refusing new work because graceful drain closed admission"
1570 );
1571 self.refuse(
1572 incoming,
1573 503,
1574 "Service Unavailable",
1575 Some((HeaderName::RetryAfter, Bytes::from_static(RETRY_AFTER))),
1576 )
1577 .await;
1578 }
1579
1580 /// Answer a request on a named transaction, with a named `To` tag.
1581 ///
1582 /// Separate from [`Self::refuse`] because RFC 3261 §9.2 needs both of the things that method
1583 /// takes for granted to be chosen: the `487` goes on the *INVITE's* transaction rather than
1584 /// the one the request in hand arrived on, and both of the responses it owes carry the
1585 /// invitation's tag rather than a fresh one each.
1586 async fn answer_request(
1587 &self,
1588 key: &TransactionKey,
1589 request: &Request,
1590 status: u16,
1591 reason: &'static str,
1592 extra: Option<(HeaderName, Bytes)>,
1593 tag: Option<&str>,
1594 ) {
1595 let Some(code) = StatusCode::new(status) else {
1596 return;
1597 };
1598 let built = ResponseBuilder::to_request(request, code, reason)
1599 .and_then(|builder| match extra {
1600 Some((name, value)) => builder.header(name, value),
1601 None => Ok(builder),
1602 })
1603 .and_then(|builder| with_to_tag(builder, request, tag));
1604 let response = match built {
1605 Ok(builder) => builder.build(),
1606 // discard: the refusal this dispatcher decided on is lost. **It reaches no counter**,
1607 // and saying so is the point — `Calls::counted` has already recorded the *decision*
1608 // (which kind of refusal was owed), so `DispatchCounts` will show the request as
1609 // refused when in fact nothing was sent. The two numbers can therefore disagree with
1610 // the wire, and an operator should know that before using them to rule a cause out
1611 // (§12.2). The peer retransmits and its own transaction times out, so nothing hangs.
1612 Err(error) => {
1613 tracing::warn!(%error, status, "could not build the response for a request");
1614 return;
1615 }
1616 };
1617 // discard: the same loss one step later and with the same gap — see above. Closing it
1618 // needs a counter for responses the endpoint could not send, which belongs with
1619 // `sipx_transport::Handle::respond` rather than here.
1620 match self.endpoint.respond(key, response).await {
1621 Ok(()) => self.calls.counted_response(status),
1622 Err(error) => {
1623 tracing::warn!(%error, status, "could not send the response for a request");
1624 }
1625 }
1626 }
1627}
1628
1629async fn wait_for_drain_deadline(deadline: Option<tokio::time::Instant>) {
1630 match deadline {
1631 Some(deadline) => tokio::time::sleep_until(deadline).await,
1632 None => std::future::pending().await,
1633 }
1634}
1635
1636/// Give a response a `To` tag if the request did not already carry one.
1637///
1638/// RFC 3261 §8.2.6.2: every response but a 100 must have one, and an out-of-dialog request
1639/// arrives without it. A refusal with no tag is a response a peer is entitled to discard, which
1640/// would turn a considered answer back into the silence this whole path exists to remove.
1641///
1642/// `tag` names one rather than minting one. Only §9.2's pair of responses passes it: the `200` for
1643/// a CANCEL and the `487` for the INVITE it withdraws are two responses about one invitation, and
1644/// the section asks that they carry the same tag. Everything else is a one-off refusal with no
1645/// second response to agree with, and takes a fresh token.
1646pub(crate) fn with_to_tag(
1647 builder: sipx_sip::build::ResponseBuilder,
1648 request: &Request,
1649 tag: Option<&str>,
1650) -> std::result::Result<sipx_sip::build::ResponseBuilder, sipx_sip::error::BuildError> {
1651 if to_tag(&request.headers).is_some() {
1652 return Ok(builder);
1653 }
1654 let Some(to) = request.headers.value(&HeaderName::To) else {
1655 return Ok(builder);
1656 };
1657 let tag = tag.map_or_else(token, str::to_owned);
1658 // Appending works in both forms of the header: after `>` in a name-addr, and after a bare
1659 // addr-spec, where the semicolon starts a header parameter (RFC 3261 §20).
1660 let value = format!("{};tag={tag}", String::from_utf8_lossy(&to));
1661 builder.set_header(&HeaderName::To, Bytes::from(value))
1662}
1663
1664/// Whether a method is defined only inside a dialog.
1665///
1666/// One arriving without a `To` tag is therefore an orphan of a dialog that is gone rather than a
1667/// new exchange, and RFC 3261 §12.2.2's 481 is the honest answer. Listing them is narrower than
1668/// the alternative of treating every unrecognised out-of-dialog request as an orphan, which
1669/// would answer 481 to things that are simply unsupported.
1670pub(crate) fn dialog_only(method: &Method) -> bool {
1671 matches!(
1672 method,
1673 Method::Bye
1674 | Method::Update
1675 | Method::Prack
1676 | Method::Refer
1677 | Method::Notify
1678 | Method::Info
1679 )
1680}
1681
1682/// Whether this stack advertises the method (RFC 3311 §4, RFC 3261 §20.5).
1683///
1684/// Read from [`sipx_sip::update::ALLOW`] rather than written out again, because that constant is
1685/// what a 405 puts on the wire and what a peer reads as permission. A second copy that drifted
1686/// would have one message advertise a method the next refuses.
1687fn advertised(method: &Method) -> bool {
1688 let token = method.as_bytes();
1689 sipx_sip::update::ALLOW
1690 .split(',')
1691 .any(|allowed| allowed.trim().as_bytes().eq_ignore_ascii_case(token))
1692}
1693
1694#[cfg(test)]
1695#[allow(
1696 clippy::unwrap_used,
1697 clippy::expect_used,
1698 clippy::panic,
1699 clippy::indexing_slicing
1700)]
1701mod tests {
1702 use super::*;
1703 use sipx_sip::{Limits, Message, parse_datagram};
1704
1705 fn calls_for_test() -> Calls {
1706 let (route_generation, _) = watch::channel(0);
1707 Calls(Arc::new(Table {
1708 routes: Mutex::new(Routing::default()),
1709 counts: Counters::default(),
1710 responses: Mutex::new(BTreeMap::new()),
1711 queue: 1,
1712 route_generation,
1713 }))
1714 }
1715
1716 fn request(text: &str) -> Request {
1717 match parse_datagram(Bytes::from(text.to_owned()), &Limits::datagram()).expect("parses") {
1718 Message::Request(r) => r,
1719 Message::Response(_) => panic!("a request"),
1720 }
1721 }
1722
1723 fn bye(call_id: &str, from_tag: &str, to_tag: &str) -> Request {
1724 request(&format!(
1725 "BYE sip:callee@192.0.2.9:5060 SIP/2.0\r\n\
1726 Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKbye\r\n\
1727 To: <sip:callee@example.com>;tag={to_tag}\r\n\
1728 From: <sip:caller@example.net>;tag={from_tag}\r\n\
1729 Call-ID: {call_id}\r\n\
1730 CSeq: 2 BYE\r\n\
1731 Max-Forwards: 70\r\n\
1732 Content-Length: 0\r\n\r\n"
1733 ))
1734 }
1735
1736 /// The key is the `Call-ID` and the peer's tag, and *not* ours — which is what lets a route
1737 /// be reserved from an INVITE, before this side has chosen a tag at all.
1738 #[test]
1739 fn a_route_is_keyed_without_our_own_tag() {
1740 let first = RouteKey::of(&bye("c@sipx", "theirs", "ours")).expect("a key");
1741 let second = RouteKey::of(&bye("c@sipx", "theirs", "a-different-local-tag")).expect("key");
1742 assert_eq!(first, second, "our own tag must not enter the key");
1743
1744 let other_call = RouteKey::of(&bye("other@sipx", "theirs", "ours")).expect("a key");
1745 assert_ne!(first, other_call);
1746 let other_peer = RouteKey::of(&bye("c@sipx", "someone-else", "ours")).expect("a key");
1747 assert_ne!(first, other_peer, "a different peer is a different route");
1748 }
1749
1750 /// RFC 3261 §8.1.1 makes both parts mandatory. A request missing either cannot be placed in
1751 /// any dialog, and the dispatcher answers 400 rather than inventing a route for it.
1752 #[test]
1753 fn a_request_that_names_no_dialog_has_no_key() {
1754 let tagless = request(
1755 "BYE sip:callee@192.0.2.9:5060 SIP/2.0\r\n\
1756 Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKbye\r\n\
1757 To: <sip:callee@example.com>;tag=ours\r\n\
1758 From: <sip:caller@example.net>\r\n\
1759 Call-ID: c@sipx\r\n\
1760 CSeq: 2 BYE\r\n\
1761 Max-Forwards: 70\r\n\
1762 Content-Length: 0\r\n\r\n",
1763 );
1764 assert!(RouteKey::of(&tagless).is_none(), "no From tag, no route");
1765 }
1766
1767 /// A full-table retain on every registration made a burst of N invitations quadratic. Dead
1768 /// routes may wait for collection, but only for the fixed amortization interval.
1769 #[test]
1770 fn registration_sweeps_dead_routes_at_a_bounded_interval() {
1771 let calls = calls_for_test();
1772 for index in 0..DEAD_ROUTE_SWEEP_INTERVAL {
1773 let key = RouteKey {
1774 call_id: format!("call-{index}").into_bytes(),
1775 peer_tag: b"peer".to_vec(),
1776 };
1777 let receiver = calls.install(key, None).1;
1778 drop(receiver);
1779 }
1780
1781 let routing = calls.lock();
1782 assert_eq!(routing.by_dialog.len(), 1);
1783 assert_eq!(routing.registrations_since_sweep, 0);
1784 }
1785
1786 /// The 405's `Allow` and this predicate must be the same list, or one message advertises a
1787 /// method the next refuses. Reading the constant is what makes that structural.
1788 #[test]
1789 fn what_is_advertised_is_exactly_the_allow_constant() {
1790 for method in [
1791 Method::Invite,
1792 Method::Ack,
1793 Method::Cancel,
1794 Method::Bye,
1795 Method::Options,
1796 Method::Update,
1797 ] {
1798 assert!(
1799 advertised(&method),
1800 "{method} is on ALLOW but not advertised"
1801 );
1802 }
1803 for method in [
1804 Method::Register,
1805 Method::Subscribe,
1806 Method::Publish,
1807 Method::Message,
1808 Method::Other(Bytes::from_static(b"FROBNICATE")),
1809 ] {
1810 assert!(!advertised(&method), "{method} is not on ALLOW");
1811 }
1812 }
1813
1814 /// A token, not a substring: `UPDATEX` is a different method, and a substring test would
1815 /// advertise it.
1816 #[test]
1817 fn advertising_matches_tokens_and_not_substrings() {
1818 assert!(!advertised(&Method::Other(Bytes::from_static(b"UPDATEX"))));
1819 assert!(!advertised(&Method::Other(Bytes::from_static(b"INV"))));
1820 }
1821
1822 /// The methods that only exist inside a dialog get 481 when no call claims them; the rest
1823 /// are either surfaced or refused 405, which are different answers to different questions.
1824 #[test]
1825 fn the_dialog_only_methods_are_the_ones_that_orphan() {
1826 for method in [
1827 Method::Bye,
1828 Method::Update,
1829 Method::Prack,
1830 Method::Refer,
1831 Method::Notify,
1832 Method::Info,
1833 ] {
1834 assert!(dialog_only(&method), "{method} exists only in a dialog");
1835 }
1836 for method in [Method::Invite, Method::Options, Method::Register] {
1837 assert!(!dialog_only(&method), "{method} can start something");
1838 }
1839 }
1840}