Skip to main content

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}